new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/l10n.js
@@ -0,0 +1,9 @@
+"use strict";
+
+const {LocalizationHelper} = require("devtools/shared/l10n");
+
+const NET_STRINGS_URI = "devtools/locale/netmonitor.properties";
+const WEBCONSOLE_STRINGS_URI = "devtools/locale/webconsole.properties";
+
+exports.L10N = new LocalizationHelper(NET_STRINGS_URI);
+exports.WEBCONSOLE_L10N = new LocalizationHelper(WEBCONSOLE_STRINGS_URI);
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -4,13 +4,16 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'har'
]
DevToolsModules(
'filter-predicates.js',
+ 'l10n.js',
'panel.js',
+ 'prefs.js',
'request-utils.js',
+ 'requests-menu-view.js',
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -1,14 +1,15 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
/* globals window, document, NetMonitorView */
+/* exported loader */
"use strict";
var { utils: Cu } = Components;
// The panel's window global is an EventEmitter firing the following events:
const EVENTS = {
// When the monitored target begins and finishes navigating.
TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
@@ -103,28 +104,36 @@ const ACTIVITY_TYPE = {
WITH_CACHE_DEFAULT: 3
},
// Enabling or disabling the cache without triggering a reload.
ENABLE_CACHE: 3,
DISABLE_CACHE: 4
};
-const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var BrowserLoaderModule = {};
+Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderModule);
+var { loader, require } = BrowserLoaderModule.BrowserLoader({
+ baseURI: "resource://devtools/client/netmonitor/",
+ window
+});
+
const promise = require("promise");
const Services = require("Services");
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
const EventEmitter = require("devtools/shared/event-emitter");
const Editor = require("devtools/client/sourceeditor/editor");
const {TimelineFront} = require("devtools/shared/fronts/timeline");
const {Task} = require("devtools/shared/task");
+const {Prefs} = require("./prefs");
XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
XPCOMUtils.defineConstant(this, "ACTIVITY_TYPE", ACTIVITY_TYPE);
XPCOMUtils.defineConstant(this, "Editor", Editor);
+XPCOMUtils.defineConstant(this, "Prefs", Prefs);
XPCOMUtils.defineLazyModuleGetter(this, "Chart",
"resource://devtools/client/shared/widgets/Chart.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
"@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
Object.defineProperty(this, "NetworkHelper", {
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -1,155 +1,62 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
/* import-globals-from ./netmonitor-controller.js */
-/* globals gNetwork, setInterval, setTimeout, clearInterval,
- clearTimeout btoa */
+/* globals Prefs, gNetwork, setInterval, setTimeout, clearInterval, clearTimeout, btoa */
+/* exported $, $all */
"use strict";
-var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-XPCOMUtils.defineLazyGetter(this, "HarExporter", function () {
- return require("devtools/client/netmonitor/har/har-exporter").HarExporter;
-});
-
XPCOMUtils.defineLazyGetter(this, "NetworkHelper", function () {
return require("devtools/shared/webconsole/network-helper");
});
-const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
const {ToolSidebar} = require("devtools/client/framework/sidebar");
-const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
-const {setImageTooltip, getImageDimensions} =
- require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
const { testing: isTesting } = require("devtools/shared/flags");
-const {LocalizationHelper} = require("devtools/shared/l10n");
-const {PrefsHelper} = require("devtools/client/shared/prefs");
-const {ViewHelpers, Heritage, WidgetMethods, setNamedTimeout} =
- require("devtools/client/shared/widgets/view-helpers");
-const {gDevTools} = require("devtools/client/framework/devtools");
-const {Curl, CurlUtils} = require("devtools/client/shared/curl");
-const {Filters, isFreetextMatch} = require("devtools/client/netmonitor/filter-predicates");
-const {getFormDataSections} = require("devtools/client/netmonitor/request-utils");
-/**
- * Localization convenience methods.
- */
-const NET_STRINGS_URI = "devtools/locale/netmonitor.properties";
-const WEBCONSOLE_STRINGS_URI = "devtools/locale/webconsole.properties";
-var L10N = new LocalizationHelper(NET_STRINGS_URI);
-const WEBCONSOLE_L10N = new LocalizationHelper(WEBCONSOLE_STRINGS_URI);
+const {ViewHelpers, Heritage} = require("devtools/client/shared/widgets/view-helpers");
const {PluralForm} = require("devtools/shared/plural-form");
+const {Filters} = require("./filter-predicates");
+const {getFormDataSections, formDataURI, writeHeaderText, getKeyWithEvent} = require("./request-utils");
+const {L10N} = require("./l10n");
+const {RequestsMenuView} = require("./requests-menu-view");
// ms
const WDA_DEFAULT_VERIFY_INTERVAL = 50;
// Use longer timeout during testing as the tests need this process to succeed
// and two seconds is quite short on slow debug builds. The timeout here should
// be at least equal to the general mochitest timeout of 45 seconds so that this
// never gets hit during testing.
// ms
const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
-/**
- * Shortcuts for accessing various network monitor preferences.
- */
-var Prefs = new PrefsHelper("devtools.netmonitor", {
- networkDetailsWidth: ["Int", "panes-network-details-width"],
- networkDetailsHeight: ["Int", "panes-network-details-height"],
- statistics: ["Bool", "statistics"],
- filters: ["Json", "filters"]
-});
-
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-const EPSILON = 0.001;
// 100 KB in bytes
const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
-// ms
-const RESIZE_REFRESH_RATE = 50;
-// ms
-const REQUESTS_REFRESH_RATE = 50;
-// tooltip show/hide delay in ms
-const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
-// px
-const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
-// px
-const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600;
-// px
-const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
-// ms
-const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
-// px
-const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60;
-// ms
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5;
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
-// px
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10;
-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_ADD = 32;
-const REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA = [255, 0, 0, 128];
-const REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA = [0, 0, 255, 128];
const REQUEST_TIME_DECIMALS = 2;
const HEADERS_SIZE_DECIMALS = 3;
const CONTENT_SIZE_DECIMALS = 2;
-const CONTENT_MIME_TYPE_ABBREVIATIONS = {
- "ecmascript": "js",
- "javascript": "js",
- "x-javascript": "js"
-};
const CONTENT_MIME_TYPE_MAPPINGS = {
"/ecmascript": Editor.modes.js,
"/javascript": Editor.modes.js,
"/x-javascript": Editor.modes.js,
"/html": Editor.modes.html,
"/xhtml": Editor.modes.html,
"/xml": Editor.modes.html,
"/atom": Editor.modes.html,
"/soap": Editor.modes.html,
"/vnd.mpeg.dash.mpd": Editor.modes.html,
"/rdf": Editor.modes.css,
"/rss": Editor.modes.css,
"/css": Editor.modes.css
};
-const LOAD_CAUSE_STRINGS = {
- [Ci.nsIContentPolicy.TYPE_INVALID]: "invalid",
- [Ci.nsIContentPolicy.TYPE_OTHER]: "other",
- [Ci.nsIContentPolicy.TYPE_SCRIPT]: "script",
- [Ci.nsIContentPolicy.TYPE_IMAGE]: "img",
- [Ci.nsIContentPolicy.TYPE_STYLESHEET]: "stylesheet",
- [Ci.nsIContentPolicy.TYPE_OBJECT]: "object",
- [Ci.nsIContentPolicy.TYPE_DOCUMENT]: "document",
- [Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "subdocument",
- [Ci.nsIContentPolicy.TYPE_REFRESH]: "refresh",
- [Ci.nsIContentPolicy.TYPE_XBL]: "xbl",
- [Ci.nsIContentPolicy.TYPE_PING]: "ping",
- [Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "xhr",
- [Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "objectSubdoc",
- [Ci.nsIContentPolicy.TYPE_DTD]: "dtd",
- [Ci.nsIContentPolicy.TYPE_FONT]: "font",
- [Ci.nsIContentPolicy.TYPE_MEDIA]: "media",
- [Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket",
- [Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp",
- [Ci.nsIContentPolicy.TYPE_XSLT]: "xslt",
- [Ci.nsIContentPolicy.TYPE_BEACON]: "beacon",
- [Ci.nsIContentPolicy.TYPE_FETCH]: "fetch",
- [Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset",
- [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest"
-};
-
-function loadCauseString(causeType) {
- return LOAD_CAUSE_STRINGS[causeType] || "unknown";
-}
const DEFAULT_EDITOR_CONFIG = {
mode: Editor.modes.text,
readOnly: true,
lineNumbers: true
};
const GENERIC_VARIABLES_VIEW_SETTINGS = {
lazyEmpty: true,
@@ -159,27 +66,16 @@ const GENERIC_VARIABLES_VIEW_SETTINGS =
editableValueTooltip: "",
editableNameTooltip: "",
preventDisableOnChange: true,
preventDescriptorModifiers: true,
eval: () => {}
};
// px
const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
-// ms
-const FREETEXT_FILTER_SEARCH_DELAY = 200;
-// Constants for formatting bytes.
-const BYTES_IN_KB = 1024;
-const BYTES_IN_MB = Math.pow(BYTES_IN_KB, 2);
-const BYTES_IN_GB = Math.pow(BYTES_IN_KB, 3);
-const MAX_BYTES_SIZE = 1000;
-const MAX_KB_SIZE = 1000 * BYTES_IN_KB;
-const MAX_MB_SIZE = 1000 * BYTES_IN_MB;
-
-const {DeferredTask} = Cu.import("resource://gre/modules/DeferredTask.jsm", {});
/**
* Object defining the network monitor view components.
*/
var NetMonitorView = {
/**
* Initializes the network monitor view.
*/
@@ -438,2062 +334,16 @@ ToolbarView.prototype = {
requestsMenu.selectedIndex = -1;
}
},
_detailsPaneToggleButton: null
};
/**
- * Functions handling the requests menu (containing details about each request,
- * like status, method, file, domain, as well as a waterfall representing
- * timing imformation).
- */
-function RequestsMenuView() {
- dumpn("RequestsMenuView was instantiated");
-
- this._flushRequests = this._flushRequests.bind(this);
- this._onHover = this._onHover.bind(this);
- this._onSelect = this._onSelect.bind(this);
- this._onSwap = this._onSwap.bind(this);
- this._onResize = this._onResize.bind(this);
- this._onScroll = this._onScroll.bind(this);
- this._byFile = this._byFile.bind(this);
- this._byDomain = this._byDomain.bind(this);
- this._byType = this._byType.bind(this);
- this._onSecurityIconClick = this._onSecurityIconClick.bind(this);
-}
-
-RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
- /**
- * Initialization function, called when the network monitor is started.
- */
- initialize: function () {
- dumpn("Initializing the RequestsMenuView");
-
- let widgetParentEl = $("#requests-menu-contents");
- this.widget = new SideMenuWidget(widgetParentEl);
- this._splitter = $("#network-inspector-view-splitter");
- this._summary = $("#requests-menu-network-summary-button");
- this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
- this.userInputTimer = Cc["@mozilla.org/timer;1"]
- .createInstance(Ci.nsITimer);
-
- // Create a tooltip for the newly appended network request item.
- // The popup will be attached to the toolbox document.
- this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
- this.tooltip.startTogglingOnHover(widgetParentEl, this._onHover, {
- toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
- interactive: true
- });
- $("#requests-menu-contents").addEventListener("scroll", this._onScroll, true);
-
- Prefs.filters.forEach(type => this.filterOn(type));
- this.sortContents(this._byTiming);
-
- this.allowFocusOnRightClick = true;
- this.maintainSelectionVisible = true;
-
- this.widget.addEventListener("select", this._onSelect, false);
- this.widget.addEventListener("swap", this._onSwap, false);
- this._splitter.addEventListener("mousemove", this._onResize, false);
- window.addEventListener("resize", this._onResize, false);
-
- this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
- this.requestsMenuSortKeyboardEvent = getKeyWithEvent(this.sortBy.bind(this), true);
- this.requestsMenuFilterEvent = getKeyWithEvent(this.filterOn.bind(this));
- this.requestsMenuFilterKeyboardEvent = getKeyWithEvent(
- this.filterOn.bind(this), true);
- this.reqeustsMenuClearEvent = this.clear.bind(this);
- this._onContextShowing = this._onContextShowing.bind(this);
- this._onContextNewTabCommand = this.openRequestInTab.bind(this);
- this._onContextCopyUrlCommand = this.copyUrl.bind(this);
- this._onContextCopyImageAsDataUriCommand =
- this.copyImageAsDataUri.bind(this);
- this._onContextCopyResponseCommand = this.copyResponse.bind(this);
- this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
- this._onContextToggleRawHeadersCommand = this.toggleRawHeaders.bind(this);
- this._onContextPerfCommand = () => NetMonitorView.toggleFrontendMode();
- this._onReloadCommand = () => NetMonitorView.reloadPage();
- this._flushRequestsTask = new DeferredTask(this._flushRequests,
- REQUESTS_REFRESH_RATE);
-
- this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
- this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
- this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
- this.toggleRawHeadersEvent = this.toggleRawHeaders.bind(this);
-
- this.requestsFreetextFilterEvent =
- this.requestsFreetextFilterEvent.bind(this);
- this.reFilterRequests = this.reFilterRequests.bind(this);
-
- this.freetextFilterBox = $("#requests-menu-filter-freetext-text");
- this.freetextFilterBox.addEventListener("input",
- this.requestsFreetextFilterEvent, false);
- this.freetextFilterBox.addEventListener("command",
- this.requestsFreetextFilterEvent, false);
-
- $("#toolbar-labels").addEventListener("click",
- this.requestsMenuSortEvent, false);
- $("#toolbar-labels").addEventListener("keydown",
- this.requestsMenuSortKeyboardEvent, false);
- $("#requests-menu-filter-buttons").addEventListener("click",
- this.requestsMenuFilterEvent, false);
- $("#requests-menu-filter-buttons").addEventListener("keydown",
- this.requestsMenuFilterKeyboardEvent, false);
- $("#requests-menu-clear-button").addEventListener("click",
- this.reqeustsMenuClearEvent, false);
- $("#network-request-popup").addEventListener("popupshowing",
- this._onContextShowing, false);
- $("#request-menu-context-newtab").addEventListener("command",
- this._onContextNewTabCommand, false);
- $("#request-menu-context-copy-url").addEventListener("command",
- this._onContextCopyUrlCommand, false);
- $("#request-menu-context-copy-response").addEventListener("command",
- this._onContextCopyResponseCommand, false);
- $("#request-menu-context-copy-image-as-data-uri").addEventListener(
- "command", this._onContextCopyImageAsDataUriCommand, false);
- $("#toggle-raw-headers").addEventListener("click",
- this.toggleRawHeadersEvent, false);
-
- window.once("connected", this._onConnect.bind(this));
- },
-
- _onConnect: function () {
- $("#requests-menu-reload-notice-button").addEventListener("command",
- this._onReloadCommand, false);
-
- if (NetMonitorController.supportsCustomRequest) {
- $("#request-menu-context-resend").addEventListener("command",
- this._onContextResendCommand, false);
- $("#custom-request-send-button").addEventListener("click",
- this.sendCustomRequestEvent, false);
- $("#custom-request-close-button").addEventListener("click",
- this.closeCustomRequestEvent, false);
- $("#headers-summary-resend").addEventListener("click",
- this.cloneSelectedRequestEvent, false);
- } else {
- $("#request-menu-context-resend").hidden = true;
- $("#headers-summary-resend").hidden = true;
- }
-
- if (NetMonitorController.supportsPerfStats) {
- $("#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);
- $("#network-statistics-back-button").addEventListener("command",
- this._onContextPerfCommand, false);
- } else {
- $("#notice-perf-message").hidden = true;
- $("#request-menu-context-perf").hidden = true;
- $("#requests-menu-network-summary-button").hidden = true;
- }
-
- if (!NetMonitorController.supportsTransferredResponseSize) {
- $("#requests-menu-transferred-header-box").hidden = true;
- $("#requests-menu-item-template .requests-menu-transferred")
- .hidden = true;
- }
- },
-
- /**
- * Destruction function, called when the network monitor is closed.
- */
- destroy: function () {
- dumpn("Destroying the RequestsMenuView");
-
- Prefs.filters = this._activeFilters;
-
- /* Destroy the tooltip */
- this.tooltip.stopTogglingOnHover();
- this.tooltip.destroy();
- $("#requests-menu-contents").removeEventListener("scroll", this._onScroll, true);
-
- this.widget.removeEventListener("select", this._onSelect, false);
- this.widget.removeEventListener("swap", this._onSwap, false);
- this._splitter.removeEventListener("mousemove", this._onResize, false);
- window.removeEventListener("resize", this._onResize, false);
-
- $("#toolbar-labels").removeEventListener("click",
- this.requestsMenuSortEvent, false);
- $("#toolbar-labels").removeEventListener("keydown",
- this.requestsMenuSortKeyboardEvent, false);
- $("#requests-menu-filter-buttons").removeEventListener("click",
- this.requestsMenuFilterEvent, false);
- $("#requests-menu-filter-buttons").removeEventListener("keydown",
- this.requestsMenuFilterKeyboardEvent, false);
- $("#requests-menu-clear-button").removeEventListener("click",
- this.reqeustsMenuClearEvent, false);
- this.freetextFilterBox.removeEventListener("input",
- this.requestsFreetextFilterEvent, false);
- this.freetextFilterBox.removeEventListener("command",
- this.requestsFreetextFilterEvent, false);
-
- this.userInputTimer.cancel();
- this._flushRequestsTask.disarm();
-
- $("#network-request-popup").removeEventListener("popupshowing",
- this._onContextShowing, false);
- $("#request-menu-context-newtab").removeEventListener("command",
- this._onContextNewTabCommand, false);
- $("#request-menu-context-copy-url").removeEventListener("command",
- this._onContextCopyUrlCommand, false);
- $("#request-menu-context-copy-response").removeEventListener("command",
- this._onContextCopyResponseCommand, false);
- $("#request-menu-context-copy-image-as-data-uri").removeEventListener(
- "command", this._onContextCopyImageAsDataUriCommand, false);
- $("#request-menu-context-resend").removeEventListener("command",
- this._onContextResendCommand, false);
- $("#request-menu-context-perf").removeEventListener("command",
- this._onContextPerfCommand, false);
-
- $("#requests-menu-reload-notice-button").removeEventListener("command",
- this._onReloadCommand, false);
- $("#requests-menu-perf-notice-button").removeEventListener("command",
- this._onContextPerfCommand, false);
- $("#requests-menu-network-summary-button").removeEventListener("command",
- this._onContextPerfCommand, false);
- $("#network-statistics-back-button").removeEventListener("command",
- this._onContextPerfCommand, false);
-
- $("#custom-request-send-button").removeEventListener("click",
- this.sendCustomRequestEvent, false);
- $("#custom-request-close-button").removeEventListener("click",
- this.closeCustomRequestEvent, false);
- $("#headers-summary-resend").removeEventListener("click",
- this.cloneSelectedRequestEvent, false);
- $("#toggle-raw-headers").removeEventListener("click",
- this.toggleRawHeadersEvent, false);
- },
-
- /**
- * Resets this container (removes all the networking information).
- */
- reset: function () {
- this.empty();
- this._addQueue = [];
- this._updateQueue = [];
- this._firstRequestStartedMillis = -1;
- this._lastRequestEndedMillis = -1;
- },
-
- /**
- * Specifies if this view may be updated lazily.
- */
- _lazyUpdate: true,
-
- get lazyUpdate() {
- return this._lazyUpdate;
- },
-
- set lazyUpdate(value) {
- this._lazyUpdate = value;
- if (!value) {
- this._flushRequests();
- }
- },
-
- /**
- * Adds a network request to this container.
- *
- * @param string id
- * An identifier coming from the network monitor controller.
- * @param string startedDateTime
- * A string representation of when the request was started, which
- * can be parsed by Date (for example "2012-09-17T19:50:03.699Z").
- * @param string method
- * Specifies the request method (e.g. "GET", "POST", etc.)
- * @param string url
- * Specifies the request's url.
- * @param boolean isXHR
- * True if this request was initiated via XHR.
- * @param object cause
- * Specifies the request's cause. Has the following properties:
- * - type: nsContentPolicyType constant
- * - loadingDocumentUri: URI of the request origin
- * - stacktrace: JS stacktrace of the request
- * @param boolean fromCache
- * Indicates if the result came from the browser cache
- * @param boolean fromServiceWorker
- * Indicates if the request has been intercepted by a Service Worker
- */
- addRequest: function (id, startedDateTime, method, url, isXHR, cause,
- fromCache, fromServiceWorker) {
- this._addQueue.push([id, startedDateTime, method, url, isXHR, cause,
- fromCache, fromServiceWorker]);
-
- // Lazy updating is disabled in some tests.
- if (!this.lazyUpdate) {
- return void this._flushRequests();
- }
-
- this._flushRequestsTask.arm();
- return undefined;
- },
-
- /**
- * Opens selected item in a new tab.
- */
- openRequestInTab: function () {
- let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
- let selected = this.selectedItem.attachment;
- win.openUILinkIn(selected.url, "tab", { relatedToCurrent: true });
- },
-
- /**
- * Copy the request url from the currently selected item.
- */
- copyUrl: function () {
- let selected = this.selectedItem.attachment;
- clipboardHelper.copyString(selected.url);
- },
-
- /**
- * Copy the request url query string parameters from the currently
- * selected item.
- */
- copyUrlParams: function () {
- let selected = this.selectedItem.attachment;
- let params = NetworkHelper.nsIURL(selected.url).query.split("&");
- let string = params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
- clipboardHelper.copyString(string);
- },
-
- /**
- * Copy the request form data parameters (or raw payload) from
- * the currently selected item.
- */
- copyPostData: Task.async(function* () {
- let selected = this.selectedItem.attachment;
-
- // Try to extract any form data parameters.
- let formDataSections = yield getFormDataSections(
- selected.requestHeaders,
- selected.requestHeadersFromUploadStream,
- selected.requestPostData,
- gNetwork.getString.bind(gNetwork));
-
- let params = [];
- formDataSections.forEach(section => {
- let paramsArray = NetworkHelper.parseQueryString(section);
- if (paramsArray) {
- params = [...params, ...paramsArray];
- }
- });
-
- let string = params
- .map(param => param.name + (param.value ? "=" + param.value : ""))
- .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
-
- // Fall back to raw payload.
- if (!string) {
- let postData = selected.requestPostData.postData.text;
- string = yield gNetwork.getString(postData);
- if (Services.appinfo.OS !== "WINNT") {
- string = string.replace(/\r/g, "");
- }
- }
-
- clipboardHelper.copyString(string);
- }),
-
- /**
- * Copy a cURL command from the currently selected item.
- */
- copyAsCurl: function () {
- let selected = this.selectedItem.attachment;
-
- Task.spawn(function* () {
- // Create a sanitized object for the Curl command generator.
- let data = {
- url: selected.url,
- method: selected.method,
- headers: [],
- httpVersion: selected.httpVersion,
- postDataText: null
- };
-
- // Fetch header values.
- for (let { name, value } of selected.requestHeaders.headers) {
- let text = yield gNetwork.getString(value);
- data.headers.push({ name: name, value: text });
- }
-
- // Fetch the request payload.
- if (selected.requestPostData) {
- let postData = selected.requestPostData.postData.text;
- data.postDataText = yield gNetwork.getString(postData);
- }
-
- clipboardHelper.copyString(Curl.generateCommand(data));
- });
- },
-
- /**
- * Copy HAR from the network panel content to the clipboard.
- */
- copyAllAsHar: function () {
- let options = this.getDefaultHarOptions();
- return HarExporter.copy(options);
- },
-
- /**
- * Save HAR from the network panel content to a file.
- */
- saveAllAsHar: function () {
- let options = this.getDefaultHarOptions();
- return HarExporter.save(options);
- },
-
- getDefaultHarOptions: function () {
- let form = NetMonitorController._target.form;
- let title = form.title || form.url;
-
- return {
- getString: gNetwork.getString.bind(gNetwork),
- view: this,
- items: NetMonitorView.RequestsMenu.items,
- title: title
- };
- },
-
- /**
- * Copy the raw request headers from the currently selected item.
- */
- copyRequestHeaders: function () {
- let selected = this.selectedItem.attachment;
- let rawHeaders = selected.requestHeaders.rawHeaders.trim();
- if (Services.appinfo.OS !== "WINNT") {
- rawHeaders = rawHeaders.replace(/\r/g, "");
- }
- clipboardHelper.copyString(rawHeaders);
- },
-
- /**
- * Copy the raw response headers from the currently selected item.
- */
- copyResponseHeaders: function () {
- let selected = this.selectedItem.attachment;
- let rawHeaders = selected.responseHeaders.rawHeaders.trim();
- if (Services.appinfo.OS !== "WINNT") {
- rawHeaders = rawHeaders.replace(/\r/g, "");
- }
- clipboardHelper.copyString(rawHeaders);
- },
-
- /**
- * Copy image as data uri.
- */
- copyImageAsDataUri: function () {
- let selected = this.selectedItem.attachment;
- let { mimeType, text, encoding } = selected.responseContent.content;
-
- gNetwork.getString(text).then(string => {
- let data = formDataURI(mimeType, encoding, string);
- clipboardHelper.copyString(data);
- });
- },
-
- /**
- * Copy response data as a string.
- */
- copyResponse: function () {
- let selected = this.selectedItem.attachment;
- let text = selected.responseContent.content.text;
-
- gNetwork.getString(text).then(string => {
- clipboardHelper.copyString(string);
- });
- },
-
- /**
- * Create a new custom request form populated with the data from
- * the currently selected request.
- */
- cloneSelectedRequest: function () {
- let selected = this.selectedItem.attachment;
-
- // Create the element node for the network request item.
- let menuView = this._createMenuView(selected.method, selected.url,
- selected.cause);
-
- // Append a network request item to this container.
- let newItem = this.push([menuView], {
- attachment: Object.create(selected, {
- isCustom: { value: true }
- })
- });
-
- // Immediately switch to new request pane.
- this.selectedItem = newItem;
- },
-
- /**
- * Send a new HTTP request using the data in the custom request form.
- */
- sendCustomRequest: function () {
- let selected = this.selectedItem.attachment;
-
- let data = {
- url: selected.url,
- method: selected.method,
- httpVersion: selected.httpVersion,
- };
- if (selected.requestHeaders) {
- data.headers = selected.requestHeaders.headers;
- }
- if (selected.requestPostData) {
- data.body = selected.requestPostData.postData.text;
- }
-
- NetMonitorController.webConsoleClient.sendHTTPRequest(data, response => {
- let id = response.eventActor.actor;
- this._preferredItemId = id;
- });
-
- this.closeCustomRequest();
- },
-
- /**
- * Remove the currently selected custom request.
- */
- closeCustomRequest: function () {
- this.remove(this.selectedItem);
- NetMonitorView.Sidebar.toggle(false);
- },
-
- /**
- * Shows raw request/response headers in textboxes.
- */
- toggleRawHeaders: function () {
- let requestTextarea = $("#raw-request-headers-textarea");
- let responseTextare = $("#raw-response-headers-textarea");
- let rawHeadersHidden = $("#raw-headers").getAttribute("hidden");
-
- if (rawHeadersHidden) {
- let selected = this.selectedItem.attachment;
- let selectedRequestHeaders = selected.requestHeaders.headers;
- let selectedResponseHeaders = selected.responseHeaders.headers;
- requestTextarea.value = writeHeaderText(selectedRequestHeaders);
- responseTextare.value = writeHeaderText(selectedResponseHeaders);
- $("#raw-headers").hidden = false;
- } else {
- requestTextarea.value = null;
- responseTextare.value = null;
- $("#raw-headers").hidden = true;
- }
- },
-
- /**
- * Handles the timeout on the freetext filter textbox
- */
- requestsFreetextFilterEvent: function () {
- this.userInputTimer.cancel();
- this._currentFreetextFilter = this.freetextFilterBox.value || "";
-
- if (this._currentFreetextFilter.length === 0) {
- this.freetextFilterBox.removeAttribute("filled");
- } else {
- this.freetextFilterBox.setAttribute("filled", true);
- }
-
- this.userInputTimer.initWithCallback(this.reFilterRequests,
- FREETEXT_FILTER_SEARCH_DELAY, Ci.nsITimer.TYPE_ONE_SHOT);
- },
-
- /**
- * Refreshes the view contents with the newly selected filters
- */
- reFilterRequests: function () {
- this.filterContents(this._filterPredicate);
- this.refreshSummary();
- this.refreshZebra();
- },
-
- /**
- * Filters all network requests in this container by a specified type.
- *
- * @param string type
- * Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
- * "flash", "ws" or "other".
- */
- filterOn: function (type = "all") {
- if (type === "all") {
- // The filter "all" is special as it doesn't toggle.
- // - If some filters are selected and 'all' is clicked, the previously
- // selected filters will be disabled and 'all' is the only active one.
- // - If 'all' is already selected, do nothing.
- if (this._activeFilters.indexOf("all") !== -1) {
- return;
- }
-
- // Uncheck all other filters and select 'all'. Must create a copy as
- // _disableFilter removes the filters from the list while it's being
- // iterated. 'all' will be enabled automatically by _disableFilter once
- // the last filter is disabled.
- this._activeFilters.slice().forEach(this._disableFilter, this);
- } else if (this._activeFilters.indexOf(type) === -1) {
- this._enableFilter(type);
- } else {
- this._disableFilter(type);
- }
-
- this.reFilterRequests();
- },
-
- /**
- * Same as `filterOn`, except that it only allows a single type exclusively.
- *
- * @param string type
- * @see RequestsMenuView.prototype.fitlerOn
- */
- filterOnlyOn: function (type = "all") {
- this._activeFilters.slice().forEach(this._disableFilter, this);
- this.filterOn(type);
- },
-
- /**
- * Disables the given filter, its button and toggles 'all' on if the filter to
- * be disabled is the last one active.
- *
- * @param string type
- * Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
- * "flash", "ws" or "other".
- */
- _disableFilter: function (type) {
- // Remove the filter from list of active filters.
- this._activeFilters.splice(this._activeFilters.indexOf(type), 1);
-
- // Remove the checked status from the filter.
- let target = $("#requests-menu-filter-" + type + "-button");
- target.removeAttribute("checked");
-
- // Check if the filter disabled was the last one. If so, toggle all on.
- if (this._activeFilters.length === 0) {
- this._enableFilter("all");
- }
- },
-
- /**
- * Enables the given filter, its button and toggles 'all' off if the filter to
- * be enabled is the first one active.
- *
- * @param string type
- * Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
- * "flash", "ws" or "other".
- */
- _enableFilter: function (type) {
- // Make sure this is a valid filter type.
- if (!Object.keys(Filters).includes(type)) {
- return;
- }
-
- // Add the filter to the list of active filters.
- this._activeFilters.push(type);
-
- // Add the checked status to the filter button.
- let target = $("#requests-menu-filter-" + type + "-button");
- target.setAttribute("checked", true);
-
- // Check if 'all' was selected before. If so, disable it.
- if (type !== "all" && this._activeFilters.indexOf("all") !== -1) {
- this._disableFilter("all");
- }
- },
-
- /**
- * Returns a predicate that can be used to test if a request matches any of
- * the active filters.
- */
- get _filterPredicate() {
- let currentFreetextFilter = this._currentFreetextFilter;
-
- return requestItem => {
- const { attachment } = requestItem;
- return this._activeFilters.some(filterName => Filters[filterName](attachment)) &&
- isFreetextMatch(attachment, currentFreetextFilter);
- };
- },
-
- /**
- * Sorts all network requests in this container by a specified detail.
- *
- * @param string type
- * Either "status", "method", "file", "domain", "type", "transferred",
- * "size" or "waterfall".
- */
- sortBy: function (type = "waterfall") {
- let target = $("#requests-menu-" + type + "-button");
- let headers = document.querySelectorAll(".requests-menu-header-button");
-
- for (let header of headers) {
- if (header != target) {
- header.removeAttribute("sorted");
- header.removeAttribute("tooltiptext");
- header.parentNode.removeAttribute("active");
- }
- }
-
- let direction = "";
- if (target) {
- if (target.getAttribute("sorted") == "ascending") {
- target.setAttribute("sorted", direction = "descending");
- target.setAttribute("tooltiptext",
- L10N.getStr("networkMenu.sortedDesc"));
- } else {
- target.setAttribute("sorted", direction = "ascending");
- target.setAttribute("tooltiptext",
- L10N.getStr("networkMenu.sortedAsc"));
- }
- // Used to style the next column.
- target.parentNode.setAttribute("active", "true");
- }
-
- // Sort by whatever was requested.
- switch (type) {
- case "status":
- if (direction == "ascending") {
- this.sortContents(this._byStatus);
- } else {
- this.sortContents((a, b) => !this._byStatus(a, b));
- }
- break;
- case "method":
- if (direction == "ascending") {
- this.sortContents(this._byMethod);
- } else {
- this.sortContents((a, b) => !this._byMethod(a, b));
- }
- break;
- case "file":
- if (direction == "ascending") {
- this.sortContents(this._byFile);
- } else {
- this.sortContents((a, b) => !this._byFile(a, b));
- }
- break;
- case "domain":
- if (direction == "ascending") {
- this.sortContents(this._byDomain);
- } else {
- this.sortContents((a, b) => !this._byDomain(a, b));
- }
- break;
- case "cause":
- if (direction == "ascending") {
- this.sortContents(this._byCause);
- } else {
- this.sortContents((a, b) => !this._byCause(a, b));
- }
- break;
- case "type":
- if (direction == "ascending") {
- this.sortContents(this._byType);
- } else {
- this.sortContents((a, b) => !this._byType(a, b));
- }
- break;
- case "transferred":
- if (direction == "ascending") {
- this.sortContents(this._byTransferred);
- } else {
- this.sortContents((a, b) => !this._byTransferred(a, b));
- }
- break;
- case "size":
- if (direction == "ascending") {
- this.sortContents(this._bySize);
- } else {
- this.sortContents((a, b) => !this._bySize(a, b));
- }
- break;
- case "waterfall":
- if (direction == "ascending") {
- this.sortContents(this._byTiming);
- } else {
- this.sortContents((a, b) => !this._byTiming(a, b));
- }
- break;
- }
-
- this.refreshSummary();
- this.refreshZebra();
- },
-
- /**
- * Removes all network requests and closes the sidebar if open.
- */
- clear: function () {
- NetMonitorController.NetworkEventsHandler.clearMarkers();
- NetMonitorView.Sidebar.toggle(false);
-
- $("#details-pane-toggle").disabled = true;
- $("#requests-menu-empty-notice").hidden = false;
-
- this.empty();
- this.refreshSummary();
- },
-
- /**
- * Predicates used when sorting items.
- *
- * @param object aFirst
- * The first item used in the comparison.
- * @param object aSecond
- * The second item used in the comparison.
- * @return number
- * -1 to sort aFirst to a lower index than aSecond
- * 0 to leave aFirst and aSecond unchanged with respect to each other
- * 1 to sort aSecond to a lower index than aFirst
- */
- _byTiming: function ({ attachment: first }, { attachment: second }) {
- return first.startedMillis > second.startedMillis;
- },
-
- _byStatus: function ({ attachment: first }, { attachment: second }) {
- return first.status == second.status
- ? first.startedMillis > second.startedMillis
- : first.status > second.status;
- },
-
- _byMethod: function ({ attachment: first }, { attachment: second }) {
- return first.method == second.method
- ? first.startedMillis > second.startedMillis
- : first.method > second.method;
- },
-
- _byFile: function ({ attachment: first }, { attachment: second }) {
- let firstUrl = this._getUriNameWithQuery(first.url).toLowerCase();
- let secondUrl = this._getUriNameWithQuery(second.url).toLowerCase();
- return firstUrl == secondUrl
- ? first.startedMillis > second.startedMillis
- : firstUrl > secondUrl;
- },
-
- _byDomain: function ({ attachment: first }, { attachment: second }) {
- let firstDomain = this._getUriHostPort(first.url).toLowerCase();
- let secondDomain = this._getUriHostPort(second.url).toLowerCase();
- return firstDomain == secondDomain
- ? first.startedMillis > second.startedMillis
- : firstDomain > secondDomain;
- },
-
- _byCause: function ({ attachment: first }, { attachment: second }) {
- let firstCause = loadCauseString(first.cause.type);
- let secondCause = loadCauseString(second.cause.type);
-
- return firstCause == secondCause
- ? first.startedMillis > second.startedMillis
- : firstCause > secondCause;
- },
-
- _byType: function ({ attachment: first }, { attachment: second }) {
- let firstType = this._getAbbreviatedMimeType(first.mimeType).toLowerCase();
- let secondType = this._getAbbreviatedMimeType(second.mimeType).toLowerCase();
-
- return firstType == secondType
- ? first.startedMillis > second.startedMillis
- : firstType > secondType;
- },
-
- _byTransferred: function ({ attachment: first }, { attachment: second }) {
- return first.transferredSize > second.transferredSize;
- },
-
- _bySize: function ({ attachment: first }, { attachment: second }) {
- return first.contentSize > second.contentSize;
- },
-
- /**
- * Refreshes the status displayed in this container's footer, providing
- * concise information about all requests.
- */
- refreshSummary: function () {
- let visibleItems = this.visibleItems;
- let visibleRequestsCount = visibleItems.length;
- if (!visibleRequestsCount) {
- this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
- return;
- }
-
- let totalBytes = this._getTotalBytesOfRequests(visibleItems);
- let totalMillis =
- this._getNewestRequest(visibleItems).attachment.endedMillis -
- this._getOldestRequest(visibleItems).attachment.startedMillis;
-
- // https://developer.mozilla.org/en-US/docs/Localization_and_Plurals
- let str = PluralForm.get(visibleRequestsCount,
- L10N.getStr("networkMenu.summary"));
-
- this._summary.setAttribute("label", str
- .replace("#1", visibleRequestsCount)
- .replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024,
- CONTENT_SIZE_DECIMALS))
- .replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000,
- REQUEST_TIME_DECIMALS))
- );
- },
-
- /**
- * Adds odd/even attributes to all the visible items in this container.
- */
- refreshZebra: function () {
- let visibleItems = this.visibleItems;
-
- for (let i = 0, len = visibleItems.length; i < len; i++) {
- let requestItem = visibleItems[i];
- let requestTarget = requestItem.target;
-
- if (i % 2 == 0) {
- requestTarget.setAttribute("even", "");
- requestTarget.removeAttribute("odd");
- } else {
- requestTarget.setAttribute("odd", "");
- requestTarget.removeAttribute("even");
- }
- }
- },
-
- /**
- * Attaches security icon click listener for the given request menu item.
- *
- * @param object item
- * The network request item to attach the listener to.
- */
- attachSecurityIconClickListener: function ({ target }) {
- let icon = $(".requests-security-state-icon", target);
- icon.addEventListener("click", this._onSecurityIconClick);
- },
-
- /**
- * Schedules adding additional information to a network request.
- *
- * @param string id
- * An identifier coming from the network monitor controller.
- * @param object data
- * An object containing several { key: value } tuples of network info.
- * Supported keys are "httpVersion", "status", "statusText" etc.
- * @param function callback
- * A function to call once the request has been updated in the view.
- */
- updateRequest: function (id, data, callback) {
- this._updateQueue.push([id, data, callback]);
-
- // Lazy updating is disabled in some tests.
- if (!this.lazyUpdate) {
- return void this._flushRequests();
- }
-
- this._flushRequestsTask.arm();
- return undefined;
- },
-
- /**
- * Starts adding all queued additional information about network requests.
- */
- _flushRequests: function () {
- // Prevent displaying any updates received after the target closed.
- if (NetMonitorView._isDestroyed) {
- return;
- }
-
- let widget = NetMonitorView.RequestsMenu.widget;
- let isScrolledToBottom = widget.isScrolledToBottom();
-
- for (let [id, startedDateTime, method, url, isXHR, cause, fromCache,
- fromServiceWorker] of this._addQueue) {
- // Convert the received date/time string to a unix timestamp.
- let unixTime = Date.parse(startedDateTime);
-
- // Create the element node for the network request item.
- let menuView = this._createMenuView(method, url, cause);
-
- // Remember the first and last event boundaries.
- this._registerFirstRequestStart(unixTime);
- this._registerLastRequestEnd(unixTime);
-
- // Append a network request item to this container.
- let requestItem = this.push([menuView, id], {
- attachment: {
- startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
- startedMillis: unixTime,
- method: method,
- url: url,
- isXHR: isXHR,
- cause: cause,
- fromCache: fromCache,
- fromServiceWorker: fromServiceWorker
- }
- });
-
- if (id == this._preferredItemId) {
- this.selectedItem = requestItem;
- }
-
- window.emit(EVENTS.REQUEST_ADDED, id);
- }
-
- if (isScrolledToBottom && this._addQueue.length) {
- widget.scrollToBottom();
- }
-
- // For each queued additional information packet, get the corresponding
- // request item in the view and update it based on the specified data.
- for (let [id, data, callback] of this._updateQueue) {
- let requestItem = this.getItemByValue(id);
- if (!requestItem) {
- // Packet corresponds to a dead request item, target navigated.
- continue;
- }
-
- // Each information packet may contain several { key: value } tuples of
- // network info, so update the view based on each one.
- for (let key in data) {
- let val = data[key];
- if (val === undefined) {
- // The information in the packet is empty, it can be safely ignored.
- continue;
- }
-
- switch (key) {
- case "requestHeaders":
- requestItem.attachment.requestHeaders = val;
- break;
- case "requestCookies":
- requestItem.attachment.requestCookies = val;
- break;
- case "requestPostData":
- // Search the POST data upload stream for request headers and add
- // them to a separate store, different from the classic headers.
- // XXX: Be really careful here! We're creating a function inside
- // a loop, so remember the actual request item we want to modify.
- let currentItem = requestItem;
- let currentStore = { headers: [], headersSize: 0 };
-
- Task.spawn(function* () {
- let postData = yield gNetwork.getString(val.postData.text);
- let payloadHeaders = CurlUtils.getHeadersFromMultipartText(
- postData);
-
- currentStore.headers = payloadHeaders;
- currentStore.headersSize = payloadHeaders.reduce(
- (acc, { name, value }) =>
- acc + name.length + value.length + 2, 0);
-
- // The `getString` promise is async, so we need to refresh the
- // information displayed in the network details pane again here.
- refreshNetworkDetailsPaneIfNecessary(currentItem);
- });
-
- requestItem.attachment.requestPostData = val;
- requestItem.attachment.requestHeadersFromUploadStream =
- currentStore;
- break;
- case "securityState":
- requestItem.attachment.securityState = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "securityInfo":
- requestItem.attachment.securityInfo = val;
- break;
- case "responseHeaders":
- requestItem.attachment.responseHeaders = val;
- break;
- case "responseCookies":
- requestItem.attachment.responseCookies = val;
- break;
- case "httpVersion":
- requestItem.attachment.httpVersion = val;
- break;
- case "remoteAddress":
- requestItem.attachment.remoteAddress = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "remotePort":
- requestItem.attachment.remotePort = val;
- break;
- case "status":
- requestItem.attachment.status = val;
- this.updateMenuView(requestItem, key, {
- status: val,
- cached: requestItem.attachment.fromCache,
- serviceWorker: requestItem.attachment.fromServiceWorker
- });
- break;
- case "statusText":
- requestItem.attachment.statusText = val;
- let text = (requestItem.attachment.status + " " +
- requestItem.attachment.statusText);
- if (requestItem.attachment.fromCache) {
- text += " (cached)";
- } else if (requestItem.attachment.fromServiceWorker) {
- text += " (service worker)";
- }
-
- this.updateMenuView(requestItem, key, text);
- break;
- case "headersSize":
- requestItem.attachment.headersSize = val;
- break;
- case "contentSize":
- requestItem.attachment.contentSize = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "transferredSize":
- if (requestItem.attachment.fromCache) {
- requestItem.attachment.transferredSize = 0;
- this.updateMenuView(requestItem, key, "cached");
- } else if (requestItem.attachment.fromServiceWorker) {
- requestItem.attachment.transferredSize = 0;
- this.updateMenuView(requestItem, key, "service worker");
- } else {
- requestItem.attachment.transferredSize = val;
- this.updateMenuView(requestItem, key, val);
- }
- break;
- case "mimeType":
- requestItem.attachment.mimeType = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "responseContent":
- // 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");
- }
- requestItem.attachment.responseContent = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "totalTime":
- requestItem.attachment.totalTime = val;
- requestItem.attachment.endedMillis =
- requestItem.attachment.startedMillis + val;
-
- this.updateMenuView(requestItem, key, val);
- this._registerLastRequestEnd(requestItem.attachment.endedMillis);
- break;
- case "eventTimings":
- requestItem.attachment.eventTimings = val;
- this._createWaterfallView(
- requestItem, val.timings,
- requestItem.attachment.fromCache ||
- requestItem.attachment.fromServiceWorker
- );
- break;
- }
- }
- refreshNetworkDetailsPaneIfNecessary(requestItem);
-
- if (callback) {
- callback();
- }
- }
-
- /**
- * Refreshes the information displayed in the sidebar, in case this update
- * may have additional information about a request which isn't shown yet
- * in the network details pane.
- *
- * @param object requestItem
- * The item to repopulate the sidebar with in case it's selected in
- * this requests menu.
- */
- function refreshNetworkDetailsPaneIfNecessary(requestItem) {
- let selectedItem = NetMonitorView.RequestsMenu.selectedItem;
- if (selectedItem == requestItem) {
- NetMonitorView.NetworkDetails.populate(selectedItem.attachment);
- }
- }
-
- // We're done flushing all the requests, clear the update queue.
- this._updateQueue = [];
- this._addQueue = [];
-
- $("#details-pane-toggle").disabled = !this.itemCount;
- $("#requests-menu-empty-notice").hidden = !!this.itemCount;
-
- // Make sure all the requests are sorted and filtered.
- // Freshly added requests may not yet contain all the information required
- // for sorting and filtering predicates, so this is done each time the
- // network requests table is flushed (don't worry, events are drained first
- // so this doesn't happen once per network event update).
- this.sortContents();
- this.filterContents();
- this.refreshSummary();
- this.refreshZebra();
-
- // Rescale all the waterfalls so that everything is visible at once.
- this._flushWaterfallViews();
- },
-
- /**
- * Customization function for creating an item's UI.
- *
- * @param string method
- * Specifies the request method (e.g. "GET", "POST", etc.)
- * @param string url
- * Specifies the request's url.
- * @param object cause
- * Specifies the request's cause. Has two properties:
- * - type: nsContentPolicyType constant
- * - uri: URI of the request origin
- * @return nsIDOMNode
- * The network request view.
- */
- _createMenuView: function (method, url, cause) {
- let template = $("#requests-menu-item-template");
- let fragment = document.createDocumentFragment();
-
- // Flatten the DOM by removing one redundant box (the template container).
- for (let node of template.childNodes) {
- fragment.appendChild(node.cloneNode(true));
- }
-
- this.updateMenuView(fragment, "method", method);
- this.updateMenuView(fragment, "url", url);
- this.updateMenuView(fragment, "cause", cause);
-
- return fragment;
- },
-
- /**
- * Get a human-readable string from a number of bytes, with the B, KB, MB, or
- * GB value. Note that the transition between abbreviations is by 1000 rather
- * than 1024 in order to keep the displayed digits smaller as "1016 KB" is
- * more awkward than 0.99 MB"
- */
- getFormattedSize(bytes) {
- if (bytes < MAX_BYTES_SIZE) {
- return L10N.getFormatStr("networkMenu.sizeB", bytes);
- } else if (bytes < MAX_KB_SIZE) {
- let kb = bytes / BYTES_IN_KB;
- let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
- return L10N.getFormatStr("networkMenu.sizeKB", size);
- } else if (bytes < MAX_MB_SIZE) {
- let mb = bytes / BYTES_IN_MB;
- let size = L10N.numberWithDecimals(mb, CONTENT_SIZE_DECIMALS);
- return L10N.getFormatStr("networkMenu.sizeMB", size);
- }
- let gb = bytes / BYTES_IN_GB;
- let size = L10N.numberWithDecimals(gb, CONTENT_SIZE_DECIMALS);
- return L10N.getFormatStr("networkMenu.sizeGB", size);
- },
-
- /**
- * Updates the information displayed in a network request item view.
- *
- * @param object item
- * The network request item in this container.
- * @param string key
- * The type of information that is to be updated.
- * @param any value
- * The new value to be shown.
- * @return object
- * A promise that is resolved once the information is displayed.
- */
- updateMenuView: Task.async(function* (item, key, value) {
- let target = item.target || item;
-
- switch (key) {
- case "method": {
- let node = $(".requests-menu-method", target);
- node.setAttribute("value", value);
- break;
- }
- case "url": {
- let uri;
- try {
- uri = NetworkHelper.nsIURL(value);
- } catch (e) {
- // User input may not make a well-formed url yet.
- break;
- }
- let nameWithQuery = this._getUriNameWithQuery(uri);
- let hostPort = this._getUriHostPort(uri);
- let host = this._getUriHost(uri);
- let unicodeUrl = NetworkHelper.convertToUnicode(unescape(uri.spec));
-
- let file = $(".requests-menu-file", target);
- file.setAttribute("value", nameWithQuery);
- file.setAttribute("tooltiptext", unicodeUrl);
-
- let domain = $(".requests-menu-domain", target);
- domain.setAttribute("value", hostPort);
- domain.setAttribute("tooltiptext", hostPort);
-
- // Mark local hosts specially, where "local" is as defined in the W3C
- // spec for secure contexts.
- // http://www.w3.org/TR/powerful-features/
- //
- // * If the name falls under 'localhost'
- // * If the name is an IPv4 address within 127.0.0.0/8
- // * If the name is an IPv6 address within ::1/128
- //
- // IPv6 parsing is a little sloppy; it assumes that the address has
- // been validated before it gets here.
- let icon = $(".requests-security-state-icon", target);
- icon.classList.remove("security-state-local");
- if (host.match(/(.+\.)?localhost$/) ||
- host.match(/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}/) ||
- host.match(/\[[0:]+1\]/)) {
- let tooltip = L10N.getStr("netmonitor.security.state.secure");
- icon.classList.add("security-state-local");
- icon.setAttribute("tooltiptext", tooltip);
- }
-
- break;
- }
- case "remoteAddress":
- let domain = $(".requests-menu-domain", target);
- let tooltip = (domain.getAttribute("value") +
- (value ? " (" + value + ")" : ""));
- domain.setAttribute("tooltiptext", tooltip);
- break;
- case "securityState": {
- let icon = $(".requests-security-state-icon", target);
- this.attachSecurityIconClickListener(item);
-
- // Security icon for local hosts is set in the "url" branch
- if (icon.classList.contains("security-state-local")) {
- break;
- }
-
- let tooltip2 = L10N.getStr("netmonitor.security.state." + value);
- icon.classList.add("security-state-" + value);
- icon.setAttribute("tooltiptext", tooltip2);
- break;
- }
- case "status": {
- let node = $(".requests-menu-status-icon", target);
- // "code" attribute is only used by css to determine the icon color
- let code;
- if (value.cached) {
- code = "cached";
- } else if (value.serviceWorker) {
- code = "service worker";
- } else {
- code = value.status;
- }
- node.setAttribute("code", code);
- let codeNode = $(".requests-menu-status-code", target);
- codeNode.setAttribute("value", value.status);
- break;
- }
- case "statusText": {
- let node = $(".requests-menu-status", target);
- node.setAttribute("tooltiptext", value);
- break;
- }
- case "cause": {
- let labelNode = $(".requests-menu-cause-label", target);
- labelNode.setAttribute("value", loadCauseString(value.type));
- if (value.loadingDocumentUri) {
- labelNode.setAttribute("tooltiptext", value.loadingDocumentUri);
- }
-
- let stackNode = $(".requests-menu-cause-stack", target);
- if (value.stacktrace && value.stacktrace.length > 0) {
- stackNode.removeAttribute("hidden");
- }
- break;
- }
- case "contentSize": {
- let node = $(".requests-menu-size", target);
-
- let text = this.getFormattedSize(value);
-
- node.setAttribute("value", text);
- node.setAttribute("tooltiptext", text);
- break;
- }
- case "transferredSize": {
- let node = $(".requests-menu-transferred", target);
-
- let text;
- if (value === null) {
- text = L10N.getStr("networkMenu.sizeUnavailable");
- } else if (value === "cached") {
- text = L10N.getStr("networkMenu.sizeCached");
- node.classList.add("theme-comment");
- } else if (value === "service worker") {
- text = L10N.getStr("networkMenu.sizeServiceWorker");
- node.classList.add("theme-comment");
- } else {
- text = this.getFormattedSize(value);
- }
-
- node.setAttribute("value", text);
- node.setAttribute("tooltiptext", text);
- break;
- }
- case "mimeType": {
- let type = this._getAbbreviatedMimeType(value);
- let node = $(".requests-menu-type", target);
- let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
- node.setAttribute("value", text);
- node.setAttribute("tooltiptext", value);
- break;
- }
- case "responseContent": {
- let { mimeType } = item.attachment;
-
- if (mimeType.includes("image/")) {
- let { text, encoding } = value.content;
- let responseBody = yield gNetwork.getString(text);
- let node = $(".requests-menu-icon", item.target);
- node.src = formDataURI(mimeType, encoding, responseBody);
- node.setAttribute("type", "thumbnail");
- node.removeAttribute("hidden");
-
- window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
- }
- break;
- }
- case "totalTime": {
- let node = $(".requests-menu-timings-total", target);
-
- // integer
- let text = L10N.getFormatStr("networkMenu.totalMS", value);
- node.setAttribute("value", text);
- node.setAttribute("tooltiptext", text);
- break;
- }
- }
- }),
-
- /**
- * Creates a waterfall representing timing information in a network
- * request item view.
- *
- * @param object item
- * The network request item in this container.
- * @param object timings
- * An object containing timing information.
- * @param boolean fromCache
- * Indicates if the result came from the browser cache or
- * a service worker
- */
- _createWaterfallView: function (item, timings, fromCache) {
- let { target } = item;
- let sections = ["blocked", "dns", "connect", "send", "wait", "receive"];
- // Skipping "blocked" because it doesn't work yet.
-
- let timingsNode = $(".requests-menu-timings", target);
- let timingsTotal = $(".requests-menu-timings-total", timingsNode);
-
- if (fromCache) {
- timingsTotal.style.display = "none";
- return;
- }
-
- // Add a set of boxes representing timing information.
- for (let key of sections) {
- let width = timings[key];
-
- // Don't render anything if it surely won't be visible.
- // One millisecond == one unscaled pixel.
- if (width > 0) {
- let timingBox = document.createElement("hbox");
- timingBox.className = "requests-menu-timings-box " + key;
- timingBox.setAttribute("width", width);
- timingsNode.insertBefore(timingBox, timingsTotal);
- }
- }
- },
-
- /**
- * Rescales and redraws all the waterfall views in this container.
- *
- * @param boolean reset
- * True if this container's width was changed.
- */
- _flushWaterfallViews: function (reset) {
- // Don't paint things while the waterfall view isn't even visible,
- // or there are no items added to this container.
- if (NetMonitorView.currentFrontendMode !=
- "network-inspector-view" || !this.itemCount) {
- return;
- }
-
- // To avoid expensive operations like getBoundingClientRect() and
- // rebuilding the waterfall background each time a new request comes in,
- // stuff is cached. However, in certain scenarios like when the window
- // is resized, this needs to be invalidated.
- if (reset) {
- this._cachedWaterfallWidth = 0;
- }
-
- // Determine the scaling to be applied to all the waterfalls so that
- // everything is visible at once. One millisecond == one unscaled pixel.
- let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
- let longestWidth = this._lastRequestEndedMillis -
- this._firstRequestStartedMillis;
- let scale = Math.min(Math.max(availableWidth / longestWidth, EPSILON), 1);
-
- // Redraw and set the canvas background for each waterfall view.
- this._showWaterfallDivisionLabels(scale);
- this._drawWaterfallBackground(scale);
-
- // Apply CSS transforms to each waterfall in this container totalTime
- // accurately translate and resize as needed.
- for (let { target, attachment } of this) {
- let timingsNode = $(".requests-menu-timings", target);
- let totalNode = $(".requests-menu-timings-total", target);
- let direction = window.isRTL ? -1 : 1;
-
- // Render the timing information at a specific horizontal translation
- // based on the delta to the first monitored event network.
- let translateX = "translateX(" + (direction *
- attachment.startedDeltaMillis) + "px)";
-
- // Based on the total time passed until the last request, rescale
- // all the waterfalls to a reasonable size.
- let scaleX = "scaleX(" + scale + ")";
-
- // Certain nodes should not be scaled, even if they're children of
- // another scaled node. In this case, apply a reversed transformation.
- let revScaleX = "scaleX(" + (1 / scale) + ")";
-
- timingsNode.style.transform = scaleX + " " + translateX;
- totalNode.style.transform = revScaleX;
- }
- },
-
- /**
- * Creates the labels displayed on the waterfall header in this container.
- *
- * @param number scale
- * The current waterfall scale.
- */
- _showWaterfallDivisionLabels: function (scale) {
- let container = $("#requests-menu-waterfall-label-wrapper");
- let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
-
- // Nuke all existing labels.
- while (container.hasChildNodes()) {
- container.firstChild.remove();
- }
-
- // Build new millisecond tick labels...
- let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
- let optimalTickIntervalFound = false;
-
- while (!optimalTickIntervalFound) {
- // Ignore any divisions that would end up being too close to each other.
- let scaledStep = scale * timingStep;
- if (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
- timingStep <<= 1;
- continue;
- }
- optimalTickIntervalFound = true;
-
- // Insert one label for each division on the current scale.
- let fragment = document.createDocumentFragment();
- let direction = window.isRTL ? -1 : 1;
-
- for (let x = 0; x < availableWidth; x += scaledStep) {
- let translateX = "translateX(" + ((direction * x) | 0) + "px)";
- let millisecondTime = x / scale;
-
- let normalizedTime = millisecondTime;
- let divisionScale = "millisecond";
-
- // If the division is greater than 1 minute.
- if (normalizedTime > 60000) {
- normalizedTime /= 60000;
- divisionScale = "minute";
- } else if (normalizedTime > 1000) {
- // If the division is greater than 1 second.
- normalizedTime /= 1000;
- divisionScale = "second";
- }
-
- // Showing too many decimals is bad UX.
- if (divisionScale == "millisecond") {
- normalizedTime |= 0;
- } else {
- normalizedTime = L10N.numberWithDecimals(normalizedTime,
- REQUEST_TIME_DECIMALS);
- }
-
- let node = document.createElement("label");
- let text = L10N.getFormatStr("networkMenu." +
- divisionScale, normalizedTime);
- node.className = "plain requests-menu-timings-division";
- node.setAttribute("division-scale", divisionScale);
- node.style.transform = translateX;
-
- node.setAttribute("value", text);
- fragment.appendChild(node);
- }
- container.appendChild(fragment);
-
- container.className = "requests-menu-waterfall-visible";
- }
- },
-
- /**
- * Creates the background displayed on each waterfall view in this container.
- *
- * @param number scale
- * The current waterfall scale.
- */
- _drawWaterfallBackground: function (scale) {
- if (!this._canvas || !this._ctx) {
- this._canvas = document.createElementNS(HTML_NS, "canvas");
- this._ctx = this._canvas.getContext("2d");
- }
- let canvas = this._canvas;
- let ctx = this._ctx;
-
- // Nuke the context.
- let canvasWidth = canvas.width = this._waterfallWidth;
- // Awww yeah, 1px, repeats on Y axis.
- let canvasHeight = canvas.height = 1;
-
- // Start over.
- let imageData = ctx.createImageData(canvasWidth, canvasHeight);
- let pixelArray = imageData.data;
-
- let buf = new ArrayBuffer(pixelArray.length);
- let view8bit = new Uint8ClampedArray(buf);
- let view32bit = new Uint32Array(buf);
-
- // Build new millisecond tick lines...
- let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
- let [r, g, b] = REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
- let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
- let optimalTickIntervalFound = false;
-
- while (!optimalTickIntervalFound) {
- // Ignore any divisions that would end up being too close to each other.
- let scaledStep = scale * timingStep;
- if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
- timingStep <<= 1;
- continue;
- }
- optimalTickIntervalFound = true;
-
- // Insert one pixel for each division on each scale.
- for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
- let increment = scaledStep * Math.pow(2, i);
- for (let x = 0; x < canvasWidth; x += increment) {
- let position = (window.isRTL ? canvasWidth - x : x) | 0;
- view32bit[position] =
- (alphaComponent << 24) | (b << 16) | (g << 8) | r;
- }
- alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
- }
- }
-
- {
- let t = NetMonitorController.NetworkEventsHandler
- .firstDocumentDOMContentLoadedTimestamp;
-
- let delta = Math.floor((t - this._firstRequestStartedMillis) * scale);
- let [r1, g1, b1, a1] =
- REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA;
- view32bit[delta] = (a1 << 24) | (r1 << 16) | (g1 << 8) | b1;
- }
- {
- let t = NetMonitorController.NetworkEventsHandler
- .firstDocumentLoadTimestamp;
-
- let delta = Math.floor((t - this._firstRequestStartedMillis) * scale);
- let [r2, g2, b2, a2] = REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA;
- view32bit[delta] = (a2 << 24) | (r2 << 16) | (g2 << 8) | b2;
- }
-
- // Flush the image data and cache the waterfall background.
- pixelArray.set(view8bit);
- ctx.putImageData(imageData, 0, 0);
- document.mozSetImageElement("waterfall-background", canvas);
- },
-
- /**
- * The selection listener for this container.
- */
- _onSelect: function ({ detail: item }) {
- if (item) {
- NetMonitorView.Sidebar.populate(item.attachment);
- NetMonitorView.Sidebar.toggle(true);
- } else {
- NetMonitorView.Sidebar.toggle(false);
- }
- },
-
- /**
- * The swap listener for this container.
- * Called when two items switch places, when the contents are sorted.
- */
- _onSwap: function ({ detail: [firstItem, secondItem] }) {
- // Reattach click listener to the security icons
- this.attachSecurityIconClickListener(firstItem);
- this.attachSecurityIconClickListener(secondItem);
- },
-
- /**
- * The predicate used when deciding whether a popup should be shown
- * over a request item or not.
- *
- * @param nsIDOMNode target
- * The element node currently being hovered.
- * @param object tooltip
- * The current tooltip instance.
- * @return {Promise}
- */
- _onHover: Task.async(function* (target, tooltip) {
- let requestItem = this.getItemForElement(target);
- if (!requestItem) {
- return false;
- }
-
- let hovered = requestItem.attachment;
- if (hovered.responseContent && target.closest(".requests-menu-icon-and-file")) {
- return this._setTooltipImageContent(tooltip, requestItem);
- } else if (hovered.cause && target.closest(".requests-menu-cause-stack")) {
- return this._setTooltipStackTraceContent(tooltip, requestItem);
- }
-
- return false;
- }),
-
- _setTooltipImageContent: Task.async(function* (tooltip, requestItem) {
- let { mimeType, text, encoding } = requestItem.attachment.responseContent.content;
-
- if (!mimeType || !mimeType.includes("image/")) {
- return false;
- }
-
- let string = yield gNetwork.getString(text);
- let src = formDataURI(mimeType, encoding, string);
- let maxDim = REQUESTS_TOOLTIP_IMAGE_MAX_DIM;
- let { naturalWidth, naturalHeight } = yield getImageDimensions(tooltip.doc, src);
- let options = { maxDim, naturalWidth, naturalHeight };
- setImageTooltip(tooltip, tooltip.doc, src, options);
-
- return $(".requests-menu-icon", requestItem.target);
- }),
-
- _setTooltipStackTraceContent: Task.async(function* (tooltip, requestItem) {
- let {stacktrace} = requestItem.attachment.cause;
-
- if (!stacktrace || stacktrace.length == 0) {
- return false;
- }
-
- let doc = tooltip.doc;
- let el = doc.createElementNS(HTML_NS, "div");
- el.className = "stack-trace-tooltip devtools-monospace";
-
- for (let f of stacktrace) {
- let { functionName, filename, lineNumber, columnNumber, asyncCause } = f;
-
- if (asyncCause) {
- // if there is asyncCause, append a "divider" row into the trace
- let asyncFrameEl = doc.createElementNS(HTML_NS, "div");
- asyncFrameEl.className = "stack-frame stack-frame-async";
- asyncFrameEl.textContent =
- WEBCONSOLE_L10N.getFormatStr("stacktrace.asyncStack", asyncCause);
- el.appendChild(asyncFrameEl);
- }
-
- // Parse a source name in format "url -> url"
- let sourceUrl = filename.split(" -> ").pop();
-
- let frameEl = doc.createElementNS(HTML_NS, "div");
- frameEl.className = "stack-frame stack-frame-call";
-
- let funcEl = doc.createElementNS(HTML_NS, "span");
- funcEl.className = "stack-frame-function-name";
- funcEl.textContent =
- functionName || WEBCONSOLE_L10N.getStr("stacktrace.anonymousFunction");
- frameEl.appendChild(funcEl);
-
- let sourceEl = doc.createElementNS(HTML_NS, "span");
- sourceEl.className = "stack-frame-source-name";
- frameEl.appendChild(sourceEl);
-
- let sourceInnerEl = doc.createElementNS(HTML_NS, "span");
- sourceInnerEl.className = "stack-frame-source-name-inner";
- sourceEl.appendChild(sourceInnerEl);
-
- sourceInnerEl.textContent = sourceUrl;
- sourceInnerEl.title = sourceUrl;
-
- let lineEl = doc.createElementNS(HTML_NS, "span");
- lineEl.className = "stack-frame-line";
- lineEl.textContent = `:${lineNumber}:${columnNumber}`;
- sourceInnerEl.appendChild(lineEl);
-
- frameEl.addEventListener("click", () => {
- // hide the tooltip immediately, not after delay
- tooltip.hide();
- NetMonitorController.viewSourceInDebugger(filename, lineNumber);
- }, false);
-
- el.appendChild(frameEl);
- }
-
- tooltip.setContent(el, {width: REQUESTS_TOOLTIP_STACK_TRACE_WIDTH});
-
- return true;
- }),
-
- /**
- * A handler that opens the security tab in the details view if secure or
- * broken security indicator is clicked.
- */
- _onSecurityIconClick: function (e) {
- let state = this.selectedItem.attachment.securityState;
- if (state !== "insecure") {
- // Choose the security tab.
- NetMonitorView.NetworkDetails.widget.selectedIndex = 5;
- }
- },
-
- /**
- * The resize listener for this container's window.
- */
- _onResize: function (e) {
- // Allow requests to settle down first.
- setNamedTimeout("resize-events",
- RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
- },
-
- /**
- * Scroll listener for the requests menu view.
- */
- _onScroll: function () {
- this.tooltip.hide();
- },
-
- /**
- * Handle the context menu opening. Hide items if no request is selected.
- */
- _onContextShowing: function () {
- let selectedItem = this.selectedItem;
-
- let resendElement = $("#request-menu-context-resend");
- resendElement.hidden = !NetMonitorController.supportsCustomRequest ||
- !selectedItem || selectedItem.attachment.isCustom;
-
- let copyUrlElement = $("#request-menu-context-copy-url");
- copyUrlElement.hidden = !selectedItem;
-
- let copyUrlParamsElement = $("#request-menu-context-copy-url-params");
- copyUrlParamsElement.hidden = !selectedItem ||
- !NetworkHelper.nsIURL(selectedItem.attachment.url).query;
-
- let copyPostDataElement = $("#request-menu-context-copy-post-data");
- copyPostDataElement.hidden = !selectedItem ||
- !selectedItem.attachment.requestPostData;
-
- let copyAsCurlElement = $("#request-menu-context-copy-as-curl");
- copyAsCurlElement.hidden = !selectedItem || !selectedItem.attachment;
-
- let copyRequestHeadersElement =
- $("#request-menu-context-copy-request-headers");
- copyRequestHeadersElement.hidden = !selectedItem ||
- !selectedItem.attachment.requestHeaders;
-
- let copyResponseHeadersElement =
- $("#response-menu-context-copy-response-headers");
- copyResponseHeadersElement.hidden = !selectedItem ||
- !selectedItem.attachment.responseHeaders;
-
- let copyResponse = $("#request-menu-context-copy-response");
- copyResponse.hidden = !selectedItem ||
- !selectedItem.attachment.responseContent ||
- !selectedItem.attachment.responseContent.content.text ||
- selectedItem.attachment.responseContent.content.text.length === 0;
-
- let copyImageAsDataUriElement =
- $("#request-menu-context-copy-image-as-data-uri");
- copyImageAsDataUriElement.hidden = !selectedItem ||
- !selectedItem.attachment.responseContent ||
- !selectedItem.attachment.responseContent.content
- .mimeType.includes("image/");
-
- let separators = $all(".request-menu-context-separator");
- Array.forEach(separators, separator => {
- separator.hidden = !selectedItem;
- });
-
- let copyAsHar = $("#request-menu-context-copy-all-as-har");
- copyAsHar.hidden = !NetMonitorView.RequestsMenu.items.length;
-
- let saveAsHar = $("#request-menu-context-save-all-as-har");
- saveAsHar.hidden = !NetMonitorView.RequestsMenu.items.length;
-
- let newTabElement = $("#request-menu-context-newtab");
- newTabElement.hidden = !selectedItem;
- },
-
- /**
- * Checks if the specified unix time is the first one to be known of,
- * and saves it if so.
- *
- * @param number unixTime
- * The milliseconds to check and save.
- */
- _registerFirstRequestStart: function (unixTime) {
- if (this._firstRequestStartedMillis == -1) {
- this._firstRequestStartedMillis = unixTime;
- }
- },
-
- /**
- * Checks if the specified unix time is the last one to be known of,
- * and saves it if so.
- *
- * @param number unixTime
- * The milliseconds to check and save.
- */
- _registerLastRequestEnd: function (unixTime) {
- if (this._lastRequestEndedMillis < unixTime) {
- this._lastRequestEndedMillis = unixTime;
- }
- },
-
- /**
- * Helpers for getting details about an nsIURL.
- *
- * @param nsIURL | string url
- * @return string
- */
- _getUriNameWithQuery: function (url) {
- if (!(url instanceof Ci.nsIURL)) {
- url = NetworkHelper.nsIURL(url);
- }
-
- let name = NetworkHelper.convertToUnicode(
- unescape(url.fileName || url.filePath || "/"));
- let query = NetworkHelper.convertToUnicode(unescape(url.query));
-
- return name + (query ? "?" + query : "");
- },
-
- _getUriHostPort: function (url) {
- if (!(url instanceof Ci.nsIURL)) {
- url = NetworkHelper.nsIURL(url);
- }
- return NetworkHelper.convertToUnicode(unescape(url.hostPort));
- },
-
- _getUriHost: function (url) {
- return this._getUriHostPort(url).replace(/:\d+$/, "");
- },
-
- /**
- * Helper for getting an abbreviated string for a mime type.
- *
- * @param string mimeType
- * @return string
- */
- _getAbbreviatedMimeType: function (mimeType) {
- if (!mimeType) {
- return "";
- }
- return (mimeType.split(";")[0].split("/")[1] || "").split("+")[0];
- },
-
- /**
- * Gets the total number of bytes representing the cumulated content size of
- * a set of requests. Returns 0 for an empty set.
- *
- * @param array itemsArray
- * @return number
- */
- _getTotalBytesOfRequests: function (itemsArray) {
- if (!itemsArray.length) {
- return 0;
- }
-
- let result = 0;
- itemsArray.forEach(item => {
- let size = item.attachment.contentSize;
- result += (typeof size == "number") ? size : 0;
- });
-
- return result;
- },
-
- /**
- * Gets the oldest (first performed) request in a set. Returns null for an
- * empty set.
- *
- * @param array itemsArray
- * @return object
- */
- _getOldestRequest: function (itemsArray) {
- if (!itemsArray.length) {
- return null;
- }
- return itemsArray.reduce((prev, curr) =>
- prev.attachment.startedMillis < curr.attachment.startedMillis ?
- prev : curr);
- },
-
- /**
- * Gets the newest (latest performed) request in a set. Returns null for an
- * empty set.
- *
- * @param array itemsArray
- * @return object
- */
- _getNewestRequest: function (itemsArray) {
- if (!itemsArray.length) {
- return null;
- }
- return itemsArray.reduce((prev, curr) =>
- prev.attachment.startedMillis > curr.attachment.startedMillis ?
- prev : curr);
- },
-
- /**
- * Gets the available waterfall width in this container.
- * @return number
- */
- get _waterfallWidth() {
- if (this._cachedWaterfallWidth == 0) {
- let container = $("#requests-menu-toolbar");
- let waterfall = $("#requests-menu-waterfall-header-box");
- let containerBounds = container.getBoundingClientRect();
- let waterfallBounds = waterfall.getBoundingClientRect();
- if (!window.isRTL) {
- this._cachedWaterfallWidth = containerBounds.width -
- waterfallBounds.left;
- } else {
- this._cachedWaterfallWidth = waterfallBounds.right;
- }
- }
- return this._cachedWaterfallWidth;
- },
-
- _splitter: null,
- _summary: null,
- _canvas: null,
- _ctx: null,
- _cachedWaterfallWidth: 0,
- _firstRequestStartedMillis: -1,
- _lastRequestEndedMillis: -1,
- _updateQueue: [],
- _addQueue: [],
- _updateTimeout: null,
- _resizeTimeout: null,
- _activeFilters: ["all"],
- _currentFreetextFilter: ""
-});
-
-/**
* Functions handling the sidebar details view.
*/
function SidebarView() {
dumpn("SidebarView was instantiated");
}
SidebarView.prototype = {
/**
@@ -3247,18 +1097,17 @@ NetworkDetailsView.prototype = {
editor.setMode(Editor.modes.js);
editor.setText(responseBody);
}
} else if (mimeType.includes("image/")) {
// Handle images.
$("#response-content-image-box").setAttribute("align", "center");
$("#response-content-image-box").setAttribute("pack", "center");
$("#response-content-image-box").hidden = false;
- $("#response-content-image").src =
- formDataURI(mimeType, encoding, responseBody);
+ $("#response-content-image").src = formDataURI(mimeType, encoding, responseBody);
// Immediately display additional information about the image:
// file name, mime type and encoding.
$("#response-content-image-name-value").setAttribute("value",
NetworkHelper.nsIURL(url).fileName);
$("#response-content-image-mime-value").setAttribute("value", mimeType);
// Wait for the image to load in order to display the width and height.
@@ -3762,28 +1611,16 @@ function parseRequestText(text, namereg,
let [, name, value] = matches;
pairs.push({name: name, value: value});
}
}
return pairs;
}
/**
- * Write out a list of headers into a chunk of text
- *
- * @param array headers
- * Array of headers info {name, value}
- * @return string text
- * List of headers in text format
- */
-function writeHeaderText(headers) {
- return headers.map(({name, value}) => name + ": " + value).join("\n");
-}
-
-/**
* Write out a list of query params into a chunk of text
*
* @param array params
* Array of query params {name, value}
* @return string
* List of query params in text format
*/
function writeQueryText(params) {
@@ -3839,59 +1676,16 @@ function responseIsFresh({ responseHeade
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.
- *
- * @param function callback
- * Function to execute execute when data-key
- * is present in event.target.
- * @param bool onlySpaceOrReturn
- * Flag to indicate if callback should only be called
- when the space or return button is pressed
- * @return function
- * Wrapped function with the target data-key as the first argument
- * and the event as the second argument.
- */
-function getKeyWithEvent(callback, onlySpaceOrReturn) {
- return function (event) {
- let key = event.target.getAttribute("data-key");
- let filterKeyboardEvent = onlySpaceOrReturn
- ? ViewHelpers.isSpaceOrReturn(event) : true;
- if (key && filterKeyboardEvent) {
- callback.call(null, key);
- }
- };
-}
-
-/**
- * Form a data: URI given a mime type, encoding, and some text.
- *
- * @param {String} mimeType the mime type
- * @param {String} encoding the encoding to use; if not set, the
- * text will be base64-encoded.
- * @param {String} text the text of the URI.
- * @return {String} a data: URI
- */
-function formDataURI(mimeType, encoding, text) {
- if (!encoding) {
- encoding = "base64";
- text = btoa(text);
- }
- return "data:" + mimeType + ";" + encoding + "," + text;
-}
-
-/**
* Makes sure certain properties are available on all objects in a data store.
*
* @param array dataStore
* A list of objects for which to check the availability of properties.
* @param array mandatoryFields
* A list of strings representing properties of objects in dataStore.
* @return object
* A promise resolved when all objects in dataStore contain the
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/prefs.js
@@ -0,0 +1,14 @@
+"use strict";
+
+const {PrefsHelper} = require("devtools/client/shared/prefs");
+
+/**
+ * Shortcuts for accessing various network monitor preferences.
+ */
+
+exports.Prefs = new PrefsHelper("devtools.netmonitor", {
+ networkDetailsWidth: ["Int", "panes-network-details-width"],
+ networkDetailsHeight: ["Int", "panes-network-details-height"],
+ statistics: ["Bool", "statistics"],
+ filters: ["Json", "filters"]
+});
--- a/devtools/client/netmonitor/request-utils.js
+++ b/devtools/client/netmonitor/request-utils.js
@@ -1,13 +1,43 @@
"use strict";
+const { Ci } = require("chrome");
+const { KeyCodes } = require("devtools/client/shared/keycodes");
const { Task } = require("devtools/shared/task");
/**
+ * 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.
+ *
+ * @param function callback
+ * Function to execute execute when data-key
+ * is present in event.target.
+ * @param bool onlySpaceOrReturn
+ * Flag to indicate if callback should only be called
+ when the space or return button is pressed
+ * @return function
+ * Wrapped function with the target data-key as the first argument
+ * and the event as the second argument.
+ */
+exports.getKeyWithEvent = function (callback, onlySpaceOrReturn) {
+ return function (event) {
+ let key = event.target.getAttribute("data-key");
+ let filterKeyboardEvent = !onlySpaceOrReturn ||
+ event.keyCode === KeyCodes.DOM_VK_SPACE ||
+ event.keyCode === KeyCodes.DOM_VK_RETURN;
+
+ if (key && filterKeyboardEvent) {
+ callback.call(null, key);
+ }
+ };
+};
+
+/**
* Extracts any urlencoded form data sections (e.g. "?foo=bar&baz=42") from a
* POST request.
*
* @param object headers
* The "requestHeaders".
* @param object uploadHeaders
* The "requestHeadersFromUploadStream".
* @param object postData
@@ -43,8 +73,70 @@ exports.getFormDataSections = Task.async
if (payloadHeaders.every(header => !section.startsWith(header.name))) {
formDataSections.push(section);
}
}
}
return formDataSections;
});
+
+/**
+ * Form a data: URI given a mime type, encoding, and some text.
+ *
+ * @param {String} mimeType the mime type
+ * @param {String} encoding the encoding to use; if not set, the
+ * text will be base64-encoded.
+ * @param {String} text the text of the URI.
+ * @return {String} a data: URI
+ */
+exports.formDataURI = function (mimeType, encoding, text) {
+ if (!encoding) {
+ encoding = "base64";
+ text = btoa(text);
+ }
+ return "data:" + mimeType + ";" + encoding + "," + text;
+};
+
+/**
+ * Write out a list of headers into a chunk of text
+ *
+ * @param array headers
+ * Array of headers info {name, value}
+ * @return string text
+ * List of headers in text format
+ */
+exports.writeHeaderText = function (headers) {
+ return headers.map(({name, value}) => name + ": " + value).join("\n");
+};
+
+/**
+ * Convert a nsIContentPolicy constant to a display string
+ */
+const LOAD_CAUSE_STRINGS = {
+ [Ci.nsIContentPolicy.TYPE_INVALID]: "invalid",
+ [Ci.nsIContentPolicy.TYPE_OTHER]: "other",
+ [Ci.nsIContentPolicy.TYPE_SCRIPT]: "script",
+ [Ci.nsIContentPolicy.TYPE_IMAGE]: "img",
+ [Ci.nsIContentPolicy.TYPE_STYLESHEET]: "stylesheet",
+ [Ci.nsIContentPolicy.TYPE_OBJECT]: "object",
+ [Ci.nsIContentPolicy.TYPE_DOCUMENT]: "document",
+ [Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "subdocument",
+ [Ci.nsIContentPolicy.TYPE_REFRESH]: "refresh",
+ [Ci.nsIContentPolicy.TYPE_XBL]: "xbl",
+ [Ci.nsIContentPolicy.TYPE_PING]: "ping",
+ [Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "xhr",
+ [Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "objectSubdoc",
+ [Ci.nsIContentPolicy.TYPE_DTD]: "dtd",
+ [Ci.nsIContentPolicy.TYPE_FONT]: "font",
+ [Ci.nsIContentPolicy.TYPE_MEDIA]: "media",
+ [Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket",
+ [Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp",
+ [Ci.nsIContentPolicy.TYPE_XSLT]: "xslt",
+ [Ci.nsIContentPolicy.TYPE_BEACON]: "beacon",
+ [Ci.nsIContentPolicy.TYPE_FETCH]: "fetch",
+ [Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset",
+ [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest"
+};
+
+exports.loadCauseString = function (causeType) {
+ return LOAD_CAUSE_STRINGS[causeType] || "unknown";
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -0,0 +1,2126 @@
+/* globals document, window, dumpn, $, $all, gNetwork, EVENTS, Prefs,
+ NetMonitorController, NetMonitorView */
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const Services = require("Services");
+const {Task} = require("devtools/shared/task");
+const {DeferredTask} = Cu.import("resource://gre/modules/DeferredTask.jsm", {});
+const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
+const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+const {setImageTooltip, getImageDimensions} =
+ require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
+const {Heritage, WidgetMethods, setNamedTimeout} =
+ require("devtools/client/shared/widgets/view-helpers");
+const {gDevTools} = require("devtools/client/framework/devtools");
+const {Curl, CurlUtils} = require("devtools/client/shared/curl");
+const {PluralForm} = require("devtools/shared/plural-form");
+const {Filters, isFreetextMatch} = require("./filter-predicates");
+const {getFormDataSections, formDataURI, writeHeaderText, getKeyWithEvent,
+ loadCauseString} = require("./request-utils");
+const {L10N, WEBCONSOLE_L10N} = require("./l10n");
+
+loader.lazyServiceGetter(this, "clipboardHelper",
+ "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
+
+loader.lazyRequireGetter(this, "HarExporter",
+ "devtools/client/netmonitor/har/har-exporter", true);
+
+loader.lazyRequireGetter(this, "NetworkHelper",
+ "devtools/shared/webconsole/network-helper");
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const EPSILON = 0.001;
+// ms
+const RESIZE_REFRESH_RATE = 50;
+// ms
+const REQUESTS_REFRESH_RATE = 50;
+// tooltip show/hide delay in ms
+const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
+// px
+const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
+// px
+const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600;
+// px
+const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
+// ms
+const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
+// px
+const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60;
+// ms
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5;
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
+// px
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10;
+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_ADD = 32;
+const REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA = [255, 0, 0, 128];
+const REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA = [0, 0, 255, 128];
+// ms
+const FREETEXT_FILTER_SEARCH_DELAY = 200;
+// Constants for formatting bytes.
+const BYTES_IN_KB = 1024;
+const BYTES_IN_MB = Math.pow(BYTES_IN_KB, 2);
+const BYTES_IN_GB = Math.pow(BYTES_IN_KB, 3);
+const MAX_BYTES_SIZE = 1000;
+const MAX_KB_SIZE = 1000 * BYTES_IN_KB;
+const MAX_MB_SIZE = 1000 * BYTES_IN_MB;
+
+// TODO: duplicated from netmonitor-view.js. Move to a format-utils.js module.
+const REQUEST_TIME_DECIMALS = 2;
+const CONTENT_SIZE_DECIMALS = 2;
+
+const CONTENT_MIME_TYPE_ABBREVIATIONS = {
+ "ecmascript": "js",
+ "javascript": "js",
+ "x-javascript": "js"
+};
+
+/**
+ * Functions handling the requests menu (containing details about each request,
+ * like status, method, file, domain, as well as a waterfall representing
+ * timing imformation).
+ */
+function RequestsMenuView() {
+ dumpn("RequestsMenuView was instantiated");
+
+ this._flushRequests = this._flushRequests.bind(this);
+ this._onHover = this._onHover.bind(this);
+ this._onSelect = this._onSelect.bind(this);
+ this._onSwap = this._onSwap.bind(this);
+ this._onResize = this._onResize.bind(this);
+ this._onScroll = this._onScroll.bind(this);
+ this._byFile = this._byFile.bind(this);
+ this._byDomain = this._byDomain.bind(this);
+ this._byType = this._byType.bind(this);
+ this._onSecurityIconClick = this._onSecurityIconClick.bind(this);
+}
+
+RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the network monitor is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the RequestsMenuView");
+
+ let widgetParentEl = $("#requests-menu-contents");
+ this.widget = new SideMenuWidget(widgetParentEl);
+ this._splitter = $("#network-inspector-view-splitter");
+ this._summary = $("#requests-menu-network-summary-button");
+ this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
+ this.userInputTimer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+
+ // Create a tooltip for the newly appended network request item.
+ this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
+ this.tooltip.startTogglingOnHover(widgetParentEl, this._onHover, {
+ toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
+ interactive: true
+ });
+ $("#requests-menu-contents").addEventListener("scroll", this._onScroll, true);
+
+ Prefs.filters.forEach(type => this.filterOn(type));
+ this.sortContents(this._byTiming);
+
+ this.allowFocusOnRightClick = true;
+ this.maintainSelectionVisible = true;
+
+ this.widget.addEventListener("select", this._onSelect, false);
+ this.widget.addEventListener("swap", this._onSwap, false);
+ this._splitter.addEventListener("mousemove", this._onResize, false);
+ window.addEventListener("resize", this._onResize, false);
+
+ this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
+ this.requestsMenuSortKeyboardEvent = getKeyWithEvent(this.sortBy.bind(this), true);
+ this.requestsMenuFilterEvent = getKeyWithEvent(this.filterOn.bind(this));
+ this.requestsMenuFilterKeyboardEvent = getKeyWithEvent(
+ this.filterOn.bind(this), true);
+ this.reqeustsMenuClearEvent = this.clear.bind(this);
+ this._onContextShowing = this._onContextShowing.bind(this);
+ this._onContextNewTabCommand = this.openRequestInTab.bind(this);
+ this._onContextCopyUrlCommand = this.copyUrl.bind(this);
+ this._onContextCopyImageAsDataUriCommand =
+ this.copyImageAsDataUri.bind(this);
+ this._onContextCopyResponseCommand = this.copyResponse.bind(this);
+ this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
+ this._onContextToggleRawHeadersCommand = this.toggleRawHeaders.bind(this);
+ this._onContextPerfCommand = () => NetMonitorView.toggleFrontendMode();
+ this._onReloadCommand = () => NetMonitorView.reloadPage();
+ this._flushRequestsTask = new DeferredTask(this._flushRequests,
+ REQUESTS_REFRESH_RATE);
+
+ this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
+ this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
+ this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
+ this.toggleRawHeadersEvent = this.toggleRawHeaders.bind(this);
+
+ this.requestsFreetextFilterEvent =
+ this.requestsFreetextFilterEvent.bind(this);
+ this.reFilterRequests = this.reFilterRequests.bind(this);
+
+ this.freetextFilterBox = $("#requests-menu-filter-freetext-text");
+ this.freetextFilterBox.addEventListener("input",
+ this.requestsFreetextFilterEvent, false);
+ this.freetextFilterBox.addEventListener("command",
+ this.requestsFreetextFilterEvent, false);
+
+ $("#toolbar-labels").addEventListener("click",
+ this.requestsMenuSortEvent, false);
+ $("#toolbar-labels").addEventListener("keydown",
+ this.requestsMenuSortKeyboardEvent, false);
+ $("#requests-menu-filter-buttons").addEventListener("click",
+ this.requestsMenuFilterEvent, false);
+ $("#requests-menu-filter-buttons").addEventListener("keydown",
+ this.requestsMenuFilterKeyboardEvent, false);
+ $("#requests-menu-clear-button").addEventListener("click",
+ this.reqeustsMenuClearEvent, false);
+ $("#network-request-popup").addEventListener("popupshowing",
+ this._onContextShowing, false);
+ $("#request-menu-context-newtab").addEventListener("command",
+ this._onContextNewTabCommand, false);
+ $("#request-menu-context-copy-url").addEventListener("command",
+ this._onContextCopyUrlCommand, false);
+ $("#request-menu-context-copy-response").addEventListener("command",
+ this._onContextCopyResponseCommand, false);
+ $("#request-menu-context-copy-image-as-data-uri").addEventListener(
+ "command", this._onContextCopyImageAsDataUriCommand, false);
+ $("#toggle-raw-headers").addEventListener("click",
+ this.toggleRawHeadersEvent, false);
+
+ window.once("connected", this._onConnect.bind(this));
+ },
+
+ _onConnect: function () {
+ $("#requests-menu-reload-notice-button").addEventListener("command",
+ this._onReloadCommand, false);
+
+ if (NetMonitorController.supportsCustomRequest) {
+ $("#request-menu-context-resend").addEventListener("command",
+ this._onContextResendCommand, false);
+ $("#custom-request-send-button").addEventListener("click",
+ this.sendCustomRequestEvent, false);
+ $("#custom-request-close-button").addEventListener("click",
+ this.closeCustomRequestEvent, false);
+ $("#headers-summary-resend").addEventListener("click",
+ this.cloneSelectedRequestEvent, false);
+ } else {
+ $("#request-menu-context-resend").hidden = true;
+ $("#headers-summary-resend").hidden = true;
+ }
+
+ if (NetMonitorController.supportsPerfStats) {
+ $("#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);
+ $("#network-statistics-back-button").addEventListener("command",
+ this._onContextPerfCommand, false);
+ } else {
+ $("#notice-perf-message").hidden = true;
+ $("#request-menu-context-perf").hidden = true;
+ $("#requests-menu-network-summary-button").hidden = true;
+ }
+
+ if (!NetMonitorController.supportsTransferredResponseSize) {
+ $("#requests-menu-transferred-header-box").hidden = true;
+ $("#requests-menu-item-template .requests-menu-transferred")
+ .hidden = true;
+ }
+ },
+
+ /**
+ * Destruction function, called when the network monitor is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the RequestsMenuView");
+
+ Prefs.filters = this._activeFilters;
+
+ /* Destroy the tooltip */
+ this.tooltip.stopTogglingOnHover();
+ this.tooltip.destroy();
+ $("#requests-menu-contents").removeEventListener("scroll", this._onScroll, true);
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ this.widget.removeEventListener("swap", this._onSwap, false);
+ this._splitter.removeEventListener("mousemove", this._onResize, false);
+ window.removeEventListener("resize", this._onResize, false);
+
+ $("#toolbar-labels").removeEventListener("click",
+ this.requestsMenuSortEvent, false);
+ $("#toolbar-labels").removeEventListener("keydown",
+ this.requestsMenuSortKeyboardEvent, false);
+ $("#requests-menu-filter-buttons").removeEventListener("click",
+ this.requestsMenuFilterEvent, false);
+ $("#requests-menu-filter-buttons").removeEventListener("keydown",
+ this.requestsMenuFilterKeyboardEvent, false);
+ $("#requests-menu-clear-button").removeEventListener("click",
+ this.reqeustsMenuClearEvent, false);
+ this.freetextFilterBox.removeEventListener("input",
+ this.requestsFreetextFilterEvent, false);
+ this.freetextFilterBox.removeEventListener("command",
+ this.requestsFreetextFilterEvent, false);
+
+ this.userInputTimer.cancel();
+ this._flushRequestsTask.disarm();
+
+ $("#network-request-popup").removeEventListener("popupshowing",
+ this._onContextShowing, false);
+ $("#request-menu-context-newtab").removeEventListener("command",
+ this._onContextNewTabCommand, false);
+ $("#request-menu-context-copy-url").removeEventListener("command",
+ this._onContextCopyUrlCommand, false);
+ $("#request-menu-context-copy-response").removeEventListener("command",
+ this._onContextCopyResponseCommand, false);
+ $("#request-menu-context-copy-image-as-data-uri").removeEventListener(
+ "command", this._onContextCopyImageAsDataUriCommand, false);
+ $("#request-menu-context-resend").removeEventListener("command",
+ this._onContextResendCommand, false);
+ $("#request-menu-context-perf").removeEventListener("command",
+ this._onContextPerfCommand, false);
+
+ $("#requests-menu-reload-notice-button").removeEventListener("command",
+ this._onReloadCommand, false);
+ $("#requests-menu-perf-notice-button").removeEventListener("command",
+ this._onContextPerfCommand, false);
+ $("#requests-menu-network-summary-button").removeEventListener("command",
+ this._onContextPerfCommand, false);
+ $("#network-statistics-back-button").removeEventListener("command",
+ this._onContextPerfCommand, false);
+
+ $("#custom-request-send-button").removeEventListener("click",
+ this.sendCustomRequestEvent, false);
+ $("#custom-request-close-button").removeEventListener("click",
+ this.closeCustomRequestEvent, false);
+ $("#headers-summary-resend").removeEventListener("click",
+ this.cloneSelectedRequestEvent, false);
+ $("#toggle-raw-headers").removeEventListener("click",
+ this.toggleRawHeadersEvent, false);
+ },
+
+ /**
+ * Resets this container (removes all the networking information).
+ */
+ reset: function () {
+ this.empty();
+ this._addQueue = [];
+ this._updateQueue = [];
+ this._firstRequestStartedMillis = -1;
+ this._lastRequestEndedMillis = -1;
+ },
+
+ /**
+ * Specifies if this view may be updated lazily.
+ */
+ _lazyUpdate: true,
+
+ get lazyUpdate() {
+ return this._lazyUpdate;
+ },
+
+ set lazyUpdate(value) {
+ this._lazyUpdate = value;
+ if (!value) {
+ this._flushRequests();
+ }
+ },
+
+ /**
+ * Adds a network request to this container.
+ *
+ * @param string id
+ * An identifier coming from the network monitor controller.
+ * @param string startedDateTime
+ * A string representation of when the request was started, which
+ * can be parsed by Date (for example "2012-09-17T19:50:03.699Z").
+ * @param string method
+ * Specifies the request method (e.g. "GET", "POST", etc.)
+ * @param string url
+ * Specifies the request's url.
+ * @param boolean isXHR
+ * True if this request was initiated via XHR.
+ * @param object cause
+ * Specifies the request's cause. Has the following properties:
+ * - type: nsContentPolicyType constant
+ * - loadingDocumentUri: URI of the request origin
+ * - stacktrace: JS stacktrace of the request
+ * @param boolean fromCache
+ * Indicates if the result came from the browser cache
+ * @param boolean fromServiceWorker
+ * Indicates if the request has been intercepted by a Service Worker
+ */
+ addRequest: function (id, startedDateTime, method, url, isXHR, cause,
+ fromCache, fromServiceWorker) {
+ this._addQueue.push([id, startedDateTime, method, url, isXHR, cause,
+ fromCache, fromServiceWorker]);
+
+ // Lazy updating is disabled in some tests.
+ if (!this.lazyUpdate) {
+ return void this._flushRequests();
+ }
+
+ this._flushRequestsTask.arm();
+ return undefined;
+ },
+
+ /**
+ * Opens selected item in a new tab.
+ */
+ openRequestInTab: function () {
+ let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+ let selected = this.selectedItem.attachment;
+ win.openUILinkIn(selected.url, "tab", { relatedToCurrent: true });
+ },
+
+ /**
+ * Copy the request url from the currently selected item.
+ */
+ copyUrl: function () {
+ let selected = this.selectedItem.attachment;
+ clipboardHelper.copyString(selected.url);
+ },
+
+ /**
+ * Copy the request url query string parameters from the currently
+ * selected item.
+ */
+ copyUrlParams: function () {
+ let selected = this.selectedItem.attachment;
+ let params = NetworkHelper.nsIURL(selected.url).query.split("&");
+ let string = params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
+ clipboardHelper.copyString(string);
+ },
+
+ /**
+ * Copy the request form data parameters (or raw payload) from
+ * the currently selected item.
+ */
+ copyPostData: Task.async(function* () {
+ let selected = this.selectedItem.attachment;
+
+ // Try to extract any form data parameters.
+ let formDataSections = yield getFormDataSections(
+ selected.requestHeaders,
+ selected.requestHeadersFromUploadStream,
+ selected.requestPostData,
+ gNetwork.getString.bind(gNetwork));
+
+ let params = [];
+ formDataSections.forEach(section => {
+ let paramsArray = NetworkHelper.parseQueryString(section);
+ if (paramsArray) {
+ params = [...params, ...paramsArray];
+ }
+ });
+
+ let string = params
+ .map(param => param.name + (param.value ? "=" + param.value : ""))
+ .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
+
+ // Fall back to raw payload.
+ if (!string) {
+ let postData = selected.requestPostData.postData.text;
+ string = yield gNetwork.getString(postData);
+ if (Services.appinfo.OS !== "WINNT") {
+ string = string.replace(/\r/g, "");
+ }
+ }
+
+ clipboardHelper.copyString(string);
+ }),
+
+ /**
+ * Copy a cURL command from the currently selected item.
+ */
+ copyAsCurl: function () {
+ let selected = this.selectedItem.attachment;
+
+ Task.spawn(function* () {
+ // Create a sanitized object for the Curl command generator.
+ let data = {
+ url: selected.url,
+ method: selected.method,
+ headers: [],
+ httpVersion: selected.httpVersion,
+ postDataText: null
+ };
+
+ // Fetch header values.
+ for (let { name, value } of selected.requestHeaders.headers) {
+ let text = yield gNetwork.getString(value);
+ data.headers.push({ name: name, value: text });
+ }
+
+ // Fetch the request payload.
+ if (selected.requestPostData) {
+ let postData = selected.requestPostData.postData.text;
+ data.postDataText = yield gNetwork.getString(postData);
+ }
+
+ clipboardHelper.copyString(Curl.generateCommand(data));
+ });
+ },
+
+ /**
+ * Copy HAR from the network panel content to the clipboard.
+ */
+ copyAllAsHar: function () {
+ let options = this.getDefaultHarOptions();
+ return HarExporter.copy(options);
+ },
+
+ /**
+ * Save HAR from the network panel content to a file.
+ */
+ saveAllAsHar: function () {
+ let options = this.getDefaultHarOptions();
+ return HarExporter.save(options);
+ },
+
+ getDefaultHarOptions: function () {
+ let form = NetMonitorController._target.form;
+ let title = form.title || form.url;
+
+ return {
+ getString: gNetwork.getString.bind(gNetwork),
+ view: this,
+ items: NetMonitorView.RequestsMenu.items,
+ title: title
+ };
+ },
+
+ /**
+ * Copy the raw request headers from the currently selected item.
+ */
+ copyRequestHeaders: function () {
+ let selected = this.selectedItem.attachment;
+ let rawHeaders = selected.requestHeaders.rawHeaders.trim();
+ if (Services.appinfo.OS !== "WINNT") {
+ rawHeaders = rawHeaders.replace(/\r/g, "");
+ }
+ clipboardHelper.copyString(rawHeaders);
+ },
+
+ /**
+ * Copy the raw response headers from the currently selected item.
+ */
+ copyResponseHeaders: function () {
+ let selected = this.selectedItem.attachment;
+ let rawHeaders = selected.responseHeaders.rawHeaders.trim();
+ if (Services.appinfo.OS !== "WINNT") {
+ rawHeaders = rawHeaders.replace(/\r/g, "");
+ }
+ clipboardHelper.copyString(rawHeaders);
+ },
+
+ /**
+ * Copy image as data uri.
+ */
+ copyImageAsDataUri: function () {
+ let selected = this.selectedItem.attachment;
+ let { mimeType, text, encoding } = selected.responseContent.content;
+
+ gNetwork.getString(text).then(string => {
+ let data = formDataURI(mimeType, encoding, string);
+ clipboardHelper.copyString(data);
+ });
+ },
+
+ /**
+ * Copy response data as a string.
+ */
+ copyResponse: function () {
+ let selected = this.selectedItem.attachment;
+ let text = selected.responseContent.content.text;
+
+ gNetwork.getString(text).then(string => {
+ clipboardHelper.copyString(string);
+ });
+ },
+
+ /**
+ * Create a new custom request form populated with the data from
+ * the currently selected request.
+ */
+ cloneSelectedRequest: function () {
+ let selected = this.selectedItem.attachment;
+
+ // Create the element node for the network request item.
+ let menuView = this._createMenuView(selected.method, selected.url,
+ selected.cause);
+
+ // Append a network request item to this container.
+ let newItem = this.push([menuView], {
+ attachment: Object.create(selected, {
+ isCustom: { value: true }
+ })
+ });
+
+ // Immediately switch to new request pane.
+ this.selectedItem = newItem;
+ },
+
+ /**
+ * Send a new HTTP request using the data in the custom request form.
+ */
+ sendCustomRequest: function () {
+ let selected = this.selectedItem.attachment;
+
+ let data = {
+ url: selected.url,
+ method: selected.method,
+ httpVersion: selected.httpVersion,
+ };
+ if (selected.requestHeaders) {
+ data.headers = selected.requestHeaders.headers;
+ }
+ if (selected.requestPostData) {
+ data.body = selected.requestPostData.postData.text;
+ }
+
+ NetMonitorController.webConsoleClient.sendHTTPRequest(data, response => {
+ let id = response.eventActor.actor;
+ this._preferredItemId = id;
+ });
+
+ this.closeCustomRequest();
+ },
+
+ /**
+ * Remove the currently selected custom request.
+ */
+ closeCustomRequest: function () {
+ this.remove(this.selectedItem);
+ NetMonitorView.Sidebar.toggle(false);
+ },
+
+ /**
+ * Shows raw request/response headers in textboxes.
+ */
+ toggleRawHeaders: function () {
+ let requestTextarea = $("#raw-request-headers-textarea");
+ let responseTextare = $("#raw-response-headers-textarea");
+ let rawHeadersHidden = $("#raw-headers").getAttribute("hidden");
+
+ if (rawHeadersHidden) {
+ let selected = this.selectedItem.attachment;
+ let selectedRequestHeaders = selected.requestHeaders.headers;
+ let selectedResponseHeaders = selected.responseHeaders.headers;
+ requestTextarea.value = writeHeaderText(selectedRequestHeaders);
+ responseTextare.value = writeHeaderText(selectedResponseHeaders);
+ $("#raw-headers").hidden = false;
+ } else {
+ requestTextarea.value = null;
+ responseTextare.value = null;
+ $("#raw-headers").hidden = true;
+ }
+ },
+
+ /**
+ * Handles the timeout on the freetext filter textbox
+ */
+ requestsFreetextFilterEvent: function () {
+ this.userInputTimer.cancel();
+ this._currentFreetextFilter = this.freetextFilterBox.value || "";
+
+ if (this._currentFreetextFilter.length === 0) {
+ this.freetextFilterBox.removeAttribute("filled");
+ } else {
+ this.freetextFilterBox.setAttribute("filled", true);
+ }
+
+ this.userInputTimer.initWithCallback(this.reFilterRequests,
+ FREETEXT_FILTER_SEARCH_DELAY, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ /**
+ * Refreshes the view contents with the newly selected filters
+ */
+ reFilterRequests: function () {
+ this.filterContents(this._filterPredicate);
+ this.refreshSummary();
+ this.refreshZebra();
+ },
+
+ /**
+ * Filters all network requests in this container by a specified type.
+ *
+ * @param string type
+ * Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
+ * "flash", "ws" or "other".
+ */
+ filterOn: function (type = "all") {
+ if (type === "all") {
+ // The filter "all" is special as it doesn't toggle.
+ // - If some filters are selected and 'all' is clicked, the previously
+ // selected filters will be disabled and 'all' is the only active one.
+ // - If 'all' is already selected, do nothing.
+ if (this._activeFilters.indexOf("all") !== -1) {
+ return;
+ }
+
+ // Uncheck all other filters and select 'all'. Must create a copy as
+ // _disableFilter removes the filters from the list while it's being
+ // iterated. 'all' will be enabled automatically by _disableFilter once
+ // the last filter is disabled.
+ this._activeFilters.slice().forEach(this._disableFilter, this);
+ } else if (this._activeFilters.indexOf(type) === -1) {
+ this._enableFilter(type);
+ } else {
+ this._disableFilter(type);
+ }
+
+ this.reFilterRequests();
+ },
+
+ /**
+ * Same as `filterOn`, except that it only allows a single type exclusively.
+ *
+ * @param string type
+ * @see RequestsMenuView.prototype.fitlerOn
+ */
+ filterOnlyOn: function (type = "all") {
+ this._activeFilters.slice().forEach(this._disableFilter, this);
+ this.filterOn(type);
+ },
+
+ /**
+ * Disables the given filter, its button and toggles 'all' on if the filter to
+ * be disabled is the last one active.
+ *
+ * @param string type
+ * Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
+ * "flash", "ws" or "other".
+ */
+ _disableFilter: function (type) {
+ // Remove the filter from list of active filters.
+ this._activeFilters.splice(this._activeFilters.indexOf(type), 1);
+
+ // Remove the checked status from the filter.
+ let target = $("#requests-menu-filter-" + type + "-button");
+ target.removeAttribute("checked");
+
+ // Check if the filter disabled was the last one. If so, toggle all on.
+ if (this._activeFilters.length === 0) {
+ this._enableFilter("all");
+ }
+ },
+
+ /**
+ * Enables the given filter, its button and toggles 'all' off if the filter to
+ * be enabled is the first one active.
+ *
+ * @param string type
+ * Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
+ * "flash", "ws" or "other".
+ */
+ _enableFilter: function (type) {
+ // Make sure this is a valid filter type.
+ if (!Object.keys(Filters).includes(type)) {
+ return;
+ }
+
+ // Add the filter to the list of active filters.
+ this._activeFilters.push(type);
+
+ // Add the checked status to the filter button.
+ let target = $("#requests-menu-filter-" + type + "-button");
+ target.setAttribute("checked", true);
+
+ // Check if 'all' was selected before. If so, disable it.
+ if (type !== "all" && this._activeFilters.indexOf("all") !== -1) {
+ this._disableFilter("all");
+ }
+ },
+
+ /**
+ * Returns a predicate that can be used to test if a request matches any of
+ * the active filters.
+ */
+ get _filterPredicate() {
+ let currentFreetextFilter = this._currentFreetextFilter;
+
+ return requestItem => {
+ const { attachment } = requestItem;
+ return this._activeFilters.some(filterName => Filters[filterName](attachment)) &&
+ isFreetextMatch(attachment, currentFreetextFilter);
+ };
+ },
+
+ /**
+ * Sorts all network requests in this container by a specified detail.
+ *
+ * @param string type
+ * Either "status", "method", "file", "domain", "type", "transferred",
+ * "size" or "waterfall".
+ */
+ sortBy: function (type = "waterfall") {
+ let target = $("#requests-menu-" + type + "-button");
+ let headers = document.querySelectorAll(".requests-menu-header-button");
+
+ for (let header of headers) {
+ if (header != target) {
+ header.removeAttribute("sorted");
+ header.removeAttribute("tooltiptext");
+ header.parentNode.removeAttribute("active");
+ }
+ }
+
+ let direction = "";
+ if (target) {
+ if (target.getAttribute("sorted") == "ascending") {
+ target.setAttribute("sorted", direction = "descending");
+ target.setAttribute("tooltiptext",
+ L10N.getStr("networkMenu.sortedDesc"));
+ } else {
+ target.setAttribute("sorted", direction = "ascending");
+ target.setAttribute("tooltiptext",
+ L10N.getStr("networkMenu.sortedAsc"));
+ }
+ // Used to style the next column.
+ target.parentNode.setAttribute("active", "true");
+ }
+
+ // Sort by whatever was requested.
+ switch (type) {
+ case "status":
+ if (direction == "ascending") {
+ this.sortContents(this._byStatus);
+ } else {
+ this.sortContents((a, b) => !this._byStatus(a, b));
+ }
+ break;
+ case "method":
+ if (direction == "ascending") {
+ this.sortContents(this._byMethod);
+ } else {
+ this.sortContents((a, b) => !this._byMethod(a, b));
+ }
+ break;
+ case "file":
+ if (direction == "ascending") {
+ this.sortContents(this._byFile);
+ } else {
+ this.sortContents((a, b) => !this._byFile(a, b));
+ }
+ break;
+ case "domain":
+ if (direction == "ascending") {
+ this.sortContents(this._byDomain);
+ } else {
+ this.sortContents((a, b) => !this._byDomain(a, b));
+ }
+ break;
+ case "cause":
+ if (direction == "ascending") {
+ this.sortContents(this._byCause);
+ } else {
+ this.sortContents((a, b) => !this._byCause(a, b));
+ }
+ break;
+ case "type":
+ if (direction == "ascending") {
+ this.sortContents(this._byType);
+ } else {
+ this.sortContents((a, b) => !this._byType(a, b));
+ }
+ break;
+ case "transferred":
+ if (direction == "ascending") {
+ this.sortContents(this._byTransferred);
+ } else {
+ this.sortContents((a, b) => !this._byTransferred(a, b));
+ }
+ break;
+ case "size":
+ if (direction == "ascending") {
+ this.sortContents(this._bySize);
+ } else {
+ this.sortContents((a, b) => !this._bySize(a, b));
+ }
+ break;
+ case "waterfall":
+ if (direction == "ascending") {
+ this.sortContents(this._byTiming);
+ } else {
+ this.sortContents((a, b) => !this._byTiming(a, b));
+ }
+ break;
+ }
+
+ this.refreshSummary();
+ this.refreshZebra();
+ },
+
+ /**
+ * Removes all network requests and closes the sidebar if open.
+ */
+ clear: function () {
+ NetMonitorController.NetworkEventsHandler.clearMarkers();
+ NetMonitorView.Sidebar.toggle(false);
+
+ $("#details-pane-toggle").disabled = true;
+ $("#requests-menu-empty-notice").hidden = false;
+
+ this.empty();
+ this.refreshSummary();
+ },
+
+ /**
+ * Predicates used when sorting items.
+ *
+ * @param object aFirst
+ * The first item used in the comparison.
+ * @param object aSecond
+ * The second item used in the comparison.
+ * @return number
+ * -1 to sort aFirst to a lower index than aSecond
+ * 0 to leave aFirst and aSecond unchanged with respect to each other
+ * 1 to sort aSecond to a lower index than aFirst
+ */
+ _byTiming: function ({ attachment: first }, { attachment: second }) {
+ return first.startedMillis > second.startedMillis;
+ },
+
+ _byStatus: function ({ attachment: first }, { attachment: second }) {
+ return first.status == second.status
+ ? first.startedMillis > second.startedMillis
+ : first.status > second.status;
+ },
+
+ _byMethod: function ({ attachment: first }, { attachment: second }) {
+ return first.method == second.method
+ ? first.startedMillis > second.startedMillis
+ : first.method > second.method;
+ },
+
+ _byFile: function ({ attachment: first }, { attachment: second }) {
+ let firstUrl = this._getUriNameWithQuery(first.url).toLowerCase();
+ let secondUrl = this._getUriNameWithQuery(second.url).toLowerCase();
+ return firstUrl == secondUrl
+ ? first.startedMillis > second.startedMillis
+ : firstUrl > secondUrl;
+ },
+
+ _byDomain: function ({ attachment: first }, { attachment: second }) {
+ let firstDomain = this._getUriHostPort(first.url).toLowerCase();
+ let secondDomain = this._getUriHostPort(second.url).toLowerCase();
+ return firstDomain == secondDomain
+ ? first.startedMillis > second.startedMillis
+ : firstDomain > secondDomain;
+ },
+
+ _byCause: function ({ attachment: first }, { attachment: second }) {
+ let firstCause = loadCauseString(first.cause.type);
+ let secondCause = loadCauseString(second.cause.type);
+
+ return firstCause == secondCause
+ ? first.startedMillis > second.startedMillis
+ : firstCause > secondCause;
+ },
+
+ _byType: function ({ attachment: first }, { attachment: second }) {
+ let firstType = this._getAbbreviatedMimeType(first.mimeType).toLowerCase();
+ let secondType = this._getAbbreviatedMimeType(second.mimeType).toLowerCase();
+
+ return firstType == secondType
+ ? first.startedMillis > second.startedMillis
+ : firstType > secondType;
+ },
+
+ _byTransferred: function ({ attachment: first }, { attachment: second }) {
+ return first.transferredSize > second.transferredSize;
+ },
+
+ _bySize: function ({ attachment: first }, { attachment: second }) {
+ return first.contentSize > second.contentSize;
+ },
+
+ /**
+ * Refreshes the status displayed in this container's footer, providing
+ * concise information about all requests.
+ */
+ refreshSummary: function () {
+ let visibleItems = this.visibleItems;
+ let visibleRequestsCount = visibleItems.length;
+ if (!visibleRequestsCount) {
+ this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
+ return;
+ }
+
+ let totalBytes = this._getTotalBytesOfRequests(visibleItems);
+ let totalMillis =
+ this._getNewestRequest(visibleItems).attachment.endedMillis -
+ this._getOldestRequest(visibleItems).attachment.startedMillis;
+
+ // https://developer.mozilla.org/en-US/docs/Localization_and_Plurals
+ let str = PluralForm.get(visibleRequestsCount,
+ L10N.getStr("networkMenu.summary"));
+
+ this._summary.setAttribute("label", str
+ .replace("#1", visibleRequestsCount)
+ .replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024,
+ CONTENT_SIZE_DECIMALS))
+ .replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000,
+ REQUEST_TIME_DECIMALS))
+ );
+ },
+
+ /**
+ * Adds odd/even attributes to all the visible items in this container.
+ */
+ refreshZebra: function () {
+ let visibleItems = this.visibleItems;
+
+ for (let i = 0, len = visibleItems.length; i < len; i++) {
+ let requestItem = visibleItems[i];
+ let requestTarget = requestItem.target;
+
+ if (i % 2 == 0) {
+ requestTarget.setAttribute("even", "");
+ requestTarget.removeAttribute("odd");
+ } else {
+ requestTarget.setAttribute("odd", "");
+ requestTarget.removeAttribute("even");
+ }
+ }
+ },
+
+ /**
+ * Attaches security icon click listener for the given request menu item.
+ *
+ * @param object item
+ * The network request item to attach the listener to.
+ */
+ attachSecurityIconClickListener: function ({ target }) {
+ let icon = $(".requests-security-state-icon", target);
+ icon.addEventListener("click", this._onSecurityIconClick);
+ },
+
+ /**
+ * Schedules adding additional information to a network request.
+ *
+ * @param string id
+ * An identifier coming from the network monitor controller.
+ * @param object data
+ * An object containing several { key: value } tuples of network info.
+ * Supported keys are "httpVersion", "status", "statusText" etc.
+ * @param function callback
+ * A function to call once the request has been updated in the view.
+ */
+ updateRequest: function (id, data, callback) {
+ this._updateQueue.push([id, data, callback]);
+
+ // Lazy updating is disabled in some tests.
+ if (!this.lazyUpdate) {
+ return void this._flushRequests();
+ }
+
+ this._flushRequestsTask.arm();
+ return undefined;
+ },
+
+ /**
+ * Starts adding all queued additional information about network requests.
+ */
+ _flushRequests: function () {
+ // Prevent displaying any updates received after the target closed.
+ if (NetMonitorView._isDestroyed) {
+ return;
+ }
+
+ let widget = NetMonitorView.RequestsMenu.widget;
+ let isScrolledToBottom = widget.isScrolledToBottom();
+
+ for (let [id, startedDateTime, method, url, isXHR, cause, fromCache,
+ fromServiceWorker] of this._addQueue) {
+ // Convert the received date/time string to a unix timestamp.
+ let unixTime = Date.parse(startedDateTime);
+
+ // Create the element node for the network request item.
+ let menuView = this._createMenuView(method, url, cause);
+
+ // Remember the first and last event boundaries.
+ this._registerFirstRequestStart(unixTime);
+ this._registerLastRequestEnd(unixTime);
+
+ // Append a network request item to this container.
+ let requestItem = this.push([menuView, id], {
+ attachment: {
+ startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
+ startedMillis: unixTime,
+ method: method,
+ url: url,
+ isXHR: isXHR,
+ cause: cause,
+ fromCache: fromCache,
+ fromServiceWorker: fromServiceWorker
+ }
+ });
+
+ if (id == this._preferredItemId) {
+ this.selectedItem = requestItem;
+ }
+
+ window.emit(EVENTS.REQUEST_ADDED, id);
+ }
+
+ if (isScrolledToBottom && this._addQueue.length) {
+ widget.scrollToBottom();
+ }
+
+ // For each queued additional information packet, get the corresponding
+ // request item in the view and update it based on the specified data.
+ for (let [id, data, callback] of this._updateQueue) {
+ let requestItem = this.getItemByValue(id);
+ if (!requestItem) {
+ // Packet corresponds to a dead request item, target navigated.
+ continue;
+ }
+
+ // Each information packet may contain several { key: value } tuples of
+ // network info, so update the view based on each one.
+ for (let key in data) {
+ let val = data[key];
+ if (val === undefined) {
+ // The information in the packet is empty, it can be safely ignored.
+ continue;
+ }
+
+ switch (key) {
+ case "requestHeaders":
+ requestItem.attachment.requestHeaders = val;
+ break;
+ case "requestCookies":
+ requestItem.attachment.requestCookies = val;
+ break;
+ case "requestPostData":
+ // Search the POST data upload stream for request headers and add
+ // them to a separate store, different from the classic headers.
+ // XXX: Be really careful here! We're creating a function inside
+ // a loop, so remember the actual request item we want to modify.
+ let currentItem = requestItem;
+ let currentStore = { headers: [], headersSize: 0 };
+
+ Task.spawn(function* () {
+ let postData = yield gNetwork.getString(val.postData.text);
+ let payloadHeaders = CurlUtils.getHeadersFromMultipartText(
+ postData);
+
+ currentStore.headers = payloadHeaders;
+ currentStore.headersSize = payloadHeaders.reduce(
+ (acc, { name, value }) =>
+ acc + name.length + value.length + 2, 0);
+
+ // The `getString` promise is async, so we need to refresh the
+ // information displayed in the network details pane again here.
+ refreshNetworkDetailsPaneIfNecessary(currentItem);
+ });
+
+ requestItem.attachment.requestPostData = val;
+ requestItem.attachment.requestHeadersFromUploadStream =
+ currentStore;
+ break;
+ case "securityState":
+ requestItem.attachment.securityState = val;
+ this.updateMenuView(requestItem, key, val);
+ break;
+ case "securityInfo":
+ requestItem.attachment.securityInfo = val;
+ break;
+ case "responseHeaders":
+ requestItem.attachment.responseHeaders = val;
+ break;
+ case "responseCookies":
+ requestItem.attachment.responseCookies = val;
+ break;
+ case "httpVersion":
+ requestItem.attachment.httpVersion = val;
+ break;
+ case "remoteAddress":
+ requestItem.attachment.remoteAddress = val;
+ this.updateMenuView(requestItem, key, val);
+ break;
+ case "remotePort":
+ requestItem.attachment.remotePort = val;
+ break;
+ case "status":
+ requestItem.attachment.status = val;
+ this.updateMenuView(requestItem, key, {
+ status: val,
+ cached: requestItem.attachment.fromCache,
+ serviceWorker: requestItem.attachment.fromServiceWorker
+ });
+ break;
+ case "statusText":
+ requestItem.attachment.statusText = val;
+ let text = (requestItem.attachment.status + " " +
+ requestItem.attachment.statusText);
+ if (requestItem.attachment.fromCache) {
+ text += " (cached)";
+ } else if (requestItem.attachment.fromServiceWorker) {
+ text += " (service worker)";
+ }
+
+ this.updateMenuView(requestItem, key, text);
+ break;
+ case "headersSize":
+ requestItem.attachment.headersSize = val;
+ break;
+ case "contentSize":
+ requestItem.attachment.contentSize = val;
+ this.updateMenuView(requestItem, key, val);
+ break;
+ case "transferredSize":
+ if (requestItem.attachment.fromCache) {
+ requestItem.attachment.transferredSize = 0;
+ this.updateMenuView(requestItem, key, "cached");
+ } else if (requestItem.attachment.fromServiceWorker) {
+ requestItem.attachment.transferredSize = 0;
+ this.updateMenuView(requestItem, key, "service worker");
+ } else {
+ requestItem.attachment.transferredSize = val;
+ this.updateMenuView(requestItem, key, val);
+ }
+ break;
+ case "mimeType":
+ requestItem.attachment.mimeType = val;
+ this.updateMenuView(requestItem, key, val);
+ break;
+ case "responseContent":
+ // 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");
+ }
+ requestItem.attachment.responseContent = val;
+ this.updateMenuView(requestItem, key, val);
+ break;
+ case "totalTime":
+ requestItem.attachment.totalTime = val;
+ requestItem.attachment.endedMillis =
+ requestItem.attachment.startedMillis + val;
+
+ this.updateMenuView(requestItem, key, val);
+ this._registerLastRequestEnd(requestItem.attachment.endedMillis);
+ break;
+ case "eventTimings":
+ requestItem.attachment.eventTimings = val;
+ this._createWaterfallView(
+ requestItem, val.timings,
+ requestItem.attachment.fromCache ||
+ requestItem.attachment.fromServiceWorker
+ );
+ break;
+ }
+ }
+ refreshNetworkDetailsPaneIfNecessary(requestItem);
+
+ if (callback) {
+ callback();
+ }
+ }
+
+ /**
+ * Refreshes the information displayed in the sidebar, in case this update
+ * may have additional information about a request which isn't shown yet
+ * in the network details pane.
+ *
+ * @param object requestItem
+ * The item to repopulate the sidebar with in case it's selected in
+ * this requests menu.
+ */
+ function refreshNetworkDetailsPaneIfNecessary(requestItem) {
+ let selectedItem = NetMonitorView.RequestsMenu.selectedItem;
+ if (selectedItem == requestItem) {
+ NetMonitorView.NetworkDetails.populate(selectedItem.attachment);
+ }
+ }
+
+ // We're done flushing all the requests, clear the update queue.
+ this._updateQueue = [];
+ this._addQueue = [];
+
+ $("#details-pane-toggle").disabled = !this.itemCount;
+ $("#requests-menu-empty-notice").hidden = !!this.itemCount;
+
+ // Make sure all the requests are sorted and filtered.
+ // Freshly added requests may not yet contain all the information required
+ // for sorting and filtering predicates, so this is done each time the
+ // network requests table is flushed (don't worry, events are drained first
+ // so this doesn't happen once per network event update).
+ this.sortContents();
+ this.filterContents();
+ this.refreshSummary();
+ this.refreshZebra();
+
+ // Rescale all the waterfalls so that everything is visible at once.
+ this._flushWaterfallViews();
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string method
+ * Specifies the request method (e.g. "GET", "POST", etc.)
+ * @param string url
+ * Specifies the request's url.
+ * @param object cause
+ * Specifies the request's cause. Has two properties:
+ * - type: nsContentPolicyType constant
+ * - uri: URI of the request origin
+ * @return nsIDOMNode
+ * The network request view.
+ */
+ _createMenuView: function (method, url, cause) {
+ let template = $("#requests-menu-item-template");
+ let fragment = document.createDocumentFragment();
+
+ // Flatten the DOM by removing one redundant box (the template container).
+ for (let node of template.childNodes) {
+ fragment.appendChild(node.cloneNode(true));
+ }
+
+ this.updateMenuView(fragment, "method", method);
+ this.updateMenuView(fragment, "url", url);
+ this.updateMenuView(fragment, "cause", cause);
+
+ return fragment;
+ },
+
+ /**
+ * Get a human-readable string from a number of bytes, with the B, KB, MB, or
+ * GB value. Note that the transition between abbreviations is by 1000 rather
+ * than 1024 in order to keep the displayed digits smaller as "1016 KB" is
+ * more awkward than 0.99 MB"
+ */
+ getFormattedSize(bytes) {
+ if (bytes < MAX_BYTES_SIZE) {
+ return L10N.getFormatStr("networkMenu.sizeB", bytes);
+ } else if (bytes < MAX_KB_SIZE) {
+ let kb = bytes / BYTES_IN_KB;
+ let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
+ return L10N.getFormatStr("networkMenu.sizeKB", size);
+ } else if (bytes < MAX_MB_SIZE) {
+ let mb = bytes / BYTES_IN_MB;
+ let size = L10N.numberWithDecimals(mb, CONTENT_SIZE_DECIMALS);
+ return L10N.getFormatStr("networkMenu.sizeMB", size);
+ }
+ let gb = bytes / BYTES_IN_GB;
+ let size = L10N.numberWithDecimals(gb, CONTENT_SIZE_DECIMALS);
+ return L10N.getFormatStr("networkMenu.sizeGB", size);
+ },
+
+ /**
+ * Updates the information displayed in a network request item view.
+ *
+ * @param object item
+ * The network request item in this container.
+ * @param string key
+ * The type of information that is to be updated.
+ * @param any value
+ * The new value to be shown.
+ * @return object
+ * A promise that is resolved once the information is displayed.
+ */
+ updateMenuView: Task.async(function* (item, key, value) {
+ let target = item.target || item;
+
+ switch (key) {
+ case "method": {
+ let node = $(".requests-menu-method", target);
+ node.setAttribute("value", value);
+ break;
+ }
+ case "url": {
+ let uri;
+ try {
+ uri = NetworkHelper.nsIURL(value);
+ } catch (e) {
+ // User input may not make a well-formed url yet.
+ break;
+ }
+ let nameWithQuery = this._getUriNameWithQuery(uri);
+ let hostPort = this._getUriHostPort(uri);
+ let host = this._getUriHost(uri);
+ let unicodeUrl = NetworkHelper.convertToUnicode(unescape(uri.spec));
+
+ let file = $(".requests-menu-file", target);
+ file.setAttribute("value", nameWithQuery);
+ file.setAttribute("tooltiptext", unicodeUrl);
+
+ let domain = $(".requests-menu-domain", target);
+ domain.setAttribute("value", hostPort);
+ domain.setAttribute("tooltiptext", hostPort);
+
+ // Mark local hosts specially, where "local" is as defined in the W3C
+ // spec for secure contexts.
+ // http://www.w3.org/TR/powerful-features/
+ //
+ // * If the name falls under 'localhost'
+ // * If the name is an IPv4 address within 127.0.0.0/8
+ // * If the name is an IPv6 address within ::1/128
+ //
+ // IPv6 parsing is a little sloppy; it assumes that the address has
+ // been validated before it gets here.
+ let icon = $(".requests-security-state-icon", target);
+ icon.classList.remove("security-state-local");
+ if (host.match(/(.+\.)?localhost$/) ||
+ host.match(/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}/) ||
+ host.match(/\[[0:]+1\]/)) {
+ let tooltip = L10N.getStr("netmonitor.security.state.secure");
+ icon.classList.add("security-state-local");
+ icon.setAttribute("tooltiptext", tooltip);
+ }
+
+ break;
+ }
+ case "remoteAddress":
+ let domain = $(".requests-menu-domain", target);
+ let tooltip = (domain.getAttribute("value") +
+ (value ? " (" + value + ")" : ""));
+ domain.setAttribute("tooltiptext", tooltip);
+ break;
+ case "securityState": {
+ let icon = $(".requests-security-state-icon", target);
+ this.attachSecurityIconClickListener(item);
+
+ // Security icon for local hosts is set in the "url" branch
+ if (icon.classList.contains("security-state-local")) {
+ break;
+ }
+
+ let tooltip2 = L10N.getStr("netmonitor.security.state." + value);
+ icon.classList.add("security-state-" + value);
+ icon.setAttribute("tooltiptext", tooltip2);
+ break;
+ }
+ case "status": {
+ let node = $(".requests-menu-status-icon", target);
+ // "code" attribute is only used by css to determine the icon color
+ let code;
+ if (value.cached) {
+ code = "cached";
+ } else if (value.serviceWorker) {
+ code = "service worker";
+ } else {
+ code = value.status;
+ }
+ node.setAttribute("code", code);
+ let codeNode = $(".requests-menu-status-code", target);
+ codeNode.setAttribute("value", value.status);
+ break;
+ }
+ case "statusText": {
+ let node = $(".requests-menu-status", target);
+ node.setAttribute("tooltiptext", value);
+ break;
+ }
+ case "cause": {
+ let labelNode = $(".requests-menu-cause-label", target);
+ labelNode.setAttribute("value", loadCauseString(value.type));
+ if (value.loadingDocumentUri) {
+ labelNode.setAttribute("tooltiptext", value.loadingDocumentUri);
+ }
+
+ let stackNode = $(".requests-menu-cause-stack", target);
+ if (value.stacktrace && value.stacktrace.length > 0) {
+ stackNode.removeAttribute("hidden");
+ }
+ break;
+ }
+ case "contentSize": {
+ let node = $(".requests-menu-size", target);
+
+ let text = this.getFormattedSize(value);
+
+ node.setAttribute("value", text);
+ node.setAttribute("tooltiptext", text);
+ break;
+ }
+ case "transferredSize": {
+ let node = $(".requests-menu-transferred", target);
+
+ let text;
+ if (value === null) {
+ text = L10N.getStr("networkMenu.sizeUnavailable");
+ } else if (value === "cached") {
+ text = L10N.getStr("networkMenu.sizeCached");
+ node.classList.add("theme-comment");
+ } else if (value === "service worker") {
+ text = L10N.getStr("networkMenu.sizeServiceWorker");
+ node.classList.add("theme-comment");
+ } else {
+ text = this.getFormattedSize(value);
+ }
+
+ node.setAttribute("value", text);
+ node.setAttribute("tooltiptext", text);
+ break;
+ }
+ case "mimeType": {
+ let type = this._getAbbreviatedMimeType(value);
+ let node = $(".requests-menu-type", target);
+ let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
+ node.setAttribute("value", text);
+ node.setAttribute("tooltiptext", value);
+ break;
+ }
+ case "responseContent": {
+ let { mimeType } = item.attachment;
+
+ if (mimeType.includes("image/")) {
+ let { text, encoding } = value.content;
+ let responseBody = yield gNetwork.getString(text);
+ let node = $(".requests-menu-icon", item.target);
+ node.src = formDataURI(mimeType, encoding, responseBody);
+ node.setAttribute("type", "thumbnail");
+ node.removeAttribute("hidden");
+
+ window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+ }
+ break;
+ }
+ case "totalTime": {
+ let node = $(".requests-menu-timings-total", target);
+
+ // integer
+ let text = L10N.getFormatStr("networkMenu.totalMS", value);
+ node.setAttribute("value", text);
+ node.setAttribute("tooltiptext", text);
+ break;
+ }
+ }
+ }),
+
+ /**
+ * Creates a waterfall representing timing information in a network
+ * request item view.
+ *
+ * @param object item
+ * The network request item in this container.
+ * @param object timings
+ * An object containing timing information.
+ * @param boolean fromCache
+ * Indicates if the result came from the browser cache or
+ * a service worker
+ */
+ _createWaterfallView: function (item, timings, fromCache) {
+ let { target } = item;
+ let sections = ["blocked", "dns", "connect", "send", "wait", "receive"];
+ // Skipping "blocked" because it doesn't work yet.
+
+ let timingsNode = $(".requests-menu-timings", target);
+ let timingsTotal = $(".requests-menu-timings-total", timingsNode);
+
+ if (fromCache) {
+ timingsTotal.style.display = "none";
+ return;
+ }
+
+ // Add a set of boxes representing timing information.
+ for (let key of sections) {
+ let width = timings[key];
+
+ // Don't render anything if it surely won't be visible.
+ // One millisecond == one unscaled pixel.
+ if (width > 0) {
+ let timingBox = document.createElement("hbox");
+ timingBox.className = "requests-menu-timings-box " + key;
+ timingBox.setAttribute("width", width);
+ timingsNode.insertBefore(timingBox, timingsTotal);
+ }
+ }
+ },
+
+ /**
+ * Rescales and redraws all the waterfall views in this container.
+ *
+ * @param boolean reset
+ * True if this container's width was changed.
+ */
+ _flushWaterfallViews: function (reset) {
+ // Don't paint things while the waterfall view isn't even visible,
+ // or there are no items added to this container.
+ if (NetMonitorView.currentFrontendMode !=
+ "network-inspector-view" || !this.itemCount) {
+ return;
+ }
+
+ // To avoid expensive operations like getBoundingClientRect() and
+ // rebuilding the waterfall background each time a new request comes in,
+ // stuff is cached. However, in certain scenarios like when the window
+ // is resized, this needs to be invalidated.
+ if (reset) {
+ this._cachedWaterfallWidth = 0;
+ }
+
+ // Determine the scaling to be applied to all the waterfalls so that
+ // everything is visible at once. One millisecond == one unscaled pixel.
+ let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
+ let longestWidth = this._lastRequestEndedMillis -
+ this._firstRequestStartedMillis;
+ let scale = Math.min(Math.max(availableWidth / longestWidth, EPSILON), 1);
+
+ // Redraw and set the canvas background for each waterfall view.
+ this._showWaterfallDivisionLabels(scale);
+ this._drawWaterfallBackground(scale);
+
+ // Apply CSS transforms to each waterfall in this container totalTime
+ // accurately translate and resize as needed.
+ for (let { target, attachment } of this) {
+ let timingsNode = $(".requests-menu-timings", target);
+ let totalNode = $(".requests-menu-timings-total", target);
+ let direction = window.isRTL ? -1 : 1;
+
+ // Render the timing information at a specific horizontal translation
+ // based on the delta to the first monitored event network.
+ let translateX = "translateX(" + (direction *
+ attachment.startedDeltaMillis) + "px)";
+
+ // Based on the total time passed until the last request, rescale
+ // all the waterfalls to a reasonable size.
+ let scaleX = "scaleX(" + scale + ")";
+
+ // Certain nodes should not be scaled, even if they're children of
+ // another scaled node. In this case, apply a reversed transformation.
+ let revScaleX = "scaleX(" + (1 / scale) + ")";
+
+ timingsNode.style.transform = scaleX + " " + translateX;
+ totalNode.style.transform = revScaleX;
+ }
+ },
+
+ /**
+ * Creates the labels displayed on the waterfall header in this container.
+ *
+ * @param number scale
+ * The current waterfall scale.
+ */
+ _showWaterfallDivisionLabels: function (scale) {
+ let container = $("#requests-menu-waterfall-label-wrapper");
+ let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
+
+ // Nuke all existing labels.
+ while (container.hasChildNodes()) {
+ container.firstChild.remove();
+ }
+
+ // Build new millisecond tick labels...
+ let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
+ let optimalTickIntervalFound = false;
+
+ while (!optimalTickIntervalFound) {
+ // Ignore any divisions that would end up being too close to each other.
+ let scaledStep = scale * timingStep;
+ if (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
+ timingStep <<= 1;
+ continue;
+ }
+ optimalTickIntervalFound = true;
+
+ // Insert one label for each division on the current scale.
+ let fragment = document.createDocumentFragment();
+ let direction = window.isRTL ? -1 : 1;
+
+ for (let x = 0; x < availableWidth; x += scaledStep) {
+ let translateX = "translateX(" + ((direction * x) | 0) + "px)";
+ let millisecondTime = x / scale;
+
+ let normalizedTime = millisecondTime;
+ let divisionScale = "millisecond";
+
+ // If the division is greater than 1 minute.
+ if (normalizedTime > 60000) {
+ normalizedTime /= 60000;
+ divisionScale = "minute";
+ } else if (normalizedTime > 1000) {
+ // If the division is greater than 1 second.
+ normalizedTime /= 1000;
+ divisionScale = "second";
+ }
+
+ // Showing too many decimals is bad UX.
+ if (divisionScale == "millisecond") {
+ normalizedTime |= 0;
+ } else {
+ normalizedTime = L10N.numberWithDecimals(normalizedTime,
+ REQUEST_TIME_DECIMALS);
+ }
+
+ let node = document.createElement("label");
+ let text = L10N.getFormatStr("networkMenu." +
+ divisionScale, normalizedTime);
+ node.className = "plain requests-menu-timings-division";
+ node.setAttribute("division-scale", divisionScale);
+ node.style.transform = translateX;
+
+ node.setAttribute("value", text);
+ fragment.appendChild(node);
+ }
+ container.appendChild(fragment);
+
+ container.className = "requests-menu-waterfall-visible";
+ }
+ },
+
+ /**
+ * Creates the background displayed on each waterfall view in this container.
+ *
+ * @param number scale
+ * The current waterfall scale.
+ */
+ _drawWaterfallBackground: function (scale) {
+ if (!this._canvas || !this._ctx) {
+ this._canvas = document.createElementNS(HTML_NS, "canvas");
+ this._ctx = this._canvas.getContext("2d");
+ }
+ let canvas = this._canvas;
+ let ctx = this._ctx;
+
+ // Nuke the context.
+ let canvasWidth = canvas.width = this._waterfallWidth;
+ // Awww yeah, 1px, repeats on Y axis.
+ let canvasHeight = canvas.height = 1;
+
+ // Start over.
+ let imageData = ctx.createImageData(canvasWidth, canvasHeight);
+ let pixelArray = imageData.data;
+
+ let buf = new ArrayBuffer(pixelArray.length);
+ let view8bit = new Uint8ClampedArray(buf);
+ let view32bit = new Uint32Array(buf);
+
+ // Build new millisecond tick lines...
+ let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
+ let [r, g, b] = REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
+ let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
+ let optimalTickIntervalFound = false;
+
+ while (!optimalTickIntervalFound) {
+ // Ignore any divisions that would end up being too close to each other.
+ let scaledStep = scale * timingStep;
+ if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
+ timingStep <<= 1;
+ continue;
+ }
+ optimalTickIntervalFound = true;
+
+ // Insert one pixel for each division on each scale.
+ for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
+ let increment = scaledStep * Math.pow(2, i);
+ for (let x = 0; x < canvasWidth; x += increment) {
+ let position = (window.isRTL ? canvasWidth - x : x) | 0;
+ view32bit[position] =
+ (alphaComponent << 24) | (b << 16) | (g << 8) | r;
+ }
+ alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
+ }
+ }
+
+ {
+ let t = NetMonitorController.NetworkEventsHandler
+ .firstDocumentDOMContentLoadedTimestamp;
+
+ let delta = Math.floor((t - this._firstRequestStartedMillis) * scale);
+ let [r1, g1, b1, a1] =
+ REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA;
+ view32bit[delta] = (a1 << 24) | (r1 << 16) | (g1 << 8) | b1;
+ }
+ {
+ let t = NetMonitorController.NetworkEventsHandler
+ .firstDocumentLoadTimestamp;
+
+ let delta = Math.floor((t - this._firstRequestStartedMillis) * scale);
+ let [r2, g2, b2, a2] = REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA;
+ view32bit[delta] = (a2 << 24) | (r2 << 16) | (g2 << 8) | b2;
+ }
+
+ // Flush the image data and cache the waterfall background.
+ pixelArray.set(view8bit);
+ ctx.putImageData(imageData, 0, 0);
+ document.mozSetImageElement("waterfall-background", canvas);
+ },
+
+ /**
+ * The selection listener for this container.
+ */
+ _onSelect: function ({ detail: item }) {
+ if (item) {
+ NetMonitorView.Sidebar.populate(item.attachment);
+ NetMonitorView.Sidebar.toggle(true);
+ } else {
+ NetMonitorView.Sidebar.toggle(false);
+ }
+ },
+
+ /**
+ * The swap listener for this container.
+ * Called when two items switch places, when the contents are sorted.
+ */
+ _onSwap: function ({ detail: [firstItem, secondItem] }) {
+ // Reattach click listener to the security icons
+ this.attachSecurityIconClickListener(firstItem);
+ this.attachSecurityIconClickListener(secondItem);
+ },
+
+ /**
+ * The predicate used when deciding whether a popup should be shown
+ * over a request item or not.
+ *
+ * @param nsIDOMNode target
+ * The element node currently being hovered.
+ * @param object tooltip
+ * The current tooltip instance.
+ * @return {Promise}
+ */
+ _onHover: Task.async(function* (target, tooltip) {
+ let requestItem = this.getItemForElement(target);
+ if (!requestItem) {
+ return false;
+ }
+
+ let hovered = requestItem.attachment;
+ if (hovered.responseContent && target.closest(".requests-menu-icon-and-file")) {
+ return this._setTooltipImageContent(tooltip, requestItem);
+ } else if (hovered.cause && target.closest(".requests-menu-cause-stack")) {
+ return this._setTooltipStackTraceContent(tooltip, requestItem);
+ }
+
+ return false;
+ }),
+
+ _setTooltipImageContent: Task.async(function* (tooltip, requestItem) {
+ let { mimeType, text, encoding } = requestItem.attachment.responseContent.content;
+
+ if (!mimeType || !mimeType.includes("image/")) {
+ return false;
+ }
+
+ let string = yield gNetwork.getString(text);
+ let src = formDataURI(mimeType, encoding, string);
+ let maxDim = REQUESTS_TOOLTIP_IMAGE_MAX_DIM;
+ let { naturalWidth, naturalHeight } = yield getImageDimensions(tooltip.doc, src);
+ let options = { maxDim, naturalWidth, naturalHeight };
+ setImageTooltip(tooltip, tooltip.doc, src, options);
+
+ return $(".requests-menu-icon", requestItem.target);
+ }),
+
+ _setTooltipStackTraceContent: Task.async(function* (tooltip, requestItem) {
+ let {stacktrace} = requestItem.attachment.cause;
+
+ if (!stacktrace || stacktrace.length == 0) {
+ return false;
+ }
+
+ let doc = tooltip.doc;
+ let el = doc.createElementNS(HTML_NS, "div");
+ el.className = "stack-trace-tooltip devtools-monospace";
+
+ for (let f of stacktrace) {
+ let { functionName, filename, lineNumber, columnNumber, asyncCause } = f;
+
+ if (asyncCause) {
+ // if there is asyncCause, append a "divider" row into the trace
+ let asyncFrameEl = doc.createElementNS(HTML_NS, "div");
+ asyncFrameEl.className = "stack-frame stack-frame-async";
+ asyncFrameEl.textContent =
+ WEBCONSOLE_L10N.getFormatStr("stacktrace.asyncStack", asyncCause);
+ el.appendChild(asyncFrameEl);
+ }
+
+ // Parse a source name in format "url -> url"
+ let sourceUrl = filename.split(" -> ").pop();
+
+ let frameEl = doc.createElementNS(HTML_NS, "div");
+ frameEl.className = "stack-frame stack-frame-call";
+
+ let funcEl = doc.createElementNS(HTML_NS, "span");
+ funcEl.className = "stack-frame-function-name";
+ funcEl.textContent =
+ functionName || WEBCONSOLE_L10N.getStr("stacktrace.anonymousFunction");
+ frameEl.appendChild(funcEl);
+
+ let sourceEl = doc.createElementNS(HTML_NS, "span");
+ sourceEl.className = "stack-frame-source-name";
+ frameEl.appendChild(sourceEl);
+
+ let sourceInnerEl = doc.createElementNS(HTML_NS, "span");
+ sourceInnerEl.className = "stack-frame-source-name-inner";
+ sourceEl.appendChild(sourceInnerEl);
+
+ sourceInnerEl.textContent = sourceUrl;
+ sourceInnerEl.title = sourceUrl;
+
+ let lineEl = doc.createElementNS(HTML_NS, "span");
+ lineEl.className = "stack-frame-line";
+ lineEl.textContent = `:${lineNumber}:${columnNumber}`;
+ sourceInnerEl.appendChild(lineEl);
+
+ frameEl.addEventListener("click", () => {
+ // hide the tooltip immediately, not after delay
+ tooltip.hide();
+ NetMonitorController.viewSourceInDebugger(filename, lineNumber);
+ }, false);
+
+ el.appendChild(frameEl);
+ }
+
+ tooltip.setContent(el, {width: REQUESTS_TOOLTIP_STACK_TRACE_WIDTH});
+
+ return true;
+ }),
+
+ /**
+ * A handler that opens the security tab in the details view if secure or
+ * broken security indicator is clicked.
+ */
+ _onSecurityIconClick: function (e) {
+ let state = this.selectedItem.attachment.securityState;
+ if (state !== "insecure") {
+ // Choose the security tab.
+ NetMonitorView.NetworkDetails.widget.selectedIndex = 5;
+ }
+ },
+
+ /**
+ * The resize listener for this container's window.
+ */
+ _onResize: function (e) {
+ // Allow requests to settle down first.
+ setNamedTimeout("resize-events",
+ RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
+ },
+
+ /**
+ * Scroll listener for the requests menu view.
+ */
+ _onScroll: function () {
+ this.tooltip.hide();
+ },
+
+ /**
+ * Handle the context menu opening. Hide items if no request is selected.
+ */
+ _onContextShowing: function () {
+ let selectedItem = this.selectedItem;
+
+ let resendElement = $("#request-menu-context-resend");
+ resendElement.hidden = !NetMonitorController.supportsCustomRequest ||
+ !selectedItem || selectedItem.attachment.isCustom;
+
+ let copyUrlElement = $("#request-menu-context-copy-url");
+ copyUrlElement.hidden = !selectedItem;
+
+ let copyUrlParamsElement = $("#request-menu-context-copy-url-params");
+ copyUrlParamsElement.hidden = !selectedItem ||
+ !NetworkHelper.nsIURL(selectedItem.attachment.url).query;
+
+ let copyPostDataElement = $("#request-menu-context-copy-post-data");
+ copyPostDataElement.hidden = !selectedItem ||
+ !selectedItem.attachment.requestPostData;
+
+ let copyAsCurlElement = $("#request-menu-context-copy-as-curl");
+ copyAsCurlElement.hidden = !selectedItem || !selectedItem.attachment;
+
+ let copyRequestHeadersElement =
+ $("#request-menu-context-copy-request-headers");
+ copyRequestHeadersElement.hidden = !selectedItem ||
+ !selectedItem.attachment.requestHeaders;
+
+ let copyResponseHeadersElement =
+ $("#response-menu-context-copy-response-headers");
+ copyResponseHeadersElement.hidden = !selectedItem ||
+ !selectedItem.attachment.responseHeaders;
+
+ let copyResponse = $("#request-menu-context-copy-response");
+ copyResponse.hidden = !selectedItem ||
+ !selectedItem.attachment.responseContent ||
+ !selectedItem.attachment.responseContent.content.text ||
+ selectedItem.attachment.responseContent.content.text.length === 0;
+
+ let copyImageAsDataUriElement =
+ $("#request-menu-context-copy-image-as-data-uri");
+ copyImageAsDataUriElement.hidden = !selectedItem ||
+ !selectedItem.attachment.responseContent ||
+ !selectedItem.attachment.responseContent.content
+ .mimeType.includes("image/");
+
+ let separators = $all(".request-menu-context-separator");
+ Array.forEach(separators, separator => {
+ separator.hidden = !selectedItem;
+ });
+
+ let copyAsHar = $("#request-menu-context-copy-all-as-har");
+ copyAsHar.hidden = !NetMonitorView.RequestsMenu.items.length;
+
+ let saveAsHar = $("#request-menu-context-save-all-as-har");
+ saveAsHar.hidden = !NetMonitorView.RequestsMenu.items.length;
+
+ let newTabElement = $("#request-menu-context-newtab");
+ newTabElement.hidden = !selectedItem;
+ },
+
+ /**
+ * Checks if the specified unix time is the first one to be known of,
+ * and saves it if so.
+ *
+ * @param number unixTime
+ * The milliseconds to check and save.
+ */
+ _registerFirstRequestStart: function (unixTime) {
+ if (this._firstRequestStartedMillis == -1) {
+ this._firstRequestStartedMillis = unixTime;
+ }
+ },
+
+ /**
+ * Checks if the specified unix time is the last one to be known of,
+ * and saves it if so.
+ *
+ * @param number unixTime
+ * The milliseconds to check and save.
+ */
+ _registerLastRequestEnd: function (unixTime) {
+ if (this._lastRequestEndedMillis < unixTime) {
+ this._lastRequestEndedMillis = unixTime;
+ }
+ },
+
+ /**
+ * Helpers for getting details about an nsIURL.
+ *
+ * @param nsIURL | string url
+ * @return string
+ */
+ _getUriNameWithQuery: function (url) {
+ if (!(url instanceof Ci.nsIURL)) {
+ url = NetworkHelper.nsIURL(url);
+ }
+
+ let name = NetworkHelper.convertToUnicode(
+ unescape(url.fileName || url.filePath || "/"));
+ let query = NetworkHelper.convertToUnicode(unescape(url.query));
+
+ return name + (query ? "?" + query : "");
+ },
+
+ _getUriHostPort: function (url) {
+ if (!(url instanceof Ci.nsIURL)) {
+ url = NetworkHelper.nsIURL(url);
+ }
+ return NetworkHelper.convertToUnicode(unescape(url.hostPort));
+ },
+
+ _getUriHost: function (url) {
+ return this._getUriHostPort(url).replace(/:\d+$/, "");
+ },
+
+ /**
+ * Helper for getting an abbreviated string for a mime type.
+ *
+ * @param string mimeType
+ * @return string
+ */
+ _getAbbreviatedMimeType: function (mimeType) {
+ if (!mimeType) {
+ return "";
+ }
+ return (mimeType.split(";")[0].split("/")[1] || "").split("+")[0];
+ },
+
+ /**
+ * Gets the total number of bytes representing the cumulated content size of
+ * a set of requests. Returns 0 for an empty set.
+ *
+ * @param array itemsArray
+ * @return number
+ */
+ _getTotalBytesOfRequests: function (itemsArray) {
+ if (!itemsArray.length) {
+ return 0;
+ }
+
+ let result = 0;
+ itemsArray.forEach(item => {
+ let size = item.attachment.contentSize;
+ result += (typeof size == "number") ? size : 0;
+ });
+
+ return result;
+ },
+
+ /**
+ * Gets the oldest (first performed) request in a set. Returns null for an
+ * empty set.
+ *
+ * @param array itemsArray
+ * @return object
+ */
+ _getOldestRequest: function (itemsArray) {
+ if (!itemsArray.length) {
+ return null;
+ }
+ return itemsArray.reduce((prev, curr) =>
+ prev.attachment.startedMillis < curr.attachment.startedMillis ?
+ prev : curr);
+ },
+
+ /**
+ * Gets the newest (latest performed) request in a set. Returns null for an
+ * empty set.
+ *
+ * @param array itemsArray
+ * @return object
+ */
+ _getNewestRequest: function (itemsArray) {
+ if (!itemsArray.length) {
+ return null;
+ }
+ return itemsArray.reduce((prev, curr) =>
+ prev.attachment.startedMillis > curr.attachment.startedMillis ?
+ prev : curr);
+ },
+
+ /**
+ * Gets the available waterfall width in this container.
+ * @return number
+ */
+ get _waterfallWidth() {
+ if (this._cachedWaterfallWidth == 0) {
+ let container = $("#requests-menu-toolbar");
+ let waterfall = $("#requests-menu-waterfall-header-box");
+ let containerBounds = container.getBoundingClientRect();
+ let waterfallBounds = waterfall.getBoundingClientRect();
+ if (!window.isRTL) {
+ this._cachedWaterfallWidth = containerBounds.width -
+ waterfallBounds.left;
+ } else {
+ this._cachedWaterfallWidth = waterfallBounds.right;
+ }
+ }
+ return this._cachedWaterfallWidth;
+ },
+
+ _splitter: null,
+ _summary: null,
+ _canvas: null,
+ _ctx: null,
+ _cachedWaterfallWidth: 0,
+ _firstRequestStartedMillis: -1,
+ _lastRequestEndedMillis: -1,
+ _updateQueue: [],
+ _addQueue: [],
+ _updateTimeout: null,
+ _resizeTimeout: null,
+ _activeFilters: ["all"],
+ _currentFreetextFilter: ""
+});
+
+exports.RequestsMenuView = RequestsMenuView;
--- a/devtools/client/netmonitor/test/browser_net_charts-02.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-02.js
@@ -4,20 +4,22 @@
"use strict";
/**
* Makes sure Pie Charts have the right internal structure when
* initialized with empty data.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, L10N, Chart } = monitor.panelWin;
+ let { document, Chart } = monitor.panelWin;
let pie = Chart.Pie(document, {
data: null,
width: 100,
height: 100
});
let node = pie.node;
--- a/devtools/client/netmonitor/test/browser_net_charts-03.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-03.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Makes sure Table Charts have the right internal structure.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, L10N, Chart } = monitor.panelWin;
+ let { document, Chart } = monitor.panelWin;
let table = Chart.Table(document, {
title: "Table title",
data: [{
label1: 1,
label2: 11.1
}, {
label1: 2,
--- a/devtools/client/netmonitor/test/browser_net_charts-04.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-04.js
@@ -4,20 +4,22 @@
"use strict";
/**
* Makes sure Pie Charts have the right internal structure when
* initialized with empty data.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, L10N, Chart } = monitor.panelWin;
+ let { document, Chart } = monitor.panelWin;
let table = Chart.Table(document, {
title: "Table title",
data: null,
totals: {
label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
label2: value => "World " + L10N.numberWithDecimals(value, 2)
}
--- a/devtools/client/netmonitor/test/browser_net_charts-05.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-05.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Makes sure Pie+Table Charts have the right internal structure.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, L10N, Chart } = monitor.panelWin;
+ let { document, Chart } = monitor.panelWin;
let chart = Chart.PieTable(document, {
title: "Table title",
data: [{
size: 1,
label: 11.1
}, {
size: 2,
--- a/devtools/client/netmonitor/test/browser_net_charts-06.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-06.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Makes sure Pie Charts correctly handle empty source data.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, L10N, Chart } = monitor.panelWin;
+ let { document, Chart } = monitor.panelWin;
let pie = Chart.Pie(document, {
data: [],
width: 100,
height: 100
});
let node = pie.node;
--- a/devtools/client/netmonitor/test/browser_net_charts-07.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-07.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Makes sure Table Charts correctly handle empty source data.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { document, L10N, Chart } = monitor.panelWin;
+ let { document, Chart } = monitor.panelWin;
let table = Chart.Table(document, {
data: [],
totals: {
label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
label2: value => "World " + L10N.numberWithDecimals(value, 2)
}
});
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -4,20 +4,22 @@
"use strict";
/**
* Tests whether complex request params and payload sent via POST are
* displayed correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
info("Starting test... ");
- let { document, L10N, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
let { RequestsMenu, NetworkDetails } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
NetworkDetails._params.lazyEmpty = false;
let wait = waitForNetworkEvents(monitor, 1, 6);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Tests if different response content types are handled correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
info("Starting test... ");
- let { document, L10N, Editor, NetMonitorView } = monitor.panelWin;
+ let { document, Editor, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 7);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
--- a/devtools/client/netmonitor/test/browser_net_footer-summary.js
+++ b/devtools/client/netmonitor/test/browser_net_footer-summary.js
@@ -5,22 +5,23 @@
/**
* Test if the summary text displayed in the network requests menu footer
* is correct.
*/
add_task(function* () {
requestLongerTimeout(2);
+ let { L10N } = require("devtools/client/netmonitor/l10n");
let { PluralForm } = require("devtools/shared/plural-form");
let { tab, monitor } = yield initNetMonitor(FILTERING_URL);
info("Starting test... ");
- let { $, L10N, NetMonitorView } = monitor.panelWin;
+ let { $, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
testStatus();
for (let i = 0; i < 2; i++) {
info(`Performing requests in batch #${i}`);
let wait = waitForNetworkEvents(monitor, 8);
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -3,24 +3,26 @@
"use strict";
/**
* Tests if very long JSON responses are handled correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
info("Starting test... ");
// This is receiving over 80 KB of json and will populate over 6000 items
// in a variables view instance. Debug builds are slow.
requestLongerTimeout(4);
- let { document, EVENTS, L10N, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Tests if JSON responses with unusal/custom MIME types are handled correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(JSON_CUSTOM_MIME_URL);
info("Starting test... ");
- let { document, EVENTS, L10N, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Tests if JSON responses with unusal/custom MIME types are handled correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(JSON_TEXT_MIME_URL);
info("Starting test... ");
- let { document, EVENTS, L10N, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Tests if JSONP responses are handled correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(JSONP_URL);
info("Starting test... ");
- let { document, EVENTS, L10N, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
let { RequestsMenu, NetworkDetails } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
NetworkDetails._json.lazyEmpty = false;
let wait = waitForNetworkEvents(monitor, 2);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Tests if the POST requests display the correct information in the UI.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
info("Starting test... ");
- let { document, EVENTS, L10N, Editor, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
let { RequestsMenu, NetworkDetails } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
NetworkDetails._params.lazyEmpty = false;
let wait = waitForNetworkEvents(monitor, 0, 2);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -4,20 +4,22 @@
"use strict";
/**
* Tests if the POST requests display the correct information in the UI,
* for raw payloads with attached content-type headers.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(POST_RAW_URL);
info("Starting test... ");
- let { document, EVENTS, L10N, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
let { RequestsMenu, NetworkDetails } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
NetworkDetails._params.lazyEmpty = false;
let wait = waitForNetworkEvents(monitor, 0, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -4,20 +4,22 @@
"use strict";
/**
* Tests if the POST requests display the correct information in the UI,
* for raw payloads with content-type headers attached to the upload stream.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(POST_RAW_WITH_HEADERS_URL);
info("Starting test... ");
- let { document, EVENTS, L10N, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 0, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
--- a/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
@@ -3,31 +3,30 @@
"use strict";
/**
* Tests if the preferences and localization objects work correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- ok(monitor.panelWin.L10N,
- "Should have a localization object available on the panel window.");
ok(monitor.panelWin.Prefs,
"Should have a preferences object available on the panel window.");
testL10N();
testPrefs();
return teardown(monitor);
function testL10N() {
- let { L10N } = monitor.panelWin;
is(typeof L10N.getStr("netmonitor.security.enabled"), "string",
"The getStr() method didn't return a valid string.");
is(typeof L10N.getFormatStr("networkMenu.totalMS", "foo"), "string",
"The getFormatStr() method didn't return a valid string.");
}
function testPrefs() {
let { Prefs } = monitor.panelWin;
--- a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
+++ b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Test if request and response body logging stays on after opening the console.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
info("Starting test... ");
- let { L10N, NetMonitorView } = monitor.panelWin;
+ let { NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
// Perform first batch of requests.
let wait = waitForNetworkEvents(monitor, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Tests if requests render correct information in the menu UI.
*/
function test() {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
initNetMonitor(SIMPLE_SJS).then(({ tab, monitor }) => {
info("Starting test... ");
- let { L10N, NetMonitorView } = monitor.panelWin;
+ let { NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
waitForNetworkEvents(monitor, 1)
.then(() => teardown(monitor))
.then(finish);
--- a/devtools/client/netmonitor/test/browser_net_simple-request-details.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Tests if requests render correct information in the details UI.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(SIMPLE_SJS);
info("Starting test... ");
- let { document, EVENTS, L10N, Editor, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 1);
tab.linkedBrowser.reload();
yield wait;
is(RequestsMenu.selectedItem, null,
--- a/devtools/client/netmonitor/test/browser_net_sort-01.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-01.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Test if the sorting mechanism works correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL);
info("Starting test... ");
- let { $all, L10N, NetMonitorView } = monitor.panelWin;
+ let { $all, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 5);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
--- a/devtools/client/netmonitor/test/browser_net_sort-02.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-02.js
@@ -3,24 +3,26 @@
"use strict";
/**
* Test if sorting columns in the network table works correctly.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SORTING_URL);
info("Starting test... ");
// It seems that this test may be slow on debug builds. This could be because
// of the heavy dom manipulation associated with sorting.
requestLongerTimeout(2);
- let { $, $all, L10N, NetMonitorView } = monitor.panelWin;
+ let { $, $all, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
// Loading the frame script and preparing the xhr request URLs so we can
// generate some requests later.
loadCommonFrameScript();
let requests = [{
url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
method: "GET1"
--- a/devtools/client/netmonitor/test/browser_net_sort-03.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-03.js
@@ -3,24 +3,26 @@
"use strict";
/**
* Test if sorting columns in the network table works correctly with new requests.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { monitor } = yield initNetMonitor(SORTING_URL);
info("Starting test... ");
// It seems that this test may be slow on debug builds. This could be because
// of the heavy dom manipulation associated with sorting.
requestLongerTimeout(2);
- let { $, $all, L10N, NetMonitorView } = monitor.panelWin;
+ let { $, $all, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
// Loading the frame script and preparing the xhr request URLs so we can
// generate some requests later.
loadCommonFrameScript();
let requests = [{
url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
method: "GET1"
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -3,21 +3,23 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests if requests display the correct status code and text in the UI.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL);
info("Starting test... ");
- let { document, EVENTS, L10N, NetMonitorView } = monitor.panelWin;
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
let { RequestsMenu, NetworkDetails } = NetMonitorView;
let requestItems = [];
RequestsMenu.lazyUpdate = false;
NetworkDetails._params.lazyEmpty = false;
const REQUEST_DATA = [
{
--- a/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
+++ b/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
@@ -3,20 +3,22 @@
"use strict";
/**
* Tests if timeline correctly displays interval divisions.
*/
add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
- let { $, $all, L10N, NetMonitorView, NetMonitorController } = monitor.panelWin;
+ let { $, $all, NetMonitorView, NetMonitorController } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
// Disable transferred size column support for this test.
// Without this, the waterfall only has enough room for one division, which
// would remove most of the value of this test.
$("#requests-menu-transferred-header-box").hidden = true;
$("#requests-menu-item-template .requests-menu-transferred").hidden = true;