netmonitor-view.js 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. /* import-globals-from ./netmonitor-controller.js */
  6. /* globals Prefs, gNetwork, setInterval, setTimeout, clearInterval, clearTimeout, btoa */
  7. /* exported $, $all */
  8. "use strict";
  9. XPCOMUtils.defineLazyGetter(this, "NetworkHelper", function () {
  10. return require("devtools/shared/webconsole/network-helper");
  11. });
  12. /* eslint-disable mozilla/reject-some-requires */
  13. const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
  14. /* eslint-disable mozilla/reject-some-requires */
  15. const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
  16. const {ToolSidebar} = require("devtools/client/framework/sidebar");
  17. const {testing: isTesting} = require("devtools/shared/flags");
  18. const {ViewHelpers, Heritage} = require("devtools/client/shared/widgets/view-helpers");
  19. const {Filters} = require("./filter-predicates");
  20. const {getFormDataSections,
  21. formDataURI,
  22. getUriHostPort} = require("./request-utils");
  23. const {L10N} = require("./l10n");
  24. const {RequestsMenuView} = require("./requests-menu-view");
  25. const {CustomRequestView} = require("./custom-request-view");
  26. const {ToolbarView} = require("./toolbar-view");
  27. const {configureStore} = require("./store");
  28. const {PerformanceStatisticsView} = require("./performance-statistics-view");
  29. // Initialize the global redux variables
  30. var gStore = configureStore();
  31. // ms
  32. const WDA_DEFAULT_VERIFY_INTERVAL = 50;
  33. // Use longer timeout during testing as the tests need this process to succeed
  34. // and two seconds is quite short on slow debug builds. The timeout here should
  35. // be at least equal to the general mochitest timeout of 45 seconds so that this
  36. // never gets hit during testing.
  37. // ms
  38. const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
  39. // 100 KB in bytes
  40. const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
  41. const HEADERS_SIZE_DECIMALS = 3;
  42. const CONTENT_MIME_TYPE_MAPPINGS = {
  43. "/ecmascript": Editor.modes.js,
  44. "/javascript": Editor.modes.js,
  45. "/x-javascript": Editor.modes.js,
  46. "/html": Editor.modes.html,
  47. "/xhtml": Editor.modes.html,
  48. "/xml": Editor.modes.html,
  49. "/atom": Editor.modes.html,
  50. "/soap": Editor.modes.html,
  51. "/vnd.mpeg.dash.mpd": Editor.modes.html,
  52. "/rdf": Editor.modes.css,
  53. "/rss": Editor.modes.css,
  54. "/css": Editor.modes.css
  55. };
  56. const DEFAULT_EDITOR_CONFIG = {
  57. mode: Editor.modes.text,
  58. readOnly: true,
  59. lineNumbers: true
  60. };
  61. const GENERIC_VARIABLES_VIEW_SETTINGS = {
  62. lazyEmpty: true,
  63. // ms
  64. lazyEmptyDelay: 10,
  65. searchEnabled: true,
  66. editableValueTooltip: "",
  67. editableNameTooltip: "",
  68. preventDisableOnChange: true,
  69. preventDescriptorModifiers: true,
  70. eval: () => {}
  71. };
  72. /**
  73. * Object defining the network monitor view components.
  74. */
  75. var NetMonitorView = {
  76. /**
  77. * Initializes the network monitor view.
  78. */
  79. initialize: function () {
  80. this._initializePanes();
  81. this.Toolbar.initialize(gStore);
  82. this.RequestsMenu.initialize(gStore);
  83. this.NetworkDetails.initialize();
  84. this.CustomRequest.initialize();
  85. this.PerformanceStatistics.initialize(gStore);
  86. },
  87. /**
  88. * Destroys the network monitor view.
  89. */
  90. destroy: function () {
  91. this._isDestroyed = true;
  92. this.Toolbar.destroy();
  93. this.RequestsMenu.destroy();
  94. this.NetworkDetails.destroy();
  95. this.CustomRequest.destroy();
  96. this._destroyPanes();
  97. },
  98. /**
  99. * Initializes the UI for all the displayed panes.
  100. */
  101. _initializePanes: function () {
  102. dumpn("Initializing the NetMonitorView panes");
  103. this._body = $("#body");
  104. this._detailsPane = $("#details-pane");
  105. this._detailsPane.setAttribute("width", Prefs.networkDetailsWidth);
  106. this._detailsPane.setAttribute("height", Prefs.networkDetailsHeight);
  107. this.toggleDetailsPane({ visible: false });
  108. // Disable the performance statistics mode.
  109. if (!Prefs.statistics) {
  110. $("#request-menu-context-perf").hidden = true;
  111. $("#notice-perf-message").hidden = true;
  112. }
  113. },
  114. /**
  115. * Destroys the UI for all the displayed panes.
  116. */
  117. _destroyPanes: Task.async(function* () {
  118. dumpn("Destroying the NetMonitorView panes");
  119. Prefs.networkDetailsWidth = this._detailsPane.getAttribute("width");
  120. Prefs.networkDetailsHeight = this._detailsPane.getAttribute("height");
  121. this._detailsPane = null;
  122. for (let p of this._editorPromises.values()) {
  123. let editor = yield p;
  124. editor.destroy();
  125. }
  126. }),
  127. /**
  128. * Gets the visibility state of the network details pane.
  129. * @return boolean
  130. */
  131. get detailsPaneHidden() {
  132. return this._detailsPane.classList.contains("pane-collapsed");
  133. },
  134. /**
  135. * Sets the network details pane hidden or visible.
  136. *
  137. * @param object flags
  138. * An object containing some of the following properties:
  139. * - visible: true if the pane should be shown, false to hide
  140. * - animated: true to display an animation on toggle
  141. * - delayed: true to wait a few cycles before toggle
  142. * - callback: a function to invoke when the toggle finishes
  143. * @param number tabIndex [optional]
  144. * The index of the intended selected tab in the details pane.
  145. */
  146. toggleDetailsPane: function (flags, tabIndex) {
  147. ViewHelpers.togglePane(flags, this._detailsPane);
  148. if (flags.visible) {
  149. this._body.classList.remove("pane-collapsed");
  150. gStore.dispatch(Actions.openSidebar(true));
  151. } else {
  152. this._body.classList.add("pane-collapsed");
  153. gStore.dispatch(Actions.openSidebar(false));
  154. }
  155. if (tabIndex !== undefined) {
  156. $("#event-details-pane").selectedIndex = tabIndex;
  157. }
  158. },
  159. /**
  160. * Gets the current mode for this tool.
  161. * @return string (e.g, "network-inspector-view" or "network-statistics-view")
  162. */
  163. get currentFrontendMode() {
  164. // The getter may be called from a timeout after the panel is destroyed.
  165. if (!this._body.selectedPanel) {
  166. return null;
  167. }
  168. return this._body.selectedPanel.id;
  169. },
  170. /**
  171. * Toggles between the frontend view modes ("Inspector" vs. "Statistics").
  172. */
  173. toggleFrontendMode: function () {
  174. if (this.currentFrontendMode != "network-inspector-view") {
  175. this.showNetworkInspectorView();
  176. } else {
  177. this.showNetworkStatisticsView();
  178. }
  179. },
  180. /**
  181. * Switches to the "Inspector" frontend view mode.
  182. */
  183. showNetworkInspectorView: function () {
  184. this._body.selectedPanel = $("#network-inspector-view");
  185. this.RequestsMenu._flushWaterfallViews(true);
  186. },
  187. /**
  188. * Switches to the "Statistics" frontend view mode.
  189. */
  190. showNetworkStatisticsView: function () {
  191. this._body.selectedPanel = $("#network-statistics-view");
  192. let controller = NetMonitorController;
  193. let requestsView = this.RequestsMenu;
  194. let statisticsView = this.PerformanceStatistics;
  195. Task.spawn(function* () {
  196. statisticsView.displayPlaceholderCharts();
  197. yield controller.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
  198. try {
  199. // • The response headers and status code are required for determining
  200. // whether a response is "fresh" (cacheable).
  201. // • The response content size and request total time are necessary for
  202. // populating the statistics view.
  203. // • The response mime type is used for categorization.
  204. yield whenDataAvailable(requestsView, [
  205. "responseHeaders", "status", "contentSize", "transferredSize", "mimeType", "totalTime"
  206. ]);
  207. } catch (ex) {
  208. // Timed out while waiting for data. Continue with what we have.
  209. console.error(ex);
  210. }
  211. statisticsView.createPrimedCacheChart(requestsView.items);
  212. statisticsView.createEmptyCacheChart(requestsView.items);
  213. });
  214. },
  215. reloadPage: function () {
  216. NetMonitorController.triggerActivity(
  217. ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT);
  218. },
  219. /**
  220. * Lazily initializes and returns a promise for a Editor instance.
  221. *
  222. * @param string id
  223. * The id of the editor placeholder node.
  224. * @return object
  225. * A promise that is resolved when the editor is available.
  226. */
  227. editor: function (id) {
  228. dumpn("Getting a NetMonitorView editor: " + id);
  229. if (this._editorPromises.has(id)) {
  230. return this._editorPromises.get(id);
  231. }
  232. let deferred = promise.defer();
  233. this._editorPromises.set(id, deferred.promise);
  234. // Initialize the source editor and store the newly created instance
  235. // in the ether of a resolved promise's value.
  236. let editor = new Editor(DEFAULT_EDITOR_CONFIG);
  237. editor.appendTo($(id)).then(() => deferred.resolve(editor));
  238. return deferred.promise;
  239. },
  240. _body: null,
  241. _detailsPane: null,
  242. _editorPromises: new Map()
  243. };
  244. /**
  245. * Functions handling the sidebar details view.
  246. */
  247. function SidebarView() {
  248. dumpn("SidebarView was instantiated");
  249. }
  250. SidebarView.prototype = {
  251. /**
  252. * Sets this view hidden or visible. It's visible by default.
  253. *
  254. * @param boolean visibleFlag
  255. * Specifies the intended visibility.
  256. */
  257. toggle: function (visibleFlag) {
  258. NetMonitorView.toggleDetailsPane({ visible: visibleFlag });
  259. NetMonitorView.RequestsMenu._flushWaterfallViews(true);
  260. },
  261. /**
  262. * Populates this view with the specified data.
  263. *
  264. * @param object data
  265. * The data source (this should be the attachment of a request item).
  266. * @return object
  267. * Returns a promise that resolves upon population of the subview.
  268. */
  269. populate: Task.async(function* (data) {
  270. let isCustom = data.isCustom;
  271. let view = isCustom ?
  272. NetMonitorView.CustomRequest :
  273. NetMonitorView.NetworkDetails;
  274. yield view.populate(data);
  275. $("#details-pane").selectedIndex = isCustom ? 0 : 1;
  276. window.emit(EVENTS.SIDEBAR_POPULATED);
  277. })
  278. };
  279. /**
  280. * Functions handling the requests details view.
  281. */
  282. function NetworkDetailsView() {
  283. dumpn("NetworkDetailsView was instantiated");
  284. // The ToolSidebar requires the panel object to be able to emit events.
  285. EventEmitter.decorate(this);
  286. this._onTabSelect = this._onTabSelect.bind(this);
  287. }
  288. NetworkDetailsView.prototype = {
  289. /**
  290. * An object containing the state of tabs.
  291. */
  292. _viewState: {
  293. // if updating[tab] is true a task is currently updating the given tab.
  294. updating: [],
  295. // if dirty[tab] is true, the tab needs to be repopulated once current
  296. // update task finishes
  297. dirty: [],
  298. // the most recently received attachment data for the request
  299. latestData: null,
  300. },
  301. /**
  302. * Initialization function, called when the network monitor is started.
  303. */
  304. initialize: function () {
  305. dumpn("Initializing the NetworkDetailsView");
  306. this.widget = $("#event-details-pane");
  307. this.sidebar = new ToolSidebar(this.widget, this, "netmonitor", {
  308. disableTelemetry: true,
  309. showAllTabsMenu: true
  310. });
  311. this._headers = new VariablesView($("#all-headers"),
  312. Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
  313. emptyText: L10N.getStr("headersEmptyText"),
  314. searchPlaceholder: L10N.getStr("headersFilterText")
  315. }));
  316. this._cookies = new VariablesView($("#all-cookies"),
  317. Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
  318. emptyText: L10N.getStr("cookiesEmptyText"),
  319. searchPlaceholder: L10N.getStr("cookiesFilterText")
  320. }));
  321. this._params = new VariablesView($("#request-params"),
  322. Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
  323. emptyText: L10N.getStr("paramsEmptyText"),
  324. searchPlaceholder: L10N.getStr("paramsFilterText")
  325. }));
  326. this._json = new VariablesView($("#response-content-json"),
  327. Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
  328. onlyEnumVisible: true,
  329. searchPlaceholder: L10N.getStr("jsonFilterText")
  330. }));
  331. VariablesViewController.attach(this._json);
  332. this._paramsQueryString = L10N.getStr("paramsQueryString");
  333. this._paramsFormData = L10N.getStr("paramsFormData");
  334. this._paramsPostPayload = L10N.getStr("paramsPostPayload");
  335. this._requestHeaders = L10N.getStr("requestHeaders");
  336. this._requestHeadersFromUpload = L10N.getStr("requestHeadersFromUpload");
  337. this._responseHeaders = L10N.getStr("responseHeaders");
  338. this._requestCookies = L10N.getStr("requestCookies");
  339. this._responseCookies = L10N.getStr("responseCookies");
  340. $("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
  341. },
  342. /**
  343. * Destruction function, called when the network monitor is closed.
  344. */
  345. destroy: function () {
  346. dumpn("Destroying the NetworkDetailsView");
  347. this.sidebar.destroy();
  348. $("tabpanels", this.widget).removeEventListener("select",
  349. this._onTabSelect);
  350. },
  351. /**
  352. * Populates this view with the specified data.
  353. *
  354. * @param object data
  355. * The data source (this should be the attachment of a request item).
  356. * @return object
  357. * Returns a promise that resolves upon population the view.
  358. */
  359. populate: function (data) {
  360. $("#request-params-box").setAttribute("flex", "1");
  361. $("#request-params-box").hidden = false;
  362. $("#request-post-data-textarea-box").hidden = true;
  363. $("#response-content-info-header").hidden = true;
  364. $("#response-content-json-box").hidden = true;
  365. $("#response-content-textarea-box").hidden = true;
  366. $("#raw-headers").hidden = true;
  367. $("#response-content-image-box").hidden = true;
  368. let isHtml = Filters.html(data);
  369. // Show the "Preview" tabpanel only for plain HTML responses.
  370. this.sidebar.toggleTab(isHtml, "preview-tab");
  371. // Show the "Security" tab only for requests that
  372. // 1) are https (state != insecure)
  373. // 2) come from a target that provides security information.
  374. let hasSecurityInfo = data.securityState &&
  375. data.securityState !== "insecure";
  376. this.sidebar.toggleTab(hasSecurityInfo, "security-tab");
  377. // Switch to the "Headers" tabpanel if the "Preview" previously selected
  378. // and this is not an HTML response or "Security" was selected but this
  379. // request has no security information.
  380. if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
  381. !hasSecurityInfo && this.widget.selectedPanel ===
  382. $("#security-tabpanel")) {
  383. this.widget.selectedIndex = 0;
  384. }
  385. this._headers.empty();
  386. this._cookies.empty();
  387. this._params.empty();
  388. this._json.empty();
  389. this._dataSrc = { src: data, populated: [] };
  390. this._onTabSelect();
  391. window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
  392. return promise.resolve();
  393. },
  394. /**
  395. * Listener handling the tab selection event.
  396. */
  397. _onTabSelect: function () {
  398. let { src, populated } = this._dataSrc || {};
  399. let tab = this.widget.selectedIndex;
  400. let view = this;
  401. // Make sure the data source is valid and don't populate the same tab twice.
  402. if (!src || populated[tab]) {
  403. return;
  404. }
  405. let viewState = this._viewState;
  406. if (viewState.updating[tab]) {
  407. // A task is currently updating this tab. If we started another update
  408. // task now it would result in a duplicated content as described in bugs
  409. // 997065 and 984687. As there's no way to stop the current task mark the
  410. // tab dirty and refresh the panel once the current task finishes.
  411. viewState.dirty[tab] = true;
  412. viewState.latestData = src;
  413. return;
  414. }
  415. Task.spawn(function* () {
  416. viewState.updating[tab] = true;
  417. switch (tab) {
  418. // "Headers"
  419. case 0:
  420. yield view._setSummary(src);
  421. yield view._setResponseHeaders(src.responseHeaders);
  422. yield view._setRequestHeaders(
  423. src.requestHeaders,
  424. src.requestHeadersFromUploadStream);
  425. break;
  426. // "Cookies"
  427. case 1:
  428. yield view._setResponseCookies(src.responseCookies);
  429. yield view._setRequestCookies(src.requestCookies);
  430. break;
  431. // "Params"
  432. case 2:
  433. yield view._setRequestGetParams(src.url);
  434. yield view._setRequestPostParams(
  435. src.requestHeaders,
  436. src.requestHeadersFromUploadStream,
  437. src.requestPostData);
  438. break;
  439. // "Response"
  440. case 3:
  441. yield view._setResponseBody(src.url, src.responseContent);
  442. break;
  443. // "Timings"
  444. case 4:
  445. yield view._setTimingsInformation(src.eventTimings);
  446. break;
  447. // "Security"
  448. case 5:
  449. yield view._setSecurityInfo(src.securityInfo, src.url);
  450. break;
  451. // "Preview"
  452. case 6:
  453. yield view._setHtmlPreview(src.responseContent);
  454. break;
  455. }
  456. viewState.updating[tab] = false;
  457. }).then(() => {
  458. if (tab == this.widget.selectedIndex) {
  459. if (viewState.dirty[tab]) {
  460. // The request information was updated while the task was running.
  461. viewState.dirty[tab] = false;
  462. view.populate(viewState.latestData);
  463. } else {
  464. // Tab is selected but not dirty. We're done here.
  465. populated[tab] = true;
  466. window.emit(EVENTS.TAB_UPDATED);
  467. if (NetMonitorController.isConnected()) {
  468. NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
  469. }
  470. }
  471. } else if (viewState.dirty[tab]) {
  472. // Tab is dirty but no longer selected. Don't refresh it now, it'll be
  473. // done if the tab is shown again.
  474. viewState.dirty[tab] = false;
  475. }
  476. }, e => console.error(e));
  477. },
  478. /**
  479. * Sets the network request summary shown in this view.
  480. *
  481. * @param object data
  482. * The data source (this should be the attachment of a request item).
  483. */
  484. _setSummary: function (data) {
  485. if (data.url) {
  486. let unicodeUrl = NetworkHelper.convertToUnicode(unescape(data.url));
  487. $("#headers-summary-url-value").setAttribute("value", unicodeUrl);
  488. $("#headers-summary-url-value").setAttribute("tooltiptext", unicodeUrl);
  489. $("#headers-summary-url").removeAttribute("hidden");
  490. } else {
  491. $("#headers-summary-url").setAttribute("hidden", "true");
  492. }
  493. if (data.method) {
  494. $("#headers-summary-method-value").setAttribute("value", data.method);
  495. $("#headers-summary-method").removeAttribute("hidden");
  496. } else {
  497. $("#headers-summary-method").setAttribute("hidden", "true");
  498. }
  499. if (data.remoteAddress) {
  500. let address = data.remoteAddress;
  501. if (address.indexOf(":") != -1) {
  502. address = `[${address}]`;
  503. }
  504. if (data.remotePort) {
  505. address += `:${data.remotePort}`;
  506. }
  507. $("#headers-summary-address-value").setAttribute("value", address);
  508. $("#headers-summary-address-value").setAttribute("tooltiptext", address);
  509. $("#headers-summary-address").removeAttribute("hidden");
  510. } else {
  511. $("#headers-summary-address").setAttribute("hidden", "true");
  512. }
  513. if (data.status) {
  514. // "code" attribute is only used by css to determine the icon color
  515. let code;
  516. if (data.fromCache) {
  517. code = "cached";
  518. } else if (data.fromServiceWorker) {
  519. code = "service worker";
  520. } else {
  521. code = data.status;
  522. }
  523. $("#headers-summary-status-circle").setAttribute("code", code);
  524. $("#headers-summary-status-value").setAttribute("value",
  525. data.status + " " + data.statusText);
  526. $("#headers-summary-status").removeAttribute("hidden");
  527. } else {
  528. $("#headers-summary-status").setAttribute("hidden", "true");
  529. }
  530. if (data.httpVersion) {
  531. $("#headers-summary-version-value").setAttribute("value",
  532. data.httpVersion);
  533. $("#headers-summary-version").removeAttribute("hidden");
  534. } else {
  535. $("#headers-summary-version").setAttribute("hidden", "true");
  536. }
  537. },
  538. /**
  539. * Sets the network request headers shown in this view.
  540. *
  541. * @param object headers
  542. * The "requestHeaders" message received from the server.
  543. * @param object uploadHeaders
  544. * The "requestHeadersFromUploadStream" inferred from the POST payload.
  545. * @return object
  546. * A promise that resolves when request headers are set.
  547. */
  548. _setRequestHeaders: Task.async(function* (headers, uploadHeaders) {
  549. if (headers && headers.headers.length) {
  550. yield this._addHeaders(this._requestHeaders, headers);
  551. }
  552. if (uploadHeaders && uploadHeaders.headers.length) {
  553. yield this._addHeaders(this._requestHeadersFromUpload, uploadHeaders);
  554. }
  555. }),
  556. /**
  557. * Sets the network response headers shown in this view.
  558. *
  559. * @param object response
  560. * The message received from the server.
  561. * @return object
  562. * A promise that resolves when response headers are set.
  563. */
  564. _setResponseHeaders: Task.async(function* (response) {
  565. if (response && response.headers.length) {
  566. response.headers.sort((a, b) => a.name > b.name);
  567. yield this._addHeaders(this._responseHeaders, response);
  568. }
  569. }),
  570. /**
  571. * Populates the headers container in this view with the specified data.
  572. *
  573. * @param string name
  574. * The type of headers to populate (request or response).
  575. * @param object response
  576. * The message received from the server.
  577. * @return object
  578. * A promise that resolves when headers are added.
  579. */
  580. _addHeaders: Task.async(function* (name, response) {
  581. let kb = response.headersSize / 1024;
  582. let size = L10N.numberWithDecimals(kb, HEADERS_SIZE_DECIMALS);
  583. let text = L10N.getFormatStr("networkMenu.sizeKB", size);
  584. let headersScope = this._headers.addScope(name + " (" + text + ")");
  585. headersScope.expanded = true;
  586. for (let header of response.headers) {
  587. let headerVar = headersScope.addItem(header.name, {}, {relaxed: true});
  588. let headerValue = yield gNetwork.getString(header.value);
  589. headerVar.setGrip(headerValue);
  590. }
  591. }),
  592. /**
  593. * Sets the network request cookies shown in this view.
  594. *
  595. * @param object response
  596. * The message received from the server.
  597. * @return object
  598. * A promise that is resolved when the request cookies are set.
  599. */
  600. _setRequestCookies: Task.async(function* (response) {
  601. if (response && response.cookies.length) {
  602. response.cookies.sort((a, b) => a.name > b.name);
  603. yield this._addCookies(this._requestCookies, response);
  604. }
  605. }),
  606. /**
  607. * Sets the network response cookies shown in this view.
  608. *
  609. * @param object response
  610. * The message received from the server.
  611. * @return object
  612. * A promise that is resolved when the response cookies are set.
  613. */
  614. _setResponseCookies: Task.async(function* (response) {
  615. if (response && response.cookies.length) {
  616. yield this._addCookies(this._responseCookies, response);
  617. }
  618. }),
  619. /**
  620. * Populates the cookies container in this view with the specified data.
  621. *
  622. * @param string name
  623. * The type of cookies to populate (request or response).
  624. * @param object response
  625. * The message received from the server.
  626. * @return object
  627. * Returns a promise that resolves upon the adding of cookies.
  628. */
  629. _addCookies: Task.async(function* (name, response) {
  630. let cookiesScope = this._cookies.addScope(name);
  631. cookiesScope.expanded = true;
  632. for (let cookie of response.cookies) {
  633. let cookieVar = cookiesScope.addItem(cookie.name, {}, {relaxed: true});
  634. let cookieValue = yield gNetwork.getString(cookie.value);
  635. cookieVar.setGrip(cookieValue);
  636. // By default the cookie name and value are shown. If this is the only
  637. // information available, then nothing else is to be displayed.
  638. let cookieProps = Object.keys(cookie);
  639. if (cookieProps.length == 2) {
  640. continue;
  641. }
  642. // Display any other information other than the cookie name and value
  643. // which may be available.
  644. let rawObject = Object.create(null);
  645. let otherProps = cookieProps.filter(e => e != "name" && e != "value");
  646. for (let prop of otherProps) {
  647. rawObject[prop] = cookie[prop];
  648. }
  649. cookieVar.populate(rawObject);
  650. cookieVar.twisty = true;
  651. cookieVar.expanded = true;
  652. }
  653. }),
  654. /**
  655. * Sets the network request get params shown in this view.
  656. *
  657. * @param string url
  658. * The request's url.
  659. */
  660. _setRequestGetParams: function (url) {
  661. let query = NetworkHelper.nsIURL(url).query;
  662. if (query) {
  663. this._addParams(this._paramsQueryString, query);
  664. }
  665. },
  666. /**
  667. * Sets the network request post params shown in this view.
  668. *
  669. * @param object headers
  670. * The "requestHeaders" message received from the server.
  671. * @param object uploadHeaders
  672. * The "requestHeadersFromUploadStream" inferred from the POST payload.
  673. * @param object postData
  674. * The "requestPostData" message received from the server.
  675. * @return object
  676. * A promise that is resolved when the request post params are set.
  677. */
  678. _setRequestPostParams: Task.async(function* (headers, uploadHeaders,
  679. postData) {
  680. if (!headers || !uploadHeaders || !postData) {
  681. return;
  682. }
  683. let formDataSections = yield getFormDataSections(
  684. headers,
  685. uploadHeaders,
  686. postData,
  687. gNetwork.getString.bind(gNetwork));
  688. this._params.onlyEnumVisible = false;
  689. // Handle urlencoded form data sections (e.g. "?foo=bar&baz=42").
  690. if (formDataSections.length > 0) {
  691. formDataSections.forEach(section => {
  692. this._addParams(this._paramsFormData, section);
  693. });
  694. } else {
  695. // Handle JSON and actual forms ("multipart/form-data" content type).
  696. let postDataLongString = postData.postData.text;
  697. let text = yield gNetwork.getString(postDataLongString);
  698. let jsonVal = null;
  699. try {
  700. jsonVal = JSON.parse(text);
  701. } catch (ex) { // eslint-disable-line
  702. }
  703. if (jsonVal) {
  704. this._params.onlyEnumVisible = true;
  705. let jsonScopeName = L10N.getStr("jsonScopeName");
  706. let jsonScope = this._params.addScope(jsonScopeName);
  707. jsonScope.expanded = true;
  708. let jsonItem = jsonScope.addItem(undefined, { enumerable: true });
  709. jsonItem.populate(jsonVal, { sorted: true });
  710. } else {
  711. // This is really awkward, but hey, it works. Let's show an empty
  712. // scope in the params view and place the source editor containing
  713. // the raw post data directly underneath.
  714. $("#request-params-box").removeAttribute("flex");
  715. let paramsScope = this._params.addScope(this._paramsPostPayload);
  716. paramsScope.expanded = true;
  717. paramsScope.locked = true;
  718. $("#request-post-data-textarea-box").hidden = false;
  719. let editor = yield NetMonitorView.editor("#request-post-data-textarea");
  720. editor.setMode(Editor.modes.text);
  721. editor.setText(text);
  722. }
  723. }
  724. window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
  725. }),
  726. /**
  727. * Populates the params container in this view with the specified data.
  728. *
  729. * @param string name
  730. * The type of params to populate (get or post).
  731. * @param string queryString
  732. * A query string of params (e.g. "?foo=bar&baz=42").
  733. */
  734. _addParams: function (name, queryString) {
  735. let paramsArray = NetworkHelper.parseQueryString(queryString);
  736. if (!paramsArray) {
  737. return;
  738. }
  739. let paramsScope = this._params.addScope(name);
  740. paramsScope.expanded = true;
  741. for (let param of paramsArray) {
  742. let paramVar = paramsScope.addItem(param.name, {}, {relaxed: true});
  743. paramVar.setGrip(param.value);
  744. }
  745. },
  746. /**
  747. * Sets the network response body shown in this view.
  748. *
  749. * @param string url
  750. * The request's url.
  751. * @param object response
  752. * The message received from the server.
  753. * @return object
  754. * A promise that is resolved when the response body is set.
  755. */
  756. _setResponseBody: Task.async(function* (url, response) {
  757. if (!response) {
  758. return;
  759. }
  760. let { mimeType, text, encoding } = response.content;
  761. let responseBody = yield gNetwork.getString(text);
  762. // Handle json, which we tentatively identify by checking the MIME type
  763. // for "json" after any word boundary. This works for the standard
  764. // "application/json", and also for custom types like "x-bigcorp-json".
  765. // Additionally, we also directly parse the response text content to
  766. // verify whether it's json or not, to handle responses incorrectly
  767. // labeled as text/plain instead.
  768. let jsonMimeType, jsonObject, jsonObjectParseError;
  769. try {
  770. jsonMimeType = /\bjson/.test(mimeType);
  771. jsonObject = JSON.parse(responseBody);
  772. } catch (e) {
  773. jsonObjectParseError = e;
  774. }
  775. if (jsonMimeType || jsonObject) {
  776. // Extract the actual json substring in case this might be a "JSONP".
  777. // This regex basically parses a function call and captures the
  778. // function name and arguments in two separate groups.
  779. let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
  780. let [_, callbackPadding, jsonpString] = // eslint-disable-line
  781. responseBody.match(jsonpRegex) || [];
  782. // Make sure this is a valid JSON object first. If so, nicely display
  783. // the parsing results in a variables view. Otherwise, simply show
  784. // the contents as plain text.
  785. if (callbackPadding && jsonpString) {
  786. try {
  787. jsonObject = JSON.parse(jsonpString);
  788. } catch (e) {
  789. jsonObjectParseError = e;
  790. }
  791. }
  792. // Valid JSON or JSONP.
  793. if (jsonObject) {
  794. $("#response-content-json-box").hidden = false;
  795. let jsonScopeName = callbackPadding
  796. ? L10N.getFormatStr("jsonpScopeName", callbackPadding)
  797. : L10N.getStr("jsonScopeName");
  798. let jsonVar = { label: jsonScopeName, rawObject: jsonObject };
  799. yield this._json.controller.setSingleVariable(jsonVar).expanded;
  800. } else {
  801. // Malformed JSON.
  802. $("#response-content-textarea-box").hidden = false;
  803. let infoHeader = $("#response-content-info-header");
  804. infoHeader.setAttribute("value", jsonObjectParseError);
  805. infoHeader.setAttribute("tooltiptext", jsonObjectParseError);
  806. infoHeader.hidden = false;
  807. let editor = yield NetMonitorView.editor("#response-content-textarea");
  808. editor.setMode(Editor.modes.js);
  809. editor.setText(responseBody);
  810. }
  811. } else if (mimeType.includes("image/")) {
  812. // Handle images.
  813. $("#response-content-image-box").setAttribute("align", "center");
  814. $("#response-content-image-box").setAttribute("pack", "center");
  815. $("#response-content-image-box").hidden = false;
  816. $("#response-content-image").src = formDataURI(mimeType, encoding, responseBody);
  817. // Immediately display additional information about the image:
  818. // file name, mime type and encoding.
  819. $("#response-content-image-name-value").setAttribute("value",
  820. NetworkHelper.nsIURL(url).fileName);
  821. $("#response-content-image-mime-value").setAttribute("value", mimeType);
  822. // Wait for the image to load in order to display the width and height.
  823. $("#response-content-image").onload = e => {
  824. // XUL images are majestic so they don't bother storing their dimensions
  825. // in width and height attributes like the rest of the folk. Hack around
  826. // this by getting the bounding client rect and subtracting the margins.
  827. let { width, height } = e.target.getBoundingClientRect();
  828. let dimensions = (width - 2) + " \u00D7 " + (height - 2);
  829. $("#response-content-image-dimensions-value").setAttribute("value",
  830. dimensions);
  831. };
  832. } else {
  833. $("#response-content-textarea-box").hidden = false;
  834. let editor = yield NetMonitorView.editor("#response-content-textarea");
  835. editor.setMode(Editor.modes.text);
  836. editor.setText(responseBody);
  837. // Maybe set a more appropriate mode in the Source Editor if possible,
  838. // but avoid doing this for very large files.
  839. if (responseBody.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
  840. let mapping = Object.keys(CONTENT_MIME_TYPE_MAPPINGS).find(key => {
  841. return mimeType.includes(key);
  842. });
  843. if (mapping) {
  844. editor.setMode(CONTENT_MIME_TYPE_MAPPINGS[mapping]);
  845. }
  846. }
  847. }
  848. window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
  849. }),
  850. /**
  851. * Sets the timings information shown in this view.
  852. *
  853. * @param object response
  854. * The message received from the server.
  855. */
  856. _setTimingsInformation: function (response) {
  857. if (!response) {
  858. return;
  859. }
  860. let { blocked, dns, connect, ssl, send, wait, receive } = response.timings;
  861. let tabboxWidth = $("#details-pane").getAttribute("width");
  862. // Other nodes also take some space.
  863. let availableWidth = tabboxWidth / 2;
  864. let scale = (response.totalTime > 0 ?
  865. Math.max(availableWidth / response.totalTime, 0) :
  866. 0);
  867. $("#timings-summary-blocked .requests-menu-timings-box")
  868. .setAttribute("width", blocked * scale);
  869. $("#timings-summary-blocked .requests-menu-timings-total")
  870. .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", blocked));
  871. $("#timings-summary-dns .requests-menu-timings-box")
  872. .setAttribute("width", dns * scale);
  873. $("#timings-summary-dns .requests-menu-timings-total")
  874. .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", dns));
  875. $("#timings-summary-connect .requests-menu-timings-box")
  876. .setAttribute("width", connect * scale);
  877. $("#timings-summary-connect .requests-menu-timings-total")
  878. .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", connect));
  879. $("#timings-summary-ssl .requests-menu-timings-box")
  880. .setAttribute("width", ssl * scale);
  881. $("#timings-summary-ssl .requests-menu-timings-total")
  882. .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", ssl));
  883. $("#timings-summary-send .requests-menu-timings-box")
  884. .setAttribute("width", send * scale);
  885. $("#timings-summary-send .requests-menu-timings-total")
  886. .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", send));
  887. $("#timings-summary-wait .requests-menu-timings-box")
  888. .setAttribute("width", wait * scale);
  889. $("#timings-summary-wait .requests-menu-timings-total")
  890. .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", wait));
  891. $("#timings-summary-receive .requests-menu-timings-box")
  892. .setAttribute("width", receive * scale);
  893. $("#timings-summary-receive .requests-menu-timings-total")
  894. .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", receive));
  895. $("#timings-summary-dns .requests-menu-timings-box")
  896. .style.transform = "translateX(" + (scale * blocked) + "px)";
  897. $("#timings-summary-connect .requests-menu-timings-box")
  898. .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
  899. $("#timings-summary-ssl .requests-menu-timings-box")
  900. .style.transform = "translateX(" + (scale * blocked) + "px)";
  901. $("#timings-summary-send .requests-menu-timings-box")
  902. .style.transform =
  903. "translateX(" + (scale * (blocked + dns + connect)) + "px)";
  904. $("#timings-summary-wait .requests-menu-timings-box")
  905. .style.transform =
  906. "translateX(" + (scale * (blocked + dns + connect + send)) + "px)";
  907. $("#timings-summary-receive .requests-menu-timings-box")
  908. .style.transform =
  909. "translateX(" + (scale * (blocked + dns + connect + send + wait)) +
  910. "px)";
  911. $("#timings-summary-dns .requests-menu-timings-total")
  912. .style.transform = "translateX(" + (scale * blocked) + "px)";
  913. $("#timings-summary-connect .requests-menu-timings-total")
  914. .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
  915. $("#timings-summary-ssl .requests-menu-timings-total")
  916. .style.transform = "translateX(" + (scale * blocked) + "px)";
  917. $("#timings-summary-send .requests-menu-timings-total")
  918. .style.transform =
  919. "translateX(" + (scale * (blocked + dns + connect)) + "px)";
  920. $("#timings-summary-wait .requests-menu-timings-total")
  921. .style.transform =
  922. "translateX(" + (scale * (blocked + dns + connect + send)) + "px)";
  923. $("#timings-summary-receive .requests-menu-timings-total")
  924. .style.transform =
  925. "translateX(" + (scale * (blocked + dns + connect + send + wait)) +
  926. "px)";
  927. },
  928. /**
  929. * Sets the preview for HTML responses shown in this view.
  930. *
  931. * @param object response
  932. * The message received from the server.
  933. * @return object
  934. * A promise that is resolved when the html preview is rendered.
  935. */
  936. _setHtmlPreview: Task.async(function* (response) {
  937. if (!response) {
  938. return promise.resolve();
  939. }
  940. let { text } = response.content;
  941. let responseBody = yield gNetwork.getString(text);
  942. // Always disable JS when previewing HTML responses.
  943. let iframe = $("#response-preview");
  944. iframe.contentDocument.docShell.allowJavascript = false;
  945. iframe.contentDocument.documentElement.innerHTML = responseBody;
  946. window.emit(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
  947. return undefined;
  948. }),
  949. /**
  950. * Sets the security information shown in this view.
  951. *
  952. * @param object securityInfo
  953. * The data received from server
  954. * @param string url
  955. * The URL of this request
  956. * @return object
  957. * A promise that is resolved when the security info is rendered.
  958. */
  959. _setSecurityInfo: Task.async(function* (securityInfo, url) {
  960. if (!securityInfo) {
  961. // We don't have security info. This could mean one of two things:
  962. // 1) This connection is not secure and this tab is not visible and thus
  963. // we shouldn't be here.
  964. // 2) We have already received securityState and the tab is visible BUT
  965. // the rest of the information is still on its way. Once it arrives
  966. // this method is called again.
  967. return;
  968. }
  969. /**
  970. * A helper that sets value and tooltiptext attributes of an element to
  971. * specified value.
  972. *
  973. * @param string selector
  974. * A selector for the element.
  975. * @param string value
  976. * The value to set. If this evaluates to false a placeholder string
  977. * <Not Available> is used instead.
  978. */
  979. function setValue(selector, value) {
  980. let label = $(selector);
  981. if (!value) {
  982. label.setAttribute("value", L10N.getStr(
  983. "netmonitor.security.notAvailable"));
  984. label.setAttribute("tooltiptext", label.getAttribute("value"));
  985. } else {
  986. label.setAttribute("value", value);
  987. label.setAttribute("tooltiptext", value);
  988. }
  989. }
  990. let errorbox = $("#security-error");
  991. let infobox = $("#security-information");
  992. if (securityInfo.state === "secure" || securityInfo.state === "weak") {
  993. infobox.hidden = false;
  994. errorbox.hidden = true;
  995. // Warning icons
  996. let cipher = $("#security-warning-cipher");
  997. if (securityInfo.state === "weak") {
  998. cipher.hidden = securityInfo.weaknessReasons.indexOf("cipher") === -1;
  999. } else {
  1000. cipher.hidden = true;
  1001. }
  1002. let enabledLabel = L10N.getStr("netmonitor.security.enabled");
  1003. let disabledLabel = L10N.getStr("netmonitor.security.disabled");
  1004. // Connection parameters
  1005. setValue("#security-protocol-version-value",
  1006. securityInfo.protocolVersion);
  1007. setValue("#security-ciphersuite-value", securityInfo.cipherSuite);
  1008. setValue("#security-keagroup-value", securityInfo.keaGroupName);
  1009. setValue("#security-signaturescheme-value", securityInfo.signatureSchemeName);
  1010. // Host header
  1011. let domain = getUriHostPort(url);
  1012. let hostHeader = L10N.getFormatStr("netmonitor.security.hostHeader",
  1013. domain);
  1014. setValue("#security-info-host-header", hostHeader);
  1015. // Parameters related to the domain
  1016. setValue("#security-http-strict-transport-security-value",
  1017. securityInfo.hsts ? enabledLabel : disabledLabel);
  1018. setValue("#security-public-key-pinning-value",
  1019. securityInfo.hpkp ? enabledLabel : disabledLabel);
  1020. // Certificate parameters
  1021. let cert = securityInfo.cert;
  1022. setValue("#security-cert-subject-cn", cert.subject.commonName);
  1023. setValue("#security-cert-subject-o", cert.subject.organization);
  1024. setValue("#security-cert-subject-ou", cert.subject.organizationalUnit);
  1025. setValue("#security-cert-issuer-cn", cert.issuer.commonName);
  1026. setValue("#security-cert-issuer-o", cert.issuer.organization);
  1027. setValue("#security-cert-issuer-ou", cert.issuer.organizationalUnit);
  1028. setValue("#security-cert-validity-begins", cert.validity.start);
  1029. setValue("#security-cert-validity-expires", cert.validity.end);
  1030. setValue("#security-cert-sha1-fingerprint", cert.fingerprint.sha1);
  1031. setValue("#security-cert-sha256-fingerprint", cert.fingerprint.sha256);
  1032. } else {
  1033. infobox.hidden = true;
  1034. errorbox.hidden = false;
  1035. // Strip any HTML from the message.
  1036. let plain = new DOMParser().parseFromString(securityInfo.errorMessage,
  1037. "text/html");
  1038. setValue("#security-error-message", plain.body.textContent);
  1039. }
  1040. }),
  1041. _dataSrc: null,
  1042. _headers: null,
  1043. _cookies: null,
  1044. _params: null,
  1045. _json: null,
  1046. _paramsQueryString: "",
  1047. _paramsFormData: "",
  1048. _paramsPostPayload: "",
  1049. _requestHeaders: "",
  1050. _responseHeaders: "",
  1051. _requestCookies: "",
  1052. _responseCookies: ""
  1053. };
  1054. /**
  1055. * DOM query helper.
  1056. * TODO: Move it into "dom-utils.js" module and "require" it when needed.
  1057. */
  1058. var $ = (selector, target = document) => target.querySelector(selector);
  1059. var $all = (selector, target = document) => target.querySelectorAll(selector);
  1060. /**
  1061. * Makes sure certain properties are available on all objects in a data store.
  1062. *
  1063. * @param array dataStore
  1064. * The request view object from which to fetch the item list.
  1065. * @param array mandatoryFields
  1066. * A list of strings representing properties of objects in dataStore.
  1067. * @return object
  1068. * A promise resolved when all objects in dataStore contain the
  1069. * properties defined in mandatoryFields.
  1070. */
  1071. function whenDataAvailable(requestsView, mandatoryFields) {
  1072. let deferred = promise.defer();
  1073. let interval = setInterval(() => {
  1074. const { attachments } = requestsView;
  1075. if (attachments.length > 0 && attachments.every(item => {
  1076. return mandatoryFields.every(field => field in item);
  1077. })) {
  1078. clearInterval(interval);
  1079. clearTimeout(timer);
  1080. deferred.resolve();
  1081. }
  1082. }, WDA_DEFAULT_VERIFY_INTERVAL);
  1083. let timer = setTimeout(() => {
  1084. clearInterval(interval);
  1085. deferred.reject(new Error("Timed out while waiting for data"));
  1086. }, WDA_DEFAULT_GIVE_UP_TIMEOUT);
  1087. return deferred.promise;
  1088. }
  1089. /**
  1090. * Preliminary setup for the NetMonitorView object.
  1091. */
  1092. NetMonitorView.Toolbar = new ToolbarView();
  1093. NetMonitorView.RequestsMenu = new RequestsMenuView();
  1094. NetMonitorView.Sidebar = new SidebarView();
  1095. NetMonitorView.CustomRequest = new CustomRequestView();
  1096. NetMonitorView.NetworkDetails = new NetworkDetailsView();
  1097. NetMonitorView.PerformanceStatistics = new PerformanceStatisticsView();