mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-12 14:20:14 +02:00
Bug 946601 - Add performance statistics/piecharts in the Network Monitor, r=rcampbell
This commit is contained in:
parent
abed7621c4
commit
99bb901b11
24 changed files with 1956 additions and 134 deletions
|
|
@ -1141,6 +1141,7 @@ pref("devtools.netmonitor.enabled", true);
|
||||||
// The default Network Monitor UI settings
|
// The default Network Monitor UI settings
|
||||||
pref("devtools.netmonitor.panes-network-details-width", 450);
|
pref("devtools.netmonitor.panes-network-details-width", 450);
|
||||||
pref("devtools.netmonitor.panes-network-details-height", 450);
|
pref("devtools.netmonitor.panes-network-details-height", 450);
|
||||||
|
pref("devtools.netmonitor.statistics", true);
|
||||||
|
|
||||||
// Enable the Tilt inspector
|
// Enable the Tilt inspector
|
||||||
pref("devtools.tilt.enabled", true);
|
pref("devtools.tilt.enabled", true);
|
||||||
|
|
|
||||||
|
|
@ -57,35 +57,64 @@ const EVENTS = {
|
||||||
// When the response body is displayed in the UI.
|
// When the response body is displayed in the UI.
|
||||||
RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
|
RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
|
||||||
|
|
||||||
// When `onTabSelect` is fired and subsequently rendered
|
// When `onTabSelect` is fired and subsequently rendered.
|
||||||
TAB_UPDATED: "NetMonitor:TabUpdated",
|
TAB_UPDATED: "NetMonitor:TabUpdated",
|
||||||
|
|
||||||
// Fired when Sidebar is finished being populated
|
// Fired when Sidebar has finished being populated.
|
||||||
SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
|
SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
|
||||||
|
|
||||||
// Fired when NetworkDetailsView is finished being populated
|
// Fired when NetworkDetailsView has finished being populated.
|
||||||
NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
|
NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
|
||||||
|
|
||||||
// Fired when NetworkDetailsView is finished being populated
|
// Fired when CustomRequestView has finished being populated.
|
||||||
CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated"
|
CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated",
|
||||||
|
|
||||||
|
// Fired when charts have been displayed in the PerformanceStatisticsView.
|
||||||
|
PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
|
||||||
|
PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
|
||||||
|
EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Descriptions for what this frontend is currently doing.
|
||||||
|
const ACTIVITY_TYPE = {
|
||||||
|
// Standing by and handling requests normally.
|
||||||
|
NONE: 0,
|
||||||
|
|
||||||
|
// Forcing the target to reload with cache enabled or disabled.
|
||||||
|
RELOAD: {
|
||||||
|
WITH_CACHE_ENABLED: 1,
|
||||||
|
WITH_CACHE_DISABLED: 2
|
||||||
|
},
|
||||||
|
|
||||||
|
// Enabling or disabling the cache without triggering a reload.
|
||||||
|
ENABLE_CACHE: 3,
|
||||||
|
DISABLE_CACHE: 4
|
||||||
};
|
};
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Task.jsm");
|
|
||||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
|
||||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||||
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
|
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
|
||||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||||
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
|
||||||
|
|
||||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||||
|
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||||
|
const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
const Editor = require("devtools/sourceeditor/editor");
|
const Editor = require("devtools/sourceeditor/editor");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Chart",
|
||||||
|
"resource:///modules/devtools/Chart.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||||
|
"resource://gre/modules/Task.jsm");
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||||
"resource://gre/modules/PluralForm.jsm");
|
"resource://gre/modules/PluralForm.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils",
|
||||||
|
"resource://gre/modules/devtools/DevToolsUtils.jsm");
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
|
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
|
||||||
"resource://gre/modules/devtools/Loader.jsm");
|
"resource://gre/modules/devtools/Loader.jsm");
|
||||||
|
|
||||||
|
|
@ -255,9 +284,81 @@ let NetMonitorController = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the activity currently performed by the frontend.
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
getCurrentActivity: function() {
|
||||||
|
return this._currentActivity || ACTIVITY_TYPE.NONE;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a specific "activity" to be performed by the frontend. This can be,
|
||||||
|
* for example, triggering reloads or enabling/disabling cache.
|
||||||
|
*
|
||||||
|
* @param number aType
|
||||||
|
* The activity type. See the ACTIVITY_TYPE const.
|
||||||
|
* @return object
|
||||||
|
* A promise resolved once the activity finishes and the frontend
|
||||||
|
* is back into "standby" mode.
|
||||||
|
*/
|
||||||
|
triggerActivity: function(aType) {
|
||||||
|
// Puts the frontend into "standby" (when there's no particular activity).
|
||||||
|
let standBy = () => {
|
||||||
|
this._currentActivity = ACTIVITY_TYPE.NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Waits for a series of "navigation start" and "navigation stop" events.
|
||||||
|
let waitForNavigation = () => {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
this._target.once("will-navigate", () => {
|
||||||
|
this._target.once("navigate", () => {
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reconfigures the tab, optionally triggering a reload.
|
||||||
|
let reconfigureTab = aOptions => {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
this._target.activeTab.reconfigure(aOptions, deferred.resolve);
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reconfigures the tab and waits for the target to finish navigating.
|
||||||
|
let reconfigureTabAndWaitForNavigation = aOptions => {
|
||||||
|
aOptions.performReload = true;
|
||||||
|
let navigationFinished = waitForNavigation();
|
||||||
|
return reconfigureTab(aOptions).then(() => navigationFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aType == ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED) {
|
||||||
|
this._currentActivity = ACTIVITY_TYPE.ENABLE_CACHE;
|
||||||
|
this._target.once("will-navigate", () => this._currentActivity = aType);
|
||||||
|
return reconfigureTabAndWaitForNavigation({ cacheEnabled: true }).then(standBy);
|
||||||
|
}
|
||||||
|
if (aType == ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED) {
|
||||||
|
this._currentActivity = ACTIVITY_TYPE.DISABLE_CACHE;
|
||||||
|
this._target.once("will-navigate", () => this._currentActivity = aType);
|
||||||
|
return reconfigureTabAndWaitForNavigation({ cacheEnabled: false }).then(standBy);
|
||||||
|
}
|
||||||
|
if (aType == ACTIVITY_TYPE.ENABLE_CACHE) {
|
||||||
|
this._currentActivity = aType;
|
||||||
|
return reconfigureTab({ cacheEnabled: true, performReload: false }).then(standBy);
|
||||||
|
}
|
||||||
|
if (aType == ACTIVITY_TYPE.DISABLE_CACHE) {
|
||||||
|
this._currentActivity = aType;
|
||||||
|
return reconfigureTab({ cacheEnabled: false, performReload: false }).then(standBy);
|
||||||
|
}
|
||||||
|
this._currentActivity = ACTIVITY_TYPE.NONE;
|
||||||
|
return promise.reject(new Error("Invalid activity type"));
|
||||||
|
},
|
||||||
|
|
||||||
_startup: null,
|
_startup: null,
|
||||||
_shutdown: null,
|
_shutdown: null,
|
||||||
_connection: null,
|
_connection: null,
|
||||||
|
_currentActivity: null,
|
||||||
client: null,
|
client: null,
|
||||||
tabClient: null,
|
tabClient: null,
|
||||||
webConsoleClient: null
|
webConsoleClient: null
|
||||||
|
|
@ -314,6 +415,11 @@ TargetEventsHandler.prototype = {
|
||||||
NetMonitorView.Sidebar.reset();
|
NetMonitorView.Sidebar.reset();
|
||||||
NetMonitorView.NetworkDetails.reset();
|
NetMonitorView.NetworkDetails.reset();
|
||||||
|
|
||||||
|
// Switch to the default network traffic inspector view.
|
||||||
|
if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {
|
||||||
|
NetMonitorView.showNetworkInspectorView();
|
||||||
|
}
|
||||||
|
|
||||||
window.emit(EVENTS.TARGET_WILL_NAVIGATE);
|
window.emit(EVENTS.TARGET_WILL_NAVIGATE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -383,7 +489,6 @@ NetworkEventsHandler.prototype = {
|
||||||
_onNetworkEvent: function(aType, aPacket) {
|
_onNetworkEvent: function(aType, aPacket) {
|
||||||
let { actor, startedDateTime, method, url, isXHR } = aPacket.eventActor;
|
let { actor, startedDateTime, method, url, isXHR } = aPacket.eventActor;
|
||||||
NetMonitorView.RequestsMenu.addRequest(actor, startedDateTime, method, url, isXHR);
|
NetMonitorView.RequestsMenu.addRequest(actor, startedDateTime, method, url, isXHR);
|
||||||
|
|
||||||
window.emit(EVENTS.NETWORK_EVENT);
|
window.emit(EVENTS.NETWORK_EVENT);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -585,7 +690,8 @@ let L10N = new ViewHelpers.L10N(NET_STRINGS_URI);
|
||||||
*/
|
*/
|
||||||
let Prefs = new ViewHelpers.Prefs("devtools.netmonitor", {
|
let Prefs = new ViewHelpers.Prefs("devtools.netmonitor", {
|
||||||
networkDetailsWidth: ["Int", "panes-network-details-width"],
|
networkDetailsWidth: ["Int", "panes-network-details-width"],
|
||||||
networkDetailsHeight: ["Int", "panes-network-details-height"]
|
networkDetailsHeight: ["Int", "panes-network-details-height"],
|
||||||
|
statistics: ["Bool", "statistics"]
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -616,6 +722,39 @@ Object.defineProperties(window, {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure certain properties are available on all objects in a data store.
|
||||||
|
*
|
||||||
|
* @param array aDataStore
|
||||||
|
* A list of objects for which to check the availability of properties.
|
||||||
|
* @param array aMandatoryFields
|
||||||
|
* A list of strings representing properties of objects in aDataStore.
|
||||||
|
* @return object
|
||||||
|
* A promise resolved when all objects in aDataStore contain the
|
||||||
|
* properties defined in aMandatoryFields.
|
||||||
|
*/
|
||||||
|
function whenDataAvailable(aDataStore, aMandatoryFields) {
|
||||||
|
let deferred = promise.defer();
|
||||||
|
|
||||||
|
let interval = setInterval(() => {
|
||||||
|
if (aDataStore.every(item => aMandatoryFields.every(field => field in item))) {
|
||||||
|
clearInterval(interval);
|
||||||
|
clearTimeout(timer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
}, WDA_DEFAULT_VERIFY_INTERVAL);
|
||||||
|
|
||||||
|
let timer = setTimeout(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
deferred.reject(new Error("Timed out while waiting for data"));
|
||||||
|
}, WDA_DEFAULT_GIVE_UP_TIMEOUT);
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WDA_DEFAULT_VERIFY_INTERVAL = 50; // ms
|
||||||
|
const WDA_DEFAULT_GIVE_UP_TIMEOUT = 2000; // ms
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method for debugging.
|
* Helper method for debugging.
|
||||||
* @param string
|
* @param string
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
|
||||||
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
||||||
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
||||||
const DEFAULT_HTTP_VERSION = "HTTP/1.1";
|
const DEFAULT_HTTP_VERSION = "HTTP/1.1";
|
||||||
|
const REQUEST_TIME_DECIMALS = 2;
|
||||||
const HEADERS_SIZE_DECIMALS = 3;
|
const HEADERS_SIZE_DECIMALS = 3;
|
||||||
const CONTENT_SIZE_DECIMALS = 2;
|
const CONTENT_SIZE_DECIMALS = 2;
|
||||||
const CONTENT_MIME_TYPE_ABBREVIATIONS = {
|
const CONTENT_MIME_TYPE_ABBREVIATIONS = {
|
||||||
|
|
@ -57,6 +58,7 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = {
|
||||||
eval: () => {},
|
eval: () => {},
|
||||||
switch: () => {}
|
switch: () => {}
|
||||||
};
|
};
|
||||||
|
const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200; // px
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object defining the network monitor view components.
|
* Object defining the network monitor view components.
|
||||||
|
|
@ -102,6 +104,14 @@ let NetMonitorView = {
|
||||||
this._detailsPane.setAttribute("width", Prefs.networkDetailsWidth);
|
this._detailsPane.setAttribute("width", Prefs.networkDetailsWidth);
|
||||||
this._detailsPane.setAttribute("height", Prefs.networkDetailsHeight);
|
this._detailsPane.setAttribute("height", Prefs.networkDetailsHeight);
|
||||||
this.toggleDetailsPane({ visible: false });
|
this.toggleDetailsPane({ visible: false });
|
||||||
|
|
||||||
|
// Disable the performance statistics mode.
|
||||||
|
if (!Prefs.statistics) {
|
||||||
|
$("#request-menu-context-perf").hidden = true;
|
||||||
|
$("#notice-perf-message").hidden = true;
|
||||||
|
$("#requests-menu-network-summary-button").hidden = true;
|
||||||
|
$("#requests-menu-network-summary-label").hidden = true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,8 +131,9 @@ let NetMonitorView = {
|
||||||
* Gets the visibility state of the network details pane.
|
* Gets the visibility state of the network details pane.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
get detailsPaneHidden()
|
get detailsPaneHidden() {
|
||||||
this._detailsPane.hasAttribute("pane-collapsed"),
|
return this._detailsPane.hasAttribute("pane-collapsed");
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the network details pane hidden or visible.
|
* Sets the network details pane hidden or visible.
|
||||||
|
|
@ -157,6 +168,66 @@ let NetMonitorView = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current mode for this tool.
|
||||||
|
* @return string (e.g, "network-inspector-view" or "network-statistics-view")
|
||||||
|
*/
|
||||||
|
get currentFrontendMode() {
|
||||||
|
return this._body.selectedPanel.id;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles between the frontend view modes ("Inspector" vs. "Statistics").
|
||||||
|
*/
|
||||||
|
toggleFrontendMode: function() {
|
||||||
|
if (this.currentFrontendMode != "network-inspector-view") {
|
||||||
|
this.showNetworkInspectorView();
|
||||||
|
} else {
|
||||||
|
this.showNetworkStatisticsView();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches to the "Inspector" frontend view mode.
|
||||||
|
*/
|
||||||
|
showNetworkInspectorView: function() {
|
||||||
|
this._body.selectedPanel = $("#network-inspector-view");
|
||||||
|
this.RequestsMenu._flushWaterfallViews(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.PerformanceStatistics;
|
||||||
|
|
||||||
|
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.attachments, [
|
||||||
|
"responseHeaders", "status", "contentSize", "mimeType", "totalTime"
|
||||||
|
]);
|
||||||
|
} catch (ex) {
|
||||||
|
// Timed out while waiting for data. Continue with what we have.
|
||||||
|
DevToolsUtils.reportException("showNetworkStatisticsView", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
statisticsView.createPrimedCacheChart(requestsView.items);
|
||||||
|
statisticsView.createEmptyCacheChart(requestsView.items);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazily initializes and returns a promise for a Editor instance.
|
* Lazily initializes and returns a promise for a Editor instance.
|
||||||
*
|
*
|
||||||
|
|
@ -263,8 +334,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
dumpn("Initializing the RequestsMenuView");
|
dumpn("Initializing the RequestsMenuView");
|
||||||
|
|
||||||
this.widget = new SideMenuWidget($("#requests-menu-contents"));
|
this.widget = new SideMenuWidget($("#requests-menu-contents"));
|
||||||
this._splitter = $('#splitter');
|
this._splitter = $("#network-inspector-view-splitter");
|
||||||
this._summary = $("#request-menu-network-summary");
|
this._summary = $("#requests-menu-network-summary-label");
|
||||||
|
this._summary.setAttribute("value", L10N.getStr("networkMenu.empty"));
|
||||||
|
|
||||||
this.allowFocusOnRightClick = true;
|
this.allowFocusOnRightClick = true;
|
||||||
this.widget.maintainSelectionVisible = false;
|
this.widget.maintainSelectionVisible = false;
|
||||||
|
|
@ -276,11 +348,12 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
|
|
||||||
this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
|
this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
|
||||||
this.requestsMenuFilterEvent = getKeyWithEvent(this.filterOn.bind(this));
|
this.requestsMenuFilterEvent = getKeyWithEvent(this.filterOn.bind(this));
|
||||||
this.clearEvent = this.clear.bind(this);
|
this.reqeustsMenuClearEvent = this.clear.bind(this);
|
||||||
this._onContextShowing = this._onContextShowing.bind(this);
|
this._onContextShowing = this._onContextShowing.bind(this);
|
||||||
this._onContextNewTabCommand = this.openRequestInTab.bind(this);
|
this._onContextNewTabCommand = this.openRequestInTab.bind(this);
|
||||||
this._onContextCopyUrlCommand = this.copyUrl.bind(this);
|
this._onContextCopyUrlCommand = this.copyUrl.bind(this);
|
||||||
this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
|
this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
|
||||||
|
this._onContextPerfCommand = () => NetMonitorView.toggleFrontendMode();
|
||||||
|
|
||||||
this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
|
this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
|
||||||
this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
|
this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
|
||||||
|
|
@ -288,11 +361,16 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
|
|
||||||
$("#toolbar-labels").addEventListener("click", this.requestsMenuSortEvent, false);
|
$("#toolbar-labels").addEventListener("click", this.requestsMenuSortEvent, false);
|
||||||
$("#requests-menu-footer").addEventListener("click", this.requestsMenuFilterEvent, false);
|
$("#requests-menu-footer").addEventListener("click", this.requestsMenuFilterEvent, false);
|
||||||
$("#requests-menu-clear-button").addEventListener("click", this.clearEvent, false);
|
$("#requests-menu-clear-button").addEventListener("click", this.reqeustsMenuClearEvent, false);
|
||||||
$("#network-request-popup").addEventListener("popupshowing", this._onContextShowing, false);
|
$("#network-request-popup").addEventListener("popupshowing", this._onContextShowing, false);
|
||||||
$("#request-menu-context-newtab").addEventListener("command", this._onContextNewTabCommand, false);
|
$("#request-menu-context-newtab").addEventListener("command", this._onContextNewTabCommand, false);
|
||||||
$("#request-menu-context-copy-url").addEventListener("command", this._onContextCopyUrlCommand, false);
|
$("#request-menu-context-copy-url").addEventListener("command", this._onContextCopyUrlCommand, false);
|
||||||
$("#request-menu-context-resend").addEventListener("command", this._onContextResendCommand, false);
|
$("#request-menu-context-resend").addEventListener("command", this._onContextResendCommand, false);
|
||||||
|
$("#request-menu-context-perf").addEventListener("command", this._onContextPerfCommand, false);
|
||||||
|
|
||||||
|
$("#requests-menu-perf-notice-button").addEventListener("command", this._onContextPerfCommand, false);
|
||||||
|
$("#requests-menu-network-summary-button").addEventListener("command", this._onContextPerfCommand, false);
|
||||||
|
$("#requests-menu-network-summary-label").addEventListener("click", this._onContextPerfCommand, false);
|
||||||
|
|
||||||
$("#custom-request-send-button").addEventListener("click", this.sendCustomRequestEvent, false);
|
$("#custom-request-send-button").addEventListener("click", this.sendCustomRequestEvent, false);
|
||||||
$("#custom-request-close-button").addEventListener("click", this.closeCustomRequestEvent, false);
|
$("#custom-request-close-button").addEventListener("click", this.closeCustomRequestEvent, false);
|
||||||
|
|
@ -311,11 +389,16 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
|
|
||||||
$("#toolbar-labels").removeEventListener("click", this.requestsMenuSortEvent, false);
|
$("#toolbar-labels").removeEventListener("click", this.requestsMenuSortEvent, false);
|
||||||
$("#requests-menu-footer").removeEventListener("click", this.requestsMenuFilterEvent, false);
|
$("#requests-menu-footer").removeEventListener("click", this.requestsMenuFilterEvent, false);
|
||||||
$("#requests-menu-clear-button").removeEventListener("click", this.clearEvent, false);
|
$("#requests-menu-clear-button").removeEventListener("click", this.reqeustsMenuClearEvent, false);
|
||||||
$("#network-request-popup").removeEventListener("popupshowing", this._onContextShowing, false);
|
$("#network-request-popup").removeEventListener("popupshowing", this._onContextShowing, false);
|
||||||
$("#request-menu-context-newtab").removeEventListener("command", this._onContextNewTabCommand, false);
|
$("#request-menu-context-newtab").removeEventListener("command", this._onContextNewTabCommand, false);
|
||||||
$("#request-menu-context-copy-url").removeEventListener("command", this._onContextCopyUrlCommand, false);
|
$("#request-menu-context-copy-url").removeEventListener("command", this._onContextCopyUrlCommand, false);
|
||||||
$("#request-menu-context-resend").removeEventListener("command", this._onContextResendCommand, false);
|
$("#request-menu-context-resend").removeEventListener("command", this._onContextResendCommand, false);
|
||||||
|
$("#request-menu-context-perf").removeEventListener("command", this._onContextPerfCommand, false);
|
||||||
|
|
||||||
|
$("#requests-menu-perf-notice-button").removeEventListener("command", this._onContextPerfCommand, false);
|
||||||
|
$("#requests-menu-network-summary-button").removeEventListener("command", this._onContextPerfCommand, false);
|
||||||
|
$("#requests-menu-network-summary-label").removeEventListener("click", this._onContextPerfCommand, false);
|
||||||
|
|
||||||
$("#custom-request-send-button").removeEventListener("click", this.sendCustomRequestEvent, false);
|
$("#custom-request-send-button").removeEventListener("click", this.sendCustomRequestEvent, false);
|
||||||
$("#custom-request-close-button").removeEventListener("click", this.closeCustomRequestEvent, false);
|
$("#custom-request-close-button").removeEventListener("click", this.closeCustomRequestEvent, false);
|
||||||
|
|
@ -327,6 +410,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
*/
|
*/
|
||||||
reset: function() {
|
reset: function() {
|
||||||
this.empty();
|
this.empty();
|
||||||
|
this.filterOn("all");
|
||||||
this._firstRequestStartedMillis = -1;
|
this._firstRequestStartedMillis = -1;
|
||||||
this._lastRequestEndedMillis = -1;
|
this._lastRequestEndedMillis = -1;
|
||||||
},
|
},
|
||||||
|
|
@ -394,6 +478,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
// Create the element node for the network request item.
|
// Create the element node for the network request item.
|
||||||
let menuView = this._createMenuView(selected.method, selected.url);
|
let menuView = this._createMenuView(selected.method, selected.url);
|
||||||
|
|
||||||
|
// Append a network request item to this container.
|
||||||
let newItem = this.push([menuView], {
|
let newItem = this.push([menuView], {
|
||||||
attachment: Object.create(selected, {
|
attachment: Object.create(selected, {
|
||||||
isCustom: { value: true }
|
isCustom: { value: true }
|
||||||
|
|
@ -457,7 +542,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
*
|
*
|
||||||
* @param string aType
|
* @param string aType
|
||||||
* Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
|
* Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
|
||||||
* or "flash".
|
* "flash" or "other".
|
||||||
*/
|
*/
|
||||||
filterOn: function(aType = "all") {
|
filterOn: function(aType = "all") {
|
||||||
let target = $("#requests-menu-filter-" + aType + "-button");
|
let target = $("#requests-menu-filter-" + aType + "-button");
|
||||||
|
|
@ -477,28 +562,31 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
this.filterContents(() => true);
|
this.filterContents(() => true);
|
||||||
break;
|
break;
|
||||||
case "html":
|
case "html":
|
||||||
this.filterContents(this._onHtml);
|
this.filterContents(e => this.isHtml(e));
|
||||||
break;
|
break;
|
||||||
case "css":
|
case "css":
|
||||||
this.filterContents(this._onCss);
|
this.filterContents(e => this.isCss(e));
|
||||||
break;
|
break;
|
||||||
case "js":
|
case "js":
|
||||||
this.filterContents(this._onJs);
|
this.filterContents(e => this.isJs(e));
|
||||||
break;
|
break;
|
||||||
case "xhr":
|
case "xhr":
|
||||||
this.filterContents(this._onXhr);
|
this.filterContents(e => this.isXHR(e));
|
||||||
break;
|
break;
|
||||||
case "fonts":
|
case "fonts":
|
||||||
this.filterContents(this._onFonts);
|
this.filterContents(e => this.isFont(e));
|
||||||
break;
|
break;
|
||||||
case "images":
|
case "images":
|
||||||
this.filterContents(this._onImages);
|
this.filterContents(e => this.isImage(e));
|
||||||
break;
|
break;
|
||||||
case "media":
|
case "media":
|
||||||
this.filterContents(this._onMedia);
|
this.filterContents(e => this.isMedia(e));
|
||||||
break;
|
break;
|
||||||
case "flash":
|
case "flash":
|
||||||
this.filterContents(this._onFlash);
|
this.filterContents(e => this.isFlash(e));
|
||||||
|
break;
|
||||||
|
case "other":
|
||||||
|
this.filterContents(e => this.isOther(e));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -611,22 +699,22 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
* @return boolean
|
* @return boolean
|
||||||
* True if the item should be visible, false otherwise.
|
* True if the item should be visible, false otherwise.
|
||||||
*/
|
*/
|
||||||
_onHtml: function({ attachment: { mimeType } })
|
isHtml: function({ attachment: { mimeType } })
|
||||||
mimeType && mimeType.contains("/html"),
|
mimeType && mimeType.contains("/html"),
|
||||||
|
|
||||||
_onCss: function({ attachment: { mimeType } })
|
isCss: function({ attachment: { mimeType } })
|
||||||
mimeType && mimeType.contains("/css"),
|
mimeType && mimeType.contains("/css"),
|
||||||
|
|
||||||
_onJs: function({ attachment: { mimeType } })
|
isJs: function({ attachment: { mimeType } })
|
||||||
mimeType && (
|
mimeType && (
|
||||||
mimeType.contains("/ecmascript") ||
|
mimeType.contains("/ecmascript") ||
|
||||||
mimeType.contains("/javascript") ||
|
mimeType.contains("/javascript") ||
|
||||||
mimeType.contains("/x-javascript")),
|
mimeType.contains("/x-javascript")),
|
||||||
|
|
||||||
_onXhr: function({ attachment: { isXHR } })
|
isXHR: function({ attachment: { isXHR } })
|
||||||
isXHR,
|
isXHR,
|
||||||
|
|
||||||
_onFonts: function({ attachment: { url, mimeType } }) // Fonts are a mess.
|
isFont: function({ attachment: { url, mimeType } }) // Fonts are a mess.
|
||||||
(mimeType && (
|
(mimeType && (
|
||||||
mimeType.contains("font/") ||
|
mimeType.contains("font/") ||
|
||||||
mimeType.contains("/font"))) ||
|
mimeType.contains("/font"))) ||
|
||||||
|
|
@ -635,22 +723,26 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
url.contains(".otf") ||
|
url.contains(".otf") ||
|
||||||
url.contains(".woff"),
|
url.contains(".woff"),
|
||||||
|
|
||||||
_onImages: function({ attachment: { mimeType } })
|
isImage: function({ attachment: { mimeType } })
|
||||||
mimeType && mimeType.contains("image/"),
|
mimeType && mimeType.contains("image/"),
|
||||||
|
|
||||||
_onMedia: function({ attachment: { mimeType } }) // Not including images.
|
isMedia: function({ attachment: { mimeType } }) // Not including images.
|
||||||
mimeType && (
|
mimeType && (
|
||||||
mimeType.contains("audio/") ||
|
mimeType.contains("audio/") ||
|
||||||
mimeType.contains("video/") ||
|
mimeType.contains("video/") ||
|
||||||
mimeType.contains("model/")),
|
mimeType.contains("model/")),
|
||||||
|
|
||||||
_onFlash: function({ attachment: { url, mimeType } }) // Flash is a mess.
|
isFlash: function({ attachment: { url, mimeType } }) // Flash is a mess.
|
||||||
(mimeType && (
|
(mimeType && (
|
||||||
mimeType.contains("/x-flv") ||
|
mimeType.contains("/x-flv") ||
|
||||||
mimeType.contains("/x-shockwave-flash"))) ||
|
mimeType.contains("/x-shockwave-flash"))) ||
|
||||||
url.contains(".swf") ||
|
url.contains(".swf") ||
|
||||||
url.contains(".flv"),
|
url.contains(".flv"),
|
||||||
|
|
||||||
|
isOther: function(e)
|
||||||
|
!this.isHtml(e) && !this.isCss(e) && !this.isJs(e) && !this.isXHR(e) &&
|
||||||
|
!this.isFont(e) && !this.isImage(e) && !this.isMedia(e) && !this.isFlash(e),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Predicates used when sorting items.
|
* Predicates used when sorting items.
|
||||||
*
|
*
|
||||||
|
|
@ -724,8 +816,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
let str = PluralForm.get(visibleRequestsCount, L10N.getStr("networkMenu.summary"));
|
let str = PluralForm.get(visibleRequestsCount, L10N.getStr("networkMenu.summary"));
|
||||||
this._summary.setAttribute("value", str
|
this._summary.setAttribute("value", str
|
||||||
.replace("#1", visibleRequestsCount)
|
.replace("#1", visibleRequestsCount)
|
||||||
.replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024, 2))
|
.replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024, CONTENT_SIZE_DECIMALS))
|
||||||
.replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000, 2))
|
.replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000, REQUEST_TIME_DECIMALS))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -838,6 +930,12 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
break;
|
break;
|
||||||
case "responseContent":
|
case "responseContent":
|
||||||
requestItem.attachment.responseContent = value;
|
requestItem.attachment.responseContent = value;
|
||||||
|
// If there's no mime type available when the response content
|
||||||
|
// is received, assume text/plain as a fallback.
|
||||||
|
if (!requestItem.attachment.mimeType) {
|
||||||
|
requestItem.attachment.mimeType = "text/plain";
|
||||||
|
this.updateMenuView(requestItem, "mimeType", "text/plain");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "totalTime":
|
case "totalTime":
|
||||||
requestItem.attachment.totalTime = value;
|
requestItem.attachment.totalTime = value;
|
||||||
|
|
@ -1021,6 +1119,11 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
startCapNode.hidden = false;
|
startCapNode.hidden = false;
|
||||||
endCapNode.hidden = false;
|
endCapNode.hidden = false;
|
||||||
|
|
||||||
|
// Don't paint things while the waterfall view isn't even visible.
|
||||||
|
if (NetMonitorView.currentFrontendMode != "network-inspector-view") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Rescale all the waterfalls so that everything is visible at once.
|
// Rescale all the waterfalls so that everything is visible at once.
|
||||||
this._flushWaterfallViews();
|
this._flushWaterfallViews();
|
||||||
},
|
},
|
||||||
|
|
@ -1134,7 +1237,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
if (divisionScale == "millisecond") {
|
if (divisionScale == "millisecond") {
|
||||||
normalizedTime |= 0;
|
normalizedTime |= 0;
|
||||||
} else {
|
} else {
|
||||||
normalizedTime = L10N.numberWithDecimals(normalizedTime, 2);
|
normalizedTime = L10N.numberWithDecimals(normalizedTime, REQUEST_TIME_DECIMALS);
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = document.createElement("label");
|
let node = document.createElement("label");
|
||||||
|
|
@ -1263,6 +1366,11 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||||
* The resize listener for this container's window.
|
* The resize listener for this container's window.
|
||||||
*/
|
*/
|
||||||
_onResize: function(e) {
|
_onResize: function(e) {
|
||||||
|
// Don't paint things while the waterfall view isn't even visible.
|
||||||
|
if (NetMonitorView.currentFrontendMode != "network-inspector-view") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Allow requests to settle down first.
|
// Allow requests to settle down first.
|
||||||
setNamedTimeout(
|
setNamedTimeout(
|
||||||
"resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
|
"resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
|
||||||
|
|
@ -1453,7 +1561,7 @@ SidebarView.prototype = {
|
||||||
|
|
||||||
return view.populate(aData).then(() => {
|
return view.populate(aData).then(() => {
|
||||||
$("#details-pane").selectedIndex = isCustom ? 0 : 1
|
$("#details-pane").selectedIndex = isCustom ? 0 : 1
|
||||||
window.emit(EVENTS.SIDEBAR_POPULATED)
|
window.emit(EVENTS.SIDEBAR_POPULATED);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1480,7 +1588,6 @@ CustomRequestView.prototype = {
|
||||||
dumpn("Initializing the CustomRequestView");
|
dumpn("Initializing the CustomRequestView");
|
||||||
|
|
||||||
this.updateCustomRequestEvent = getKeyWithEvent(this.onUpdate.bind(this));
|
this.updateCustomRequestEvent = getKeyWithEvent(this.onUpdate.bind(this));
|
||||||
|
|
||||||
$("#custom-pane").addEventListener("input", this.updateCustomRequestEvent, false);
|
$("#custom-pane").addEventListener("input", this.updateCustomRequestEvent, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1555,18 +1662,12 @@ CustomRequestView.prototype = {
|
||||||
break;
|
break;
|
||||||
case 'body':
|
case 'body':
|
||||||
value = $("#custom-postdata-value").value;
|
value = $("#custom-postdata-value").value;
|
||||||
selectedItem.attachment.requestPostData = {
|
selectedItem.attachment.requestPostData = { postData: { text: value } };
|
||||||
postData: {
|
|
||||||
text: value
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case 'headers':
|
case 'headers':
|
||||||
let headersText = $("#custom-headers-value").value;
|
let headersText = $("#custom-headers-value").value;
|
||||||
value = parseHeaderText(headersText);
|
value = parseHeaderText(headersText);
|
||||||
selectedItem.attachment.requestHeaders = {
|
selectedItem.attachment.requestHeaders = { headers: value };
|
||||||
headers: value
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2172,6 +2273,170 @@ NetworkDetailsView.prototype = {
|
||||||
_responseCookies: ""
|
_responseCookies: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions handling the performance statistics view.
|
||||||
|
*/
|
||||||
|
function PerformanceStatisticsView() {
|
||||||
|
}
|
||||||
|
|
||||||
|
PerformanceStatisticsView.prototype = {
|
||||||
|
/**
|
||||||
|
* 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 aItems
|
||||||
|
* @see this._sanitizeChartDataSource
|
||||||
|
*/
|
||||||
|
createPrimedCacheChart: function(aItems) {
|
||||||
|
this._createChart({
|
||||||
|
id: "#primed-cache-chart",
|
||||||
|
title: "charts.cacheEnabled",
|
||||||
|
data: this._sanitizeChartDataSource(aItems),
|
||||||
|
sorted: true,
|
||||||
|
totals: {
|
||||||
|
size: L10N.getStr("charts.totalSize"),
|
||||||
|
time: L10N.getStr("charts.totalTime"),
|
||||||
|
cached: L10N.getStr("charts.totalCached"),
|
||||||
|
count: L10N.getStr("charts.totalCount")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.emit(EVENTS.PRIMED_CACHE_CHART_DISPLAYED);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates and displays the empty cache chart in this container.
|
||||||
|
*
|
||||||
|
* @param array aItems
|
||||||
|
* @see this._sanitizeChartDataSource
|
||||||
|
*/
|
||||||
|
createEmptyCacheChart: function(aItems) {
|
||||||
|
this._createChart({
|
||||||
|
id: "#empty-cache-chart",
|
||||||
|
title: "charts.cacheDisabled",
|
||||||
|
data: this._sanitizeChartDataSource(aItems, true),
|
||||||
|
sorted: true,
|
||||||
|
totals: {
|
||||||
|
size: L10N.getStr("charts.totalSize"),
|
||||||
|
time: L10N.getStr("charts.totalTime"),
|
||||||
|
cached: L10N.getStr("charts.totalCached"),
|
||||||
|
count: L10N.getStr("charts.totalCount")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.emit(EVENTS.EMPTY_CACHE_CHART_DISPLAYED);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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/sorted/totals: @see Chart.jsm for details
|
||||||
|
*/
|
||||||
|
_createChart: function({ id, title, data, sorted, totals }) {
|
||||||
|
let container = $(id);
|
||||||
|
|
||||||
|
// Nuke all existing charts of the specified type.
|
||||||
|
while (container.hasChildNodes()) {
|
||||||
|
container.firstChild.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new chart.
|
||||||
|
let chart = Chart.PieTable(document, {
|
||||||
|
diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER,
|
||||||
|
title: L10N.getStr(title),
|
||||||
|
data: data,
|
||||||
|
sorted: sorted,
|
||||||
|
totals: totals
|
||||||
|
});
|
||||||
|
|
||||||
|
chart.on("click", (_, item) => {
|
||||||
|
NetMonitorView.RequestsMenu.filterOn(item.label);
|
||||||
|
NetMonitorView.showNetworkInspectorView();
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(chart.node);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes the data source used for creating charts, to follow the
|
||||||
|
* data format spec defined in Chart.jsm.
|
||||||
|
*
|
||||||
|
* @param array aItems
|
||||||
|
* A collection of request items used as the data source for the chart.
|
||||||
|
* @param boolean aEmptyCache
|
||||||
|
* True if the cache is considered enabled, false for disabled.
|
||||||
|
*/
|
||||||
|
_sanitizeChartDataSource: function(aItems, aEmptyCache) {
|
||||||
|
let data = [
|
||||||
|
"html", "css", "js", "xhr", "fonts", "images", "media", "flash", "other"
|
||||||
|
].map(e => ({
|
||||||
|
cached: 0,
|
||||||
|
count: 0,
|
||||||
|
label: e,
|
||||||
|
size: 0,
|
||||||
|
time: 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (let requestItem of aItems) {
|
||||||
|
let details = requestItem.attachment;
|
||||||
|
let type;
|
||||||
|
|
||||||
|
if (RequestsMenuView.prototype.isHtml(requestItem)) {
|
||||||
|
type = 0; // "html"
|
||||||
|
} else if (RequestsMenuView.prototype.isCss(requestItem)) {
|
||||||
|
type = 1; // "css"
|
||||||
|
} else if (RequestsMenuView.prototype.isJs(requestItem)) {
|
||||||
|
type = 2; // "js"
|
||||||
|
} else if (RequestsMenuView.prototype.isFont(requestItem)) {
|
||||||
|
type = 4; // "fonts"
|
||||||
|
} else if (RequestsMenuView.prototype.isImage(requestItem)) {
|
||||||
|
type = 5; // "images"
|
||||||
|
} else if (RequestsMenuView.prototype.isMedia(requestItem)) {
|
||||||
|
type = 6; // "media"
|
||||||
|
} else if (RequestsMenuView.prototype.isFlash(requestItem)) {
|
||||||
|
type = 7; // "flash"
|
||||||
|
} else if (RequestsMenuView.prototype.isXHR(requestItem)) {
|
||||||
|
// Verify XHR last, to categorize other mime types in their own blobs.
|
||||||
|
type = 3; // "xhr"
|
||||||
|
} else {
|
||||||
|
type = 8; // "other"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aEmptyCache || !responseIsFresh(details)) {
|
||||||
|
data[type].time += details.totalTime || 0;
|
||||||
|
data[type].size += details.contentSize || 0;
|
||||||
|
} else {
|
||||||
|
data[type].cached++;
|
||||||
|
}
|
||||||
|
data[type].count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let chartItem of data) {
|
||||||
|
let size = L10N.numberWithDecimals(chartItem.size / 1024, CONTENT_SIZE_DECIMALS);
|
||||||
|
let time = L10N.numberWithDecimals(chartItem.time / 1000, REQUEST_TIME_DECIMALS);
|
||||||
|
chartItem.size = L10N.getFormatStr("charts.sizeKB", size);
|
||||||
|
chartItem.time = L10N.getFormatStr("charts.totalMS", time);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.filter(e => e.count > 0);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DOM query helper.
|
* DOM query helper.
|
||||||
*/
|
*/
|
||||||
|
|
@ -2194,8 +2459,8 @@ nsIURL.store = new Map();
|
||||||
/**
|
/**
|
||||||
* Parse a url's query string into its components
|
* Parse a url's query string into its components
|
||||||
*
|
*
|
||||||
* @param string aQueryString
|
* @param string aQueryString
|
||||||
* The query part of a url
|
* The query part of a url
|
||||||
* @return array
|
* @return array
|
||||||
* Array of query params {name, value}
|
* Array of query params {name, value}
|
||||||
*/
|
*/
|
||||||
|
|
@ -2216,8 +2481,8 @@ function parseQueryString(aQueryString) {
|
||||||
/**
|
/**
|
||||||
* Parse text representation of HTTP headers.
|
* Parse text representation of HTTP headers.
|
||||||
*
|
*
|
||||||
* @param string aText
|
* @param string aText
|
||||||
* Text of headers
|
* Text of headers
|
||||||
* @return array
|
* @return array
|
||||||
* Array of headers info {name, value}
|
* Array of headers info {name, value}
|
||||||
*/
|
*/
|
||||||
|
|
@ -2228,8 +2493,8 @@ function parseHeaderText(aText) {
|
||||||
/**
|
/**
|
||||||
* Parse readable text list of a query string.
|
* Parse readable text list of a query string.
|
||||||
*
|
*
|
||||||
* @param string aText
|
* @param string aText
|
||||||
* Text of query string represetation
|
* Text of query string represetation
|
||||||
* @return array
|
* @return array
|
||||||
* Array of query params {name, value}
|
* Array of query params {name, value}
|
||||||
*/
|
*/
|
||||||
|
|
@ -2241,8 +2506,8 @@ function parseQueryText(aText) {
|
||||||
* Parse a text representation of a name:value list with
|
* Parse a text representation of a name:value list with
|
||||||
* the given name:value divider character.
|
* the given name:value divider character.
|
||||||
*
|
*
|
||||||
* @param string aText
|
* @param string aText
|
||||||
* Text of list
|
* Text of list
|
||||||
* @return array
|
* @return array
|
||||||
* Array of headers info {name, value}
|
* Array of headers info {name, value}
|
||||||
*/
|
*/
|
||||||
|
|
@ -2295,6 +2560,44 @@ function writeQueryString(aParams) {
|
||||||
return [(name + "=" + value) for ({name, value} of aParams)].join("&");
|
return [(name + "=" + value) for ({name, value} of aParams)].join("&");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.filter(e => e.name.toLowerCase() == "cache-control")[0];
|
||||||
|
let expires = list.filter(e => e.name.toLowerCase() == "expires")[0];
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to get a wrapped function which can be bound to as an event listener directly and is executed only when data-key is present in event.target.
|
* Helper method to get a wrapped function which can be bound to as an event listener directly and is executed only when data-key is present in event.target.
|
||||||
*
|
*
|
||||||
|
|
@ -2320,3 +2623,4 @@ NetMonitorView.RequestsMenu = new RequestsMenuView();
|
||||||
NetMonitorView.Sidebar = new SidebarView();
|
NetMonitorView.Sidebar = new SidebarView();
|
||||||
NetMonitorView.CustomRequest = new CustomRequestView();
|
NetMonitorView.CustomRequest = new CustomRequestView();
|
||||||
NetMonitorView.NetworkDetails = new NetworkDetailsView();
|
NetMonitorView.NetworkDetails = new NetworkDetailsView();
|
||||||
|
NetMonitorView.PerformanceStatistics = new PerformanceStatisticsView();
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#response-content-image-box {
|
#custom-pane {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#custom-pane {
|
#response-content-image-box {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,6 +24,10 @@
|
||||||
display: none; /* This doesn't work yet. */
|
display: none; /* This doesn't work yet. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#network-statistics-charts {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive sidebar */
|
/* Responsive sidebar */
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
#toolbar-spacer,
|
#toolbar-spacer,
|
||||||
|
|
@ -35,18 +39,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 701px) and (max-width: 1024px) {
|
@media (min-width: 701px) and (max-width: 1280px) {
|
||||||
#body:not([pane-collapsed]) .requests-menu-footer-button,
|
#body:not([pane-collapsed]) .requests-menu-filter-button,
|
||||||
#body:not([pane-collapsed]) .requests-menu-footer-spacer {
|
#body:not([pane-collapsed]) .requests-menu-footer-spacer {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 701px) {
|
@media (min-width: 701px) {
|
||||||
#requests-menu-spacer-start {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#network-table[waterfall-overflows] .requests-menu-waterfall {
|
#network-table[waterfall-overflows] .requests-menu-waterfall {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,16 @@
|
||||||
<menuitem id="request-menu-context-resend"
|
<menuitem id="request-menu-context-resend"
|
||||||
label="&netmonitorUI.summary.editAndResend;"
|
label="&netmonitorUI.summary.editAndResend;"
|
||||||
accesskey="&netmonitorUI.summary.editAndResend.accesskey;"/>
|
accesskey="&netmonitorUI.summary.editAndResend.accesskey;"/>
|
||||||
|
<menuseparator/>
|
||||||
|
<menuitem id="request-menu-context-perf"
|
||||||
|
label="&netmonitorUI.context.perfTools;"
|
||||||
|
accesskey="&netmonitorUI.context.perfTools.accesskey;"/>
|
||||||
</menupopup>
|
</menupopup>
|
||||||
</popupset>
|
</popupset>
|
||||||
|
|
||||||
<box id="body"
|
<deck id="body" class="theme-sidebar" flex="1">
|
||||||
class="devtools-responsive-container theme-sidebar"
|
|
||||||
flex="1">
|
<box id="network-inspector-view" class="devtools-responsive-container">
|
||||||
<vbox id="network-table" flex="1">
|
<vbox id="network-table" flex="1">
|
||||||
<toolbar id="requests-menu-toolbar"
|
<toolbar id="requests-menu-toolbar"
|
||||||
class="devtools-toolbar"
|
class="devtools-toolbar"
|
||||||
|
|
@ -118,9 +122,20 @@
|
||||||
disabled="true"
|
disabled="true"
|
||||||
tabindex="0"/>
|
tabindex="0"/>
|
||||||
</toolbar>
|
</toolbar>
|
||||||
<label id="requests-menu-empty-notice"
|
|
||||||
class="side-menu-widget-empty-text"
|
<vbox id="requests-menu-empty-notice"
|
||||||
value="&netmonitorUI.emptyNotice2;"/>
|
class="side-menu-widget-empty-text">
|
||||||
|
<hbox id="notice-perf-message" align="center">
|
||||||
|
<label value="&netmonitorUI.perfNotice1;"/>
|
||||||
|
<button id="requests-menu-perf-notice-button"
|
||||||
|
class="devtools-toolbarbutton"/>
|
||||||
|
<label value="&netmonitorUI.perfNotice2;"/>
|
||||||
|
</hbox>
|
||||||
|
<hbox id="notice-reload-message" align="center">
|
||||||
|
<label value="&netmonitorUI.emptyNotice3;"/>
|
||||||
|
</hbox>
|
||||||
|
</vbox>
|
||||||
|
|
||||||
<vbox id="requests-menu-contents" flex="1" context="network-request-popup">
|
<vbox id="requests-menu-contents" flex="1" context="network-request-popup">
|
||||||
<hbox id="requests-menu-item-template" hidden="true">
|
<hbox id="requests-menu-item-template" hidden="true">
|
||||||
<hbox class="requests-menu-subitem requests-menu-status-and-method"
|
<hbox class="requests-menu-subitem requests-menu-status-and-method"
|
||||||
|
|
@ -151,70 +166,70 @@
|
||||||
</hbox>
|
</hbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
<hbox id="requests-menu-footer">
|
<hbox id="requests-menu-footer">
|
||||||
<spacer id="requests-menu-spacer-start"
|
|
||||||
class="requests-menu-footer-spacer"
|
|
||||||
flex="100"/>
|
|
||||||
<button id="requests-menu-filter-all-button"
|
<button id="requests-menu-filter-all-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
checked="true"
|
checked="true"
|
||||||
data-key="all"
|
data-key="all"
|
||||||
label="&netmonitorUI.footer.filterAll;">
|
label="&netmonitorUI.footer.filterAll;">
|
||||||
</button>
|
</button>
|
||||||
<button id="requests-menu-filter-html-button"
|
<button id="requests-menu-filter-html-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
data-key="html"
|
data-key="html"
|
||||||
label="&netmonitorUI.footer.filterHTML;">
|
label="&netmonitorUI.footer.filterHTML;">
|
||||||
</button>
|
</button>
|
||||||
<button id="requests-menu-filter-css-button"
|
<button id="requests-menu-filter-css-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
data-key="css"
|
data-key="css"
|
||||||
label="&netmonitorUI.footer.filterCSS;">
|
label="&netmonitorUI.footer.filterCSS;">
|
||||||
</button>
|
</button>
|
||||||
<button id="requests-menu-filter-js-button"
|
<button id="requests-menu-filter-js-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
data-key="js"
|
data-key="js"
|
||||||
label="&netmonitorUI.footer.filterJS;">
|
label="&netmonitorUI.footer.filterJS;">
|
||||||
</button>
|
</button>
|
||||||
<button id="requests-menu-filter-xhr-button"
|
<button id="requests-menu-filter-xhr-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
data-key="xhr"
|
data-key="xhr"
|
||||||
label="&netmonitorUI.footer.filterXHR;">
|
label="&netmonitorUI.footer.filterXHR;">
|
||||||
</button>
|
</button>
|
||||||
<button id="requests-menu-filter-fonts-button"
|
<button id="requests-menu-filter-fonts-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
data-key="fonts"
|
data-key="fonts"
|
||||||
label="&netmonitorUI.footer.filterFonts;">
|
label="&netmonitorUI.footer.filterFonts;">
|
||||||
</button>
|
</button>
|
||||||
<button id="requests-menu-filter-images-button"
|
<button id="requests-menu-filter-images-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
data-key="images"
|
data-key="images"
|
||||||
label="&netmonitorUI.footer.filterImages;">
|
label="&netmonitorUI.footer.filterImages;">
|
||||||
</button>
|
</button>
|
||||||
<button id="requests-menu-filter-media-button"
|
<button id="requests-menu-filter-media-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
data-key="media"
|
data-key="media"
|
||||||
label="&netmonitorUI.footer.filterMedia;">
|
label="&netmonitorUI.footer.filterMedia;">
|
||||||
</button>
|
</button>
|
||||||
<button id="requests-menu-filter-flash-button"
|
<button id="requests-menu-filter-flash-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-filter-button requests-menu-footer-button"
|
||||||
data-key="flash"
|
data-key="flash"
|
||||||
label="&netmonitorUI.footer.filterFlash;">
|
label="&netmonitorUI.footer.filterFlash;">
|
||||||
</button>
|
</button>
|
||||||
<spacer id="requests-menu-spacer-end"
|
<spacer id="requests-menu-spacer"
|
||||||
class="requests-menu-footer-spacer"
|
class="requests-menu-footer-spacer"
|
||||||
flex="100"/>
|
flex="100"/>
|
||||||
<label id="request-menu-network-summary"
|
<button id="requests-menu-network-summary-button"
|
||||||
|
class="requests-menu-footer-button"
|
||||||
|
tooltiptext="&netmonitorUI.footer.perf;"/>
|
||||||
|
<label id="requests-menu-network-summary-label"
|
||||||
class="plain requests-menu-footer-label"
|
class="plain requests-menu-footer-label"
|
||||||
flex="1"
|
crop="end"
|
||||||
crop="end"/>
|
tooltiptext="&netmonitorUI.footer.perf;"/>
|
||||||
<button id="requests-menu-clear-button"
|
<button id="requests-menu-clear-button"
|
||||||
class="requests-menu-footer-button"
|
class="requests-menu-footer-button"
|
||||||
label="&netmonitorUI.footer.clear;">
|
label="&netmonitorUI.footer.clear;"/>
|
||||||
</button>
|
|
||||||
</hbox>
|
</hbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
|
|
||||||
<splitter id="splitter" class="devtools-side-splitter"/>
|
<splitter id="network-inspector-view-splitter"
|
||||||
|
class="devtools-side-splitter"/>
|
||||||
|
|
||||||
<deck id="details-pane"
|
<deck id="details-pane"
|
||||||
hidden="true">
|
hidden="true">
|
||||||
|
|
@ -460,4 +475,24 @@
|
||||||
</deck>
|
</deck>
|
||||||
</box>
|
</box>
|
||||||
|
|
||||||
|
<box id="network-statistics-view">
|
||||||
|
<toolbar id="network-statistics-toolbar"
|
||||||
|
class="devtools-toolbar">
|
||||||
|
<button id="network-statistics-back-button"
|
||||||
|
class="devtools-toolbarbutton"
|
||||||
|
onclick="NetMonitorView.toggleFrontendMode()"
|
||||||
|
label="&netmonitorUI.backButton;"/>
|
||||||
|
</toolbar>
|
||||||
|
<box id="network-statistics-charts"
|
||||||
|
class="devtools-responsive-container"
|
||||||
|
flex="1">
|
||||||
|
<vbox id="primed-cache-chart" pack="center" flex="1"/>
|
||||||
|
<splitter id="network-statistics-view-splitter"
|
||||||
|
class="devtools-side-splitter"/>
|
||||||
|
<vbox id="empty-cache-chart" pack="center" flex="1"/>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
|
||||||
|
</deck>
|
||||||
|
|
||||||
</window>
|
</window>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ support-files =
|
||||||
html_post-raw-test-page.html
|
html_post-raw-test-page.html
|
||||||
html_simple-test-page.html
|
html_simple-test-page.html
|
||||||
html_sorting-test-page.html
|
html_sorting-test-page.html
|
||||||
|
html_statistics-test-page.html
|
||||||
html_status-codes-test-page.html
|
html_status-codes-test-page.html
|
||||||
sjs_content-type-test-server.sjs
|
sjs_content-type-test-server.sjs
|
||||||
sjs_simple-test-server.sjs
|
sjs_simple-test-server.sjs
|
||||||
|
|
@ -26,6 +27,11 @@ support-files =
|
||||||
[browser_net_accessibility-01.js]
|
[browser_net_accessibility-01.js]
|
||||||
[browser_net_accessibility-02.js]
|
[browser_net_accessibility-02.js]
|
||||||
[browser_net_autoscroll.js]
|
[browser_net_autoscroll.js]
|
||||||
|
[browser_net_charts-01.js]
|
||||||
|
[browser_net_charts-02.js]
|
||||||
|
[browser_net_charts-03.js]
|
||||||
|
[browser_net_charts-04.js]
|
||||||
|
[browser_net_charts-05.js]
|
||||||
[browser_net_clear.js]
|
[browser_net_clear.js]
|
||||||
[browser_net_content-type.js]
|
[browser_net_content-type.js]
|
||||||
[browser_net_copy_url.js]
|
[browser_net_copy_url.js]
|
||||||
|
|
@ -57,6 +63,8 @@ support-files =
|
||||||
[browser_net_sort-01.js]
|
[browser_net_sort-01.js]
|
||||||
[browser_net_sort-02.js]
|
[browser_net_sort-02.js]
|
||||||
[browser_net_sort-03.js]
|
[browser_net_sort-03.js]
|
||||||
|
[browser_net_statistics-01.js]
|
||||||
|
[browser_net_statistics-02.js]
|
||||||
[browser_net_status-codes.js]
|
[browser_net_status-codes.js]
|
||||||
[browser_net_timeline_ticks.js]
|
[browser_net_timeline_ticks.js]
|
||||||
[browser_net_timing-division.js]
|
[browser_net_timing-division.js]
|
||||||
|
|
|
||||||
70
browser/devtools/netmonitor/test/browser_net_charts-01.js
Normal file
70
browser/devtools/netmonitor/test/browser_net_charts-01.js
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure Pie Charts have the right internal structure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
|
info("Starting test... ");
|
||||||
|
|
||||||
|
let { document, Chart } = aMonitor.panelWin;
|
||||||
|
let container = document.createElement("box");
|
||||||
|
|
||||||
|
let pie = Chart.Pie(document, {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
data: [{
|
||||||
|
size: 1,
|
||||||
|
label: "foo"
|
||||||
|
}, {
|
||||||
|
size: 2,
|
||||||
|
label: "bar"
|
||||||
|
}, {
|
||||||
|
size: 3,
|
||||||
|
label: "baz"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
let node = pie.node;
|
||||||
|
let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
|
||||||
|
let labels = node.querySelectorAll(".pie-chart-label");
|
||||||
|
|
||||||
|
ok(node.classList.contains("pie-chart-container") &&
|
||||||
|
node.classList.contains("generic-chart-container"),
|
||||||
|
"A pie chart container was created successfully.");
|
||||||
|
|
||||||
|
is(slices.length, 3,
|
||||||
|
"There should be 3 pie chart slices created.");
|
||||||
|
ok(slices[0].getAttribute("d").match(/\s*M 50,50 L 49\.\d+,97\.\d+ A 47\.5,47\.5 0 0 1 49\.\d+,2\.5\d* Z/),
|
||||||
|
"The first slice has the correct data.");
|
||||||
|
ok(slices[1].getAttribute("d").match(/\s*M 50,50 L 91\.\d+,26\.\d+ A 47\.5,47\.5 0 0 1 49\.\d+,97\.\d+ Z/),
|
||||||
|
"The second slice has the correct data.");
|
||||||
|
ok(slices[2].getAttribute("d").match(/\s*M 50,50 L 50\.\d+,2\.5\d* A 47\.5,47\.5 0 0 1 91\.\d+,26\.\d+ Z/),
|
||||||
|
"The third slice has the correct data.");
|
||||||
|
|
||||||
|
ok(slices[0].hasAttribute("largest"),
|
||||||
|
"The first slice should be the largest one.");
|
||||||
|
ok(slices[2].hasAttribute("smallest"),
|
||||||
|
"The third slice should be the smallest one.");
|
||||||
|
|
||||||
|
ok(slices[0].getAttribute("name"), "baz",
|
||||||
|
"The first slice's name is correct.");
|
||||||
|
ok(slices[1].getAttribute("name"), "bar",
|
||||||
|
"The first slice's name is correct.");
|
||||||
|
ok(slices[2].getAttribute("name"), "foo",
|
||||||
|
"The first slice's name is correct.");
|
||||||
|
|
||||||
|
is(labels.length, 3,
|
||||||
|
"There should be 3 pie chart labels created.");
|
||||||
|
is(labels[0].textContent, "baz",
|
||||||
|
"The first label's text is correct.");
|
||||||
|
is(labels[1].textContent, "bar",
|
||||||
|
"The first label's text is correct.");
|
||||||
|
is(labels[2].textContent, "foo",
|
||||||
|
"The first label's text is correct.");
|
||||||
|
|
||||||
|
teardown(aMonitor).then(finish);
|
||||||
|
});
|
||||||
|
}
|
||||||
49
browser/devtools/netmonitor/test/browser_net_charts-02.js
Normal file
49
browser/devtools/netmonitor/test/browser_net_charts-02.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure Pie Charts have the right internal structure when
|
||||||
|
* initialized with empty data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
|
info("Starting test... ");
|
||||||
|
|
||||||
|
let { document, L10N, Chart } = aMonitor.panelWin;
|
||||||
|
let container = document.createElement("box");
|
||||||
|
|
||||||
|
let pie = Chart.Pie(document, {
|
||||||
|
data: null,
|
||||||
|
width: 100,
|
||||||
|
height: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
let node = pie.node;
|
||||||
|
let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
|
||||||
|
let labels = node.querySelectorAll(".pie-chart-label");
|
||||||
|
|
||||||
|
ok(node.classList.contains("pie-chart-container") &&
|
||||||
|
node.classList.contains("generic-chart-container"),
|
||||||
|
"A pie chart container was created successfully.");
|
||||||
|
|
||||||
|
is(slices.length, 1,
|
||||||
|
"There should be 1 pie chart slice created.");
|
||||||
|
ok(slices[0].getAttribute("d").match(/\s*M 50,50 L 50\.\d+,2\.5\d* A 47\.5,47\.5 0 1 1 49\.\d+,2\.5\d* Z/),
|
||||||
|
"The first slice has the correct data.");
|
||||||
|
|
||||||
|
ok(slices[0].hasAttribute("largest"),
|
||||||
|
"The first slice should be the largest one.");
|
||||||
|
ok(slices[0].hasAttribute("smallest"),
|
||||||
|
"The first slice should also be the smallest one.");
|
||||||
|
ok(slices[0].getAttribute("name"), L10N.getStr("pieChart.empty"),
|
||||||
|
"The first slice's name is correct.");
|
||||||
|
|
||||||
|
is(labels.length, 1,
|
||||||
|
"There should be 1 pie chart label created.");
|
||||||
|
is(labels[0].textContent, "Loading",
|
||||||
|
"The first label's text is correct.");
|
||||||
|
|
||||||
|
teardown(aMonitor).then(finish);
|
||||||
|
});
|
||||||
|
}
|
||||||
100
browser/devtools/netmonitor/test/browser_net_charts-03.js
Normal file
100
browser/devtools/netmonitor/test/browser_net_charts-03.js
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure Table Charts have the right internal structure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
|
info("Starting test... ");
|
||||||
|
|
||||||
|
let { document, Chart } = aMonitor.panelWin;
|
||||||
|
let container = document.createElement("box");
|
||||||
|
|
||||||
|
let table = Chart.Table(document, {
|
||||||
|
title: "Table title",
|
||||||
|
data: [{
|
||||||
|
label1: 1,
|
||||||
|
label2: "11.1foo"
|
||||||
|
}, {
|
||||||
|
label1: 2,
|
||||||
|
label2: "12.2bar"
|
||||||
|
}, {
|
||||||
|
label1: 3,
|
||||||
|
label2: "13.3baz"
|
||||||
|
}],
|
||||||
|
totals: {
|
||||||
|
label1: "Hello %S",
|
||||||
|
label2: "World %S"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let node = table.node;
|
||||||
|
let title = node.querySelector(".table-chart-title");
|
||||||
|
let grid = node.querySelector(".table-chart-grid");
|
||||||
|
let totals = node.querySelector(".table-chart-totals");
|
||||||
|
let rows = grid.querySelectorAll(".table-chart-row");
|
||||||
|
let sums = node.querySelectorAll(".table-chart-summary-label");
|
||||||
|
|
||||||
|
ok(node.classList.contains("table-chart-container") &&
|
||||||
|
node.classList.contains("generic-chart-container"),
|
||||||
|
"A table chart container was created successfully.");
|
||||||
|
|
||||||
|
ok(title,
|
||||||
|
"A title node was created successfully.");
|
||||||
|
is(title.getAttribute("value"), "Table title",
|
||||||
|
"The title node displays the correct text.");
|
||||||
|
|
||||||
|
is(rows.length, 3,
|
||||||
|
"There should be 3 table chart rows created.");
|
||||||
|
|
||||||
|
ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||||
|
"A colored blob exists for the firt row.");
|
||||||
|
is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "label1",
|
||||||
|
"The first column of the first row exists.");
|
||||||
|
is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label2",
|
||||||
|
"The second column of the first row exists.");
|
||||||
|
is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "1",
|
||||||
|
"The first column of the first row displays the correct text.");
|
||||||
|
is(rows[0].querySelectorAll("label")[1].getAttribute("value"), "11.1foo",
|
||||||
|
"The second column of the first row displays the correct text.");
|
||||||
|
|
||||||
|
ok(rows[1].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||||
|
"A colored blob exists for the second row.");
|
||||||
|
is(rows[1].querySelectorAll("label")[0].getAttribute("name"), "label1",
|
||||||
|
"The first column of the second row exists.");
|
||||||
|
is(rows[1].querySelectorAll("label")[1].getAttribute("name"), "label2",
|
||||||
|
"The second column of the second row exists.");
|
||||||
|
is(rows[1].querySelectorAll("label")[0].getAttribute("value"), "2",
|
||||||
|
"The first column of the second row displays the correct text.");
|
||||||
|
is(rows[1].querySelectorAll("label")[1].getAttribute("value"), "12.2bar",
|
||||||
|
"The second column of the first row displays the correct text.");
|
||||||
|
|
||||||
|
ok(rows[2].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||||
|
"A colored blob exists for the third row.");
|
||||||
|
is(rows[2].querySelectorAll("label")[0].getAttribute("name"), "label1",
|
||||||
|
"The first column of the third row exists.");
|
||||||
|
is(rows[2].querySelectorAll("label")[1].getAttribute("name"), "label2",
|
||||||
|
"The second column of the third row exists.");
|
||||||
|
is(rows[2].querySelectorAll("label")[0].getAttribute("value"), "3",
|
||||||
|
"The first column of the third row displays the correct text.");
|
||||||
|
is(rows[2].querySelectorAll("label")[1].getAttribute("value"), "13.3baz",
|
||||||
|
"The second column of the third row displays the correct text.");
|
||||||
|
|
||||||
|
is(sums.length, 2,
|
||||||
|
"There should be 2 total summaries created.");
|
||||||
|
|
||||||
|
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("name"), "label1",
|
||||||
|
"The first sum's type is correct.");
|
||||||
|
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("value"), "Hello 6",
|
||||||
|
"The first sum's value is correct.");
|
||||||
|
|
||||||
|
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("name"), "label2",
|
||||||
|
"The second sum's type is correct.");
|
||||||
|
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("value"), "World 36.60",
|
||||||
|
"The second sum's value is correct.");
|
||||||
|
|
||||||
|
teardown(aMonitor).then(finish);
|
||||||
|
});
|
||||||
|
}
|
||||||
70
browser/devtools/netmonitor/test/browser_net_charts-04.js
Normal file
70
browser/devtools/netmonitor/test/browser_net_charts-04.js
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure Pie Charts have the right internal structure when
|
||||||
|
* initialized with empty data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
|
info("Starting test... ");
|
||||||
|
|
||||||
|
let { document, L10N, Chart } = aMonitor.panelWin;
|
||||||
|
let container = document.createElement("box");
|
||||||
|
|
||||||
|
let table = Chart.Table(document, {
|
||||||
|
title: "Table title",
|
||||||
|
data: null,
|
||||||
|
totals: {
|
||||||
|
label1: "Hello %S",
|
||||||
|
label2: "World %S"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let node = table.node;
|
||||||
|
let title = node.querySelector(".table-chart-title");
|
||||||
|
let grid = node.querySelector(".table-chart-grid");
|
||||||
|
let totals = node.querySelector(".table-chart-totals");
|
||||||
|
let rows = grid.querySelectorAll(".table-chart-row");
|
||||||
|
let sums = node.querySelectorAll(".table-chart-summary-label");
|
||||||
|
|
||||||
|
ok(node.classList.contains("table-chart-container") &&
|
||||||
|
node.classList.contains("generic-chart-container"),
|
||||||
|
"A table chart container was created successfully.");
|
||||||
|
|
||||||
|
ok(title,
|
||||||
|
"A title node was created successfully.");
|
||||||
|
is(title.getAttribute("value"), "Table title",
|
||||||
|
"The title node displays the correct text.");
|
||||||
|
|
||||||
|
is(rows.length, 1,
|
||||||
|
"There should be 1 table chart row created.");
|
||||||
|
|
||||||
|
ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||||
|
"A colored blob exists for the firt row.");
|
||||||
|
is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "size",
|
||||||
|
"The first column of the first row exists.");
|
||||||
|
is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label",
|
||||||
|
"The second column of the first row exists.");
|
||||||
|
is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "",
|
||||||
|
"The first column of the first row displays the correct text.");
|
||||||
|
is(rows[0].querySelectorAll("label")[1].getAttribute("value"), L10N.getStr("tableChart.empty"),
|
||||||
|
"The second column of the first row displays the correct text.");
|
||||||
|
|
||||||
|
is(sums.length, 2,
|
||||||
|
"There should be 2 total summaries created.");
|
||||||
|
|
||||||
|
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("name"), "label1",
|
||||||
|
"The first sum's type is correct.");
|
||||||
|
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("value"), "Hello 0",
|
||||||
|
"The first sum's value is correct.");
|
||||||
|
|
||||||
|
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("name"), "label2",
|
||||||
|
"The second sum's type is correct.");
|
||||||
|
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("value"), "World 0",
|
||||||
|
"The second sum's value is correct.");
|
||||||
|
|
||||||
|
teardown(aMonitor).then(finish);
|
||||||
|
});
|
||||||
|
}
|
||||||
60
browser/devtools/netmonitor/test/browser_net_charts-05.js
Normal file
60
browser/devtools/netmonitor/test/browser_net_charts-05.js
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure Pie+Table Charts have the right internal structure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
|
info("Starting test... ");
|
||||||
|
|
||||||
|
let { document, Chart } = aMonitor.panelWin;
|
||||||
|
let container = document.createElement("box");
|
||||||
|
|
||||||
|
let chart = Chart.PieTable(document, {
|
||||||
|
title: "Table title",
|
||||||
|
data: [{
|
||||||
|
size: 1,
|
||||||
|
label: "11.1foo"
|
||||||
|
}, {
|
||||||
|
size: 2,
|
||||||
|
label: "12.2bar"
|
||||||
|
}, {
|
||||||
|
size: 3,
|
||||||
|
label: "13.3baz"
|
||||||
|
}],
|
||||||
|
totals: {
|
||||||
|
size: "Hello %S",
|
||||||
|
label: "World %S"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(chart.pie, "The pie chart proxy is accessible.");
|
||||||
|
ok(chart.table, "The table chart proxy is accessible.");
|
||||||
|
|
||||||
|
let node = chart.node;
|
||||||
|
let slices = node.querySelectorAll(".pie-chart-slice");
|
||||||
|
let rows = node.querySelectorAll(".table-chart-row");
|
||||||
|
let sums = node.querySelectorAll(".table-chart-summary-label");
|
||||||
|
|
||||||
|
ok(node.classList.contains("pie-table-chart-container"),
|
||||||
|
"A pie+table chart container was created successfully.");
|
||||||
|
|
||||||
|
ok(node.querySelector(".table-chart-title"),
|
||||||
|
"A title node was created successfully.");
|
||||||
|
ok(node.querySelector(".pie-chart-container"),
|
||||||
|
"A pie chart was created successfully.");
|
||||||
|
ok(node.querySelector(".table-chart-container"),
|
||||||
|
"A table chart was created successfully.");
|
||||||
|
|
||||||
|
is(rows.length, 3,
|
||||||
|
"There should be 3 pie chart slices created.");
|
||||||
|
is(rows.length, 3,
|
||||||
|
"There should be 3 table chart rows created.");
|
||||||
|
is(sums.length, 2,
|
||||||
|
"There should be 2 total summaries created.");
|
||||||
|
|
||||||
|
teardown(aMonitor).then(finish);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ function test() {
|
||||||
})
|
})
|
||||||
|
|
||||||
function testStatus() {
|
function testStatus() {
|
||||||
let summary = $("#request-menu-network-summary");
|
let summary = $("#requests-menu-network-summary-label");
|
||||||
let value = summary.getAttribute("value");
|
let value = summary.getAttribute("value");
|
||||||
info("Current summary: " + value);
|
info("Current summary: " + value);
|
||||||
|
|
||||||
|
|
@ -89,13 +89,7 @@ function test() {
|
||||||
let totalRequestsCount = RequestsMenu.itemCount;
|
let totalRequestsCount = RequestsMenu.itemCount;
|
||||||
info("Current requests: " + visibleRequestsCount + " of " + totalRequestsCount + ".");
|
info("Current requests: " + visibleRequestsCount + " of " + totalRequestsCount + ".");
|
||||||
|
|
||||||
if (!totalRequestsCount) {
|
if (!totalRequestsCount || !visibleRequestsCount) {
|
||||||
is(value, "",
|
|
||||||
"The current summary text is incorrect, expected an empty string.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!visibleRequestsCount) {
|
|
||||||
is(value, L10N.getStr("networkMenu.empty"),
|
is(value, L10N.getStr("networkMenu.empty"),
|
||||||
"The current summary text is incorrect, expected an 'empty' label.");
|
"The current summary text is incorrect, expected an 'empty' label.");
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the statistics view is populated correctly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
initNetMonitor(STATISTICS_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
|
info("Starting test... ");
|
||||||
|
|
||||||
|
let panel = aMonitor.panelWin;
|
||||||
|
let { document, $, $all, EVENTS, NetMonitorView } = panel;
|
||||||
|
|
||||||
|
is(NetMonitorView.currentFrontendMode, "network-inspector-view",
|
||||||
|
"The initial frontend mode is correct.");
|
||||||
|
|
||||||
|
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.");
|
||||||
|
|
||||||
|
waitFor(panel, EVENTS.PLACEHOLDER_CHARTS_DISPLAYED).then(() => {
|
||||||
|
is($("#primed-cache-chart").childNodes.length, 1,
|
||||||
|
"There should be a placeholder primed cache chart created now.");
|
||||||
|
is($("#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,
|
||||||
|
"Two placeholder pie chart appear to be rendered correctly.");
|
||||||
|
is($all(".table-chart-container[placeholder=true]").length, 2,
|
||||||
|
"Two placeholder table chart appear to be rendered correctly.");
|
||||||
|
|
||||||
|
promise.all([
|
||||||
|
waitFor(panel, EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
|
||||||
|
waitFor(panel, EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
|
||||||
|
]).then(() => {
|
||||||
|
is($("#primed-cache-chart").childNodes.length, 1,
|
||||||
|
"There should be a real primed cache chart created now.");
|
||||||
|
is($("#empty-cache-chart").childNodes.length, 1,
|
||||||
|
"There should be a real empty cache chart created now.");
|
||||||
|
|
||||||
|
is($all(".pie-chart-container:not([placeholder=true])").length, 2,
|
||||||
|
"Two real pie chart appear to be rendered correctly.");
|
||||||
|
is($all(".table-chart-container:not([placeholder=true])").length, 2,
|
||||||
|
"Two real table chart appear to be rendered correctly.");
|
||||||
|
|
||||||
|
teardown(aMonitor).then(finish);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
NetMonitorView.toggleFrontendMode();
|
||||||
|
|
||||||
|
is(NetMonitorView.currentFrontendMode, "network-statistics-view",
|
||||||
|
"The current frontend mode is correct.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the network inspector view is shown when the target navigates
|
||||||
|
* away while in the statistics view.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
initNetMonitor(STATISTICS_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||||
|
info("Starting test... ");
|
||||||
|
|
||||||
|
let panel = aMonitor.panelWin;
|
||||||
|
let { document, EVENTS, NetMonitorView } = panel;
|
||||||
|
|
||||||
|
is(NetMonitorView.currentFrontendMode, "network-inspector-view",
|
||||||
|
"The initial frontend mode is correct.");
|
||||||
|
|
||||||
|
promise.all([
|
||||||
|
waitFor(panel, EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
|
||||||
|
waitFor(panel, EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
|
||||||
|
]).then(() => {
|
||||||
|
is(NetMonitorView.currentFrontendMode, "network-statistics-view",
|
||||||
|
"The frontend mode is currently in the statistics view.");
|
||||||
|
|
||||||
|
waitFor(panel, EVENTS.TARGET_WILL_NAVIGATE).then(() => {
|
||||||
|
is(NetMonitorView.currentFrontendMode, "network-inspector-view",
|
||||||
|
"The frontend mode switched back to the inspector view.");
|
||||||
|
|
||||||
|
waitFor(panel, EVENTS.TARGET_DID_NAVIGATE).then(() => {
|
||||||
|
is(NetMonitorView.currentFrontendMode, "network-inspector-view",
|
||||||
|
"The frontend mode is still in the inspector view.");
|
||||||
|
|
||||||
|
teardown(aMonitor).then(finish);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
aDebuggee.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
NetMonitorView.toggleFrontendMode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
|
||||||
const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
|
const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
|
||||||
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
|
const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
|
||||||
const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html";
|
const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html";
|
||||||
|
const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
|
||||||
|
|
||||||
const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
|
const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
|
||||||
const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
|
const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<!-- Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Network Monitor test page</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<p>Statistics test</p>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function get(aAddress) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("GET", aAddress, true);
|
||||||
|
xhr.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=txt");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=xml");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=html");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=css");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=js");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=json");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=jsonp");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=font");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=image");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=audio");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=video");
|
||||||
|
get("sjs_content-type-test-server.sjs?sts=304&fmt=flash");
|
||||||
|
get("test-image.png");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -7,118 +7,151 @@ function handleRequest(request, response) {
|
||||||
response.processAsync();
|
response.processAsync();
|
||||||
|
|
||||||
let params = request.queryString.split("&");
|
let params = request.queryString.split("&");
|
||||||
let format = params.filter((s) => s.contains("fmt="))[0].split("=")[1];
|
let format = (params.filter((s) => s.contains("fmt="))[0] || "").split("=")[1];
|
||||||
|
let status = (params.filter((s) => s.contains("sts="))[0] || "").split("=")[1] || 200;
|
||||||
|
|
||||||
|
let cachedCount = 0;
|
||||||
|
let cacheExpire = 60; // seconds
|
||||||
|
|
||||||
|
function maybeMakeCached() {
|
||||||
|
if (status != 304) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Spice things up a little!
|
||||||
|
if (cachedCount % 2) {
|
||||||
|
response.setHeader("Cache-Control", "max-age=" + cacheExpire, false);
|
||||||
|
} else {
|
||||||
|
response.setHeader("Expires", Date(Date.now() + cacheExpire * 1000), false);
|
||||||
|
}
|
||||||
|
cachedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer).initWithCallback(() => {
|
Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer).initWithCallback(() => {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case "txt": {
|
case "txt": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "DA DA DA");
|
response.setStatusLine(request.httpVersion, status, "DA DA DA");
|
||||||
response.setHeader("Content-Type", "text/plain", false);
|
response.setHeader("Content-Type", "text/plain", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("Братан, ты вообще качаешься?");
|
response.write("Братан, ты вообще качаешься?");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "xml": {
|
case "xml": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "text/xml; charset=utf-8", false);
|
response.setHeader("Content-Type", "text/xml; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("<label value='greeting'>Hello XML!</label>");
|
response.write("<label value='greeting'>Hello XML!</label>");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "html": {
|
case "html": {
|
||||||
let content = params.filter((s) => s.contains("res="))[0].split("=")[1];
|
let content = params.filter((s) => s.contains("res="))[0].split("=")[1];
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
|
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write(content || "<p>Hello HTML!</p>");
|
response.write(content || "<p>Hello HTML!</p>");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "html-long": {
|
case "html-long": {
|
||||||
let str = new Array(102400 /* 100 KB in bytes */).join(".");
|
let str = new Array(102400 /* 100 KB in bytes */).join(".");
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
|
response.setHeader("Content-Type", "text/html; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("<p>" + str + "</p>");
|
response.write("<p>" + str + "</p>");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "css": {
|
case "css": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "text/css; charset=utf-8", false);
|
response.setHeader("Content-Type", "text/css; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("body:pre { content: 'Hello CSS!' }");
|
response.write("body:pre { content: 'Hello CSS!' }");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "js": {
|
case "js": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "application/javascript; charset=utf-8", false);
|
response.setHeader("Content-Type", "application/javascript; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("function() { return 'Hello JS!'; }");
|
response.write("function() { return 'Hello JS!'; }");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "json": {
|
case "json": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "application/json; charset=utf-8", false);
|
response.setHeader("Content-Type", "application/json; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("{ \"greeting\": \"Hello JSON!\" }");
|
response.write("{ \"greeting\": \"Hello JSON!\" }");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "jsonp": {
|
case "jsonp": {
|
||||||
let fun = params.filter((s) => s.contains("jsonp="))[0].split("=")[1];
|
let fun = params.filter((s) => s.contains("jsonp="))[0].split("=")[1];
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
|
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write(fun + "({ \"greeting\": \"Hello JSONP!\" })");
|
response.write(fun + "({ \"greeting\": \"Hello JSONP!\" })");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "json-long": {
|
case "json-long": {
|
||||||
let str = "{ \"greeting\": \"Hello long string JSON!\" },";
|
let str = "{ \"greeting\": \"Hello long string JSON!\" },";
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
|
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("[" + new Array(2048).join(str).slice(0, -1) + "]");
|
response.write("[" + new Array(2048).join(str).slice(0, -1) + "]");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "json-malformed": {
|
case "json-malformed": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
|
response.setHeader("Content-Type", "text/json; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("{ \"greeting\": \"Hello malformed JSON!\" },");
|
response.write("{ \"greeting\": \"Hello malformed JSON!\" },");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "json-custom-mime": {
|
case "json-custom-mime": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "text/x-bigcorp-json; charset=utf-8", false);
|
response.setHeader("Content-Type", "text/x-bigcorp-json; charset=utf-8", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.write("{ \"greeting\": \"Hello oddly-named JSON!\" }");
|
response.write("{ \"greeting\": \"Hello oddly-named JSON!\" }");
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "font": {
|
case "font": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "font/woff", false);
|
response.setHeader("Content-Type", "font/woff", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "image": {
|
case "image": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "image/png", false);
|
response.setHeader("Content-Type", "image/png", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "audio": {
|
case "audio": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "audio/ogg", false);
|
response.setHeader("Content-Type", "audio/ogg", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "video": {
|
case "video": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "video/webm", false);
|
response.setHeader("Content-Type", "video/webm", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "flash": {
|
case "flash": {
|
||||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
response.setStatusLine(request.httpVersion, status, "OK");
|
||||||
response.setHeader("Content-Type", "application/x-shockwave-flash", false);
|
response.setHeader("Content-Type", "application/x-shockwave-flash", false);
|
||||||
|
maybeMakeCached();
|
||||||
response.finish();
|
response.finish();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
422
browser/devtools/shared/widgets/Chart.jsm
Normal file
422
browser/devtools/shared/widgets/Chart.jsm
Normal file
|
|
@ -0,0 +1,422 @@
|
||||||
|
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
const Cu = Components.utils;
|
||||||
|
|
||||||
|
const NET_STRINGS_URI = "chrome://browser/locale/devtools/netmonitor.properties";
|
||||||
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||||
|
const PI = Math.PI;
|
||||||
|
const TAU = PI * 2;
|
||||||
|
const EPSILON = 0.0000001;
|
||||||
|
const NAMED_SLICE_MIN_ANGLE = TAU / 8;
|
||||||
|
const NAMED_SLICE_TEXT_DISTANCE_RATIO = 1.9;
|
||||||
|
const HOVERED_SLICE_TRANSLATE_DISTANCE_RATIO = 20;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = ["Chart"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Localization convenience methods.
|
||||||
|
*/
|
||||||
|
let L10N = new ViewHelpers.L10N(NET_STRINGS_URI);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for creating charts.
|
||||||
|
* Example usage: let myChart = Chart.Pie(document, { ... });
|
||||||
|
*/
|
||||||
|
let Chart = {
|
||||||
|
Pie: createPieChart,
|
||||||
|
Table: createTableChart,
|
||||||
|
PieTable: createPieTableChart
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple pie chart proxy for the underlying view.
|
||||||
|
* Each item in the `slices` property represents a [data, node] pair containing
|
||||||
|
* the data used to create the slice and the nsIDOMNode displaying it.
|
||||||
|
*
|
||||||
|
* @param nsIDOMNode node
|
||||||
|
* The node representing the view for this chart.
|
||||||
|
*/
|
||||||
|
function PieChart(node) {
|
||||||
|
this.node = node;
|
||||||
|
this.slices = new WeakMap();
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple table chart proxy for the underlying view.
|
||||||
|
* Each item in the `rows` property represents a [data, node] pair containing
|
||||||
|
* the data used to create the row and the nsIDOMNode displaying it.
|
||||||
|
*
|
||||||
|
* @param nsIDOMNode node
|
||||||
|
* The node representing the view for this chart.
|
||||||
|
*/
|
||||||
|
function TableChart(node) {
|
||||||
|
this.node = node;
|
||||||
|
this.rows = new WeakMap();
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple pie+table chart proxy for the underlying view.
|
||||||
|
*
|
||||||
|
* @param nsIDOMNode node
|
||||||
|
* The node representing the view for this chart.
|
||||||
|
* @param PieChart pie
|
||||||
|
* The pie chart proxy.
|
||||||
|
* @param TableChart table
|
||||||
|
* The table chart proxy.
|
||||||
|
*/
|
||||||
|
function PieTableChart(node, pie, table) {
|
||||||
|
this.node = node;
|
||||||
|
this.pie = pie;
|
||||||
|
this.table = table;
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the DOM for a pie+table chart.
|
||||||
|
*
|
||||||
|
* @param nsIDocument document
|
||||||
|
* The document responsible with creating the DOM.
|
||||||
|
* @param object
|
||||||
|
* An object containing all or some of the following properties:
|
||||||
|
* - title: a string displayed as the table chart's (description)/local
|
||||||
|
* - diameter: the diameter of the pie chart, in pixels
|
||||||
|
* - data: an array of items used to display each slice in the pie
|
||||||
|
* and each row in the table;
|
||||||
|
* @see `createPieChart` and `createTableChart` for details.
|
||||||
|
* - sorted: a flag specifying if the `data` should be sorted
|
||||||
|
* ascending by `size`.
|
||||||
|
* - totals: @see `createTableChart` for details.
|
||||||
|
* @return PieTableChart
|
||||||
|
* A pie+table chart proxy instance, which emits the following events:
|
||||||
|
* - "mouseenter", when the mouse enters a slice or a row
|
||||||
|
* - "mouseleave", when the mouse leaves a slice or a row
|
||||||
|
* - "click", when the mouse enters a slice or a row
|
||||||
|
*/
|
||||||
|
function createPieTableChart(document, { sorted, title, diameter, data, totals }) {
|
||||||
|
if (sorted) {
|
||||||
|
data = data.slice().sort((a, b) => +(parseFloat(a.size) < parseFloat(b.size)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pie = Chart.Pie(document, {
|
||||||
|
width: diameter,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
|
||||||
|
let table = Chart.Table(document, {
|
||||||
|
title: title,
|
||||||
|
data: data,
|
||||||
|
totals: totals
|
||||||
|
});
|
||||||
|
|
||||||
|
let container = document.createElement("hbox");
|
||||||
|
container.className = "pie-table-chart-container";
|
||||||
|
container.appendChild(pie.node);
|
||||||
|
container.appendChild(table.node);
|
||||||
|
|
||||||
|
let proxy = new PieTableChart(container, pie, table);
|
||||||
|
|
||||||
|
pie.on("click", (event, item) => {
|
||||||
|
proxy.emit(event, item)
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on("click", (event, item) => {
|
||||||
|
proxy.emit(event, item)
|
||||||
|
});
|
||||||
|
|
||||||
|
pie.on("mouseenter", (event, item) => {
|
||||||
|
proxy.emit(event, item);
|
||||||
|
if (table.rows.has(item)) {
|
||||||
|
table.rows.get(item).setAttribute("focused", "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pie.on("mouseleave", (event, item) => {
|
||||||
|
proxy.emit(event, item);
|
||||||
|
if (table.rows.has(item)) {
|
||||||
|
table.rows.get(item).removeAttribute("focused");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on("mouseenter", (event, item) => {
|
||||||
|
proxy.emit(event, item);
|
||||||
|
if (pie.slices.has(item)) {
|
||||||
|
pie.slices.get(item).setAttribute("focused", "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.on("mouseleave", (event, item) => {
|
||||||
|
proxy.emit(event, item);
|
||||||
|
if (pie.slices.has(item)) {
|
||||||
|
pie.slices.get(item).removeAttribute("focused");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the DOM for a pie chart based on the specified properties.
|
||||||
|
*
|
||||||
|
* @param nsIDocument document
|
||||||
|
* The document responsible with creating the DOM.
|
||||||
|
* @param object
|
||||||
|
* An object containing all or some of the following properties:
|
||||||
|
* - data: an array of items used to display each slice; all the items
|
||||||
|
* should be objects containing a `size` and a `label` property.
|
||||||
|
* e.g: [{
|
||||||
|
* size: 1,
|
||||||
|
* label: "foo"
|
||||||
|
* }, {
|
||||||
|
* size: 2,
|
||||||
|
* label: "bar"
|
||||||
|
* }];
|
||||||
|
* - width: the width of the chart, in pixels
|
||||||
|
* - height: optional, the height of the chart, in pixels.
|
||||||
|
* - centerX: optional, the X-axis center of the chart, in pixels.
|
||||||
|
* - centerY: optional, the Y-axis center of the chart, in pixels.
|
||||||
|
* - radius: optional, the radius of the chart, in pixels.
|
||||||
|
* @return PieChart
|
||||||
|
* A pie chart proxy instance, which emits the following events:
|
||||||
|
* - "mouseenter", when the mouse enters a slice
|
||||||
|
* - "mouseleave", when the mouse leaves a slice
|
||||||
|
* - "click", when the mouse clicks a slice
|
||||||
|
*/
|
||||||
|
function createPieChart(document, { data, width, height, centerX, centerY, radius }) {
|
||||||
|
height = height || width;
|
||||||
|
centerX = centerX || width / 2;
|
||||||
|
centerY = centerY || height / 2;
|
||||||
|
radius = radius || (width + height) / 4;
|
||||||
|
let isPlaceholder = false;
|
||||||
|
|
||||||
|
// Filter out very small sizes, as they'll just render invisible slices.
|
||||||
|
data = data ? data.filter(e => parseFloat(e.size) > EPSILON) : null;
|
||||||
|
|
||||||
|
// If there's no data available, display an empty placeholder.
|
||||||
|
if (!data || !data.length) {
|
||||||
|
data = emptyPieChartData;
|
||||||
|
isPlaceholder = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let container = document.createElementNS(SVG_NS, "svg");
|
||||||
|
container.setAttribute("class", "generic-chart-container pie-chart-container");
|
||||||
|
container.setAttribute("pack", "center");
|
||||||
|
container.setAttribute("flex", "1");
|
||||||
|
container.setAttribute("width", width);
|
||||||
|
container.setAttribute("height", height);
|
||||||
|
container.setAttribute("viewBox", "0 0 " + width + " " + height);
|
||||||
|
container.setAttribute("slices", data.length);
|
||||||
|
container.setAttribute("placeholder", isPlaceholder);
|
||||||
|
|
||||||
|
let proxy = new PieChart(container);
|
||||||
|
|
||||||
|
let total = data.reduce((acc, e) => acc + parseFloat(e.size), 0);
|
||||||
|
let angles = data.map(e => parseFloat(e.size) / total * (TAU - EPSILON));
|
||||||
|
let largest = data.reduce((a, b) => parseFloat(a.size) > parseFloat(b.size) ? a : b);
|
||||||
|
let smallest = data.reduce((a, b) => parseFloat(a.size) < parseFloat(b.size) ? a : b);
|
||||||
|
|
||||||
|
let textDistance = radius / NAMED_SLICE_TEXT_DISTANCE_RATIO;
|
||||||
|
let translateDistance = radius / HOVERED_SLICE_TRANSLATE_DISTANCE_RATIO;
|
||||||
|
let startAngle = TAU;
|
||||||
|
let endAngle = 0;
|
||||||
|
let midAngle = 0;
|
||||||
|
radius -= translateDistance;
|
||||||
|
|
||||||
|
for (let i = data.length - 1; i >= 0; i--) {
|
||||||
|
let sliceInfo = data[i];
|
||||||
|
let sliceAngle = angles[i];
|
||||||
|
if (!sliceInfo.size || sliceAngle < EPSILON) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
endAngle = startAngle - sliceAngle;
|
||||||
|
midAngle = (startAngle + endAngle) / 2;
|
||||||
|
|
||||||
|
let x1 = centerX + radius * Math.sin(startAngle);
|
||||||
|
let y1 = centerY - radius * Math.cos(startAngle);
|
||||||
|
let x2 = centerX + radius * Math.sin(endAngle);
|
||||||
|
let y2 = centerY - radius * Math.cos(endAngle);
|
||||||
|
let largeArcFlag = Math.abs(startAngle - endAngle) > PI ? 1 : 0;
|
||||||
|
|
||||||
|
let pathNode = document.createElementNS(SVG_NS, "path");
|
||||||
|
pathNode.setAttribute("class", "pie-chart-slice chart-colored-blob");
|
||||||
|
pathNode.setAttribute("name", sliceInfo.label);
|
||||||
|
pathNode.setAttribute("d",
|
||||||
|
" M " + centerX + "," + centerY +
|
||||||
|
" L " + x2 + "," + y2 +
|
||||||
|
" A " + radius + "," + radius +
|
||||||
|
" 0 " + largeArcFlag +
|
||||||
|
" 1 " + x1 + "," + y1 +
|
||||||
|
" Z");
|
||||||
|
|
||||||
|
if (sliceInfo == largest) {
|
||||||
|
pathNode.setAttribute("largest", "");
|
||||||
|
}
|
||||||
|
if (sliceInfo == smallest) {
|
||||||
|
pathNode.setAttribute("smallest", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
let hoverX = translateDistance * Math.sin(midAngle);
|
||||||
|
let hoverY = -translateDistance * Math.cos(midAngle);
|
||||||
|
let hoverTransform = "transform: translate(" + hoverX + "px, " + hoverY + "px)";
|
||||||
|
pathNode.setAttribute("style", hoverTransform);
|
||||||
|
|
||||||
|
proxy.slices.set(sliceInfo, pathNode);
|
||||||
|
delegate(proxy, ["click", "mouseenter", "mouseleave"], pathNode, sliceInfo);
|
||||||
|
container.appendChild(pathNode);
|
||||||
|
|
||||||
|
if (sliceInfo.label && sliceAngle > NAMED_SLICE_MIN_ANGLE) {
|
||||||
|
let textX = centerX + textDistance * Math.sin(midAngle);
|
||||||
|
let textY = centerY - textDistance * Math.cos(midAngle);
|
||||||
|
let label = document.createElementNS(SVG_NS, "text");
|
||||||
|
label.appendChild(document.createTextNode(sliceInfo.label));
|
||||||
|
label.setAttribute("class", "pie-chart-label");
|
||||||
|
label.setAttribute("style", data.length > 1 ? hoverTransform : "");
|
||||||
|
label.setAttribute("x", data.length > 1 ? textX : centerX);
|
||||||
|
label.setAttribute("y", data.length > 1 ? textY : centerY);
|
||||||
|
container.appendChild(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
startAngle = endAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the DOM for a table chart based on the specified properties.
|
||||||
|
*
|
||||||
|
* @param nsIDocument document
|
||||||
|
* The document responsible with creating the DOM.
|
||||||
|
* @param object
|
||||||
|
* An object containing all or some of the following properties:
|
||||||
|
* - title: a string displayed as the chart's (description)/local
|
||||||
|
* - data: an array of items used to display each row; all the items
|
||||||
|
* should be objects representing columns, for which the
|
||||||
|
* properties' values will be displayed in each cell of a row.
|
||||||
|
* e.g: [{
|
||||||
|
* size: 1,
|
||||||
|
* label2: "1foo",
|
||||||
|
* label3: "2yolo"
|
||||||
|
* }, {
|
||||||
|
* size: 2,
|
||||||
|
* label2: "3bar",
|
||||||
|
* label3: "4swag"
|
||||||
|
* }];
|
||||||
|
* - totals: an object specifying for which rows in the `data` array
|
||||||
|
* the sum of their cells is to be displayed in the chart;
|
||||||
|
* e.g: {
|
||||||
|
* label1: "Total size: %S",
|
||||||
|
* label3: "Total lolz: %S"
|
||||||
|
* }
|
||||||
|
* @return TableChart
|
||||||
|
* A table chart proxy instance, which emits the following events:
|
||||||
|
* - "mouseenter", when the mouse enters a row
|
||||||
|
* - "mouseleave", when the mouse leaves a row
|
||||||
|
* - "click", when the mouse clicks a row
|
||||||
|
*/
|
||||||
|
function createTableChart(document, { data, totals, title }) {
|
||||||
|
let isPlaceholder = false;
|
||||||
|
|
||||||
|
// If there's no data available, display an empty placeholder.
|
||||||
|
if (!data || !data.length) {
|
||||||
|
data = emptyTableChartData;
|
||||||
|
isPlaceholder = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let container = document.createElement("vbox");
|
||||||
|
container.className = "generic-chart-container table-chart-container";
|
||||||
|
container.setAttribute("pack", "center");
|
||||||
|
container.setAttribute("flex", "1");
|
||||||
|
container.setAttribute("rows", data.length);
|
||||||
|
container.setAttribute("placeholder", isPlaceholder);
|
||||||
|
|
||||||
|
let proxy = new TableChart(container);
|
||||||
|
|
||||||
|
let titleNode = document.createElement("label");
|
||||||
|
titleNode.className = "plain table-chart-title";
|
||||||
|
titleNode.setAttribute("value", title);
|
||||||
|
container.appendChild(titleNode);
|
||||||
|
|
||||||
|
let tableNode = document.createElement("vbox");
|
||||||
|
tableNode.className = "plain table-chart-grid";
|
||||||
|
container.appendChild(tableNode);
|
||||||
|
|
||||||
|
for (let rowInfo of data) {
|
||||||
|
let rowNode = document.createElement("hbox");
|
||||||
|
rowNode.className = "table-chart-row";
|
||||||
|
rowNode.setAttribute("align", "center");
|
||||||
|
|
||||||
|
let boxNode = document.createElement("hbox");
|
||||||
|
boxNode.className = "table-chart-row-box chart-colored-blob";
|
||||||
|
boxNode.setAttribute("name", rowInfo.label);
|
||||||
|
rowNode.appendChild(boxNode);
|
||||||
|
|
||||||
|
for (let [key, value] in Iterator(rowInfo)) {
|
||||||
|
let labelNode = document.createElement("label");
|
||||||
|
labelNode.className = "plain table-chart-row-label";
|
||||||
|
labelNode.setAttribute("name", key);
|
||||||
|
labelNode.setAttribute("value", value);
|
||||||
|
rowNode.appendChild(labelNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.rows.set(rowInfo, rowNode);
|
||||||
|
delegate(proxy, ["click", "mouseenter", "mouseleave"], rowNode, rowInfo);
|
||||||
|
tableNode.appendChild(rowNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalsNode = document.createElement("vbox");
|
||||||
|
totalsNode.className = "table-chart-totals";
|
||||||
|
|
||||||
|
for (let [key, value] in Iterator(totals || {})) {
|
||||||
|
let total = data.reduce((acc, e) => acc + parseFloat(e[key]), 0);
|
||||||
|
let formatted = !isNaN(total) ? L10N.numberWithDecimals(total, 2) : 0;
|
||||||
|
let labelNode = document.createElement("label");
|
||||||
|
labelNode.className = "plain table-chart-summary-label";
|
||||||
|
labelNode.setAttribute("name", key);
|
||||||
|
labelNode.setAttribute("value", value.replace("%S", formatted));
|
||||||
|
totalsNode.appendChild(labelNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(totalsNode);
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "emptyPieChartData", () => {
|
||||||
|
return [{ size: 1, label: L10N.getStr("pieChart.empty") }];
|
||||||
|
});
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "emptyTableChartData", () => {
|
||||||
|
return [{ size: "", label: L10N.getStr("tableChart.empty") }];
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegates DOM events emitted by an nsIDOMNode to an EventEmitter proxy.
|
||||||
|
*
|
||||||
|
* @param EventEmitter emitter
|
||||||
|
* The event emitter proxy instance.
|
||||||
|
* @param array events
|
||||||
|
* An array of events, e.g. ["mouseenter", "mouseleave"].
|
||||||
|
* @param nsIDOMNode node
|
||||||
|
* The element firing the DOM events.
|
||||||
|
* @param any args
|
||||||
|
* The arguments passed when emitting events through the proxy.
|
||||||
|
*/
|
||||||
|
function delegate(emitter, events, node, args) {
|
||||||
|
for (let event of events) {
|
||||||
|
node.addEventListener(event, emitter.emit.bind(emitter, event, args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,9 +11,14 @@
|
||||||
- A good criteria is the language in which you'd find the best
|
- A good criteria is the language in which you'd find the best
|
||||||
- documentation on web development on the web. -->
|
- documentation on web development on the web. -->
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (netmonitorUI.emptyNotice2): This is the label displayed
|
<!-- LOCALIZATION NOTE (netmonitorUI.perfNotice1/2): These are the labels displayed
|
||||||
|
- in the network table when empty to start performance analysis. -->
|
||||||
|
<!ENTITY netmonitorUI.perfNotice1 "• Click on the">
|
||||||
|
<!ENTITY netmonitorUI.perfNotice2 "button to start performance analysis.">
|
||||||
|
|
||||||
|
<!-- LOCALIZATION NOTE (netmonitorUI.emptyNotice3): This is the label displayed
|
||||||
- in the network table when empty. -->
|
- in the network table when empty. -->
|
||||||
<!ENTITY netmonitorUI.emptyNotice2 "Perform a request or reload the page to see detailed information about network activity.">
|
<!ENTITY netmonitorUI.emptyNotice3 "• Perform a request or reload the page to see detailed information about network activity.">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (netmonitorUI.toolbar.status2): This is the label displayed
|
<!-- LOCALIZATION NOTE (netmonitorUI.toolbar.status2): This is the label displayed
|
||||||
- in the network table toolbar, above the "status" column. -->
|
- in the network table toolbar, above the "status" column. -->
|
||||||
|
|
@ -99,10 +104,18 @@
|
||||||
- in the network details footer for the "Flash" filtering button. -->
|
- in the network details footer for the "Flash" filtering button. -->
|
||||||
<!ENTITY netmonitorUI.footer.filterFlash "Flash">
|
<!ENTITY netmonitorUI.footer.filterFlash "Flash">
|
||||||
|
|
||||||
|
<!-- LOCALIZATION NOTE (debuggerUI.footer.filterOther): This is the label displayed
|
||||||
|
- in the network details footer for the "Other" filtering button. -->
|
||||||
|
<!ENTITY netmonitorUI.footer.filterOther "Other">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (debuggerUI.footer.clear): This is the label displayed
|
<!-- LOCALIZATION NOTE (debuggerUI.footer.clear): This is the label displayed
|
||||||
- in the network details footer for the "Clear" button. -->
|
- in the network details footer for the "Clear" button. -->
|
||||||
<!ENTITY netmonitorUI.footer.clear "Clear">
|
<!ENTITY netmonitorUI.footer.clear "Clear">
|
||||||
|
|
||||||
|
<!-- LOCALIZATION NOTE (debuggerUI.footer.clear): This is the label displayed
|
||||||
|
- in the network details footer for the performance analysis button. -->
|
||||||
|
<!ENTITY netmonitorUI.footer.perf "Toggle performance analysis...">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (debuggerUI.panesButton.tooltip): This is the tooltip for
|
<!-- LOCALIZATION NOTE (debuggerUI.panesButton.tooltip): This is the tooltip for
|
||||||
- the button that toggles the panes visible or hidden in the netmonitor UI. -->
|
- the button that toggles the panes visible or hidden in the netmonitor UI. -->
|
||||||
<!ENTITY netmonitorUI.panesButton.tooltip "Toggle network info">
|
<!ENTITY netmonitorUI.panesButton.tooltip "Toggle network info">
|
||||||
|
|
@ -173,22 +186,30 @@
|
||||||
- in a "receive" state. -->
|
- in a "receive" state. -->
|
||||||
<!ENTITY netmonitorUI.timings.receive "Receiving:">
|
<!ENTITY netmonitorUI.timings.receive "Receiving:">
|
||||||
|
|
||||||
|
<!-- LOCALIZATION NOTE (netmonitorUI.context.perfTools): This is the label displayed
|
||||||
|
- on the context menu that shows the performance analysis tools -->
|
||||||
|
<!ENTITY netmonitorUI.context.perfTools "Start Performance Analysis...">
|
||||||
|
|
||||||
|
<!-- LOCALIZATION NOTE (netmonitorUI.context.perfTools.accesskey): This is the access key
|
||||||
|
- for the performance analysis menu item displayed in the context menu for a request -->
|
||||||
|
<!ENTITY netmonitorUI.context.perfTools.accesskey "S">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyUrl): This is the label displayed
|
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyUrl): This is the label displayed
|
||||||
- on the context menu that copies the selected request's url -->
|
- on the context menu that copies the selected request's url -->
|
||||||
<!ENTITY netmonitorUI.context.copyUrl "Copy URL">
|
<!ENTITY netmonitorUI.context.copyUrl "Copy URL">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyUrl.accesskey): This is the access key
|
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyUrl.accesskey): This is the access key
|
||||||
- for the Copy URL menu item displayed in the context menu for a request -->
|
- for the Copy URL menu item displayed in the context menu for a request -->
|
||||||
<!ENTITY netmonitorUI.context.copyUrl.accesskey "C">
|
<!ENTITY netmonitorUI.context.copyUrl.accesskey "C">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (debuggerUI.summary.editAndResend): This is the label displayed
|
<!-- LOCALIZATION NOTE (debuggerUI.summary.editAndResend): This is the label displayed
|
||||||
- on the button in the headers tab that opens a form to edit and resend the currently
|
- on the button in the headers tab that opens a form to edit and resend the currently
|
||||||
displayed request -->
|
displayed request -->
|
||||||
<!ENTITY netmonitorUI.summary.editAndResend "Edit and Resend">
|
<!ENTITY netmonitorUI.summary.editAndResend "Edit and Resend">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (debuggerUI.summary.editAndResend.accesskey): This is the access key
|
<!-- LOCALIZATION NOTE (debuggerUI.summary.editAndResend.accesskey): This is the access key
|
||||||
- for the "Edit and Resend" menu item displayed in the context menu for a request -->
|
- for the "Edit and Resend" menu item displayed in the context menu for a request -->
|
||||||
<!ENTITY netmonitorUI.summary.editAndResend.accesskey "R">
|
<!ENTITY netmonitorUI.summary.editAndResend.accesskey "R">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (netmonitorUI.context.newTab): This is the label
|
<!-- LOCALIZATION NOTE (netmonitorUI.context.newTab): This is the label
|
||||||
- for the Open in New Tab menu item displayed in the context menu of the
|
- for the Open in New Tab menu item displayed in the context menu of the
|
||||||
|
|
@ -198,7 +219,7 @@
|
||||||
<!-- LOCALIZATION NOTE (netmonitorUI.context.newTab.accesskey): This is the access key
|
<!-- LOCALIZATION NOTE (netmonitorUI.context.newTab.accesskey): This is the access key
|
||||||
- for the Open in New Tab menu item displayed in the context menu of the
|
- for the Open in New Tab menu item displayed in the context menu of the
|
||||||
- network container -->
|
- network container -->
|
||||||
<!ENTITY netmonitorUI.context.newTab.accesskey "O">
|
<!ENTITY netmonitorUI.context.newTab.accesskey "O">
|
||||||
|
|
||||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.newRequest): This is the label displayed
|
<!-- LOCALIZATION NOTE (debuggerUI.custom.newRequest): This is the label displayed
|
||||||
- as the title of the new custom request form -->
|
- as the title of the new custom request form -->
|
||||||
|
|
@ -223,3 +244,7 @@
|
||||||
<!-- LOCALIZATION NOTE (debuggerUI.custom.cancel): This is the label displayed
|
<!-- LOCALIZATION NOTE (debuggerUI.custom.cancel): This is the label displayed
|
||||||
- on the button which cancels and closes the custom request form -->
|
- on the button which cancels and closes the custom request form -->
|
||||||
<!ENTITY netmonitorUI.custom.cancel "Cancel">
|
<!ENTITY netmonitorUI.custom.cancel "Cancel">
|
||||||
|
|
||||||
|
<!-- LOCALIZATION NOTE (debuggerUI.backButton): This is the label displayed
|
||||||
|
- on the button which exists the performance statistics view -->
|
||||||
|
<!ENTITY netmonitorUI.backButton "Back">
|
||||||
|
|
|
||||||
|
|
@ -135,3 +135,49 @@ networkMenu.second=%S s
|
||||||
# LOCALIZATION NOTE (networkMenu.minute): This is the label displayed
|
# LOCALIZATION NOTE (networkMenu.minute): This is the label displayed
|
||||||
# in the network menu specifying timing interval divisions (in minutes).
|
# in the network menu specifying timing interval divisions (in minutes).
|
||||||
networkMenu.minute=%S min
|
networkMenu.minute=%S min
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (networkMenu.minute): This is the label displayed
|
||||||
|
# in the network menu specifying timing interval divisions (in minutes).
|
||||||
|
networkMenu.minute=%S min
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (pieChart.empty): This is the label displayed
|
||||||
|
# for pie charts (e.g., in the performance analysis view) when there is
|
||||||
|
# no data available yet.
|
||||||
|
pieChart.empty=Loading
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (tableChart.empty): This is the label displayed
|
||||||
|
# for table charts (e.g., in the performance analysis view) when there is
|
||||||
|
# no data available yet.
|
||||||
|
tableChart.empty=Please wait…
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (charts.sizeKB): This is the label displayed
|
||||||
|
# in pie or table charts specifying the size of a request (in kilobytes).
|
||||||
|
charts.sizeKB=%S KB
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (charts.totalMS): This is the label displayed
|
||||||
|
# in pie or table charts specifying the time for a request to finish (in milliseconds).
|
||||||
|
charts.totalMS=%S ms
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (charts.cacheEnabled): This is the label displayed
|
||||||
|
# in the performance analysis view for "cache enabled" charts.
|
||||||
|
charts.cacheEnabled=Primed cache
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (charts.cacheDisabled): This is the label displayed
|
||||||
|
# in the performance analysis view for "cache disabled" charts.
|
||||||
|
charts.cacheDisabled=Empty cache
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (charts.totalSize): This is the label displayed
|
||||||
|
# in the performance analysis view for total requests size, in kilobytes.
|
||||||
|
charts.totalSize=Size: %S KB
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (charts.totalTime): This is the label displayed
|
||||||
|
# in the performance analysis view for total requests time, in milliseconds.
|
||||||
|
charts.totalTime=Time: %S ms
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (charts.totalCached): This is the label displayed
|
||||||
|
# in the performance analysis view for total cached responses.
|
||||||
|
charts.totalCached=Cached responses: %S
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (charts.totalCount): This is the label displayed
|
||||||
|
# in the performance analysis view for total requests.
|
||||||
|
charts.totalCount=Total requests: %S
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#requests-menu-empty-notice {
|
#requests-menu-empty-notice {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
font-size: 110%;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark #requests-menu-empty-notice {
|
.theme-dark #requests-menu-empty-notice {
|
||||||
|
|
@ -17,6 +17,18 @@
|
||||||
color: #585959; /* Grey foreground text */
|
color: #585959; /* Grey foreground text */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#requests-menu-perf-notice-button {
|
||||||
|
min-width: 30px;
|
||||||
|
min-height: 28px;
|
||||||
|
margin: 0;
|
||||||
|
list-style-image: url(profiler-stopwatch.png);
|
||||||
|
-moz-image-region: rect(0px,16px,16px,0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#requests-menu-perf-notice-button .button-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
%filter substitution
|
%filter substitution
|
||||||
%define table_itemDarkStartBorder rgba(0,0,0,0.2)
|
%define table_itemDarkStartBorder rgba(0,0,0,0.2)
|
||||||
%define table_itemDarkEndBorder rgba(128,128,128,0.15)
|
%define table_itemDarkEndBorder rgba(128,128,128,0.15)
|
||||||
|
|
@ -475,12 +487,11 @@ box.requests-menu-status {
|
||||||
/* Network request details tabpanels */
|
/* Network request details tabpanels */
|
||||||
|
|
||||||
.theme-dark .tabpanel-content {
|
.theme-dark .tabpanel-content {
|
||||||
|
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
|
||||||
color: #f5f7fa; /* Light foreground text */
|
color: #f5f7fa; /* Light foreground text */
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .tabpanel-summary-label {
|
/* Summary tabpanel */
|
||||||
color: #f5f7fa; /* Dark foreground text */
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabpanel-summary-container {
|
.tabpanel-summary-container {
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
|
|
@ -578,7 +589,7 @@ box.requests-menu-status {
|
||||||
min-width: 1em;
|
min-width: 1em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 2px 1.5vw;
|
padding: 2px 0.75vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .requests-menu-footer-button,
|
.theme-dark .requests-menu-footer-button,
|
||||||
|
|
@ -595,14 +606,14 @@ box.requests-menu-status {
|
||||||
min-width: 2px;
|
min-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-dark .requests-menu-footer-spacer:not(:first-of-type),
|
.theme-dark .requests-menu-footer-spacer:not(:first-child),
|
||||||
.theme-dark .requests-menu-footer-button:not(:first-of-type) {
|
.theme-dark .requests-menu-footer-button:not(:first-child) {
|
||||||
-moz-border-start: 1px solid @table_itemDarkStartBorder@;
|
-moz-border-start: 1px solid @table_itemDarkStartBorder@;
|
||||||
box-shadow: -1px 0 0 @table_itemDarkEndBorder@;
|
box-shadow: -1px 0 0 @table_itemDarkEndBorder@;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-light .requests-menu-footer-spacer:not(:first-of-type),
|
.theme-light .requests-menu-footer-spacer:not(:first-child),
|
||||||
.theme-light .requests-menu-footer-button:not(:first-of-type) {
|
.theme-light .requests-menu-footer-button:not(:first-child) {
|
||||||
-moz-border-start: 1px solid @table_itemLightStartBorder@;
|
-moz-border-start: 1px solid @table_itemLightStartBorder@;
|
||||||
box-shadow: -1px 0 0 @table_itemLightEndBorder@;
|
box-shadow: -1px 0 0 @table_itemLightEndBorder@;
|
||||||
}
|
}
|
||||||
|
|
@ -628,10 +639,179 @@ box.requests-menu-status {
|
||||||
}
|
}
|
||||||
|
|
||||||
.requests-menu-footer-label {
|
.requests-menu-footer-label {
|
||||||
padding-top: 2px;
|
padding-top: 3px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Performance analysis buttons */
|
||||||
|
|
||||||
|
#requests-menu-network-summary-button {
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: transparent;
|
||||||
|
list-style-image: url(profiler-stopwatch.png);
|
||||||
|
-moz-image-region: rect(0px,16px,16px,0px);
|
||||||
|
-moz-padding-end: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#requests-menu-network-summary-label {
|
||||||
|
-moz-padding-start: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#requests-menu-network-summary-label:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Performance analysis view */
|
||||||
|
|
||||||
|
#network-statistics-toolbar {
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#network-statistics-back-button {
|
||||||
|
min-width: 4em;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
-moz-border-start: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#network-statistics-view-splitter {
|
||||||
|
border-color: rgba(0,0,0,0.2);
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#network-statistics-charts {
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark #network-statistics-charts {
|
||||||
|
background: url(background-noise-toolbar.png), #343c45; /* Toolbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light #network-statistics-charts {
|
||||||
|
background: url(background-noise-toolbar.png), #f0f1f2; /* Toolbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
#network-statistics-charts .pie-chart-container {
|
||||||
|
-moz-margin-start: 3vw;
|
||||||
|
-moz-margin-end: 1vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#network-statistics-charts .table-chart-container {
|
||||||
|
-moz-margin-start: 1vw;
|
||||||
|
-moz-margin-end: 3vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob[name=html] {
|
||||||
|
fill: #5e88b0; /* Blue-Grey highlight */
|
||||||
|
background: #5e88b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob[name=html] {
|
||||||
|
fill: #5f88b0; /* Blue-Grey highlight */
|
||||||
|
background: #5f88b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob[name=css] {
|
||||||
|
fill: #46afe3; /* Blue highlight */
|
||||||
|
background: #46afe3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob[name=css] {
|
||||||
|
fill: #0088cc; /* Blue highlight */
|
||||||
|
background: #0088cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob[name=js] {
|
||||||
|
fill: #d99b28; /* Light Orange highlight */
|
||||||
|
background: #d99b28;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob[name=js] {
|
||||||
|
fill: #d97e00; /* Light Orange highlight */
|
||||||
|
background: #d97e00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob[name=xhr] {
|
||||||
|
fill: #d96629; /* Orange highlight */
|
||||||
|
background: #d96629;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob[name=xhr] {
|
||||||
|
fill: #f13c00; /* Orange highlight */
|
||||||
|
background: #f13c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob[name=fonts] {
|
||||||
|
fill: #6b7abb; /* Purple highlight */
|
||||||
|
background: #6b7abb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob[name=fonts] {
|
||||||
|
fill: #5b5fff; /* Purple highlight */
|
||||||
|
background: #5b5fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob[name=images] {
|
||||||
|
fill: #df80ff; /* Pink highlight */
|
||||||
|
background: #df80ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob[name=images] {
|
||||||
|
fill: #b82ee5; /* Pink highlight */
|
||||||
|
background: #b82ee5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob[name=media] {
|
||||||
|
fill: #70bf53; /* Green highlight */
|
||||||
|
background: #70bf53;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob[name=media] {
|
||||||
|
fill: #2cbb0f; /* Green highlight */
|
||||||
|
background: #2cbb0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob[name=flash] {
|
||||||
|
fill: #eb5368; /* Red highlight */
|
||||||
|
background: #eb5368;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob[name=flash] {
|
||||||
|
fill: #ed2655; /* Red highlight */
|
||||||
|
background: #ed2655;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-row-label[name=cached] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-row-label[name=count] {
|
||||||
|
width: 3em;
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-row-label[name=label] {
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-row-label[name=size] {
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-row-label[name=time] {
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive sidebar */
|
/* Responsive sidebar */
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
#requests-menu-toolbar {
|
#requests-menu-toolbar {
|
||||||
|
|
@ -644,7 +824,7 @@ box.requests-menu-status {
|
||||||
|
|
||||||
.requests-menu-footer-button,
|
.requests-menu-footer-button,
|
||||||
.requests-menu-footer-label {
|
.requests-menu-footer-label {
|
||||||
padding: 2px 2vw;
|
padding: 2px 1vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
#details-pane {
|
#details-pane {
|
||||||
|
|
|
||||||
|
|
@ -723,7 +723,9 @@
|
||||||
.theme-light #breadcrumb-separator-normal,
|
.theme-light #breadcrumb-separator-normal,
|
||||||
.theme-light .scrollbutton-up > .toolbarbutton-icon,
|
.theme-light .scrollbutton-up > .toolbarbutton-icon,
|
||||||
.theme-light .scrollbutton-down > .toolbarbutton-icon,
|
.theme-light .scrollbutton-down > .toolbarbutton-icon,
|
||||||
.theme-light #black-boxed-message-button .button-icon {
|
.theme-light #black-boxed-message-button .button-icon,
|
||||||
|
.theme-light #requests-menu-perf-notice-button .button-icon,
|
||||||
|
.theme-light #requests-menu-network-summary-button .button-icon {
|
||||||
filter: url(filters.svg#invert);
|
filter: url(filters.svg#invert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -792,4 +792,145 @@
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Charts */
|
||||||
|
|
||||||
|
.generic-chart-container {
|
||||||
|
/* Hack: force hardware acceleration */
|
||||||
|
transform: translateZ(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .generic-chart-container {
|
||||||
|
color: #f5f7fa; /* Light foreground text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .generic-chart-container {
|
||||||
|
color: #585959; /* Grey foreground text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .chart-colored-blob {
|
||||||
|
fill: #b8c8d9; /* Light content text */
|
||||||
|
background: #b8c8d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .chart-colored-blob {
|
||||||
|
fill: #8fa1b2; /* Grey content text */
|
||||||
|
background: #8fa1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Charts: Pie */
|
||||||
|
|
||||||
|
.pie-chart-slice {
|
||||||
|
stroke-width: 1px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .pie-chart-slice {
|
||||||
|
stroke: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .pie-chart-slice {
|
||||||
|
stroke: rgba(255,255,255,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .pie-chart-slice[largest] {
|
||||||
|
stroke-width: 2px;
|
||||||
|
stroke: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .pie-chart-slice[largest] {
|
||||||
|
stroke: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-chart-label {
|
||||||
|
text-anchor: middle;
|
||||||
|
dominant-baseline: middle;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .pie-chart-label {
|
||||||
|
fill: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .pie-chart-label {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-chart-container[slices="1"] > .pie-chart-slice {
|
||||||
|
stroke-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-chart-slice,
|
||||||
|
.pie-chart-label {
|
||||||
|
transition: all 0.1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-chart-slice:not(:hover):not([focused]),
|
||||||
|
.pie-chart-slice:not(:hover):not([focused]) + .pie-chart-label {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Charts: Table */
|
||||||
|
|
||||||
|
.table-chart-title {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
font-size: 120%;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-row {
|
||||||
|
margin-top: 1px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-grid:hover > .table-chart-row {
|
||||||
|
transition: opacity 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-grid:not(:hover) > .table-chart-row {
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generic-chart-container:hover > .table-chart-grid:hover > .table-chart-row:not(:hover),
|
||||||
|
.generic-chart-container:hover ~ .table-chart-container > .table-chart-grid > .table-chart-row:not([focused]) {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-row-box {
|
||||||
|
width: 8px;
|
||||||
|
height: 1.5em;
|
||||||
|
-moz-margin-end: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-row-label {
|
||||||
|
width: 8em;
|
||||||
|
-moz-padding-end: 6px;
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-totals {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .table-chart-totals {
|
||||||
|
border-top: 1px solid #b6babf; /* Grey foreground text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .table-chart-totals {
|
||||||
|
border-top: 1px solid #585959; /* Grey foreground text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-chart-summary-label {
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 1px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark .table-chart-summary-label {
|
||||||
|
color: #f5f7fa; /* Light foreground text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light .table-chart-summary-label {
|
||||||
|
color: #18191a; /* Dark foreground text */
|
||||||
|
}
|
||||||
|
|
||||||
%include ../../shared/devtools/app-manager/manifest-editor.inc.css
|
%include ../../shared/devtools/app-manager/manifest-editor.inc.css
|
||||||
|
|
|
||||||
|
|
@ -781,7 +781,12 @@ BrowserTabActor.prototype = {
|
||||||
reload = true;
|
reload = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reload) {
|
// Reload if:
|
||||||
|
// - there's an explicit `performReload` flag and it's true
|
||||||
|
// - there's no `performReload` flag, but it makes sense to do so
|
||||||
|
let hasExplicitReloadFlag = "performReload" in options;
|
||||||
|
if ((hasExplicitReloadFlag && options.performReload) ||
|
||||||
|
(!hasExplicitReloadFlag && reload)) {
|
||||||
this.onReload();
|
this.onReload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue