12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- /* globals document, window, dumpn, $, gNetwork, EVENTS, Prefs,
- NetMonitorController, NetMonitorView */
- "use strict";
- /* eslint-disable mozilla/reject-some-requires */
- const { Cu } = require("chrome");
- const {Task} = require("devtools/shared/task");
- const {DeferredTask} = Cu.import("resource://gre/modules/DeferredTask.jsm", {});
- /* eslint-disable mozilla/reject-some-requires */
- 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 {CurlUtils} = require("devtools/client/shared/curl");
- const {Filters, isFreetextMatch} = require("./filter-predicates");
- const {Sorters} = require("./sort-predicates");
- const {L10N, WEBCONSOLE_L10N} = require("./l10n");
- const {formDataURI,
- writeHeaderText,
- getKeyWithEvent,
- getAbbreviatedMimeType,
- getUriNameWithQuery,
- getUriHostPort,
- getUriHost,
- loadCauseString} = require("./request-utils");
- const Actions = require("./actions/index");
- const RequestListContextMenu = require("./request-list-context-menu");
- 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];
- // 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"
- };
- // A smart store watcher to notify store changes as necessary
- function storeWatcher(initialValue, reduceValue, onChange) {
- let currentValue = initialValue;
- return () => {
- const oldValue = currentValue;
- const newValue = reduceValue(currentValue);
- if (newValue !== oldValue) {
- currentValue = newValue;
- onChange(newValue, oldValue);
- }
- };
- }
- /**
- * 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._onSecurityIconClick = this._onSecurityIconClick.bind(this);
- }
- RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
- /**
- * Initialization function, called when the network monitor is started.
- */
- initialize: function (store) {
- dumpn("Initializing the RequestsMenuView");
- this.store = store;
- this.contextMenu = new RequestListContextMenu();
- let widgetParentEl = $("#requests-menu-contents");
- this.widget = new SideMenuWidget(widgetParentEl);
- this._splitter = $("#network-inspector-view-splitter");
- // 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
- });
- this.sortContents((a, b) => Sorters.waterfall(a.attachment, b.attachment));
- 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._onContextMenu = this._onContextMenu.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.reFilterRequests = this.reFilterRequests.bind(this);
- $("#toolbar-labels").addEventListener("click",
- this.requestsMenuSortEvent, false);
- $("#toolbar-labels").addEventListener("keydown",
- this.requestsMenuSortKeyboardEvent, false);
- $("#toggle-raw-headers").addEventListener("click",
- this.toggleRawHeadersEvent, false);
- $("#requests-menu-contents").addEventListener("scroll", this._onScroll, true);
- $("#requests-menu-contents").addEventListener("contextmenu", this._onContextMenu);
- this.unsubscribeStore = store.subscribe(storeWatcher(
- null,
- () => store.getState().filters,
- (newFilters) => {
- this._activeFilters = newFilters.types
- .toSeq()
- .filter((checked, key) => checked)
- .keySeq()
- .toArray();
- this._currentFreetextFilter = newFilters.url;
- this.reFilterRequests();
- }
- ));
- Prefs.filters.forEach(type =>
- store.dispatch(Actions.toggleFilterType(type)));
- window.once("connected", this._onConnect.bind(this));
- },
- _onConnect: function () {
- $("#requests-menu-reload-notice-button").addEventListener("command",
- this._onReloadCommand, false);
- if (NetMonitorController.supportsCustomRequest) {
- $("#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 {
- $("#headers-summary-resend").hidden = true;
- }
- if (NetMonitorController.supportsPerfStats) {
- $("#requests-menu-perf-notice-button").addEventListener("command",
- this._onContextPerfCommand, false);
- $("#network-statistics-back-button").addEventListener("command",
- this._onContextPerfCommand, false);
- } else {
- $("#notice-perf-message").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);
- $("#requests-menu-contents").removeEventListener("contextmenu", this._onContextMenu);
- 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);
- this._flushRequestsTask.disarm();
- $("#requests-menu-reload-notice-button").removeEventListener("command",
- this._onReloadCommand, false);
- $("#requests-menu-perf-notice-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);
- this.unsubscribeStore();
- },
- /**
- * Resets this container (removes all the networking information).
- */
- reset: function () {
- this.empty();
- this._addQueue = [];
- this._updateQueue = [];
- this._firstRequestStartedMillis = -1;
- this._lastRequestEndedMillis = -1;
- this.resetNotPersistent();
- },
- /**
- * Reset informations that "devtools.webconsole.persistlog == true".
- */
- resetNotPersistent: function () {
- this._firstRequestStartedMillisNotPersistent = -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;
- },
- /**
- * 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;
- // display Status-Line above other response headers
- let selectedStatusLine = selected.httpVersion
- + " " + selected.status
- + " " + selected.statusText
- + "\n";
- requestTextarea.value = writeHeaderText(selectedRequestHeaders);
- // sometimes it's empty
- if (selected.responseHeaders) {
- let selectedResponseHeaders = selected.responseHeaders.headers;
- responseTextare.value = selectedStatusLine
- + writeHeaderText(selectedResponseHeaders);
- } else {
- responseTextare.value = selectedStatusLine;
- }
- $("#raw-headers").hidden = false;
- } else {
- requestTextarea.value = null;
- responseTextare.value = null;
- $("#raw-headers").hidden = true;
- }
- },
- /**
- * Refreshes the view contents with the newly selected filters
- */
- reFilterRequests: function () {
- this.filterContents(this._filterPredicate);
- this.updateRequests();
- this.refreshZebra();
- },
- /**
- * 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((a, b) => Sorters.status(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.status(a.attachment, b.attachment));
- }
- break;
- case "method":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.method(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.method(a.attachment, b.attachment));
- }
- break;
- case "file":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.file(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.file(a.attachment, b.attachment));
- }
- break;
- case "domain":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.domain(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.domain(a.attachment, b.attachment));
- }
- break;
- case "cause":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.cause(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.cause(a.attachment, b.attachment));
- }
- break;
- case "type":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.type(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.type(a.attachment, b.attachment));
- }
- break;
- case "transferred":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.transferred(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.transferred(a.attachment, b.attachment));
- }
- break;
- case "size":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.size(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.size(a.attachment, b.attachment));
- }
- break;
- case "waterfall":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.waterfall(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.waterfall(a.attachment, b.attachment));
- }
- break;
- }
- this.updateRequests();
- this.refreshZebra();
- },
- /**
- * Removes all network requests and closes the sidebar if open.
- */
- clear: function () {
- NetMonitorController.NetworkEventsHandler.clearMarkers();
- NetMonitorView.Sidebar.toggle(false);
- $("#requests-menu-empty-notice").hidden = false;
- this.empty();
- this.updateRequests();
- },
- /**
- * Update store request itmes and trigger related UI update
- */
- updateRequests: function () {
- this.store.dispatch(Actions.updateRequests(this.visibleItems));
- },
- /**
- * 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: {
- firstRequestStartedMillisNotPersistent: this._firstRequestStartedMillisNotPersistent,
- 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 = [];
- $("#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.updateRequests();
- 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 = getUriNameWithQuery(uri);
- let hostPort = getUriHostPort(uri);
- let host = 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 = 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", "ssl", "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();
- },
- /**
- * Open context menu
- */
- _onContextMenu: function (e) {
- e.preventDefault();
- this.contextMenu.open(e);
- },
- /**
- * 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;
- }
- if (this._firstRequestStartedMillisNotPersistent == -1) {
- this._firstRequestStartedMillisNotPersistent = 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;
- }
- },
- /**
- * 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,
- _firstRequestStartedMillisNotPersistent: -1,
- _lastRequestEndedMillis: -1,
- _updateQueue: [],
- _addQueue: [],
- _updateTimeout: null,
- _resizeTimeout: null,
- _activeFilters: ["all"],
- _currentFreetextFilter: ""
- });
- exports.RequestsMenuView = RequestsMenuView;
|