mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 08:18:41 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
15
devtools/client/netmonitor/.eslintrc.js
Normal file
15
devtools/client/netmonitor/.eslintrc.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the devtools eslintrc.
|
||||
"extends": "../../.eslintrc.js",
|
||||
|
||||
"rules": {
|
||||
// The netmonitor is being migrated to HTML and cleaned of
|
||||
// chrome-privileged code, so this rule disallows requiring chrome
|
||||
// code. Some files in the netmonitor disable this rule still. The
|
||||
// goal is to enable the rule globally on all files.
|
||||
/* eslint-disable max-len */
|
||||
"mozilla/reject-some-requires": ["error", "^(chrome|chrome:.*|resource:.*|devtools/server/.*|.*\\.jsm|devtools/shared/platform/(chome|content)/.*)$"],
|
||||
},
|
||||
};
|
||||
57
devtools/client/netmonitor/actions/filters.js
Normal file
57
devtools/client/netmonitor/actions/filters.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
TOGGLE_FILTER_TYPE,
|
||||
ENABLE_FILTER_TYPE_ONLY,
|
||||
SET_FILTER_TEXT,
|
||||
} = require("../constants");
|
||||
|
||||
/**
|
||||
* Toggle an existing filter type state.
|
||||
* If type 'all' is specified, all the other filter types are set to false.
|
||||
* Available filter types are defined in filters reducer.
|
||||
*
|
||||
* @param {string} filter - A filter type is going to be updated
|
||||
*/
|
||||
function toggleFilterType(filter) {
|
||||
return {
|
||||
type: TOGGLE_FILTER_TYPE,
|
||||
filter,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable filter type exclusively.
|
||||
* Except filter type is set to true, all the other filter types are set
|
||||
* to false.
|
||||
* Available filter types are defined in filters reducer.
|
||||
*
|
||||
* @param {string} filter - A filter type is going to be updated
|
||||
*/
|
||||
function enableFilterTypeOnly(filter) {
|
||||
return {
|
||||
type: ENABLE_FILTER_TYPE_ONLY,
|
||||
filter,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter text.
|
||||
*
|
||||
* @param {string} url - A filter text is going to be set
|
||||
*/
|
||||
function setFilterText(url) {
|
||||
return {
|
||||
type: SET_FILTER_TEXT,
|
||||
url,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
toggleFilterType,
|
||||
enableFilterTypeOnly,
|
||||
setFilterText,
|
||||
};
|
||||
9
devtools/client/netmonitor/actions/index.js
Normal file
9
devtools/client/netmonitor/actions/index.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const filters = require("./filters");
|
||||
const sidebar = require("./sidebar");
|
||||
|
||||
module.exports = Object.assign({}, filters, sidebar);
|
||||
10
devtools/client/netmonitor/actions/moz.build
Normal file
10
devtools/client/netmonitor/actions/moz.build
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'filters.js',
|
||||
'index.js',
|
||||
'sidebar.js',
|
||||
)
|
||||
49
devtools/client/netmonitor/actions/sidebar.js
Normal file
49
devtools/client/netmonitor/actions/sidebar.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
DISABLE_TOGGLE_BUTTON,
|
||||
SHOW_SIDEBAR,
|
||||
TOGGLE_SIDEBAR,
|
||||
} = require("../constants");
|
||||
|
||||
/**
|
||||
* Change ToggleButton disabled state.
|
||||
*
|
||||
* @param {boolean} disabled - expected button disabled state
|
||||
*/
|
||||
function disableToggleButton(disabled) {
|
||||
return {
|
||||
type: DISABLE_TOGGLE_BUTTON,
|
||||
disabled: disabled,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Change sidebar visible state.
|
||||
*
|
||||
* @param {boolean} visible - expected sidebar visible state
|
||||
*/
|
||||
function showSidebar(visible) {
|
||||
return {
|
||||
type: SHOW_SIDEBAR,
|
||||
visible: visible,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle to show/hide sidebar.
|
||||
*/
|
||||
function toggleSidebar() {
|
||||
return {
|
||||
type: TOGGLE_SIDEBAR,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
disableToggleButton,
|
||||
showSidebar,
|
||||
toggleSidebar,
|
||||
};
|
||||
49
devtools/client/netmonitor/components/filter-buttons.js
Normal file
49
devtools/client/netmonitor/components/filter-buttons.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { L10N } = require("../l10n");
|
||||
const Actions = require("../actions/index");
|
||||
|
||||
const { button, div } = DOM;
|
||||
|
||||
function FilterButtons({
|
||||
filterTypes,
|
||||
triggerFilterType,
|
||||
}) {
|
||||
const buttons = filterTypes.entrySeq().map(([type, checked]) => {
|
||||
let classList = ["menu-filter-button"];
|
||||
checked && classList.push("checked");
|
||||
|
||||
return button({
|
||||
id: `requests-menu-filter-${type}-button`,
|
||||
className: classList.join(" "),
|
||||
"data-key": type,
|
||||
onClick: triggerFilterType,
|
||||
onKeyDown: triggerFilterType,
|
||||
"aria-pressed": checked,
|
||||
}, L10N.getStr(`netmonitor.toolbar.filter.${type}`));
|
||||
}).toArray();
|
||||
|
||||
return div({ id: "requests-menu-filter-buttons" }, buttons);
|
||||
}
|
||||
|
||||
FilterButtons.PropTypes = {
|
||||
state: PropTypes.object.isRequired,
|
||||
triggerFilterType: PropTypes.func.iRequired,
|
||||
};
|
||||
|
||||
module.exports = connect(
|
||||
(state) => ({ filterTypes: state.filters.types }),
|
||||
(dispatch) => ({
|
||||
triggerFilterType: (evt) => {
|
||||
if (evt.type === "keydown" && (evt.key !== "" || evt.key !== "Enter")) {
|
||||
return;
|
||||
}
|
||||
dispatch(Actions.toggleFilterType(evt.target.dataset.key));
|
||||
},
|
||||
})
|
||||
)(FilterButtons);
|
||||
10
devtools/client/netmonitor/components/moz.build
Normal file
10
devtools/client/netmonitor/components/moz.build
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'filter-buttons.js',
|
||||
'search-box.js',
|
||||
'toggle-button.js',
|
||||
)
|
||||
24
devtools/client/netmonitor/components/search-box.js
Normal file
24
devtools/client/netmonitor/components/search-box.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const SearchBox = require("devtools/client/shared/components/search-box");
|
||||
const { L10N } = require("../l10n");
|
||||
const Actions = require("../actions/index");
|
||||
const { FREETEXT_FILTER_SEARCH_DELAY } = require("../constants");
|
||||
|
||||
module.exports = connect(
|
||||
(state) => ({
|
||||
delay: FREETEXT_FILTER_SEARCH_DELAY,
|
||||
keyShortcut: L10N.getStr("netmonitor.toolbar.filterFreetext.key"),
|
||||
placeholder: L10N.getStr("netmonitor.toolbar.filterFreetext.label"),
|
||||
type: "filter",
|
||||
}),
|
||||
(dispatch) => ({
|
||||
onChange: (url) => {
|
||||
dispatch(Actions.setFilterText(url));
|
||||
},
|
||||
})
|
||||
)(SearchBox);
|
||||
69
devtools/client/netmonitor/components/toggle-button.js
Normal file
69
devtools/client/netmonitor/components/toggle-button.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals NetMonitorView */
|
||||
"use strict";
|
||||
|
||||
const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { L10N } = require("../l10n");
|
||||
const Actions = require("../actions/index");
|
||||
|
||||
// Shortcuts
|
||||
const { button } = DOM;
|
||||
|
||||
/**
|
||||
* Button used to toggle sidebar
|
||||
*/
|
||||
function ToggleButton({
|
||||
disabled,
|
||||
onToggle,
|
||||
visible,
|
||||
}) {
|
||||
let className = ["devtools-button"];
|
||||
if (!visible) {
|
||||
className.push("pane-collapsed");
|
||||
}
|
||||
let titleMsg = visible ? L10N.getStr("collapseDetailsPane") :
|
||||
L10N.getStr("expandDetailsPane");
|
||||
|
||||
return button({
|
||||
id: "details-pane-toggle",
|
||||
className: className.join(" "),
|
||||
title: titleMsg,
|
||||
disabled: disabled,
|
||||
tabIndex: "0",
|
||||
onMouseDown: onToggle,
|
||||
});
|
||||
}
|
||||
|
||||
ToggleButton.propTypes = {
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
visible: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
module.exports = connect(
|
||||
(state) => ({
|
||||
disabled: state.sidebar.toggleButtonDisabled,
|
||||
visible: state.sidebar.visible,
|
||||
}),
|
||||
(dispatch) => ({
|
||||
onToggle: () => {
|
||||
dispatch(Actions.toggleSidebar());
|
||||
|
||||
let requestsMenu = NetMonitorView.RequestsMenu;
|
||||
let selectedIndex = requestsMenu.selectedIndex;
|
||||
|
||||
// Make sure there's a selection if the button is pressed, to avoid
|
||||
// showing an empty network details pane.
|
||||
if (selectedIndex == -1 && requestsMenu.itemCount) {
|
||||
requestsMenu.selectedIndex = 0;
|
||||
} else {
|
||||
requestsMenu.selectedIndex = -1;
|
||||
}
|
||||
},
|
||||
})
|
||||
)(ToggleButton);
|
||||
19
devtools/client/netmonitor/constants.js
Normal file
19
devtools/client/netmonitor/constants.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const general = {
|
||||
FREETEXT_FILTER_SEARCH_DELAY: 200,
|
||||
};
|
||||
|
||||
const actionTypes = {
|
||||
TOGGLE_FILTER_TYPE: "TOGGLE_FILTER_TYPE",
|
||||
ENABLE_FILTER_TYPE_ONLY: "ENABLE_FILTER_TYPE_ONLY",
|
||||
TOGGLE_SIDEBAR: "TOGGLE_SIDEBAR",
|
||||
SHOW_SIDEBAR: "SHOW_SIDEBAR",
|
||||
DISABLE_TOGGLE_BUTTON: "DISABLE_TOGGLE_BUTTON",
|
||||
SET_FILTER_TEXT: "SET_FILTER_TEXT",
|
||||
};
|
||||
|
||||
module.exports = Object.assign({}, general, actionTypes);
|
||||
216
devtools/client/netmonitor/custom-request-view.js
Normal file
216
devtools/client/netmonitor/custom-request-view.js
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals window, dumpn, gNetwork, $, EVENTS, NetMonitorView */
|
||||
"use strict";
|
||||
|
||||
const {Task} = require("devtools/shared/task");
|
||||
const {writeHeaderText, getKeyWithEvent} = require("./request-utils");
|
||||
|
||||
loader.lazyRequireGetter(this, "NetworkHelper",
|
||||
"devtools/shared/webconsole/network-helper");
|
||||
|
||||
/**
|
||||
* Functions handling the custom request view.
|
||||
*/
|
||||
function CustomRequestView() {
|
||||
dumpn("CustomRequestView was instantiated");
|
||||
}
|
||||
|
||||
CustomRequestView.prototype = {
|
||||
/**
|
||||
* Initialization function, called when the network monitor is started.
|
||||
*/
|
||||
initialize: function () {
|
||||
dumpn("Initializing the CustomRequestView");
|
||||
|
||||
this.updateCustomRequestEvent = getKeyWithEvent(this.onUpdate.bind(this));
|
||||
$("#custom-pane").addEventListener("input",
|
||||
this.updateCustomRequestEvent, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the network monitor is closed.
|
||||
*/
|
||||
destroy: function () {
|
||||
dumpn("Destroying the CustomRequestView");
|
||||
|
||||
$("#custom-pane").removeEventListener("input",
|
||||
this.updateCustomRequestEvent, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this view with the specified data.
|
||||
*
|
||||
* @param object data
|
||||
* The data source (this should be the attachment of a request item).
|
||||
* @return object
|
||||
* Returns a promise that resolves upon population the view.
|
||||
*/
|
||||
populate: Task.async(function* (data) {
|
||||
$("#custom-url-value").value = data.url;
|
||||
$("#custom-method-value").value = data.method;
|
||||
this.updateCustomQuery(data.url);
|
||||
|
||||
if (data.requestHeaders) {
|
||||
let headers = data.requestHeaders.headers;
|
||||
$("#custom-headers-value").value = writeHeaderText(headers);
|
||||
}
|
||||
if (data.requestPostData) {
|
||||
let postData = data.requestPostData.postData.text;
|
||||
$("#custom-postdata-value").value = yield gNetwork.getString(postData);
|
||||
}
|
||||
|
||||
window.emit(EVENTS.CUSTOMREQUESTVIEW_POPULATED);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Handle user input in the custom request form.
|
||||
*
|
||||
* @param object field
|
||||
* the field that the user updated.
|
||||
*/
|
||||
onUpdate: function (field) {
|
||||
let selectedItem = NetMonitorView.RequestsMenu.selectedItem;
|
||||
let value;
|
||||
|
||||
switch (field) {
|
||||
case "method":
|
||||
value = $("#custom-method-value").value.trim();
|
||||
selectedItem.attachment.method = value;
|
||||
break;
|
||||
case "url":
|
||||
value = $("#custom-url-value").value;
|
||||
this.updateCustomQuery(value);
|
||||
selectedItem.attachment.url = value;
|
||||
break;
|
||||
case "query":
|
||||
let query = $("#custom-query-value").value;
|
||||
this.updateCustomUrl(query);
|
||||
field = "url";
|
||||
value = $("#custom-url-value").value;
|
||||
selectedItem.attachment.url = value;
|
||||
break;
|
||||
case "body":
|
||||
value = $("#custom-postdata-value").value;
|
||||
selectedItem.attachment.requestPostData = { postData: { text: value } };
|
||||
break;
|
||||
case "headers":
|
||||
let headersText = $("#custom-headers-value").value;
|
||||
value = parseHeadersText(headersText);
|
||||
selectedItem.attachment.requestHeaders = { headers: value };
|
||||
break;
|
||||
}
|
||||
|
||||
NetMonitorView.RequestsMenu.updateMenuView(selectedItem, field, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the query string field based on the url.
|
||||
*
|
||||
* @param object url
|
||||
* The URL to extract query string from.
|
||||
*/
|
||||
updateCustomQuery: function (url) {
|
||||
let paramsArray = NetworkHelper.parseQueryString(
|
||||
NetworkHelper.nsIURL(url).query);
|
||||
|
||||
if (!paramsArray) {
|
||||
$("#custom-query").hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$("#custom-query").hidden = false;
|
||||
$("#custom-query-value").value = writeQueryText(paramsArray);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the url based on the query string field.
|
||||
*
|
||||
* @param object queryText
|
||||
* The contents of the query string field.
|
||||
*/
|
||||
updateCustomUrl: function (queryText) {
|
||||
let params = parseQueryText(queryText);
|
||||
let queryString = writeQueryString(params);
|
||||
|
||||
let url = $("#custom-url-value").value;
|
||||
let oldQuery = NetworkHelper.nsIURL(url).query;
|
||||
let path = url.replace(oldQuery, queryString);
|
||||
|
||||
$("#custom-url-value").value = path;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse text representation of multiple HTTP headers.
|
||||
*
|
||||
* @param string text
|
||||
* Text of headers
|
||||
* @return array
|
||||
* Array of headers info {name, value}
|
||||
*/
|
||||
function parseHeadersText(text) {
|
||||
return parseRequestText(text, "\\S+?", ":");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse readable text list of a query string.
|
||||
*
|
||||
* @param string text
|
||||
* Text of query string represetation
|
||||
* @return array
|
||||
* Array of query params {name, value}
|
||||
*/
|
||||
function parseQueryText(text) {
|
||||
return parseRequestText(text, ".+?", "=");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a text representation of a name[divider]value list with
|
||||
* the given name regex and divider character.
|
||||
*
|
||||
* @param string text
|
||||
* Text of list
|
||||
* @return array
|
||||
* Array of headers info {name, value}
|
||||
*/
|
||||
function parseRequestText(text, namereg, divider) {
|
||||
let regex = new RegExp("(" + namereg + ")\\" + divider + "\\s*(.+)");
|
||||
let pairs = [];
|
||||
|
||||
for (let line of text.split("\n")) {
|
||||
let matches;
|
||||
if (matches = regex.exec(line)) { // eslint-disable-line
|
||||
let [, name, value] = matches;
|
||||
pairs.push({name: name, value: value});
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out a list of query params into a chunk of text
|
||||
*
|
||||
* @param array params
|
||||
* Array of query params {name, value}
|
||||
* @return string
|
||||
* List of query params in text format
|
||||
*/
|
||||
function writeQueryText(params) {
|
||||
return params.map(({name, value}) => name + "=" + value).join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out a list of query params into a query string
|
||||
*
|
||||
* @param array params
|
||||
* Array of query params {name, value}
|
||||
* @return string
|
||||
* Query string that can be appended to a url.
|
||||
*/
|
||||
function writeQueryString(params) {
|
||||
return params.map(({name, value}) => name + "=" + value).join("&");
|
||||
}
|
||||
|
||||
exports.CustomRequestView = CustomRequestView;
|
||||
86
devtools/client/netmonitor/events.js
Normal file
86
devtools/client/netmonitor/events.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
"use strict";
|
||||
|
||||
// The panel's window global is an EventEmitter firing the following events:
|
||||
const EVENTS = {
|
||||
// When the monitored target begins and finishes navigating.
|
||||
TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
|
||||
TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate",
|
||||
|
||||
// When a network or timeline event is received.
|
||||
// See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for
|
||||
// more information about what each packet is supposed to deliver.
|
||||
NETWORK_EVENT: "NetMonitor:NetworkEvent",
|
||||
TIMELINE_EVENT: "NetMonitor:TimelineEvent",
|
||||
|
||||
// When a network event is added to the view
|
||||
REQUEST_ADDED: "NetMonitor:RequestAdded",
|
||||
|
||||
// When request headers begin and finish receiving.
|
||||
UPDATING_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdating:RequestHeaders",
|
||||
RECEIVED_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdated:RequestHeaders",
|
||||
|
||||
// When request cookies begin and finish receiving.
|
||||
UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies",
|
||||
RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies",
|
||||
|
||||
// When request post data begins and finishes receiving.
|
||||
UPDATING_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdating:RequestPostData",
|
||||
RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData",
|
||||
|
||||
// When security information begins and finishes receiving.
|
||||
UPDATING_SECURITY_INFO: "NetMonitor::NetworkEventUpdating:SecurityInfo",
|
||||
RECEIVED_SECURITY_INFO: "NetMonitor::NetworkEventUpdated:SecurityInfo",
|
||||
|
||||
// When response headers begin and finish receiving.
|
||||
UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders",
|
||||
RECEIVED_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdated:ResponseHeaders",
|
||||
|
||||
// When response cookies begin and finish receiving.
|
||||
UPDATING_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdating:ResponseCookies",
|
||||
RECEIVED_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdated:ResponseCookies",
|
||||
|
||||
// When event timings begin and finish receiving.
|
||||
UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings",
|
||||
RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings",
|
||||
|
||||
// When response content begins, updates and finishes receiving.
|
||||
STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
|
||||
UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
|
||||
RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
|
||||
|
||||
// When the request post params are displayed in the UI.
|
||||
REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
|
||||
|
||||
// When the response body is displayed in the UI.
|
||||
RESPONSE_BODY_DISPLAYED: "NetMonitor:ResponseBodyAvailable",
|
||||
|
||||
// When the html response preview is displayed in the UI.
|
||||
RESPONSE_HTML_PREVIEW_DISPLAYED: "NetMonitor:ResponseHtmlPreviewAvailable",
|
||||
|
||||
// When the image response thumbnail is displayed in the UI.
|
||||
RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
|
||||
"NetMonitor:ResponseImageThumbnailAvailable",
|
||||
|
||||
// When a tab is selected in the NetworkDetailsView and subsequently rendered.
|
||||
TAB_UPDATED: "NetMonitor:TabUpdated",
|
||||
|
||||
// Fired when Sidebar has finished being populated.
|
||||
SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
|
||||
|
||||
// Fired when NetworkDetailsView has finished being populated.
|
||||
NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
|
||||
|
||||
// Fired when CustomRequestView has finished being populated.
|
||||
CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated",
|
||||
|
||||
// Fired when charts have been displayed in the PerformanceStatisticsView.
|
||||
PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
|
||||
PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
|
||||
EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
|
||||
|
||||
// Fired once the NetMonitorController establishes a connection to the debug
|
||||
// target.
|
||||
CONNECTED: "connected",
|
||||
};
|
||||
|
||||
exports.EVENTS = EVENTS;
|
||||
129
devtools/client/netmonitor/filter-predicates.js
Normal file
129
devtools/client/netmonitor/filter-predicates.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Predicates used when filtering items.
|
||||
*
|
||||
* @param object item
|
||||
* The filtered item.
|
||||
* @return boolean
|
||||
* True if the item should be visible, false otherwise.
|
||||
*/
|
||||
function all() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isHtml({ mimeType }) {
|
||||
return mimeType && mimeType.includes("/html");
|
||||
}
|
||||
|
||||
function isCss({ mimeType }) {
|
||||
return mimeType && mimeType.includes("/css");
|
||||
}
|
||||
|
||||
function isJs({ mimeType }) {
|
||||
return mimeType && (
|
||||
mimeType.includes("/ecmascript") ||
|
||||
mimeType.includes("/javascript") ||
|
||||
mimeType.includes("/x-javascript"));
|
||||
}
|
||||
|
||||
function isXHR(item) {
|
||||
// Show the request it is XHR, except if the request is a WS upgrade
|
||||
return item.isXHR && !isWS(item);
|
||||
}
|
||||
|
||||
function isFont({ url, mimeType }) {
|
||||
// Fonts are a mess.
|
||||
return (mimeType && (
|
||||
mimeType.includes("font/") ||
|
||||
mimeType.includes("/font"))) ||
|
||||
url.includes(".eot") ||
|
||||
url.includes(".ttf") ||
|
||||
url.includes(".otf") ||
|
||||
url.includes(".woff");
|
||||
}
|
||||
|
||||
function isImage({ mimeType }) {
|
||||
return mimeType && mimeType.includes("image/");
|
||||
}
|
||||
|
||||
function isMedia({ mimeType }) {
|
||||
// Not including images.
|
||||
return mimeType && (
|
||||
mimeType.includes("audio/") ||
|
||||
mimeType.includes("video/") ||
|
||||
mimeType.includes("model/"));
|
||||
}
|
||||
|
||||
function isFlash({ url, mimeType }) {
|
||||
// Flash is a mess.
|
||||
return (mimeType && (
|
||||
mimeType.includes("/x-flv") ||
|
||||
mimeType.includes("/x-shockwave-flash"))) ||
|
||||
url.includes(".swf") ||
|
||||
url.includes(".flv");
|
||||
}
|
||||
|
||||
function isWS({ requestHeaders, responseHeaders }) {
|
||||
// Detect a websocket upgrade if request has an Upgrade header with value 'websocket'
|
||||
if (!requestHeaders || !Array.isArray(requestHeaders.headers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the 'upgrade' header.
|
||||
let upgradeHeader = requestHeaders.headers.find(header => {
|
||||
return (header.name == "Upgrade");
|
||||
});
|
||||
|
||||
// If no header found on request, check response - mainly to get
|
||||
// something we can unit test, as it is impossible to set
|
||||
// the Upgrade header on outgoing XHR as per the spec.
|
||||
if (!upgradeHeader && responseHeaders &&
|
||||
Array.isArray(responseHeaders.headers)) {
|
||||
upgradeHeader = responseHeaders.headers.find(header => {
|
||||
return (header.name == "Upgrade");
|
||||
});
|
||||
}
|
||||
|
||||
// Return false if there is no such header or if its value isn't 'websocket'.
|
||||
if (!upgradeHeader || upgradeHeader.value != "websocket") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isOther(item) {
|
||||
let tests = [isHtml, isCss, isJs, isXHR, isFont, isImage, isMedia, isFlash, isWS];
|
||||
return tests.every(is => !is(item));
|
||||
}
|
||||
|
||||
function isFreetextMatch({ url }, text) {
|
||||
let lowerCaseUrl = url.toLowerCase();
|
||||
let lowerCaseText = text.toLowerCase();
|
||||
let textLength = text.length;
|
||||
// Support negative filtering
|
||||
if (text.startsWith("-") && textLength > 1) {
|
||||
lowerCaseText = lowerCaseText.substring(1, textLength);
|
||||
return !lowerCaseUrl.includes(lowerCaseText);
|
||||
}
|
||||
|
||||
// no text is a positive match
|
||||
return !text || lowerCaseUrl.includes(lowerCaseText);
|
||||
}
|
||||
|
||||
exports.Filters = {
|
||||
all: all,
|
||||
html: isHtml,
|
||||
css: isCss,
|
||||
js: isJs,
|
||||
xhr: isXHR,
|
||||
fonts: isFont,
|
||||
images: isImage,
|
||||
media: isMedia,
|
||||
flash: isFlash,
|
||||
ws: isWS,
|
||||
other: isOther,
|
||||
};
|
||||
|
||||
exports.isFreetextMatch = isFreetextMatch;
|
||||
273
devtools/client/netmonitor/har/har-automation.js
Normal file
273
devtools/client/netmonitor/har/har-automation.js
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
/* eslint-disable mozilla/reject-some-requires */
|
||||
const { Ci } = require("chrome");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
const { resolve } = require("promise");
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyRequireGetter(this, "HarCollector", "devtools/client/netmonitor/har/har-collector", true);
|
||||
loader.lazyRequireGetter(this, "HarExporter", "devtools/client/netmonitor/har/har-exporter", true);
|
||||
loader.lazyRequireGetter(this, "HarUtils", "devtools/client/netmonitor/har/har-utils", true);
|
||||
|
||||
const prefDomain = "devtools.netmonitor.har.";
|
||||
|
||||
// Helper tracer. Should be generic sharable by other modules (bug 1171927)
|
||||
const trace = {
|
||||
log: function (...args) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This object is responsible for automated HAR export. It listens
|
||||
* for Network activity, collects all HTTP data and triggers HAR
|
||||
* export when the page is loaded.
|
||||
*
|
||||
* The user needs to enable the following preference to make the
|
||||
* auto-export work: devtools.netmonitor.har.enableAutoExportToFile
|
||||
*
|
||||
* HAR files are stored within directory that is specified in this
|
||||
* preference: devtools.netmonitor.har.defaultLogDir
|
||||
*
|
||||
* If the default log directory preference isn't set the following
|
||||
* directory is used by default: <profile>/har/logs
|
||||
*/
|
||||
var HarAutomation = Class({
|
||||
// Initialization
|
||||
|
||||
initialize: function (toolbox) {
|
||||
this.toolbox = toolbox;
|
||||
|
||||
let target = toolbox.target;
|
||||
target.makeRemote().then(() => {
|
||||
this.startMonitoring(target.client, target.form);
|
||||
});
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
if (this.collector) {
|
||||
this.collector.stop();
|
||||
}
|
||||
|
||||
if (this.tabWatcher) {
|
||||
this.tabWatcher.disconnect();
|
||||
}
|
||||
},
|
||||
|
||||
// Automation
|
||||
|
||||
startMonitoring: function (client, tabGrip, callback) {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tabGrip) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.debuggerClient = client;
|
||||
this.tabClient = this.toolbox.target.activeTab;
|
||||
this.webConsoleClient = this.toolbox.target.activeConsole;
|
||||
|
||||
this.tabWatcher = new TabWatcher(this.toolbox, this);
|
||||
this.tabWatcher.connect();
|
||||
},
|
||||
|
||||
pageLoadBegin: function (response) {
|
||||
this.resetCollector();
|
||||
},
|
||||
|
||||
resetCollector: function () {
|
||||
if (this.collector) {
|
||||
this.collector.stop();
|
||||
}
|
||||
|
||||
// A page is about to be loaded, start collecting HTTP
|
||||
// data from events sent from the backend.
|
||||
this.collector = new HarCollector({
|
||||
webConsoleClient: this.webConsoleClient,
|
||||
debuggerClient: this.debuggerClient
|
||||
});
|
||||
|
||||
this.collector.start();
|
||||
},
|
||||
|
||||
/**
|
||||
* A page is done loading, export collected data. Note that
|
||||
* some requests for additional page resources might be pending,
|
||||
* so export all after all has been properly received from the backend.
|
||||
*
|
||||
* This collector still works and collects any consequent HTTP
|
||||
* traffic (e.g. XHRs) happening after the page is loaded and
|
||||
* The additional traffic can be exported by executing
|
||||
* triggerExport on this object.
|
||||
*/
|
||||
pageLoadDone: function (response) {
|
||||
trace.log("HarAutomation.pageLoadDone; ", response);
|
||||
|
||||
if (this.collector) {
|
||||
this.collector.waitForHarLoad().then(collector => {
|
||||
return this.autoExport();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
autoExport: function () {
|
||||
let autoExport = Services.prefs.getBoolPref(prefDomain +
|
||||
"enableAutoExportToFile");
|
||||
|
||||
if (!autoExport) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// Auto export to file is enabled, so save collected data
|
||||
// into a file and use all the default options.
|
||||
let data = {
|
||||
fileName: Services.prefs.getCharPref(prefDomain + "defaultFileName"),
|
||||
};
|
||||
|
||||
return this.executeExport(data);
|
||||
},
|
||||
|
||||
// Public API
|
||||
|
||||
/**
|
||||
* Export all what is currently collected.
|
||||
*/
|
||||
triggerExport: function (data) {
|
||||
if (!data.fileName) {
|
||||
data.fileName = Services.prefs.getCharPref(prefDomain +
|
||||
"defaultFileName");
|
||||
}
|
||||
|
||||
return this.executeExport(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear currently collected data.
|
||||
*/
|
||||
clear: function () {
|
||||
this.resetCollector();
|
||||
},
|
||||
|
||||
// HAR Export
|
||||
|
||||
/**
|
||||
* Execute HAR export. This method fetches all data from the
|
||||
* Network panel (asynchronously) and saves it into a file.
|
||||
*/
|
||||
executeExport: function (data) {
|
||||
let items = this.collector.getItems();
|
||||
let form = this.toolbox.target.form;
|
||||
let title = form.title || form.url;
|
||||
|
||||
let options = {
|
||||
getString: this.getString.bind(this),
|
||||
view: this,
|
||||
items: items,
|
||||
};
|
||||
|
||||
options.defaultFileName = data.fileName;
|
||||
options.compress = data.compress;
|
||||
options.title = data.title || title;
|
||||
options.id = data.id;
|
||||
options.jsonp = data.jsonp;
|
||||
options.includeResponseBodies = data.includeResponseBodies;
|
||||
options.jsonpCallback = data.jsonpCallback;
|
||||
options.forceExport = data.forceExport;
|
||||
|
||||
trace.log("HarAutomation.executeExport; " + data.fileName, options);
|
||||
|
||||
return HarExporter.fetchHarData(options).then(jsonString => {
|
||||
// Save the HAR file if the file name is provided.
|
||||
if (jsonString && options.defaultFileName) {
|
||||
let file = getDefaultTargetFile(options);
|
||||
if (file) {
|
||||
HarUtils.saveToFile(file, jsonString, options.compress);
|
||||
}
|
||||
}
|
||||
|
||||
return jsonString;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches the full text of a string.
|
||||
*/
|
||||
getString: function (stringGrip) {
|
||||
return this.webConsoleClient.getString(stringGrip);
|
||||
},
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
function TabWatcher(toolbox, listener) {
|
||||
this.target = toolbox.target;
|
||||
this.listener = listener;
|
||||
|
||||
this.onTabNavigated = this.onTabNavigated.bind(this);
|
||||
}
|
||||
|
||||
TabWatcher.prototype = {
|
||||
// Connection
|
||||
|
||||
connect: function () {
|
||||
this.target.on("navigate", this.onTabNavigated);
|
||||
this.target.on("will-navigate", this.onTabNavigated);
|
||||
},
|
||||
|
||||
disconnect: function () {
|
||||
if (!this.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.target.off("navigate", this.onTabNavigated);
|
||||
this.target.off("will-navigate", this.onTabNavigated);
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
|
||||
/**
|
||||
* Called for each location change in the monitored tab.
|
||||
*
|
||||
* @param string aType
|
||||
* Packet type.
|
||||
* @param object aPacket
|
||||
* Packet received from the server.
|
||||
*/
|
||||
onTabNavigated: function (type, packet) {
|
||||
switch (type) {
|
||||
case "will-navigate": {
|
||||
this.listener.pageLoadBegin(packet);
|
||||
break;
|
||||
}
|
||||
case "navigate": {
|
||||
this.listener.pageLoadDone(packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Protocol Helpers
|
||||
|
||||
/**
|
||||
* Returns target file for exported HAR data.
|
||||
*/
|
||||
function getDefaultTargetFile(options) {
|
||||
let path = options.defaultLogDir ||
|
||||
Services.prefs.getCharPref("devtools.netmonitor.har.defaultLogDir");
|
||||
let folder = HarUtils.getLocalDirectory(path);
|
||||
let fileName = HarUtils.getHarFileName(options.defaultFileName,
|
||||
options.jsonp, options.compress);
|
||||
|
||||
folder.append(fileName);
|
||||
folder.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.HarAutomation = HarAutomation;
|
||||
491
devtools/client/netmonitor/har/har-builder.js
Normal file
491
devtools/client/netmonitor/har/har-builder.js
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { defer, all } = require("promise");
|
||||
const { LocalizationHelper } = require("devtools/shared/l10n");
|
||||
const Services = require("Services");
|
||||
const appInfo = Services.appinfo;
|
||||
const { CurlUtils } = require("devtools/client/shared/curl");
|
||||
const { getFormDataSections } = require("devtools/client/netmonitor/request-utils");
|
||||
|
||||
loader.lazyRequireGetter(this, "NetworkHelper", "devtools/shared/webconsole/network-helper");
|
||||
|
||||
loader.lazyGetter(this, "L10N", () => {
|
||||
return new LocalizationHelper("devtools/client/locales/har.properties");
|
||||
});
|
||||
|
||||
const HAR_VERSION = "1.1";
|
||||
|
||||
/**
|
||||
* This object is responsible for building HAR file. See HAR spec:
|
||||
* https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html
|
||||
* http://www.softwareishard.com/blog/har-12-spec/
|
||||
*
|
||||
* @param {Object} options configuration object
|
||||
*
|
||||
* The following options are supported:
|
||||
*
|
||||
* - items {Array}: List of Network requests to be exported. It is possible
|
||||
* to use directly: NetMonitorView.RequestsMenu.items
|
||||
*
|
||||
* - id {String}: ID of the exported page.
|
||||
*
|
||||
* - title {String}: Title of the exported page.
|
||||
*
|
||||
* - includeResponseBodies {Boolean}: Set to true to include HTTP response
|
||||
* bodies in the result data structure.
|
||||
*/
|
||||
var HarBuilder = function (options) {
|
||||
this._options = options;
|
||||
this._pageMap = [];
|
||||
};
|
||||
|
||||
HarBuilder.prototype = {
|
||||
// Public API
|
||||
|
||||
/**
|
||||
* This is the main method used to build the entire result HAR data.
|
||||
* The process is asynchronous since it can involve additional RDP
|
||||
* communication (e.g. resolving long strings).
|
||||
*
|
||||
* @returns {Promise} A promise that resolves to the HAR object when
|
||||
* the entire build process is done.
|
||||
*/
|
||||
build: function () {
|
||||
this.promises = [];
|
||||
|
||||
// Build basic structure for data.
|
||||
let log = this.buildLog();
|
||||
|
||||
// Build entries.
|
||||
let items = this._options.items;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let file = items[i].attachment;
|
||||
log.entries.push(this.buildEntry(log, file));
|
||||
}
|
||||
|
||||
// Some data needs to be fetched from the backend during the
|
||||
// build process, so wait till all is done.
|
||||
let { resolve, promise } = defer();
|
||||
all(this.promises).then(results => resolve({ log: log }));
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
// Helpers
|
||||
|
||||
buildLog: function () {
|
||||
return {
|
||||
version: HAR_VERSION,
|
||||
creator: {
|
||||
name: appInfo.name,
|
||||
version: appInfo.version
|
||||
},
|
||||
browser: {
|
||||
name: appInfo.name,
|
||||
version: appInfo.version
|
||||
},
|
||||
pages: [],
|
||||
entries: [],
|
||||
};
|
||||
},
|
||||
|
||||
buildPage: function (file) {
|
||||
let page = {};
|
||||
|
||||
// Page start time is set when the first request is processed
|
||||
// (see buildEntry)
|
||||
page.startedDateTime = 0;
|
||||
page.id = "page_" + this._options.id;
|
||||
page.title = this._options.title;
|
||||
|
||||
return page;
|
||||
},
|
||||
|
||||
getPage: function (log, file) {
|
||||
let id = this._options.id;
|
||||
let page = this._pageMap[id];
|
||||
if (page) {
|
||||
return page;
|
||||
}
|
||||
|
||||
this._pageMap[id] = page = this.buildPage(file);
|
||||
log.pages.push(page);
|
||||
|
||||
return page;
|
||||
},
|
||||
|
||||
buildEntry: function (log, file) {
|
||||
let page = this.getPage(log, file);
|
||||
|
||||
let entry = {};
|
||||
entry.pageref = page.id;
|
||||
entry.startedDateTime = dateToJSON(new Date(file.startedMillis));
|
||||
entry.time = file.endedMillis - file.startedMillis;
|
||||
|
||||
entry.request = this.buildRequest(file);
|
||||
entry.response = this.buildResponse(file);
|
||||
entry.cache = this.buildCache(file);
|
||||
entry.timings = file.eventTimings ? file.eventTimings.timings : {};
|
||||
|
||||
if (file.remoteAddress) {
|
||||
entry.serverIPAddress = file.remoteAddress;
|
||||
}
|
||||
|
||||
if (file.remotePort) {
|
||||
entry.connection = file.remotePort + "";
|
||||
}
|
||||
|
||||
// Compute page load start time according to the first request start time.
|
||||
if (!page.startedDateTime) {
|
||||
page.startedDateTime = entry.startedDateTime;
|
||||
page.pageTimings = this.buildPageTimings(page, file);
|
||||
}
|
||||
|
||||
return entry;
|
||||
},
|
||||
|
||||
buildPageTimings: function (page, file) {
|
||||
// Event timing info isn't available
|
||||
let timings = {
|
||||
onContentLoad: -1,
|
||||
onLoad: -1
|
||||
};
|
||||
|
||||
return timings;
|
||||
},
|
||||
|
||||
buildRequest: function (file) {
|
||||
let request = {
|
||||
bodySize: 0
|
||||
};
|
||||
|
||||
request.method = file.method;
|
||||
request.url = file.url;
|
||||
request.httpVersion = file.httpVersion || "";
|
||||
|
||||
request.headers = this.buildHeaders(file.requestHeaders);
|
||||
request.headers = this.appendHeadersPostData(request.headers, file);
|
||||
request.cookies = this.buildCookies(file.requestCookies);
|
||||
|
||||
request.queryString = NetworkHelper.parseQueryString(
|
||||
NetworkHelper.nsIURL(file.url).query) || [];
|
||||
|
||||
request.postData = this.buildPostData(file);
|
||||
|
||||
request.headersSize = file.requestHeaders.headersSize;
|
||||
|
||||
// Set request body size, but make sure the body is fetched
|
||||
// from the backend.
|
||||
if (file.requestPostData) {
|
||||
this.fetchData(file.requestPostData.postData.text).then(value => {
|
||||
request.bodySize = value.length;
|
||||
});
|
||||
}
|
||||
|
||||
return request;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch all header values from the backend (if necessary) and
|
||||
* build the result HAR structure.
|
||||
*
|
||||
* @param {Object} input Request or response header object.
|
||||
*/
|
||||
buildHeaders: function (input) {
|
||||
if (!input) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.buildNameValuePairs(input.headers);
|
||||
},
|
||||
|
||||
appendHeadersPostData: function (input = [], file) {
|
||||
if (!file.requestPostData) {
|
||||
return input;
|
||||
}
|
||||
|
||||
this.fetchData(file.requestPostData.postData.text).then(value => {
|
||||
let multipartHeaders = CurlUtils.getHeadersFromMultipartText(value);
|
||||
for (let header of multipartHeaders) {
|
||||
input.push(header);
|
||||
}
|
||||
});
|
||||
|
||||
return input;
|
||||
},
|
||||
|
||||
buildCookies: function (input) {
|
||||
if (!input) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.buildNameValuePairs(input.cookies);
|
||||
},
|
||||
|
||||
buildNameValuePairs: function (entries) {
|
||||
let result = [];
|
||||
|
||||
// HAR requires headers array to be presented, so always
|
||||
// return at least an empty array.
|
||||
if (!entries) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Make sure header values are fully fetched from the server.
|
||||
entries.forEach(entry => {
|
||||
this.fetchData(entry.value).then(value => {
|
||||
result.push({
|
||||
name: entry.name,
|
||||
value: value
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
buildPostData: function (file) {
|
||||
let postData = {
|
||||
mimeType: findValue(file.requestHeaders.headers, "content-type"),
|
||||
params: [],
|
||||
text: ""
|
||||
};
|
||||
|
||||
if (!file.requestPostData) {
|
||||
return postData;
|
||||
}
|
||||
|
||||
if (file.requestPostData.postDataDiscarded) {
|
||||
postData.comment = L10N.getStr("har.requestBodyNotIncluded");
|
||||
return postData;
|
||||
}
|
||||
|
||||
// Load request body from the backend.
|
||||
this.fetchData(file.requestPostData.postData.text).then(postDataText => {
|
||||
postData.text = postDataText;
|
||||
|
||||
// If we are dealing with URL encoded body, parse parameters.
|
||||
let { headers } = file.requestHeaders;
|
||||
if (CurlUtils.isUrlEncodedRequest({ headers, postDataText })) {
|
||||
postData.mimeType = "application/x-www-form-urlencoded";
|
||||
|
||||
// Extract form parameters and produce nice HAR array.
|
||||
getFormDataSections(
|
||||
file.requestHeaders,
|
||||
file.requestHeadersFromUploadStream,
|
||||
file.requestPostData,
|
||||
this._options.getString
|
||||
).then(formDataSections => {
|
||||
formDataSections.forEach(section => {
|
||||
let paramsArray = NetworkHelper.parseQueryString(section);
|
||||
if (paramsArray) {
|
||||
postData.params = [...postData.params, ...paramsArray];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return postData;
|
||||
},
|
||||
|
||||
buildResponse: function (file) {
|
||||
let response = {
|
||||
status: 0
|
||||
};
|
||||
|
||||
// Arbitrary value if it's aborted to make sure status has a number
|
||||
if (file.status) {
|
||||
response.status = parseInt(file.status, 10);
|
||||
}
|
||||
|
||||
let responseHeaders = file.responseHeaders;
|
||||
|
||||
response.statusText = file.statusText || "";
|
||||
response.httpVersion = file.httpVersion || "";
|
||||
|
||||
response.headers = this.buildHeaders(responseHeaders);
|
||||
response.cookies = this.buildCookies(file.responseCookies);
|
||||
response.content = this.buildContent(file);
|
||||
|
||||
let headers = responseHeaders ? responseHeaders.headers : null;
|
||||
let headersSize = responseHeaders ? responseHeaders.headersSize : -1;
|
||||
|
||||
response.redirectURL = findValue(headers, "Location");
|
||||
response.headersSize = headersSize;
|
||||
|
||||
// 'bodySize' is size of the received response body in bytes.
|
||||
// Set to zero in case of responses coming from the cache (304).
|
||||
// Set to -1 if the info is not available.
|
||||
if (typeof file.transferredSize != "number") {
|
||||
response.bodySize = (response.status == 304) ? 0 : -1;
|
||||
} else {
|
||||
response.bodySize = file.transferredSize;
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
buildContent: function (file) {
|
||||
let content = {
|
||||
mimeType: file.mimeType,
|
||||
size: -1
|
||||
};
|
||||
|
||||
let responseContent = file.responseContent;
|
||||
if (responseContent && responseContent.content) {
|
||||
content.size = responseContent.content.size;
|
||||
content.encoding = responseContent.content.encoding;
|
||||
}
|
||||
|
||||
let includeBodies = this._options.includeResponseBodies;
|
||||
let contentDiscarded = responseContent ?
|
||||
responseContent.contentDiscarded : false;
|
||||
|
||||
// The comment is appended only if the response content
|
||||
// is explicitly discarded.
|
||||
if (!includeBodies || contentDiscarded) {
|
||||
content.comment = L10N.getStr("har.responseBodyNotIncluded");
|
||||
return content;
|
||||
}
|
||||
|
||||
if (responseContent) {
|
||||
let text = responseContent.content.text;
|
||||
this.fetchData(text).then(value => {
|
||||
content.text = value;
|
||||
});
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
buildCache: function (file) {
|
||||
let cache = {};
|
||||
|
||||
if (!file.fromCache) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
// There is no such info yet in the Net panel.
|
||||
// cache.beforeRequest = {};
|
||||
|
||||
if (file.cacheEntry) {
|
||||
cache.afterRequest = this.buildCacheEntry(file.cacheEntry);
|
||||
} else {
|
||||
cache.afterRequest = null;
|
||||
}
|
||||
|
||||
return cache;
|
||||
},
|
||||
|
||||
buildCacheEntry: function (cacheEntry) {
|
||||
let cache = {};
|
||||
|
||||
cache.expires = findValue(cacheEntry, "Expires");
|
||||
cache.lastAccess = findValue(cacheEntry, "Last Fetched");
|
||||
cache.eTag = "";
|
||||
cache.hitCount = findValue(cacheEntry, "Fetch Count");
|
||||
|
||||
return cache;
|
||||
},
|
||||
|
||||
getBlockingEndTime: function (file) {
|
||||
if (file.resolveStarted && file.connectStarted) {
|
||||
return file.resolvingTime;
|
||||
}
|
||||
|
||||
if (file.connectStarted) {
|
||||
return file.connectingTime;
|
||||
}
|
||||
|
||||
if (file.sendStarted) {
|
||||
return file.sendingTime;
|
||||
}
|
||||
|
||||
return (file.sendingTime > file.startTime) ?
|
||||
file.sendingTime : file.waitingForTime;
|
||||
},
|
||||
|
||||
// RDP Helpers
|
||||
|
||||
fetchData: function (string) {
|
||||
let promise = this._options.getString(string).then(value => {
|
||||
return value;
|
||||
});
|
||||
|
||||
// Building HAR is asynchronous and not done till all
|
||||
// collected promises are resolved.
|
||||
this.promises.push(promise);
|
||||
|
||||
return promise;
|
||||
}
|
||||
};
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Find specified value within an array of name-value pairs
|
||||
* (used for headers, cookies and cache entries)
|
||||
*/
|
||||
function findValue(arr, name) {
|
||||
if (!arr) {
|
||||
return "";
|
||||
}
|
||||
|
||||
name = name.toLowerCase();
|
||||
let result = arr.find(entry => entry.name.toLowerCase() == name);
|
||||
return result ? result.value : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HAR representation of a date.
|
||||
* (YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.45+01:00)
|
||||
* See also HAR Schema: http://janodvarko.cz/har/viewer/
|
||||
*
|
||||
* Note: it would be great if we could utilize Date.toJSON(), but
|
||||
* it doesn't return proper time zone offset.
|
||||
*
|
||||
* An example:
|
||||
* This helper returns: 2015-05-29T16:10:30.424+02:00
|
||||
* Date.toJSON() returns: 2015-05-29T14:10:30.424Z
|
||||
*
|
||||
* @param date {Date} The date object we want to convert.
|
||||
*/
|
||||
function dateToJSON(date) {
|
||||
function f(n, c) {
|
||||
if (!c) {
|
||||
c = 2;
|
||||
}
|
||||
let s = new String(n);
|
||||
while (s.length < c) {
|
||||
s = "0" + s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
let result = date.getFullYear() + "-" +
|
||||
f(date.getMonth() + 1) + "-" +
|
||||
f(date.getDate()) + "T" +
|
||||
f(date.getHours()) + ":" +
|
||||
f(date.getMinutes()) + ":" +
|
||||
f(date.getSeconds()) + "." +
|
||||
f(date.getMilliseconds(), 3);
|
||||
|
||||
let offset = date.getTimezoneOffset();
|
||||
let positive = offset > 0;
|
||||
|
||||
// Convert to positive number before using Math.floor (see issue 5512)
|
||||
offset = Math.abs(offset);
|
||||
let offsetHours = Math.floor(offset / 60);
|
||||
let offsetMinutes = Math.floor(offset % 60);
|
||||
let prettyOffset = (positive > 0 ? "-" : "+") + f(offsetHours) +
|
||||
":" + f(offsetMinutes);
|
||||
|
||||
return result + prettyOffset;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.HarBuilder = HarBuilder;
|
||||
462
devtools/client/netmonitor/har/har-collector.js
Normal file
462
devtools/client/netmonitor/har/har-collector.js
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { defer, all } = require("promise");
|
||||
const { makeInfallible } = require("devtools/shared/DevToolsUtils");
|
||||
const Services = require("Services");
|
||||
|
||||
// Helper tracer. Should be generic sharable by other modules (bug 1171927)
|
||||
const trace = {
|
||||
log: function (...args) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This object is responsible for collecting data related to all
|
||||
* HTTP requests executed by the page (including inner iframes).
|
||||
*/
|
||||
function HarCollector(options) {
|
||||
this.webConsoleClient = options.webConsoleClient;
|
||||
this.debuggerClient = options.debuggerClient;
|
||||
|
||||
this.onNetworkEvent = this.onNetworkEvent.bind(this);
|
||||
this.onNetworkEventUpdate = this.onNetworkEventUpdate.bind(this);
|
||||
this.onRequestHeaders = this.onRequestHeaders.bind(this);
|
||||
this.onRequestCookies = this.onRequestCookies.bind(this);
|
||||
this.onRequestPostData = this.onRequestPostData.bind(this);
|
||||
this.onResponseHeaders = this.onResponseHeaders.bind(this);
|
||||
this.onResponseCookies = this.onResponseCookies.bind(this);
|
||||
this.onResponseContent = this.onResponseContent.bind(this);
|
||||
this.onEventTimings = this.onEventTimings.bind(this);
|
||||
|
||||
this.onPageLoadTimeout = this.onPageLoadTimeout.bind(this);
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
HarCollector.prototype = {
|
||||
// Connection
|
||||
|
||||
start: function () {
|
||||
this.debuggerClient.addListener("networkEvent", this.onNetworkEvent);
|
||||
this.debuggerClient.addListener("networkEventUpdate",
|
||||
this.onNetworkEventUpdate);
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
this.debuggerClient.removeListener("networkEvent", this.onNetworkEvent);
|
||||
this.debuggerClient.removeListener("networkEventUpdate",
|
||||
this.onNetworkEventUpdate);
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
// Any pending requests events will be ignored (they turn
|
||||
// into zombies, since not present in the files array).
|
||||
this.files = new Map();
|
||||
this.items = [];
|
||||
this.firstRequestStart = -1;
|
||||
this.lastRequestStart = -1;
|
||||
this.requests = [];
|
||||
},
|
||||
|
||||
waitForHarLoad: function () {
|
||||
// There should be yet another timeout e.g.:
|
||||
// 'devtools.netmonitor.har.pageLoadTimeout'
|
||||
// that should force export even if page isn't fully loaded.
|
||||
let deferred = defer();
|
||||
this.waitForResponses().then(() => {
|
||||
trace.log("HarCollector.waitForHarLoad; DONE HAR loaded!");
|
||||
deferred.resolve(this);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
waitForResponses: function () {
|
||||
trace.log("HarCollector.waitForResponses; " + this.requests.length);
|
||||
|
||||
// All requests for additional data must be received to have complete
|
||||
// HTTP info to generate the result HAR file. So, wait for all current
|
||||
// promises. Note that new promises (requests) can be generated during the
|
||||
// process of HTTP data collection.
|
||||
return waitForAll(this.requests).then(() => {
|
||||
// All responses are received from the backend now. We yet need to
|
||||
// wait for a little while to see if a new request appears. If yes,
|
||||
// lets's start gathering HTTP data again. If no, we can declare
|
||||
// the page loaded.
|
||||
// If some new requests appears in the meantime the promise will
|
||||
// be rejected and we need to wait for responses all over again.
|
||||
return this.waitForTimeout().then(() => {
|
||||
// Page loaded!
|
||||
}, () => {
|
||||
trace.log("HarCollector.waitForResponses; NEW requests " +
|
||||
"appeared during page timeout!");
|
||||
|
||||
// New requests executed, let's wait again.
|
||||
return this.waitForResponses();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Page Loaded Timeout
|
||||
|
||||
/**
|
||||
* The page is loaded when there are no new requests within given period
|
||||
* of time. The time is set in preferences:
|
||||
* 'devtools.netmonitor.har.pageLoadedTimeout'
|
||||
*/
|
||||
waitForTimeout: function () {
|
||||
// The auto-export is not done if the timeout is set to zero (or less).
|
||||
// This is useful in cases where the export is done manually through
|
||||
// API exposed to the content.
|
||||
let timeout = Services.prefs.getIntPref(
|
||||
"devtools.netmonitor.har.pageLoadedTimeout");
|
||||
|
||||
trace.log("HarCollector.waitForTimeout; " + timeout);
|
||||
|
||||
this.pageLoadDeferred = defer();
|
||||
|
||||
if (timeout <= 0) {
|
||||
this.pageLoadDeferred.resolve();
|
||||
return this.pageLoadDeferred.promise;
|
||||
}
|
||||
|
||||
this.pageLoadTimeout = setTimeout(this.onPageLoadTimeout, timeout);
|
||||
|
||||
return this.pageLoadDeferred.promise;
|
||||
},
|
||||
|
||||
onPageLoadTimeout: function () {
|
||||
trace.log("HarCollector.onPageLoadTimeout;");
|
||||
|
||||
// Ha, page has been loaded. Resolve the final timeout promise.
|
||||
this.pageLoadDeferred.resolve();
|
||||
},
|
||||
|
||||
resetPageLoadTimeout: function () {
|
||||
// Remove the current timeout.
|
||||
if (this.pageLoadTimeout) {
|
||||
trace.log("HarCollector.resetPageLoadTimeout;");
|
||||
|
||||
clearTimeout(this.pageLoadTimeout);
|
||||
this.pageLoadTimeout = null;
|
||||
}
|
||||
|
||||
// Reject the current page load promise
|
||||
if (this.pageLoadDeferred) {
|
||||
this.pageLoadDeferred.reject();
|
||||
this.pageLoadDeferred = null;
|
||||
}
|
||||
},
|
||||
|
||||
// Collected Data
|
||||
|
||||
getFile: function (actorId) {
|
||||
return this.files.get(actorId);
|
||||
},
|
||||
|
||||
getItems: function () {
|
||||
return this.items;
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onNetworkEvent: function (type, packet) {
|
||||
// Skip events from different console actors.
|
||||
if (packet.from != this.webConsoleClient.actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
trace.log("HarCollector.onNetworkEvent; " + type, packet);
|
||||
|
||||
let { actor, startedDateTime, method, url, isXHR } = packet.eventActor;
|
||||
let startTime = Date.parse(startedDateTime);
|
||||
|
||||
if (this.firstRequestStart == -1) {
|
||||
this.firstRequestStart = startTime;
|
||||
}
|
||||
|
||||
if (this.lastRequestEnd < startTime) {
|
||||
this.lastRequestEnd = startTime;
|
||||
}
|
||||
|
||||
let file = this.getFile(actor);
|
||||
if (file) {
|
||||
console.error("HarCollector.onNetworkEvent; ERROR " +
|
||||
"existing file conflict!");
|
||||
return;
|
||||
}
|
||||
|
||||
file = {
|
||||
startedDeltaMillis: startTime - this.firstRequestStart,
|
||||
startedMillis: startTime,
|
||||
method: method,
|
||||
url: url,
|
||||
isXHR: isXHR
|
||||
};
|
||||
|
||||
this.files.set(actor, file);
|
||||
|
||||
// Mimic the Net panel data structure
|
||||
this.items.push({
|
||||
attachment: file
|
||||
});
|
||||
},
|
||||
|
||||
onNetworkEventUpdate: function (type, packet) {
|
||||
let actor = packet.from;
|
||||
|
||||
// Skip events from unknown actors (not in the list).
|
||||
// It can happen when there are zombie requests received after
|
||||
// the target is closed or multiple tabs are attached through
|
||||
// one connection (one DebuggerClient object).
|
||||
let file = this.getFile(packet.from);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
trace.log("HarCollector.onNetworkEventUpdate; " +
|
||||
packet.updateType, packet);
|
||||
|
||||
let includeResponseBodies = Services.prefs.getBoolPref(
|
||||
"devtools.netmonitor.har.includeResponseBodies");
|
||||
|
||||
let request;
|
||||
switch (packet.updateType) {
|
||||
case "requestHeaders":
|
||||
request = this.getData(actor, "getRequestHeaders",
|
||||
this.onRequestHeaders);
|
||||
break;
|
||||
case "requestCookies":
|
||||
request = this.getData(actor, "getRequestCookies",
|
||||
this.onRequestCookies);
|
||||
break;
|
||||
case "requestPostData":
|
||||
request = this.getData(actor, "getRequestPostData",
|
||||
this.onRequestPostData);
|
||||
break;
|
||||
case "responseHeaders":
|
||||
request = this.getData(actor, "getResponseHeaders",
|
||||
this.onResponseHeaders);
|
||||
break;
|
||||
case "responseCookies":
|
||||
request = this.getData(actor, "getResponseCookies",
|
||||
this.onResponseCookies);
|
||||
break;
|
||||
case "responseStart":
|
||||
file.httpVersion = packet.response.httpVersion;
|
||||
file.status = packet.response.status;
|
||||
file.statusText = packet.response.statusText;
|
||||
break;
|
||||
case "responseContent":
|
||||
file.contentSize = packet.contentSize;
|
||||
file.mimeType = packet.mimeType;
|
||||
file.transferredSize = packet.transferredSize;
|
||||
|
||||
if (includeResponseBodies) {
|
||||
request = this.getData(actor, "getResponseContent",
|
||||
this.onResponseContent);
|
||||
}
|
||||
break;
|
||||
case "eventTimings":
|
||||
request = this.getData(actor, "getEventTimings",
|
||||
this.onEventTimings);
|
||||
break;
|
||||
}
|
||||
|
||||
if (request) {
|
||||
this.requests.push(request);
|
||||
}
|
||||
|
||||
this.resetPageLoadTimeout();
|
||||
},
|
||||
|
||||
getData: function (actor, method, callback) {
|
||||
let deferred = defer();
|
||||
|
||||
if (!this.webConsoleClient[method]) {
|
||||
console.error("HarCollector.getData; ERROR " +
|
||||
"Unknown method!");
|
||||
return deferred.resolve();
|
||||
}
|
||||
|
||||
let file = this.getFile(actor);
|
||||
|
||||
trace.log("HarCollector.getData; REQUEST " + method +
|
||||
", " + file.url, file);
|
||||
|
||||
this.webConsoleClient[method](actor, response => {
|
||||
trace.log("HarCollector.getData; RESPONSE " + method +
|
||||
", " + file.url, response);
|
||||
|
||||
callback(response);
|
||||
deferred.resolve(response);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestHeaders" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
onRequestHeaders: function (response) {
|
||||
let file = this.getFile(response.from);
|
||||
file.requestHeaders = response;
|
||||
|
||||
this.getLongHeaders(response.headers);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestCookies" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
onRequestCookies: function (response) {
|
||||
let file = this.getFile(response.from);
|
||||
file.requestCookies = response;
|
||||
|
||||
this.getLongHeaders(response.cookies);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestPostData" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
onRequestPostData: function (response) {
|
||||
trace.log("HarCollector.onRequestPostData;", response);
|
||||
|
||||
let file = this.getFile(response.from);
|
||||
file.requestPostData = response;
|
||||
|
||||
// Resolve long string
|
||||
let text = response.postData.text;
|
||||
if (typeof text == "object") {
|
||||
this.getString(text).then(value => {
|
||||
response.postData.text = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseHeaders" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
onResponseHeaders: function (response) {
|
||||
let file = this.getFile(response.from);
|
||||
file.responseHeaders = response;
|
||||
|
||||
this.getLongHeaders(response.headers);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseCookies" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
onResponseCookies: function (response) {
|
||||
let file = this.getFile(response.from);
|
||||
file.responseCookies = response;
|
||||
|
||||
this.getLongHeaders(response.cookies);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseContent" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
onResponseContent: function (response) {
|
||||
let file = this.getFile(response.from);
|
||||
file.responseContent = response;
|
||||
|
||||
// Resolve long string
|
||||
let text = response.content.text;
|
||||
if (typeof text == "object") {
|
||||
this.getString(text).then(value => {
|
||||
response.content.text = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "eventTimings" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
onEventTimings: function (response) {
|
||||
let file = this.getFile(response.from);
|
||||
file.eventTimings = response;
|
||||
|
||||
let totalTime = response.totalTime;
|
||||
file.totalTime = totalTime;
|
||||
file.endedMillis = file.startedMillis + totalTime;
|
||||
},
|
||||
|
||||
// Helpers
|
||||
|
||||
getLongHeaders: makeInfallible(function (headers) {
|
||||
for (let header of headers) {
|
||||
if (typeof header.value == "object") {
|
||||
this.getString(header.value).then(value => {
|
||||
header.value = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Fetches the full text of a string.
|
||||
*
|
||||
* @param object | string stringGrip
|
||||
* The long string grip containing the corresponding actor.
|
||||
* If you pass in a plain string (by accident or because you're lazy),
|
||||
* then a promise of the same string is simply returned.
|
||||
* @return object Promise
|
||||
* A promise that is resolved when the full string contents
|
||||
* are available, or rejected if something goes wrong.
|
||||
*/
|
||||
getString: function (stringGrip) {
|
||||
let promise = this.webConsoleClient.getString(stringGrip);
|
||||
this.requests.push(promise);
|
||||
return promise;
|
||||
}
|
||||
};
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Helper function that allows to wait for array of promises. It is
|
||||
* possible to dynamically add new promises in the provided array.
|
||||
* The function will wait even for the newly added promises.
|
||||
* (this isn't possible with the default Promise.all);
|
||||
*/
|
||||
function waitForAll(promises) {
|
||||
// Remove all from the original array and get clone of it.
|
||||
let clone = promises.splice(0, promises.length);
|
||||
|
||||
// Wait for all promises in the given array.
|
||||
return all(clone).then(() => {
|
||||
// If there are new promises (in the original array)
|
||||
// to wait for - chain them!
|
||||
if (promises.length) {
|
||||
return waitForAll(promises);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.HarCollector = HarCollector;
|
||||
187
devtools/client/netmonitor/har/har-exporter.js
Normal file
187
devtools/client/netmonitor/har/har-exporter.js
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
/* eslint-disable mozilla/reject-some-requires */
|
||||
const { Cc, Ci } = require("chrome");
|
||||
const Services = require("Services");
|
||||
/* eslint-disable mozilla/reject-some-requires */
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const { resolve } = require("promise");
|
||||
const { HarUtils } = require("./har-utils.js");
|
||||
const { HarBuilder } = require("./har-builder.js");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function () {
|
||||
return Cc["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(Ci.nsIClipboardHelper);
|
||||
});
|
||||
|
||||
var uid = 1;
|
||||
|
||||
// Helper tracer. Should be generic sharable by other modules (bug 1171927)
|
||||
const trace = {
|
||||
log: function (...args) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This object represents the main public API designed to access
|
||||
* Network export logic. Clients, such as the Network panel itself,
|
||||
* should use this API to export collected HTTP data from the panel.
|
||||
*/
|
||||
const HarExporter = {
|
||||
// Public API
|
||||
|
||||
/**
|
||||
* Save collected HTTP data from the Network panel into HAR file.
|
||||
*
|
||||
* @param Object options
|
||||
* Configuration object
|
||||
*
|
||||
* The following options are supported:
|
||||
*
|
||||
* - includeResponseBodies {Boolean}: If set to true, HTTP response bodies
|
||||
* are also included in the HAR file (can produce significantly bigger
|
||||
* amount of data).
|
||||
*
|
||||
* - items {Array}: List of Network requests to be exported. It is possible
|
||||
* to use directly: NetMonitorView.RequestsMenu.items
|
||||
*
|
||||
* - jsonp {Boolean}: If set to true the export format is HARP (support
|
||||
* for JSONP syntax).
|
||||
*
|
||||
* - jsonpCallback {String}: Default name of JSONP callback (used for
|
||||
* HARP format).
|
||||
*
|
||||
* - compress {Boolean}: If set to true the final HAR file is zipped.
|
||||
* This represents great disk-space optimization.
|
||||
*
|
||||
* - defaultFileName {String}: Default name of the target HAR file.
|
||||
* The default file name supports formatters, see:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleFormat
|
||||
*
|
||||
* - defaultLogDir {String}: Default log directory for automated logs.
|
||||
*
|
||||
* - id {String}: ID of the page (used in the HAR file).
|
||||
*
|
||||
* - title {String}: Title of the page (used in the HAR file).
|
||||
*
|
||||
* - forceExport {Boolean}: The result HAR file is created even if
|
||||
* there are no HTTP entries.
|
||||
*/
|
||||
save: function (options) {
|
||||
// Set default options related to save operation.
|
||||
options.defaultFileName = Services.prefs.getCharPref(
|
||||
"devtools.netmonitor.har.defaultFileName");
|
||||
options.compress = Services.prefs.getBoolPref(
|
||||
"devtools.netmonitor.har.compress");
|
||||
|
||||
// Get target file for exported data. Bail out, if the user
|
||||
// presses cancel.
|
||||
let file = HarUtils.getTargetFile(options.defaultFileName,
|
||||
options.jsonp, options.compress);
|
||||
|
||||
if (!file) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
trace.log("HarExporter.save; " + options.defaultFileName, options);
|
||||
|
||||
return this.fetchHarData(options).then(jsonString => {
|
||||
if (!HarUtils.saveToFile(file, jsonString, options.compress)) {
|
||||
let msg = "Failed to save HAR file at: " + options.defaultFileName;
|
||||
console.error(msg);
|
||||
}
|
||||
return jsonString;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy HAR string into the clipboard.
|
||||
*
|
||||
* @param Object options
|
||||
* Configuration object, see save() for detailed description.
|
||||
*/
|
||||
copy: function (options) {
|
||||
return this.fetchHarData(options).then(jsonString => {
|
||||
clipboardHelper.copyString(jsonString);
|
||||
return jsonString;
|
||||
});
|
||||
},
|
||||
|
||||
// Helpers
|
||||
|
||||
fetchHarData: function (options) {
|
||||
// Generate page ID
|
||||
options.id = options.id || uid++;
|
||||
|
||||
// Set default generic HAR export options.
|
||||
options.jsonp = options.jsonp ||
|
||||
Services.prefs.getBoolPref("devtools.netmonitor.har.jsonp");
|
||||
options.includeResponseBodies = options.includeResponseBodies ||
|
||||
Services.prefs.getBoolPref(
|
||||
"devtools.netmonitor.har.includeResponseBodies");
|
||||
options.jsonpCallback = options.jsonpCallback ||
|
||||
Services.prefs.getCharPref("devtools.netmonitor.har.jsonpCallback");
|
||||
options.forceExport = options.forceExport ||
|
||||
Services.prefs.getBoolPref("devtools.netmonitor.har.forceExport");
|
||||
|
||||
// Build HAR object.
|
||||
return this.buildHarData(options).then(har => {
|
||||
// Do not export an empty HAR file, unless the user
|
||||
// explicitly says so (using the forceExport option).
|
||||
if (!har.log.entries.length && !options.forceExport) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
let jsonString = this.stringify(har);
|
||||
if (!jsonString) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// If JSONP is wanted, wrap the string in a function call
|
||||
if (options.jsonp) {
|
||||
// This callback name is also used in HAR Viewer by default.
|
||||
// http://www.softwareishard.com/har/viewer/
|
||||
let callbackName = options.jsonpCallback || "onInputData";
|
||||
jsonString = callbackName + "(" + jsonString + ");";
|
||||
}
|
||||
|
||||
return jsonString;
|
||||
}).then(null, function onError(err) {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Build HAR data object. This object contains all HTTP data
|
||||
* collected by the Network panel. The process is asynchronous
|
||||
* since it can involve additional RDP communication (e.g. resolving
|
||||
* long strings).
|
||||
*/
|
||||
buildHarData: function (options) {
|
||||
// Build HAR object from collected data.
|
||||
let builder = new HarBuilder(options);
|
||||
return builder.build();
|
||||
},
|
||||
|
||||
/**
|
||||
* Build JSON string from the HAR data object.
|
||||
*/
|
||||
stringify: function (har) {
|
||||
if (!har) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(har, null, " ");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
exports.HarExporter = HarExporter;
|
||||
189
devtools/client/netmonitor/har/har-utils.js
Normal file
189
devtools/client/netmonitor/har/har-utils.js
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
/* eslint-disable mozilla/reject-some-requires */
|
||||
const { Ci, Cc, CC } = require("chrome");
|
||||
/* eslint-disable mozilla/reject-some-requires */
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "dirService", function () {
|
||||
return Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "ZipWriter", function () {
|
||||
return CC("@mozilla.org/zipwriter;1", "nsIZipWriter");
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "LocalFile", function () {
|
||||
return new CC("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath");
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "getMostRecentBrowserWindow", function () {
|
||||
return require("sdk/window/utils").getMostRecentBrowserWindow;
|
||||
});
|
||||
|
||||
const nsIFilePicker = Ci.nsIFilePicker;
|
||||
|
||||
const OPEN_FLAGS = {
|
||||
RDONLY: parseInt("0x01", 16),
|
||||
WRONLY: parseInt("0x02", 16),
|
||||
CREATE_FILE: parseInt("0x08", 16),
|
||||
APPEND: parseInt("0x10", 16),
|
||||
TRUNCATE: parseInt("0x20", 16),
|
||||
EXCL: parseInt("0x80", 16)
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper API for HAR export features.
|
||||
*/
|
||||
var HarUtils = {
|
||||
/**
|
||||
* Open File Save As dialog and let the user pick the proper file
|
||||
* location for generated HAR log.
|
||||
*/
|
||||
getTargetFile: function (fileName, jsonp, compress) {
|
||||
let browser = getMostRecentBrowserWindow();
|
||||
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
fp.init(browser, null, nsIFilePicker.modeSave);
|
||||
fp.appendFilter(
|
||||
"HTTP Archive Files", "*.har; *.harp; *.json; *.jsonp; *.zip");
|
||||
fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText);
|
||||
fp.filterIndex = 1;
|
||||
|
||||
fp.defaultString = this.getHarFileName(fileName, jsonp, compress);
|
||||
|
||||
let rv = fp.show();
|
||||
if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) {
|
||||
return fp.file;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getHarFileName: function (defaultFileName, jsonp, compress) {
|
||||
let extension = jsonp ? ".harp" : ".har";
|
||||
|
||||
// Read more about toLocaleFormat & format string.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleFormat
|
||||
let now = new Date();
|
||||
let name = now.toLocaleFormat(defaultFileName);
|
||||
name = name.replace(/\:/gm, "-", "");
|
||||
name = name.replace(/\//gm, "_", "");
|
||||
|
||||
let fileName = name + extension;
|
||||
|
||||
// Default file extension is zip if compressing is on.
|
||||
if (compress) {
|
||||
fileName += ".zip";
|
||||
}
|
||||
|
||||
return fileName;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save HAR string into a given file. The file might be compressed
|
||||
* if specified in the options.
|
||||
*
|
||||
* @param {File} file Target file where the HAR string (JSON)
|
||||
* should be stored.
|
||||
* @param {String} jsonString HAR data (JSON or JSONP)
|
||||
* @param {Boolean} compress The result file is zipped if set to true.
|
||||
*/
|
||||
saveToFile: function (file, jsonString, compress) {
|
||||
let openFlags = OPEN_FLAGS.WRONLY | OPEN_FLAGS.CREATE_FILE |
|
||||
OPEN_FLAGS.TRUNCATE;
|
||||
|
||||
try {
|
||||
let foStream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
|
||||
let permFlags = parseInt("0666", 8);
|
||||
foStream.init(file, openFlags, permFlags, 0);
|
||||
|
||||
let convertor = Cc["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Ci.nsIConverterOutputStream);
|
||||
convertor.init(foStream, "UTF-8", 0, 0);
|
||||
|
||||
// The entire jsonString can be huge so, write the data in chunks.
|
||||
let chunkLength = 1024 * 1024;
|
||||
for (let i = 0; i <= jsonString.length; i++) {
|
||||
let data = jsonString.substr(i, chunkLength + 1);
|
||||
if (data) {
|
||||
convertor.writeString(data);
|
||||
}
|
||||
|
||||
i = i + chunkLength;
|
||||
}
|
||||
|
||||
// this closes foStream
|
||||
convertor.close();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no compressing then bail out.
|
||||
if (!compress) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remember name of the original file, it'll be replaced by a zip file.
|
||||
let originalFilePath = file.path;
|
||||
let originalFileName = file.leafName;
|
||||
|
||||
try {
|
||||
// Rename using unique name (the file is going to be removed).
|
||||
file.moveTo(null, "temp" + (new Date()).getTime() + "temphar");
|
||||
|
||||
// Create compressed file with the original file path name.
|
||||
let zipFile = Cc["@mozilla.org/file/local;1"]
|
||||
.createInstance(Ci.nsILocalFile);
|
||||
zipFile.initWithPath(originalFilePath);
|
||||
|
||||
// The file within the zipped file doesn't use .zip extension.
|
||||
let fileName = originalFileName;
|
||||
if (fileName.indexOf(".zip") == fileName.length - 4) {
|
||||
fileName = fileName.substr(0, fileName.indexOf(".zip"));
|
||||
}
|
||||
|
||||
let zip = new ZipWriter();
|
||||
zip.open(zipFile, openFlags);
|
||||
zip.addEntryFile(fileName, Ci.nsIZipWriter.COMPRESSION_DEFAULT,
|
||||
file, false);
|
||||
zip.close();
|
||||
|
||||
// Remove the original file (now zipped).
|
||||
file.remove(true);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
// Something went wrong (disk space?) rename the original file back.
|
||||
file.moveTo(null, originalFileName);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getLocalDirectory: function (path) {
|
||||
let dir;
|
||||
|
||||
if (!path) {
|
||||
dir = dirService.get("ProfD", Ci.nsILocalFile);
|
||||
dir.append("har");
|
||||
dir.append("logs");
|
||||
} else {
|
||||
dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
dir.initWithPath(path);
|
||||
}
|
||||
|
||||
return dir;
|
||||
},
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
exports.HarUtils = HarUtils;
|
||||
15
devtools/client/netmonitor/har/moz.build
Normal file
15
devtools/client/netmonitor/har/moz.build
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'har-automation.js',
|
||||
'har-builder.js',
|
||||
'har-collector.js',
|
||||
'har-exporter.js',
|
||||
'har-utils.js',
|
||||
'toolbox-overlay.js',
|
||||
)
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
6
devtools/client/netmonitor/har/test/.eslintrc.js
Normal file
6
devtools/client/netmonitor/har/test/.eslintrc.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../../.eslintrc.mochitests.js"
|
||||
};
|
||||
12
devtools/client/netmonitor/har/test/browser.ini
Normal file
12
devtools/client/netmonitor/har/test/browser.ini
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = clipboard
|
||||
support-files =
|
||||
head.js
|
||||
html_har_post-data-test-page.html
|
||||
!/devtools/client/netmonitor/test/head.js
|
||||
!/devtools/client/netmonitor/test/html_simple-test-page.html
|
||||
|
||||
[browser_net_har_copy_all_as_har.js]
|
||||
[browser_net_har_post_data.js]
|
||||
[browser_net_har_throttle_upload.js]
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Basic tests for exporting Network panel content into HAR format.
|
||||
*/
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
tab.linkedBrowser.reload();
|
||||
yield wait;
|
||||
|
||||
yield RequestsMenu.contextMenu.copyAllAsHar();
|
||||
|
||||
let jsonString = SpecialPowers.getClipboardData("text/unicode");
|
||||
let har = JSON.parse(jsonString);
|
||||
|
||||
// Check out HAR log
|
||||
isnot(har.log, null, "The HAR log must exist");
|
||||
is(har.log.creator.name, "Firefox", "The creator field must be set");
|
||||
is(har.log.browser.name, "Firefox", "The browser field must be set");
|
||||
is(har.log.pages.length, 1, "There must be one page");
|
||||
is(har.log.entries.length, 1, "There must be one request");
|
||||
|
||||
let entry = har.log.entries[0];
|
||||
is(entry.request.method, "GET", "Check the method");
|
||||
is(entry.request.url, SIMPLE_URL, "Check the URL");
|
||||
is(entry.request.headers.length, 9, "Check number of request headers");
|
||||
is(entry.response.status, 200, "Check response status");
|
||||
is(entry.response.statusText, "OK", "Check response status text");
|
||||
is(entry.response.headers.length, 6, "Check number of response headers");
|
||||
is(entry.response.content.mimeType, // eslint-disable-line
|
||||
"text/html", "Check response content type"); // eslint-disable-line
|
||||
isnot(entry.response.content.text, undefined, // eslint-disable-line
|
||||
"Check response body");
|
||||
isnot(entry.timings, undefined, "Check timings");
|
||||
|
||||
return teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests for exporting POST data into HAR format.
|
||||
*/
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(
|
||||
HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
|
||||
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
// Execute one POST request on the page and wait till its done.
|
||||
let wait = waitForNetworkEvents(monitor, 0, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.executeTest();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
// Copy HAR into the clipboard (asynchronous).
|
||||
let jsonString = yield RequestsMenu.contextMenu.copyAllAsHar();
|
||||
let har = JSON.parse(jsonString);
|
||||
|
||||
// Check out the HAR log.
|
||||
isnot(har.log, null, "The HAR log must exist");
|
||||
is(har.log.pages.length, 1, "There must be one page");
|
||||
is(har.log.entries.length, 1, "There must be one request");
|
||||
|
||||
let entry = har.log.entries[0];
|
||||
is(entry.request.postData.mimeType, "application/json",
|
||||
"Check post data content type");
|
||||
is(entry.request.postData.text, "{'first': 'John', 'last': 'Doe'}",
|
||||
"Check post data payload");
|
||||
|
||||
// Clean up
|
||||
return teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Test timing of upload when throttling.
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function* () {
|
||||
yield throttleUploadTest(true);
|
||||
yield throttleUploadTest(false);
|
||||
});
|
||||
|
||||
function* throttleUploadTest(actuallyThrottle) {
|
||||
let { tab, monitor } = yield initNetMonitor(
|
||||
HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
|
||||
|
||||
info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
const size = 4096;
|
||||
const uploadSize = actuallyThrottle ? size / 3 : 0;
|
||||
|
||||
const request = {
|
||||
"NetworkMonitor.throttleData": {
|
||||
roundTripTimeMean: 0,
|
||||
roundTripTimeMax: 0,
|
||||
downloadBPSMean: 200000,
|
||||
downloadBPSMax: 200000,
|
||||
uploadBPSMean: uploadSize,
|
||||
uploadBPSMax: uploadSize,
|
||||
},
|
||||
};
|
||||
let client = monitor._controller.webConsoleClient;
|
||||
|
||||
info("sending throttle request");
|
||||
let deferred = promise.defer();
|
||||
client.setPreferences(request, response => {
|
||||
deferred.resolve(response);
|
||||
});
|
||||
yield deferred.promise;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
// Execute one POST request on the page and wait till its done.
|
||||
let wait = waitForNetworkEvents(monitor, 0, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, { size }, function* (args) {
|
||||
content.wrappedJSObject.executeTest2(args.size);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
// Copy HAR into the clipboard (asynchronous).
|
||||
let jsonString = yield RequestsMenu.contextMenu.copyAllAsHar();
|
||||
let har = JSON.parse(jsonString);
|
||||
|
||||
// Check out the HAR log.
|
||||
isnot(har.log, null, "The HAR log must exist");
|
||||
is(har.log.pages.length, 1, "There must be one page");
|
||||
is(har.log.entries.length, 1, "There must be one request");
|
||||
|
||||
let entry = har.log.entries[0];
|
||||
is(entry.request.postData.text, "x".repeat(size),
|
||||
"Check post data payload");
|
||||
|
||||
const wasTwoSeconds = entry.timings.send >= 2000;
|
||||
if (actuallyThrottle) {
|
||||
ok(wasTwoSeconds, "upload should have taken more than 2 seconds");
|
||||
} else {
|
||||
ok(!wasTwoSeconds, "upload should not have taken more than 2 seconds");
|
||||
}
|
||||
|
||||
// Clean up
|
||||
yield teardown(monitor);
|
||||
}
|
||||
14
devtools/client/netmonitor/har/test/head.js
Normal file
14
devtools/client/netmonitor/har/test/head.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
|
||||
/* import-globals-from ../../test/head.js */
|
||||
|
||||
// Load the NetMonitor head.js file to share its API.
|
||||
var netMonitorHead = "chrome://mochitests/content/browser/devtools/client/netmonitor/test/head.js";
|
||||
Services.scriptloader.loadSubScript(netMonitorHead, this);
|
||||
|
||||
// Directory with HAR related test files.
|
||||
const HAR_EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/har/test/";
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<title>Network Monitor Test Page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>HAR POST data test</p>
|
||||
|
||||
<script type="text/javascript">
|
||||
function post(aAddress, aData) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", aAddress, true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(aData);
|
||||
}
|
||||
|
||||
function executeTest() {
|
||||
var url = "html_har_post-data-test-page.html";
|
||||
var data = "{'first': 'John', 'last': 'Doe'}";
|
||||
post(url, data);
|
||||
}
|
||||
|
||||
function executeTest2(size) {
|
||||
var url = "html_har_post-data-test-page.html";
|
||||
var data = "x".repeat(size);
|
||||
post(url, data);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
85
devtools/client/netmonitor/har/toolbox-overlay.js
Normal file
85
devtools/client/netmonitor/har/toolbox-overlay.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
|
||||
loader.lazyRequireGetter(this, "HarAutomation", "devtools/client/netmonitor/har/har-automation", true);
|
||||
|
||||
// Map of all created overlays. There is always one instance of
|
||||
// an overlay per Toolbox instance (i.e. one per browser tab).
|
||||
const overlays = new WeakMap();
|
||||
|
||||
/**
|
||||
* This object is responsible for initialization and cleanup for HAR
|
||||
* export feature. It represents an overlay for the Toolbox
|
||||
* following the same life time by listening to its events.
|
||||
*
|
||||
* HAR APIs are designed for integration with tools (such as Selenium)
|
||||
* that automates the browser. Primarily, it is for automating web apps
|
||||
* and getting HAR file for every loaded page.
|
||||
*/
|
||||
function ToolboxOverlay(toolbox) {
|
||||
this.toolbox = toolbox;
|
||||
|
||||
this.onInit = this.onInit.bind(this);
|
||||
this.onDestroy = this.onDestroy.bind(this);
|
||||
|
||||
this.toolbox.on("ready", this.onInit);
|
||||
this.toolbox.on("destroy", this.onDestroy);
|
||||
}
|
||||
|
||||
ToolboxOverlay.prototype = {
|
||||
/**
|
||||
* Executed when the toolbox is ready.
|
||||
*/
|
||||
onInit: function () {
|
||||
let autoExport = Services.prefs.getBoolPref(
|
||||
"devtools.netmonitor.har.enableAutoExportToFile");
|
||||
|
||||
if (!autoExport) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.initAutomation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Executed when the toolbox is destroyed.
|
||||
*/
|
||||
onDestroy: function (eventId, toolbox) {
|
||||
this.destroyAutomation();
|
||||
},
|
||||
|
||||
// Automation
|
||||
|
||||
initAutomation: function () {
|
||||
this.automation = new HarAutomation(this.toolbox);
|
||||
},
|
||||
|
||||
destroyAutomation: function () {
|
||||
if (this.automation) {
|
||||
this.automation.destroy();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Registration
|
||||
function register(toolbox) {
|
||||
if (overlays.has(toolbox)) {
|
||||
throw Error("There is an existing overlay for the toolbox");
|
||||
}
|
||||
|
||||
// Instantiate an overlay for the toolbox.
|
||||
let overlay = new ToolboxOverlay(toolbox);
|
||||
overlays.set(toolbox, overlay);
|
||||
}
|
||||
|
||||
function get(toolbox) {
|
||||
return overlays.get(toolbox);
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.register = register;
|
||||
exports.get = get;
|
||||
9
devtools/client/netmonitor/l10n.js
Normal file
9
devtools/client/netmonitor/l10n.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
const {LocalizationHelper} = require("devtools/shared/l10n");
|
||||
|
||||
const NET_STRINGS_URI = "devtools/client/locales/netmonitor.properties";
|
||||
const WEBCONSOLE_STRINGS_URI = "devtools/client/locales/webconsole.properties";
|
||||
|
||||
exports.L10N = new LocalizationHelper(NET_STRINGS_URI);
|
||||
exports.WEBCONSOLE_L10N = new LocalizationHelper(WEBCONSOLE_STRINGS_URI);
|
||||
31
devtools/client/netmonitor/moz.build
Normal file
31
devtools/client/netmonitor/moz.build
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'actions',
|
||||
'components',
|
||||
'har',
|
||||
'reducers',
|
||||
'selectors'
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'constants.js',
|
||||
'custom-request-view.js',
|
||||
'events.js',
|
||||
'filter-predicates.js',
|
||||
'l10n.js',
|
||||
'panel.js',
|
||||
'performance-statistics-view.js',
|
||||
'prefs.js',
|
||||
'request-list-context-menu.js',
|
||||
'request-utils.js',
|
||||
'requests-menu-view.js',
|
||||
'sort-predicates.js',
|
||||
'store.js',
|
||||
'toolbar-view.js',
|
||||
)
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
816
devtools/client/netmonitor/netmonitor-controller.js
Normal file
816
devtools/client/netmonitor/netmonitor-controller.js
Normal file
|
|
@ -0,0 +1,816 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals window, document, NetMonitorView, gStore, Actions */
|
||||
/* exported loader */
|
||||
"use strict";
|
||||
|
||||
var { utils: Cu } = Components;
|
||||
|
||||
// Descriptions for what this frontend is currently doing.
|
||||
const ACTIVITY_TYPE = {
|
||||
// Standing by and handling requests normally.
|
||||
NONE: 0,
|
||||
|
||||
// Forcing the target to reload with cache enabled or disabled.
|
||||
RELOAD: {
|
||||
WITH_CACHE_ENABLED: 1,
|
||||
WITH_CACHE_DISABLED: 2,
|
||||
WITH_CACHE_DEFAULT: 3
|
||||
},
|
||||
|
||||
// Enabling or disabling the cache without triggering a reload.
|
||||
ENABLE_CACHE: 3,
|
||||
DISABLE_CACHE: 4
|
||||
};
|
||||
|
||||
var BrowserLoaderModule = {};
|
||||
Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderModule);
|
||||
var { loader, require } = BrowserLoaderModule.BrowserLoader({
|
||||
baseURI: "resource://devtools/client/netmonitor/",
|
||||
window
|
||||
});
|
||||
|
||||
const promise = require("promise");
|
||||
const Services = require("Services");
|
||||
/* eslint-disable mozilla/reject-some-requires */
|
||||
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const Editor = require("devtools/client/sourceeditor/editor");
|
||||
const {TimelineFront} = require("devtools/shared/fronts/timeline");
|
||||
const {Task} = require("devtools/shared/task");
|
||||
const {Prefs} = require("./prefs");
|
||||
const {EVENTS} = require("./events");
|
||||
const Actions = require("./actions/index");
|
||||
|
||||
XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
|
||||
XPCOMUtils.defineConstant(this, "ACTIVITY_TYPE", ACTIVITY_TYPE);
|
||||
XPCOMUtils.defineConstant(this, "Editor", Editor);
|
||||
XPCOMUtils.defineConstant(this, "Prefs", Prefs);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Chart",
|
||||
"resource://devtools/client/shared/widgets/Chart.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
|
||||
"@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
|
||||
|
||||
Object.defineProperty(this, "NetworkHelper", {
|
||||
get: function () {
|
||||
return require("devtools/shared/webconsole/network-helper");
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Object defining the network monitor controller components.
|
||||
*/
|
||||
var NetMonitorController = {
|
||||
/**
|
||||
* Initializes the view and connects the monitor client.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved when the monitor finishes startup.
|
||||
*/
|
||||
startupNetMonitor: Task.async(function* () {
|
||||
if (this._startup) {
|
||||
return this._startup.promise;
|
||||
}
|
||||
this._startup = promise.defer();
|
||||
{
|
||||
NetMonitorView.initialize();
|
||||
yield this.connect();
|
||||
}
|
||||
this._startup.resolve();
|
||||
return undefined;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Destroys the view and disconnects the monitor client from the server.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved when the monitor finishes shutdown.
|
||||
*/
|
||||
shutdownNetMonitor: Task.async(function* () {
|
||||
if (this._shutdown) {
|
||||
return this._shutdown.promise;
|
||||
}
|
||||
this._shutdown = promise.defer();
|
||||
{
|
||||
NetMonitorView.destroy();
|
||||
this.TargetEventsHandler.disconnect();
|
||||
this.NetworkEventsHandler.disconnect();
|
||||
yield this.disconnect();
|
||||
}
|
||||
this._shutdown.resolve();
|
||||
return undefined;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Initiates remote or chrome network monitoring based on the current target,
|
||||
* wiring event handlers as necessary. Since the TabTarget will have already
|
||||
* started listening to network requests by now, this is largely
|
||||
* netmonitor-specific initialization.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved when the monitor finishes connecting.
|
||||
*/
|
||||
connect: Task.async(function* () {
|
||||
if (this._connection) {
|
||||
return this._connection.promise;
|
||||
}
|
||||
this._connection = promise.defer();
|
||||
|
||||
// Some actors like AddonActor or RootActor for chrome debugging
|
||||
// aren't actual tabs.
|
||||
if (this._target.isTabActor) {
|
||||
this.tabClient = this._target.activeTab;
|
||||
}
|
||||
|
||||
let connectTimeline = () => {
|
||||
// Don't start up waiting for timeline markers if the server isn't
|
||||
// recent enough to emit the markers we're interested in.
|
||||
if (this._target.getTrait("documentLoadingMarkers")) {
|
||||
this.timelineFront = new TimelineFront(this._target.client,
|
||||
this._target.form);
|
||||
return this.timelineFront.start({ withDocLoadingEvents: true });
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
this.webConsoleClient = this._target.activeConsole;
|
||||
yield connectTimeline();
|
||||
|
||||
this.TargetEventsHandler.connect();
|
||||
this.NetworkEventsHandler.connect();
|
||||
|
||||
window.emit(EVENTS.CONNECTED);
|
||||
|
||||
this._connection.resolve();
|
||||
this._connected = true;
|
||||
return undefined;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Disconnects the debugger client and removes event handlers as necessary.
|
||||
*/
|
||||
disconnect: Task.async(function* () {
|
||||
if (this._disconnection) {
|
||||
return this._disconnection.promise;
|
||||
}
|
||||
this._disconnection = promise.defer();
|
||||
|
||||
// Wait for the connection to finish first.
|
||||
if (!this.isConnected()) {
|
||||
yield this._connection.promise;
|
||||
}
|
||||
|
||||
// When debugging local or a remote instance, the connection is closed by
|
||||
// the RemoteTarget. The webconsole actor is stopped on disconnect.
|
||||
this.tabClient = null;
|
||||
this.webConsoleClient = null;
|
||||
|
||||
// The timeline front wasn't initialized and started if the server wasn't
|
||||
// recent enough to emit the markers we were interested in.
|
||||
if (this._target.getTrait("documentLoadingMarkers")) {
|
||||
yield this.timelineFront.destroy();
|
||||
this.timelineFront = null;
|
||||
}
|
||||
|
||||
this._disconnection.resolve();
|
||||
this._connected = false;
|
||||
return undefined;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Checks whether the netmonitor connection is active.
|
||||
* @return boolean
|
||||
*/
|
||||
isConnected: function () {
|
||||
return !!this._connected;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the activity currently performed by the frontend.
|
||||
* @return number
|
||||
*/
|
||||
getCurrentActivity: function () {
|
||||
return this._currentActivity || ACTIVITY_TYPE.NONE;
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers a specific "activity" to be performed by the frontend.
|
||||
* This can be, for example, triggering reloads or enabling/disabling cache.
|
||||
*
|
||||
* @param number type
|
||||
* The activity type. See the ACTIVITY_TYPE const.
|
||||
* @return object
|
||||
* A promise resolved once the activity finishes and the frontend
|
||||
* is back into "standby" mode.
|
||||
*/
|
||||
triggerActivity: function (type) {
|
||||
// Puts the frontend into "standby" (when there's no particular activity).
|
||||
let standBy = () => {
|
||||
this._currentActivity = ACTIVITY_TYPE.NONE;
|
||||
};
|
||||
|
||||
// Waits for a series of "navigation start" and "navigation stop" events.
|
||||
let waitForNavigation = () => {
|
||||
let deferred = promise.defer();
|
||||
this._target.once("will-navigate", () => {
|
||||
this._target.once("navigate", () => {
|
||||
deferred.resolve();
|
||||
});
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
// Reconfigures the tab, optionally triggering a reload.
|
||||
let reconfigureTab = options => {
|
||||
let deferred = promise.defer();
|
||||
this._target.activeTab.reconfigure(options, deferred.resolve);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
// Reconfigures the tab and waits for the target to finish navigating.
|
||||
let reconfigureTabAndWaitForNavigation = options => {
|
||||
options.performReload = true;
|
||||
let navigationFinished = waitForNavigation();
|
||||
return reconfigureTab(options).then(() => navigationFinished);
|
||||
};
|
||||
if (type == ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT) {
|
||||
return reconfigureTabAndWaitForNavigation({}).then(standBy);
|
||||
}
|
||||
if (type == ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED) {
|
||||
this._currentActivity = ACTIVITY_TYPE.ENABLE_CACHE;
|
||||
this._target.once("will-navigate", () => {
|
||||
this._currentActivity = type;
|
||||
});
|
||||
return reconfigureTabAndWaitForNavigation({
|
||||
cacheDisabled: false,
|
||||
performReload: true
|
||||
}).then(standBy);
|
||||
}
|
||||
if (type == ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED) {
|
||||
this._currentActivity = ACTIVITY_TYPE.DISABLE_CACHE;
|
||||
this._target.once("will-navigate", () => {
|
||||
this._currentActivity = type;
|
||||
});
|
||||
return reconfigureTabAndWaitForNavigation({
|
||||
cacheDisabled: true,
|
||||
performReload: true
|
||||
}).then(standBy);
|
||||
}
|
||||
if (type == ACTIVITY_TYPE.ENABLE_CACHE) {
|
||||
this._currentActivity = type;
|
||||
return reconfigureTab({
|
||||
cacheDisabled: false,
|
||||
performReload: false
|
||||
}).then(standBy);
|
||||
}
|
||||
if (type == ACTIVITY_TYPE.DISABLE_CACHE) {
|
||||
this._currentActivity = type;
|
||||
return reconfigureTab({
|
||||
cacheDisabled: true,
|
||||
performReload: false
|
||||
}).then(standBy);
|
||||
}
|
||||
this._currentActivity = ACTIVITY_TYPE.NONE;
|
||||
return promise.reject(new Error("Invalid activity type"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects the specified request in the waterfall and opens the details view.
|
||||
*
|
||||
* @param string requestId
|
||||
* The actor ID of the request to inspect.
|
||||
* @return object
|
||||
* A promise resolved once the task finishes.
|
||||
*/
|
||||
inspectRequest: function (requestId) {
|
||||
// Look for the request in the existing ones or wait for it to appear, if
|
||||
// the network monitor is still loading.
|
||||
let deferred = promise.defer();
|
||||
let request = null;
|
||||
let inspector = function () {
|
||||
let predicate = i => i.value === requestId;
|
||||
request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate);
|
||||
if (!request) {
|
||||
// Reset filters so that the request is visible.
|
||||
gStore.dispatch(Actions.toggleFilterType("all"));
|
||||
request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate);
|
||||
}
|
||||
|
||||
// If the request was found, select it. Otherwise this function will be
|
||||
// called again once new requests arrive.
|
||||
if (request) {
|
||||
window.off(EVENTS.REQUEST_ADDED, inspector);
|
||||
NetMonitorView.RequestsMenu.selectedItem = request;
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
inspector();
|
||||
if (!request) {
|
||||
window.on(EVENTS.REQUEST_ADDED, inspector);
|
||||
}
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter that tells if the server supports sending custom network requests.
|
||||
* @type boolean
|
||||
*/
|
||||
get supportsCustomRequest() {
|
||||
return this.webConsoleClient &&
|
||||
(this.webConsoleClient.traits.customNetworkRequest ||
|
||||
!this._target.isApp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter that tells if the server includes the transferred (compressed /
|
||||
* encoded) response size.
|
||||
* @type boolean
|
||||
*/
|
||||
get supportsTransferredResponseSize() {
|
||||
return this.webConsoleClient &&
|
||||
this.webConsoleClient.traits.transferredResponseSize;
|
||||
},
|
||||
|
||||
/**
|
||||
* Getter that tells if the server can do network performance statistics.
|
||||
* @type boolean
|
||||
*/
|
||||
get supportsPerfStats() {
|
||||
return this.tabClient &&
|
||||
(this.tabClient.traits.reconfigure || !this._target.isApp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a given source in Debugger
|
||||
*/
|
||||
viewSourceInDebugger(sourceURL, sourceLine) {
|
||||
return this._toolbox.viewSourceInDebugger(sourceURL, sourceLine);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions handling target-related lifetime events.
|
||||
*/
|
||||
function TargetEventsHandler() {
|
||||
this._onTabNavigated = this._onTabNavigated.bind(this);
|
||||
this._onTabDetached = this._onTabDetached.bind(this);
|
||||
}
|
||||
|
||||
TargetEventsHandler.prototype = {
|
||||
get target() {
|
||||
return NetMonitorController._target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Listen for events emitted by the current tab target.
|
||||
*/
|
||||
connect: function () {
|
||||
dumpn("TargetEventsHandler is connecting...");
|
||||
this.target.on("close", this._onTabDetached);
|
||||
this.target.on("navigate", this._onTabNavigated);
|
||||
this.target.on("will-navigate", this._onTabNavigated);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove events emitted by the current tab target.
|
||||
*/
|
||||
disconnect: function () {
|
||||
if (!this.target) {
|
||||
return;
|
||||
}
|
||||
dumpn("TargetEventsHandler is disconnecting...");
|
||||
this.target.off("close", this._onTabDetached);
|
||||
this.target.off("navigate", this._onTabNavigated);
|
||||
this.target.off("will-navigate", this._onTabNavigated);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called for each location change in the monitored tab.
|
||||
*
|
||||
* @param string type
|
||||
* Packet type.
|
||||
* @param object packet
|
||||
* Packet received from the server.
|
||||
*/
|
||||
_onTabNavigated: function (type, packet) {
|
||||
switch (type) {
|
||||
case "will-navigate": {
|
||||
// Reset UI.
|
||||
if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
|
||||
NetMonitorView.RequestsMenu.reset();
|
||||
NetMonitorView.Sidebar.toggle(false);
|
||||
}
|
||||
// Switch to the default network traffic inspector view.
|
||||
if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {
|
||||
NetMonitorView.showNetworkInspectorView();
|
||||
}
|
||||
// Clear any accumulated markers.
|
||||
NetMonitorController.NetworkEventsHandler.clearMarkers();
|
||||
|
||||
window.emit(EVENTS.TARGET_WILL_NAVIGATE);
|
||||
break;
|
||||
}
|
||||
case "navigate": {
|
||||
window.emit(EVENTS.TARGET_DID_NAVIGATE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the monitored tab is closed.
|
||||
*/
|
||||
_onTabDetached: function () {
|
||||
NetMonitorController.shutdownNetMonitor();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions handling target network events.
|
||||
*/
|
||||
function NetworkEventsHandler() {
|
||||
this._markers = [];
|
||||
|
||||
this._onNetworkEvent = this._onNetworkEvent.bind(this);
|
||||
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
|
||||
this._onDocLoadingMarker = this._onDocLoadingMarker.bind(this);
|
||||
this._onRequestHeaders = this._onRequestHeaders.bind(this);
|
||||
this._onRequestCookies = this._onRequestCookies.bind(this);
|
||||
this._onRequestPostData = this._onRequestPostData.bind(this);
|
||||
this._onResponseHeaders = this._onResponseHeaders.bind(this);
|
||||
this._onResponseCookies = this._onResponseCookies.bind(this);
|
||||
this._onResponseContent = this._onResponseContent.bind(this);
|
||||
this._onEventTimings = this._onEventTimings.bind(this);
|
||||
}
|
||||
|
||||
NetworkEventsHandler.prototype = {
|
||||
get client() {
|
||||
return NetMonitorController._target.client;
|
||||
},
|
||||
|
||||
get webConsoleClient() {
|
||||
return NetMonitorController.webConsoleClient;
|
||||
},
|
||||
|
||||
get timelineFront() {
|
||||
return NetMonitorController.timelineFront;
|
||||
},
|
||||
|
||||
get firstDocumentDOMContentLoadedTimestamp() {
|
||||
let marker = this._markers.filter(e => {
|
||||
return e.name == "document::DOMContentLoaded";
|
||||
})[0];
|
||||
|
||||
return marker ? marker.unixTime / 1000 : -1;
|
||||
},
|
||||
|
||||
get firstDocumentLoadTimestamp() {
|
||||
let marker = this._markers.filter(e => e.name == "document::Load")[0];
|
||||
return marker ? marker.unixTime / 1000 : -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Connect to the current target client.
|
||||
*/
|
||||
connect: function () {
|
||||
dumpn("NetworkEventsHandler is connecting...");
|
||||
this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
|
||||
if (this.timelineFront) {
|
||||
this.timelineFront.on("doc-loading", this._onDocLoadingMarker);
|
||||
}
|
||||
|
||||
this._displayCachedEvents();
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnect from the client.
|
||||
*/
|
||||
disconnect: function () {
|
||||
if (!this.client) {
|
||||
return;
|
||||
}
|
||||
dumpn("NetworkEventsHandler is disconnecting...");
|
||||
this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
|
||||
if (this.timelineFront) {
|
||||
this.timelineFront.off("doc-loading", this._onDocLoadingMarker);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Display any network events already in the cache.
|
||||
*/
|
||||
_displayCachedEvents: function () {
|
||||
for (let cachedEvent of this.webConsoleClient.getNetworkEvents()) {
|
||||
// First add the request to the timeline.
|
||||
this._onNetworkEvent("networkEvent", cachedEvent);
|
||||
// Then replay any updates already received.
|
||||
for (let update of cachedEvent.updates) {
|
||||
this._onNetworkEventUpdate("networkEventUpdate", {
|
||||
packet: {
|
||||
updateType: update
|
||||
},
|
||||
networkInfo: cachedEvent
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "DOMContentLoaded" and "Load" events sent by the timeline actor.
|
||||
* @param object marker
|
||||
*/
|
||||
_onDocLoadingMarker: function (marker) {
|
||||
window.emit(EVENTS.TIMELINE_EVENT, marker);
|
||||
this._markers.push(marker);
|
||||
},
|
||||
|
||||
/**
|
||||
* The "networkEvent" message type handler.
|
||||
*
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEvent: function (type, networkInfo) {
|
||||
let { actor,
|
||||
startedDateTime,
|
||||
request: { method, url },
|
||||
isXHR,
|
||||
cause,
|
||||
fromCache,
|
||||
fromServiceWorker
|
||||
} = networkInfo;
|
||||
|
||||
NetMonitorView.RequestsMenu.addRequest(
|
||||
actor, startedDateTime, method, url, isXHR, cause, fromCache,
|
||||
fromServiceWorker
|
||||
);
|
||||
window.emit(EVENTS.NETWORK_EVENT, actor);
|
||||
},
|
||||
|
||||
/**
|
||||
* The "networkEventUpdate" message type handler.
|
||||
*
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEventUpdate: function (type, { packet, networkInfo }) {
|
||||
let { actor } = networkInfo;
|
||||
|
||||
switch (packet.updateType) {
|
||||
case "requestHeaders":
|
||||
this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders);
|
||||
window.emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
|
||||
break;
|
||||
case "requestCookies":
|
||||
this.webConsoleClient.getRequestCookies(actor, this._onRequestCookies);
|
||||
window.emit(EVENTS.UPDATING_REQUEST_COOKIES, actor);
|
||||
break;
|
||||
case "requestPostData":
|
||||
this.webConsoleClient.getRequestPostData(actor,
|
||||
this._onRequestPostData);
|
||||
window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
|
||||
break;
|
||||
case "securityInfo":
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
securityState: networkInfo.securityInfo,
|
||||
});
|
||||
this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
|
||||
window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
|
||||
break;
|
||||
case "responseHeaders":
|
||||
this.webConsoleClient.getResponseHeaders(actor,
|
||||
this._onResponseHeaders);
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
|
||||
break;
|
||||
case "responseCookies":
|
||||
this.webConsoleClient.getResponseCookies(actor,
|
||||
this._onResponseCookies);
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
|
||||
break;
|
||||
case "responseStart":
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
httpVersion: networkInfo.response.httpVersion,
|
||||
remoteAddress: networkInfo.response.remoteAddress,
|
||||
remotePort: networkInfo.response.remotePort,
|
||||
status: networkInfo.response.status,
|
||||
statusText: networkInfo.response.statusText,
|
||||
headersSize: networkInfo.response.headersSize
|
||||
});
|
||||
window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
|
||||
break;
|
||||
case "responseContent":
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
contentSize: networkInfo.response.bodySize,
|
||||
transferredSize: networkInfo.response.transferredSize,
|
||||
mimeType: networkInfo.response.content.mimeType
|
||||
});
|
||||
this.webConsoleClient.getResponseContent(actor,
|
||||
this._onResponseContent);
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
|
||||
break;
|
||||
case "eventTimings":
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
totalTime: networkInfo.totalTime
|
||||
});
|
||||
this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
|
||||
window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestHeaders" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onRequestHeaders: function (response) {
|
||||
NetMonitorView.RequestsMenu.updateRequest(response.from, {
|
||||
requestHeaders: response
|
||||
}, () => {
|
||||
window.emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestCookies" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onRequestCookies: function (response) {
|
||||
NetMonitorView.RequestsMenu.updateRequest(response.from, {
|
||||
requestCookies: response
|
||||
}, () => {
|
||||
window.emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "requestPostData" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onRequestPostData: function (response) {
|
||||
NetMonitorView.RequestsMenu.updateRequest(response.from, {
|
||||
requestPostData: response
|
||||
}, () => {
|
||||
window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, response.from);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "securityInfo" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onSecurityInfo: function (response) {
|
||||
NetMonitorView.RequestsMenu.updateRequest(response.from, {
|
||||
securityInfo: response.securityInfo
|
||||
}, () => {
|
||||
window.emit(EVENTS.RECEIVED_SECURITY_INFO, response.from);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseHeaders" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onResponseHeaders: function (response) {
|
||||
NetMonitorView.RequestsMenu.updateRequest(response.from, {
|
||||
responseHeaders: response
|
||||
}, () => {
|
||||
window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseCookies" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onResponseCookies: function (response) {
|
||||
NetMonitorView.RequestsMenu.updateRequest(response.from, {
|
||||
responseCookies: response
|
||||
}, () => {
|
||||
window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "responseContent" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onResponseContent: function (response) {
|
||||
NetMonitorView.RequestsMenu.updateRequest(response.from, {
|
||||
responseContent: response
|
||||
}, () => {
|
||||
window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles additional information received for a "eventTimings" packet.
|
||||
*
|
||||
* @param object response
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onEventTimings: function (response) {
|
||||
NetMonitorView.RequestsMenu.updateRequest(response.from, {
|
||||
eventTimings: response
|
||||
}, () => {
|
||||
window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all accumulated markers.
|
||||
*/
|
||||
clearMarkers: function () {
|
||||
this._markers.length = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches the full text of a LongString.
|
||||
*
|
||||
* @param object | string stringGrip
|
||||
* The long string grip containing the corresponding actor.
|
||||
* If you pass in a plain string (by accident or because you're lazy),
|
||||
* then a promise of the same string is simply returned.
|
||||
* @return object Promise
|
||||
* A promise that is resolved when the full string contents
|
||||
* are available, or rejected if something goes wrong.
|
||||
*/
|
||||
getString: function (stringGrip) {
|
||||
return this.webConsoleClient.getString(stringGrip);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if this is document is in RTL mode.
|
||||
* @return boolean
|
||||
*/
|
||||
XPCOMUtils.defineLazyGetter(window, "isRTL", function () {
|
||||
return window.getComputedStyle(document.documentElement, null)
|
||||
.direction == "rtl";
|
||||
});
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the panel window.
|
||||
*/
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
/**
|
||||
* Preliminary setup for the NetMonitorController object.
|
||||
*/
|
||||
NetMonitorController.TargetEventsHandler = new TargetEventsHandler();
|
||||
NetMonitorController.NetworkEventsHandler = new NetworkEventsHandler();
|
||||
|
||||
/**
|
||||
* Export some properties to the global scope for easier access.
|
||||
*/
|
||||
Object.defineProperties(window, {
|
||||
"gNetwork": {
|
||||
get: function () {
|
||||
return NetMonitorController.NetworkEventsHandler;
|
||||
},
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper method for debugging.
|
||||
* @param string
|
||||
*/
|
||||
function dumpn(str) {
|
||||
if (wantLogging) {
|
||||
dump("NET-FRONTEND: " + str + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
var wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
|
||||
1230
devtools/client/netmonitor/netmonitor-view.js
Normal file
1230
devtools/client/netmonitor/netmonitor-view.js
Normal file
File diff suppressed because it is too large
Load diff
741
devtools/client/netmonitor/netmonitor.xul
Normal file
741
devtools/client/netmonitor/netmonitor.xul
Normal file
|
|
@ -0,0 +1,741 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/netmonitor.css" type="text/css"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<script type="text/javascript" src="netmonitor-controller.js"/>
|
||||
<script type="text/javascript" src="netmonitor-view.js"/>
|
||||
|
||||
<deck id="body"
|
||||
class="theme-sidebar"
|
||||
flex="1"
|
||||
data-localization-bundle="devtools/client/locales/netmonitor.properties">
|
||||
|
||||
<vbox id="network-inspector-view" flex="1">
|
||||
<hbox id="netmonitor-toolbar" class="devtools-toolbar">
|
||||
<html:div xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="react-clear-button-hook"/>
|
||||
<html:div xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="react-filter-buttons-hook"/>
|
||||
<spacer id="requests-menu-spacer"
|
||||
flex="1"/>
|
||||
<toolbarbutton id="requests-menu-network-summary-button"
|
||||
class="devtools-toolbarbutton icon-and-text"
|
||||
data-localization="tooltiptext=netmonitor.toolbar.perf"/>
|
||||
<html:div xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="react-search-box-hook"/>
|
||||
<html:div xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="react-details-pane-toggle-hook"/>
|
||||
</hbox>
|
||||
<hbox id="network-table-and-sidebar"
|
||||
class="devtools-responsive-container"
|
||||
flex="1">
|
||||
<vbox id="network-table" flex="1" class="devtools-main-content">
|
||||
<toolbar id="requests-menu-toolbar"
|
||||
class="devtools-toolbar"
|
||||
align="center">
|
||||
<hbox id="toolbar-labels" flex="1">
|
||||
<hbox id="requests-menu-status-header-box"
|
||||
class="requests-menu-header requests-menu-status"
|
||||
align="center">
|
||||
<button id="requests-menu-status-button"
|
||||
class="requests-menu-header-button requests-menu-status"
|
||||
data-key="status"
|
||||
data-localization="label=netmonitor.toolbar.status3"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-method-header-box"
|
||||
class="requests-menu-header requests-menu-method"
|
||||
align="center">
|
||||
<button id="requests-menu-method-button"
|
||||
class="requests-menu-header-button requests-menu-method"
|
||||
data-key="method"
|
||||
data-localization="label=netmonitor.toolbar.method"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-icon-and-file-header-box"
|
||||
class="requests-menu-header requests-menu-icon-and-file"
|
||||
align="center">
|
||||
<button id="requests-menu-file-button"
|
||||
class="requests-menu-header-button requests-menu-file"
|
||||
data-key="file"
|
||||
data-localization="label=netmonitor.toolbar.file"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-domain-header-box"
|
||||
class="requests-menu-header requests-menu-security-and-domain"
|
||||
align="center">
|
||||
<button id="requests-menu-domain-button"
|
||||
class="requests-menu-header-button requests-menu-security-and-domain"
|
||||
data-key="domain"
|
||||
data-localization="label=netmonitor.toolbar.domain"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-cause-header-box"
|
||||
class="requests-menu-header requests-menu-cause"
|
||||
align="center">
|
||||
<button id="requests-menu-cause-button"
|
||||
class="requests-menu-header-button requests-menu-cause"
|
||||
data-key="cause"
|
||||
data-localization="label=netmonitor.toolbar.cause"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-type-header-box"
|
||||
class="requests-menu-header requests-menu-type"
|
||||
align="center">
|
||||
<button id="requests-menu-type-button"
|
||||
class="requests-menu-header-button requests-menu-type"
|
||||
data-key="type"
|
||||
data-localization="label=netmonitor.toolbar.type"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-transferred-header-box"
|
||||
class="requests-menu-header requests-menu-transferred"
|
||||
align="center">
|
||||
<button id="requests-menu-transferred-button"
|
||||
class="requests-menu-header-button requests-menu-transferred"
|
||||
data-key="transferred"
|
||||
data-localization="label=netmonitor.toolbar.transferred"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-size-header-box"
|
||||
class="requests-menu-header requests-menu-size"
|
||||
align="center">
|
||||
<button id="requests-menu-size-button"
|
||||
class="requests-menu-header-button requests-menu-size"
|
||||
data-key="size"
|
||||
data-localization="label=netmonitor.toolbar.size"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-waterfall-header-box"
|
||||
class="requests-menu-header requests-menu-waterfall"
|
||||
align="center"
|
||||
flex="1">
|
||||
<button id="requests-menu-waterfall-button"
|
||||
class="requests-menu-header-button requests-menu-waterfall"
|
||||
data-key="waterfall"
|
||||
pack="start"
|
||||
data-localization="label=netmonitor.toolbar.waterfall"
|
||||
flex="1">
|
||||
<image id="requests-menu-waterfall-image"/>
|
||||
<box id="requests-menu-waterfall-label-wrapper">
|
||||
<label id="requests-menu-waterfall-label"
|
||||
class="plain requests-menu-waterfall"
|
||||
data-localization="value=netmonitor.toolbar.waterfall"/>
|
||||
</box>
|
||||
</button>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<vbox id="requests-menu-empty-notice"
|
||||
class="side-menu-widget-empty-text">
|
||||
<hbox id="notice-reload-message" align="center">
|
||||
<label data-localization="content=netmonitor.reloadNotice1"/>
|
||||
<button id="requests-menu-reload-notice-button"
|
||||
class="devtools-toolbarbutton"
|
||||
standalone="true"
|
||||
data-localization="label=netmonitor.reloadNotice2"/>
|
||||
<label data-localization="content=netmonitor.reloadNotice3"/>
|
||||
</hbox>
|
||||
<hbox id="notice-perf-message" align="center">
|
||||
<label data-localization="content=netmonitor.perfNotice1"/>
|
||||
<button id="requests-menu-perf-notice-button"
|
||||
class="devtools-toolbarbutton"
|
||||
standalone="true"
|
||||
data-localization="tooltiptext=netmonitor.perfNotice3"/>
|
||||
<label data-localization="content=netmonitor.perfNotice2"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<vbox id="requests-menu-contents" flex="1">
|
||||
<hbox id="requests-menu-item-template" hidden="true">
|
||||
<hbox class="requests-menu-subitem requests-menu-status"
|
||||
align="center">
|
||||
<box class="requests-menu-status-icon"/>
|
||||
<label class="plain requests-menu-status-code"
|
||||
crop="end"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-method-box"
|
||||
align="center">
|
||||
<label class="plain requests-menu-method"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-icon-and-file"
|
||||
align="center">
|
||||
<image class="requests-menu-icon" hidden="true"/>
|
||||
<label class="plain requests-menu-file"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-security-and-domain"
|
||||
align="center">
|
||||
<image class="requests-security-state-icon" />
|
||||
<label class="plain requests-menu-domain"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-cause" align="center">
|
||||
<label class="requests-menu-cause-stack" value="JS" hidden="true"/>
|
||||
<label class="plain requests-menu-cause-label" flex="1" crop="end"/>
|
||||
</hbox>
|
||||
<label class="plain requests-menu-subitem requests-menu-type"
|
||||
crop="end"/>
|
||||
<label class="plain requests-menu-subitem requests-menu-transferred"
|
||||
crop="end"/>
|
||||
<label class="plain requests-menu-subitem requests-menu-size"
|
||||
crop="end"/>
|
||||
<hbox class="requests-menu-subitem requests-menu-waterfall"
|
||||
align="center"
|
||||
flex="1">
|
||||
<hbox class="requests-menu-timings"
|
||||
align="center">
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
||||
<splitter id="network-inspector-view-splitter"
|
||||
class="devtools-side-splitter"/>
|
||||
|
||||
<deck id="details-pane"
|
||||
hidden="true">
|
||||
<vbox id="custom-pane"
|
||||
class="tabpanel-content">
|
||||
<hbox align="baseline">
|
||||
<label data-localization="content=netmonitor.custom.newRequest"
|
||||
class="plain tabpanel-summary-label
|
||||
custom-header"/>
|
||||
<hbox flex="1" pack="end"
|
||||
class="devtools-toolbarbutton-group">
|
||||
<button id="custom-request-send-button"
|
||||
class="devtools-toolbarbutton"
|
||||
data-localization="label=netmonitor.custom.send"/>
|
||||
<button id="custom-request-close-button"
|
||||
class="devtools-toolbarbutton"
|
||||
data-localization="label=netmonitor.custom.cancel"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<hbox id="custom-method-and-url"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<textbox id="custom-method-value"
|
||||
data-key="method"/>
|
||||
<textbox id="custom-url-value"
|
||||
flex="1"
|
||||
data-key="url"/>
|
||||
</hbox>
|
||||
<vbox id="custom-query"
|
||||
class="tabpanel-summary-container custom-section">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.custom.query"/>
|
||||
<textbox id="custom-query-value"
|
||||
class="tabpanel-summary-input"
|
||||
multiline="true"
|
||||
rows="4"
|
||||
wrap="off"
|
||||
data-key="query"/>
|
||||
</vbox>
|
||||
<vbox id="custom-headers"
|
||||
class="tabpanel-summary-container custom-section">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.custom.headers"/>
|
||||
<textbox id="custom-headers-value"
|
||||
class="tabpanel-summary-input"
|
||||
multiline="true"
|
||||
rows="8"
|
||||
wrap="off"
|
||||
data-key="headers"/>
|
||||
</vbox>
|
||||
<vbox id="custom-postdata"
|
||||
class="tabpanel-summary-container custom-section">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.custom.postData"/>
|
||||
<textbox id="custom-postdata-value"
|
||||
class="tabpanel-summary-input"
|
||||
multiline="true"
|
||||
rows="6"
|
||||
wrap="off"
|
||||
data-key="body"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<tabbox id="event-details-pane"
|
||||
class="devtools-sidebar-tabs"
|
||||
handleCtrlTab="false">
|
||||
<tabs>
|
||||
<tab id="headers-tab"
|
||||
crop="end"
|
||||
data-localization="label=netmonitor.tab.headers"/>
|
||||
<tab id="cookies-tab"
|
||||
crop="end"
|
||||
data-localization="label=netmonitor.tab.cookies"/>
|
||||
<tab id="params-tab"
|
||||
crop="end"
|
||||
data-localization="label=netmonitor.tab.params"/>
|
||||
<tab id="response-tab"
|
||||
crop="end"
|
||||
data-localization="label=netmonitor.tab.response"/>
|
||||
<tab id="timings-tab"
|
||||
crop="end"
|
||||
data-localization="label=netmonitor.tab.timings"/>
|
||||
<tab id="security-tab"
|
||||
crop="end"
|
||||
data-localization="label=netmonitor.tab.security"/>
|
||||
<tab id="preview-tab"
|
||||
crop="end"
|
||||
data-localization="label=netmonitor.tab.preview"/>
|
||||
</tabs>
|
||||
<tabpanels flex="1">
|
||||
<tabpanel id="headers-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<hbox id="headers-summary-url"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.summary.url"/>
|
||||
<textbox id="headers-summary-url-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox id="headers-summary-method"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.summary.method"/>
|
||||
<label id="headers-summary-method-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="headers-summary-address"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.summary.address"/>
|
||||
<textbox id="headers-summary-address-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox id="headers-summary-status"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.summary.status"/>
|
||||
<box id="headers-summary-status-circle"
|
||||
class="requests-menu-status-icon"/>
|
||||
<label id="headers-summary-status-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
<button id="headers-summary-resend"
|
||||
class="devtools-toolbarbutton"
|
||||
data-localization="label=netmonitor.summary.editAndResend"/>
|
||||
<button id="toggle-raw-headers"
|
||||
class="devtools-toolbarbutton"
|
||||
data-localization="label=netmonitor.summary.rawHeaders"/>
|
||||
</hbox>
|
||||
<hbox id="headers-summary-version"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.summary.version"/>
|
||||
<label id="headers-summary-version-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox id="raw-headers"
|
||||
class="tabpanel-summary-container"
|
||||
align="center"
|
||||
hidden="true">
|
||||
<vbox id="raw-request-headers-textarea-box" flex="1" hidden="false">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.summary.rawHeaders.requestHeaders"/>
|
||||
<textbox id="raw-request-headers-textarea"
|
||||
class="raw-response-textarea"
|
||||
flex="1" multiline="true" readonly="true"/>
|
||||
</vbox>
|
||||
<vbox id="raw-response-headers-textarea-box" flex="1" hidden="false">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.summary.rawHeaders.responseHeaders"/>
|
||||
<textbox id="raw-response-headers-textarea"
|
||||
class="raw-response-textarea"
|
||||
flex="1" multiline="true" readonly="true"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<vbox id="all-headers" flex="1"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="cookies-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<vbox id="all-cookies" flex="1"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="params-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<vbox id="request-params-box" flex="1" hidden="true">
|
||||
<vbox id="request-params" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="request-post-data-textarea-box" flex="1" hidden="true">
|
||||
<vbox id="request-post-data-textarea" flex="1"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="response-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<label id="response-content-info-header"/>
|
||||
<vbox id="response-content-json-box" flex="1" hidden="true">
|
||||
<vbox id="response-content-json" flex="1" context="network-response-popup" />
|
||||
</vbox>
|
||||
<vbox id="response-content-textarea-box" flex="1" hidden="true">
|
||||
<vbox id="response-content-textarea" flex="1"/>
|
||||
</vbox>
|
||||
<vbox id="response-content-image-box" flex="1" hidden="true">
|
||||
<image id="response-content-image"/>
|
||||
<hbox>
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.response.name"/>
|
||||
<label id="response-content-image-name-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.response.dimensions"/>
|
||||
<label id="response-content-image-dimensions-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.response.mime"/>
|
||||
<label id="response-content-image-mime-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="timings-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<hbox id="timings-summary-blocked"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.blocked"/>
|
||||
<hbox class="requests-menu-timings-box blocked"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-dns"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.dns"/>
|
||||
<hbox class="requests-menu-timings-box dns"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-connect"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.connect"/>
|
||||
<hbox class="requests-menu-timings-box connect"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-send"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.send"/>
|
||||
<hbox class="requests-menu-timings-box send"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-wait"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.wait"/>
|
||||
<hbox class="requests-menu-timings-box wait"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-receive"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.receive"/>
|
||||
<hbox class="requests-menu-timings-box receive"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="security-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox id="security-error"
|
||||
class="tabpanel-summary-container"
|
||||
flex="1">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.security.error"/>
|
||||
<hbox class="security-info-section"
|
||||
flex="1">
|
||||
<textbox id="security-error-message"
|
||||
class="plain"
|
||||
flex="1"
|
||||
multiline="true"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox id="security-information"
|
||||
flex="1">
|
||||
<vbox id="security-info-connection"
|
||||
class="tabpanel-summary-container">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.security.connection"/>
|
||||
<vbox class="security-info-section">
|
||||
<hbox id="security-protocol-version"
|
||||
class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.security.protocolVersion"/>
|
||||
<textbox id="security-protocol-version-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox id="security-ciphersuite"
|
||||
class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.security.cipherSuite"/>
|
||||
<textbox id="security-ciphersuite-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
<image class="security-warning-icon"
|
||||
id="security-warning-cipher"
|
||||
data-localization="tooltiptext=netmonitor.security.warning.cipher" />
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox id="security-info-domain"
|
||||
class="tabpanel-summary-container">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
id="security-info-host-header"/>
|
||||
<vbox class="security-info-section">
|
||||
<hbox id="security-http-strict-transport-security"
|
||||
class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.security.hsts"/>
|
||||
<textbox id="security-http-strict-transport-security-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox id="security-public-key-pinning"
|
||||
class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.security.hpkp"/>
|
||||
<textbox id="security-public-key-pinning-value"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox id="security-info-certificate"
|
||||
class="tabpanel-summary-container">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.security.certificate"/>
|
||||
<vbox class="security-info-section">
|
||||
<vbox class="tabpanel-summary-container">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.subjectinfo.label" flex="1"/>
|
||||
</vbox>
|
||||
<vbox class="security-info-section">
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.certdetail.cn"/>
|
||||
<textbox id="security-cert-subject-cn"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.certdetail.o"/>
|
||||
<textbox id="security-cert-subject-o"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.certdetail.ou"/>
|
||||
<textbox id="security-cert-subject-ou"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox class="tabpanel-summary-container">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.issuerinfo.label"
|
||||
flex="1"/>
|
||||
</vbox>
|
||||
<vbox class="security-info-section">
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.certdetail.cn"/>
|
||||
<textbox id="security-cert-issuer-cn"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.certdetail.o"/>
|
||||
<textbox id="security-cert-issuer-o"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.certdetail.ou"/>
|
||||
<textbox id="security-cert-issuer-ou"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox class="tabpanel-summary-container">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.periodofvalidity.label"
|
||||
flex="1"/>
|
||||
</vbox>
|
||||
<vbox class="security-info-section">
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.begins"/>
|
||||
<textbox id="security-cert-validity-begins"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.expires"/>
|
||||
<textbox id="security-cert-validity-expires"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox class="tabpanel-summary-container">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.fingerprints.label"
|
||||
flex="1"/>
|
||||
</vbox>
|
||||
<vbox class="security-info-section">
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.certdetail.sha256fingerprint"/>
|
||||
<textbox id="security-cert-sha256-fingerprint"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
<hbox class="tabpanel-summary-container"
|
||||
align="baseline">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=certmgr.certdetail.sha1fingerprint"/>
|
||||
<textbox id="security-cert-sha1-fingerprint"
|
||||
class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
|
||||
flex="1"
|
||||
readonly="true"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel id="preview-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<html:iframe id="response-preview"
|
||||
frameborder="0"
|
||||
sandbox=""/>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
</deck>
|
||||
</hbox>
|
||||
|
||||
</vbox>
|
||||
|
||||
<box id="network-statistics-view">
|
||||
<toolbar id="network-statistics-toolbar"
|
||||
class="devtools-toolbar">
|
||||
<button id="network-statistics-back-button"
|
||||
class="devtools-toolbarbutton"
|
||||
data-localization="label=netmonitor.backButton"/>
|
||||
</toolbar>
|
||||
<box id="network-statistics-charts"
|
||||
class="devtools-responsive-container"
|
||||
flex="1">
|
||||
<vbox id="primed-cache-chart" pack="center" flex="1"/>
|
||||
<splitter id="network-statistics-view-splitter"
|
||||
class="devtools-side-splitter"/>
|
||||
<vbox id="empty-cache-chart" pack="center" flex="1"/>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
</deck>
|
||||
|
||||
</window>
|
||||
77
devtools/client/netmonitor/panel.js
Normal file
77
devtools/client/netmonitor/panel.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const promise = require("promise");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const { localizeMarkup } = require("devtools/shared/l10n");
|
||||
|
||||
function NetMonitorPanel(iframeWindow, toolbox) {
|
||||
this.panelWin = iframeWindow;
|
||||
this.panelDoc = iframeWindow.document;
|
||||
this._toolbox = toolbox;
|
||||
|
||||
this._view = this.panelWin.NetMonitorView;
|
||||
this._controller = this.panelWin.NetMonitorController;
|
||||
this._controller._target = this.target;
|
||||
this._controller._toolbox = this._toolbox;
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.NetMonitorPanel = NetMonitorPanel;
|
||||
|
||||
NetMonitorPanel.prototype = {
|
||||
/**
|
||||
* Open is effectively an asynchronous constructor.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved when the NetMonitor completes opening.
|
||||
*/
|
||||
open: Task.async(function* () {
|
||||
if (this._opening) {
|
||||
return this._opening;
|
||||
}
|
||||
// Localize all the nodes containing a data-localization attribute.
|
||||
localizeMarkup(this.panelDoc);
|
||||
|
||||
let deferred = promise.defer();
|
||||
this._opening = deferred.promise;
|
||||
|
||||
// Local monitoring needs to make the target remote.
|
||||
if (!this.target.isRemote) {
|
||||
yield this.target.makeRemote();
|
||||
}
|
||||
|
||||
yield this._controller.startupNetMonitor();
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
|
||||
deferred.resolve(this);
|
||||
return this._opening;
|
||||
}),
|
||||
|
||||
// DevToolPanel API
|
||||
|
||||
get target() {
|
||||
return this._toolbox.target;
|
||||
},
|
||||
|
||||
destroy: Task.async(function* () {
|
||||
if (this._destroying) {
|
||||
return this._destroying;
|
||||
}
|
||||
let deferred = promise.defer();
|
||||
this._destroying = deferred.promise;
|
||||
|
||||
yield this._controller.shutdownNetMonitor();
|
||||
this.emit("destroyed");
|
||||
|
||||
deferred.resolve();
|
||||
return this._destroying;
|
||||
})
|
||||
};
|
||||
265
devtools/client/netmonitor/performance-statistics-view.js
Normal file
265
devtools/client/netmonitor/performance-statistics-view.js
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* import-globals-from ./netmonitor-controller.js */
|
||||
/* globals $ */
|
||||
"use strict";
|
||||
|
||||
const {PluralForm} = require("devtools/shared/plural-form");
|
||||
const {Filters} = require("./filter-predicates");
|
||||
const {L10N} = require("./l10n");
|
||||
const Actions = require("./actions/index");
|
||||
|
||||
const REQUEST_TIME_DECIMALS = 2;
|
||||
const CONTENT_SIZE_DECIMALS = 2;
|
||||
|
||||
// px
|
||||
const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
|
||||
|
||||
/**
|
||||
* Functions handling the performance statistics view.
|
||||
*/
|
||||
function PerformanceStatisticsView() {
|
||||
}
|
||||
|
||||
PerformanceStatisticsView.prototype = {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function (store) {
|
||||
this.store = store;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes and displays empty charts in this container.
|
||||
*/
|
||||
displayPlaceholderCharts: function () {
|
||||
this._createChart({
|
||||
id: "#primed-cache-chart",
|
||||
title: "charts.cacheEnabled"
|
||||
});
|
||||
this._createChart({
|
||||
id: "#empty-cache-chart",
|
||||
title: "charts.cacheDisabled"
|
||||
});
|
||||
window.emit(EVENTS.PLACEHOLDER_CHARTS_DISPLAYED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates and displays the primed cache chart in this container.
|
||||
*
|
||||
* @param array items
|
||||
* @see this._sanitizeChartDataSource
|
||||
*/
|
||||
createPrimedCacheChart: function (items) {
|
||||
this._createChart({
|
||||
id: "#primed-cache-chart",
|
||||
title: "charts.cacheEnabled",
|
||||
data: this._sanitizeChartDataSource(items),
|
||||
strings: this._commonChartStrings,
|
||||
totals: this._commonChartTotals,
|
||||
sorted: true
|
||||
});
|
||||
window.emit(EVENTS.PRIMED_CACHE_CHART_DISPLAYED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates and displays the empty cache chart in this container.
|
||||
*
|
||||
* @param array items
|
||||
* @see this._sanitizeChartDataSource
|
||||
*/
|
||||
createEmptyCacheChart: function (items) {
|
||||
this._createChart({
|
||||
id: "#empty-cache-chart",
|
||||
title: "charts.cacheDisabled",
|
||||
data: this._sanitizeChartDataSource(items, true),
|
||||
strings: this._commonChartStrings,
|
||||
totals: this._commonChartTotals,
|
||||
sorted: true
|
||||
});
|
||||
window.emit(EVENTS.EMPTY_CACHE_CHART_DISPLAYED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Common stringifier predicates used for items and totals in both the
|
||||
* "primed" and "empty" cache charts.
|
||||
*/
|
||||
_commonChartStrings: {
|
||||
size: value => {
|
||||
let string = L10N.numberWithDecimals(value / 1024, CONTENT_SIZE_DECIMALS);
|
||||
return L10N.getFormatStr("charts.sizeKB", string);
|
||||
},
|
||||
time: value => {
|
||||
let string = L10N.numberWithDecimals(value / 1000, REQUEST_TIME_DECIMALS);
|
||||
return L10N.getFormatStr("charts.totalS", string);
|
||||
}
|
||||
},
|
||||
_commonChartTotals: {
|
||||
size: total => {
|
||||
let string = L10N.numberWithDecimals(total / 1024, CONTENT_SIZE_DECIMALS);
|
||||
return L10N.getFormatStr("charts.totalSize", string);
|
||||
},
|
||||
time: total => {
|
||||
let seconds = total / 1000;
|
||||
let string = L10N.numberWithDecimals(seconds, REQUEST_TIME_DECIMALS);
|
||||
return PluralForm.get(seconds,
|
||||
L10N.getStr("charts.totalSeconds")).replace("#1", string);
|
||||
},
|
||||
cached: total => {
|
||||
return L10N.getFormatStr("charts.totalCached", total);
|
||||
},
|
||||
count: total => {
|
||||
return L10N.getFormatStr("charts.totalCount", total);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a specific chart to this container.
|
||||
*
|
||||
* @param object
|
||||
* An object containing all or some the following properties:
|
||||
* - id: either "#primed-cache-chart" or "#empty-cache-chart"
|
||||
* - title/data/strings/totals/sorted: @see Chart.jsm for details
|
||||
*/
|
||||
_createChart: function ({ id, title, data, strings, totals, sorted }) {
|
||||
let container = $(id);
|
||||
|
||||
// Nuke all existing charts of the specified type.
|
||||
while (container.hasChildNodes()) {
|
||||
container.firstChild.remove();
|
||||
}
|
||||
|
||||
// Create a new chart.
|
||||
let chart = Chart.PieTable(document, {
|
||||
diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER,
|
||||
title: L10N.getStr(title),
|
||||
data: data,
|
||||
strings: strings,
|
||||
totals: totals,
|
||||
sorted: sorted
|
||||
});
|
||||
|
||||
chart.on("click", (_, item) => {
|
||||
// Reset FilterButtons and enable one filter exclusively
|
||||
this.store.dispatch(Actions.enableFilterTypeOnly(item.label));
|
||||
NetMonitorView.showNetworkInspectorView();
|
||||
});
|
||||
|
||||
container.appendChild(chart.node);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sanitizes the data source used for creating charts, to follow the
|
||||
* data format spec defined in Chart.jsm.
|
||||
*
|
||||
* @param array items
|
||||
* A collection of request items used as the data source for the chart.
|
||||
* @param boolean emptyCache
|
||||
* True if the cache is considered enabled, false for disabled.
|
||||
*/
|
||||
_sanitizeChartDataSource: function (items, emptyCache) {
|
||||
let data = [
|
||||
"html", "css", "js", "xhr", "fonts", "images", "media", "flash", "ws", "other"
|
||||
].map(e => ({
|
||||
cached: 0,
|
||||
count: 0,
|
||||
label: e,
|
||||
size: 0,
|
||||
time: 0
|
||||
}));
|
||||
|
||||
for (let requestItem of items) {
|
||||
let details = requestItem.attachment;
|
||||
let type;
|
||||
|
||||
if (Filters.html(details)) {
|
||||
// "html"
|
||||
type = 0;
|
||||
} else if (Filters.css(details)) {
|
||||
// "css"
|
||||
type = 1;
|
||||
} else if (Filters.js(details)) {
|
||||
// "js"
|
||||
type = 2;
|
||||
} else if (Filters.fonts(details)) {
|
||||
// "fonts"
|
||||
type = 4;
|
||||
} else if (Filters.images(details)) {
|
||||
// "images"
|
||||
type = 5;
|
||||
} else if (Filters.media(details)) {
|
||||
// "media"
|
||||
type = 6;
|
||||
} else if (Filters.flash(details)) {
|
||||
// "flash"
|
||||
type = 7;
|
||||
} else if (Filters.ws(details)) {
|
||||
// "ws"
|
||||
type = 8;
|
||||
} else if (Filters.xhr(details)) {
|
||||
// Verify XHR last, to categorize other mime types in their own blobs.
|
||||
// "xhr"
|
||||
type = 3;
|
||||
} else {
|
||||
// "other"
|
||||
type = 9;
|
||||
}
|
||||
|
||||
if (emptyCache || !responseIsFresh(details)) {
|
||||
data[type].time += details.totalTime || 0;
|
||||
data[type].size += details.contentSize || 0;
|
||||
} else {
|
||||
data[type].cached++;
|
||||
}
|
||||
data[type].count++;
|
||||
}
|
||||
|
||||
return data.filter(e => e.count > 0);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the "Expiration Calculations" defined in section 13.2.4 of the
|
||||
* "HTTP/1.1: Caching in HTTP" spec holds true for a collection of headers.
|
||||
*
|
||||
* @param object
|
||||
* An object containing the { responseHeaders, status } properties.
|
||||
* @return boolean
|
||||
* True if the response is fresh and loaded from cache.
|
||||
*/
|
||||
function responseIsFresh({ responseHeaders, status }) {
|
||||
// Check for a "304 Not Modified" status and response headers availability.
|
||||
if (status != 304 || !responseHeaders) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let list = responseHeaders.headers;
|
||||
let cacheControl = list.filter(e => {
|
||||
return e.name.toLowerCase() == "cache-control";
|
||||
})[0];
|
||||
|
||||
let expires = list.filter(e => e.name.toLowerCase() == "expires")[0];
|
||||
|
||||
// Check the "Cache-Control" header for a maximum age value.
|
||||
if (cacheControl) {
|
||||
let maxAgeMatch =
|
||||
cacheControl.value.match(/s-maxage\s*=\s*(\d+)/) ||
|
||||
cacheControl.value.match(/max-age\s*=\s*(\d+)/);
|
||||
|
||||
if (maxAgeMatch && maxAgeMatch.pop() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the "Expires" header for a valid date.
|
||||
if (expires && Date.parse(expires.value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
exports.PerformanceStatisticsView = PerformanceStatisticsView;
|
||||
14
devtools/client/netmonitor/prefs.js
Normal file
14
devtools/client/netmonitor/prefs.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"use strict";
|
||||
|
||||
const {PrefsHelper} = require("devtools/client/shared/prefs");
|
||||
|
||||
/**
|
||||
* Shortcuts for accessing various network monitor preferences.
|
||||
*/
|
||||
|
||||
exports.Prefs = new PrefsHelper("devtools.netmonitor", {
|
||||
networkDetailsWidth: ["Int", "panes-network-details-width"],
|
||||
networkDetailsHeight: ["Int", "panes-network-details-height"],
|
||||
statistics: ["Bool", "statistics"],
|
||||
filters: ["Json", "filters"]
|
||||
});
|
||||
80
devtools/client/netmonitor/reducers/filters.js
Normal file
80
devtools/client/netmonitor/reducers/filters.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const I = require("devtools/client/shared/vendor/immutable");
|
||||
const {
|
||||
TOGGLE_FILTER_TYPE,
|
||||
ENABLE_FILTER_TYPE_ONLY,
|
||||
SET_FILTER_TEXT,
|
||||
} = require("../constants");
|
||||
|
||||
const FilterTypes = I.Record({
|
||||
all: false,
|
||||
html: false,
|
||||
css: false,
|
||||
js: false,
|
||||
xhr: false,
|
||||
fonts: false,
|
||||
images: false,
|
||||
media: false,
|
||||
flash: false,
|
||||
ws: false,
|
||||
other: false,
|
||||
});
|
||||
|
||||
const Filters = I.Record({
|
||||
types: new FilterTypes({ all: true }),
|
||||
url: "",
|
||||
});
|
||||
|
||||
function toggleFilterType(state, action) {
|
||||
let { filter } = action;
|
||||
let newState;
|
||||
|
||||
// Ignore unknown filter type
|
||||
if (!state.has(filter)) {
|
||||
return state;
|
||||
}
|
||||
if (filter === "all") {
|
||||
return new FilterTypes({ all: true });
|
||||
}
|
||||
|
||||
newState = state.withMutations(types => {
|
||||
types.set("all", false);
|
||||
types.set(filter, !state.get(filter));
|
||||
});
|
||||
|
||||
if (!newState.includes(true)) {
|
||||
newState = new FilterTypes({ all: true });
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
function enableFilterTypeOnly(state, action) {
|
||||
let { filter } = action;
|
||||
|
||||
// Ignore unknown filter type
|
||||
if (!state.has(filter)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return new FilterTypes({ [filter]: true });
|
||||
}
|
||||
|
||||
function filters(state = new Filters(), action) {
|
||||
switch (action.type) {
|
||||
case TOGGLE_FILTER_TYPE:
|
||||
return state.set("types", toggleFilterType(state.types, action));
|
||||
case ENABLE_FILTER_TYPE_ONLY:
|
||||
return state.set("types", enableFilterTypeOnly(state.types, action));
|
||||
case SET_FILTER_TEXT:
|
||||
return state.set("url", action.url);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = filters;
|
||||
13
devtools/client/netmonitor/reducers/index.js
Normal file
13
devtools/client/netmonitor/reducers/index.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { combineReducers } = require("devtools/client/shared/vendor/redux");
|
||||
const filters = require("./filters");
|
||||
const sidebar = require("./sidebar");
|
||||
|
||||
module.exports = combineReducers({
|
||||
filters,
|
||||
sidebar,
|
||||
});
|
||||
10
devtools/client/netmonitor/reducers/moz.build
Normal file
10
devtools/client/netmonitor/reducers/moz.build
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'filters.js',
|
||||
'index.js',
|
||||
'sidebar.js',
|
||||
)
|
||||
43
devtools/client/netmonitor/reducers/sidebar.js
Normal file
43
devtools/client/netmonitor/reducers/sidebar.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const I = require("devtools/client/shared/vendor/immutable");
|
||||
const {
|
||||
DISABLE_TOGGLE_BUTTON,
|
||||
SHOW_SIDEBAR,
|
||||
TOGGLE_SIDEBAR,
|
||||
} = require("../constants");
|
||||
|
||||
const SidebarState = I.Record({
|
||||
toggleButtonDisabled: true,
|
||||
visible: false,
|
||||
});
|
||||
|
||||
function disableToggleButton(state, action) {
|
||||
return state.set("toggleButtonDisabled", action.disabled);
|
||||
}
|
||||
|
||||
function showSidebar(state, action) {
|
||||
return state.set("visible", action.visible);
|
||||
}
|
||||
|
||||
function toggleSidebar(state, action) {
|
||||
return state.set("visible", !state.visible);
|
||||
}
|
||||
|
||||
function sidebar(state = new SidebarState(), action) {
|
||||
switch (action.type) {
|
||||
case DISABLE_TOGGLE_BUTTON:
|
||||
return disableToggleButton(state, action);
|
||||
case SHOW_SIDEBAR:
|
||||
return showSidebar(state, action);
|
||||
case TOGGLE_SIDEBAR:
|
||||
return toggleSidebar(state, action);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = sidebar;
|
||||
357
devtools/client/netmonitor/request-list-context-menu.js
Normal file
357
devtools/client/netmonitor/request-list-context-menu.js
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
/* 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 NetMonitorController, NetMonitorView, gNetwork */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const { Curl } = require("devtools/client/shared/curl");
|
||||
const { gDevTools } = require("devtools/client/framework/devtools");
|
||||
const Menu = require("devtools/client/framework/menu");
|
||||
const MenuItem = require("devtools/client/framework/menu-item");
|
||||
const { L10N } = require("./l10n");
|
||||
const { formDataURI, getFormDataSections } = require("./request-utils");
|
||||
|
||||
loader.lazyRequireGetter(this, "HarExporter",
|
||||
"devtools/client/netmonitor/har/har-exporter", true);
|
||||
|
||||
loader.lazyServiceGetter(this, "clipboardHelper",
|
||||
"@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
|
||||
|
||||
loader.lazyRequireGetter(this, "NetworkHelper",
|
||||
"devtools/shared/webconsole/network-helper");
|
||||
|
||||
function RequestListContextMenu() {}
|
||||
|
||||
RequestListContextMenu.prototype = {
|
||||
get selectedItem() {
|
||||
return NetMonitorView.RequestsMenu.selectedItem;
|
||||
},
|
||||
|
||||
get items() {
|
||||
return NetMonitorView.RequestsMenu.items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the context menu opening. Hide items if no request is selected.
|
||||
* Since visible attribute only accept boolean value but the method call may
|
||||
* return undefined, we use !! to force convert any object to boolean
|
||||
*/
|
||||
open({ screenX = 0, screenY = 0 } = {}) {
|
||||
let selectedItem = this.selectedItem;
|
||||
|
||||
let menu = new Menu();
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-copy-url",
|
||||
label: L10N.getStr("netmonitor.context.copyUrl"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
|
||||
visible: !!selectedItem,
|
||||
click: () => this.copyUrl(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-copy-url-params",
|
||||
label: L10N.getStr("netmonitor.context.copyUrlParams"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
|
||||
visible: !!(selectedItem &&
|
||||
NetworkHelper.nsIURL(selectedItem.attachment.url).query),
|
||||
click: () => this.copyUrlParams(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-copy-post-data",
|
||||
label: L10N.getStr("netmonitor.context.copyPostData"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
|
||||
visible: !!(selectedItem && selectedItem.attachment.requestPostData),
|
||||
click: () => this.copyPostData(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-copy-as-curl",
|
||||
label: L10N.getStr("netmonitor.context.copyAsCurl"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
|
||||
visible: !!(selectedItem && selectedItem.attachment),
|
||||
click: () => this.copyAsCurl(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
type: "separator",
|
||||
visible: !!selectedItem,
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-copy-request-headers",
|
||||
label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
|
||||
visible: !!(selectedItem && selectedItem.attachment.requestHeaders),
|
||||
click: () => this.copyRequestHeaders(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "response-menu-context-copy-response-headers",
|
||||
label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"),
|
||||
visible: !!(selectedItem && selectedItem.attachment.responseHeaders),
|
||||
click: () => this.copyResponseHeaders(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-copy-response",
|
||||
label: L10N.getStr("netmonitor.context.copyResponse"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
|
||||
visible: !!(selectedItem &&
|
||||
selectedItem.attachment.responseContent &&
|
||||
selectedItem.attachment.responseContent.content.text &&
|
||||
selectedItem.attachment.responseContent.content.text.length !== 0),
|
||||
click: () => this.copyResponse(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-copy-image-as-data-uri",
|
||||
label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
|
||||
visible: !!(selectedItem &&
|
||||
selectedItem.attachment.responseContent &&
|
||||
selectedItem.attachment.responseContent.content
|
||||
.mimeType.includes("image/")),
|
||||
click: () => this.copyImageAsDataUri(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
type: "separator",
|
||||
visible: !!selectedItem,
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-copy-all-as-har",
|
||||
label: L10N.getStr("netmonitor.context.copyAllAsHar"),
|
||||
accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
|
||||
visible: !!this.items.length,
|
||||
click: () => this.copyAllAsHar(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-save-all-as-har",
|
||||
label: L10N.getStr("netmonitor.context.saveAllAsHar"),
|
||||
accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
|
||||
visible: !!this.items.length,
|
||||
click: () => this.saveAllAsHar(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
type: "separator",
|
||||
visible: !!selectedItem,
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-resend",
|
||||
label: L10N.getStr("netmonitor.context.editAndResend"),
|
||||
accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
|
||||
visible: !!(NetMonitorController.supportsCustomRequest &&
|
||||
selectedItem &&
|
||||
!selectedItem.attachment.isCustom),
|
||||
click: () => NetMonitorView.RequestsMenu.cloneSelectedRequest(),
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
type: "separator",
|
||||
visible: !!selectedItem,
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-newtab",
|
||||
label: L10N.getStr("netmonitor.context.newTab"),
|
||||
accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
|
||||
visible: !!selectedItem,
|
||||
click: () => this.openRequestInTab()
|
||||
}));
|
||||
|
||||
menu.append(new MenuItem({
|
||||
id: "request-menu-context-perf",
|
||||
label: L10N.getStr("netmonitor.context.perfTools"),
|
||||
accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
|
||||
visible: !!NetMonitorController.supportsPerfStats,
|
||||
click: () => NetMonitorView.toggleFrontendMode()
|
||||
}));
|
||||
|
||||
menu.popup(screenX, screenY, NetMonitorController._toolbox);
|
||||
return menu;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens selected item in a new tab.
|
||||
*/
|
||||
openRequestInTab() {
|
||||
let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
|
||||
let { url } = this.selectedItem.attachment;
|
||||
win.openUILinkIn(url, "tab", { relatedToCurrent: true });
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the request url from the currently selected item.
|
||||
*/
|
||||
copyUrl() {
|
||||
clipboardHelper.copyString(this.selectedItem.attachment.url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the request url query string parameters from the currently
|
||||
* selected item.
|
||||
*/
|
||||
copyUrlParams() {
|
||||
let { url } = this.selectedItem.attachment;
|
||||
let params = NetworkHelper.nsIURL(url).query.split("&");
|
||||
let string = params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
|
||||
clipboardHelper.copyString(string);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the request form data parameters (or raw payload) from
|
||||
* the currently selected item.
|
||||
*/
|
||||
copyPostData: Task.async(function* () {
|
||||
let selected = this.selectedItem.attachment;
|
||||
|
||||
// Try to extract any form data parameters.
|
||||
let formDataSections = yield getFormDataSections(
|
||||
selected.requestHeaders,
|
||||
selected.requestHeadersFromUploadStream,
|
||||
selected.requestPostData,
|
||||
gNetwork.getString.bind(gNetwork));
|
||||
|
||||
let params = [];
|
||||
formDataSections.forEach(section => {
|
||||
let paramsArray = NetworkHelper.parseQueryString(section);
|
||||
if (paramsArray) {
|
||||
params = [...params, ...paramsArray];
|
||||
}
|
||||
});
|
||||
|
||||
let string = params
|
||||
.map(param => param.name + (param.value ? "=" + param.value : ""))
|
||||
.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
|
||||
|
||||
// Fall back to raw payload.
|
||||
if (!string) {
|
||||
let postData = selected.requestPostData.postData.text;
|
||||
string = yield gNetwork.getString(postData);
|
||||
if (Services.appinfo.OS !== "WINNT") {
|
||||
string = string.replace(/\r/g, "");
|
||||
}
|
||||
}
|
||||
|
||||
clipboardHelper.copyString(string);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Copy a cURL command from the currently selected item.
|
||||
*/
|
||||
copyAsCurl: Task.async(function* () {
|
||||
let selected = this.selectedItem.attachment;
|
||||
|
||||
// Create a sanitized object for the Curl command generator.
|
||||
let data = {
|
||||
url: selected.url,
|
||||
method: selected.method,
|
||||
headers: [],
|
||||
httpVersion: selected.httpVersion,
|
||||
postDataText: null
|
||||
};
|
||||
|
||||
// Fetch header values.
|
||||
for (let { name, value } of selected.requestHeaders.headers) {
|
||||
let text = yield gNetwork.getString(value);
|
||||
data.headers.push({ name: name, value: text });
|
||||
}
|
||||
|
||||
// Fetch the request payload.
|
||||
if (selected.requestPostData) {
|
||||
let postData = selected.requestPostData.postData.text;
|
||||
data.postDataText = yield gNetwork.getString(postData);
|
||||
}
|
||||
|
||||
clipboardHelper.copyString(Curl.generateCommand(data));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Copy the raw request headers from the currently selected item.
|
||||
*/
|
||||
copyRequestHeaders() {
|
||||
let selected = this.selectedItem.attachment;
|
||||
let rawHeaders = selected.requestHeaders.rawHeaders.trim();
|
||||
if (Services.appinfo.OS !== "WINNT") {
|
||||
rawHeaders = rawHeaders.replace(/\r/g, "");
|
||||
}
|
||||
clipboardHelper.copyString(rawHeaders);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the raw response headers from the currently selected item.
|
||||
*/
|
||||
copyResponseHeaders() {
|
||||
let selected = this.selectedItem.attachment;
|
||||
let rawHeaders = selected.responseHeaders.rawHeaders.trim();
|
||||
if (Services.appinfo.OS !== "WINNT") {
|
||||
rawHeaders = rawHeaders.replace(/\r/g, "");
|
||||
}
|
||||
clipboardHelper.copyString(rawHeaders);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy image as data uri.
|
||||
*/
|
||||
copyImageAsDataUri() {
|
||||
let selected = this.selectedItem.attachment;
|
||||
let { mimeType, text, encoding } = selected.responseContent.content;
|
||||
|
||||
gNetwork.getString(text).then(string => {
|
||||
let data = formDataURI(mimeType, encoding, string);
|
||||
clipboardHelper.copyString(data);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy response data as a string.
|
||||
*/
|
||||
copyResponse() {
|
||||
let selected = this.selectedItem.attachment;
|
||||
let text = selected.responseContent.content.text;
|
||||
|
||||
gNetwork.getString(text).then(string => {
|
||||
clipboardHelper.copyString(string);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy HAR from the network panel content to the clipboard.
|
||||
*/
|
||||
copyAllAsHar() {
|
||||
let options = this.getDefaultHarOptions();
|
||||
return HarExporter.copy(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Save HAR from the network panel content to a file.
|
||||
*/
|
||||
saveAllAsHar() {
|
||||
let options = this.getDefaultHarOptions();
|
||||
return HarExporter.save(options);
|
||||
},
|
||||
|
||||
getDefaultHarOptions() {
|
||||
let form = NetMonitorController._target.form;
|
||||
let title = form.title || form.url;
|
||||
|
||||
return {
|
||||
getString: gNetwork.getString.bind(gNetwork),
|
||||
view: NetMonitorView.RequestsMenu,
|
||||
items: NetMonitorView.RequestsMenu.items,
|
||||
title: title
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RequestListContextMenu;
|
||||
185
devtools/client/netmonitor/request-utils.js
Normal file
185
devtools/client/netmonitor/request-utils.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
"use strict";
|
||||
/* eslint-disable mozilla/reject-some-requires */
|
||||
const { Ci } = require("chrome");
|
||||
const { KeyCodes } = require("devtools/client/shared/keycodes");
|
||||
const { Task } = require("devtools/shared/task");
|
||||
const NetworkHelper = require("devtools/shared/webconsole/network-helper");
|
||||
|
||||
/**
|
||||
* Helper method to get a wrapped function which can be bound to as
|
||||
* an event listener directly and is executed only when data-key is
|
||||
* present in event.target.
|
||||
*
|
||||
* @param function callback
|
||||
* Function to execute execute when data-key
|
||||
* is present in event.target.
|
||||
* @param bool onlySpaceOrReturn
|
||||
* Flag to indicate if callback should only be called
|
||||
when the space or return button is pressed
|
||||
* @return function
|
||||
* Wrapped function with the target data-key as the first argument
|
||||
* and the event as the second argument.
|
||||
*/
|
||||
exports.getKeyWithEvent = function (callback, onlySpaceOrReturn) {
|
||||
return function (event) {
|
||||
let key = event.target.getAttribute("data-key");
|
||||
let filterKeyboardEvent = !onlySpaceOrReturn ||
|
||||
event.keyCode === KeyCodes.DOM_VK_SPACE ||
|
||||
event.keyCode === KeyCodes.DOM_VK_RETURN;
|
||||
|
||||
if (key && filterKeyboardEvent) {
|
||||
callback.call(null, key);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts any urlencoded form data sections (e.g. "?foo=bar&baz=42") from a
|
||||
* POST request.
|
||||
*
|
||||
* @param object headers
|
||||
* The "requestHeaders".
|
||||
* @param object uploadHeaders
|
||||
* The "requestHeadersFromUploadStream".
|
||||
* @param object postData
|
||||
* The "requestPostData".
|
||||
* @param object getString
|
||||
Callback to retrieve a string from a LongStringGrip.
|
||||
* @return array
|
||||
* A promise that is resolved with the extracted form data.
|
||||
*/
|
||||
exports.getFormDataSections = Task.async(function* (headers, uploadHeaders, postData,
|
||||
getString) {
|
||||
let formDataSections = [];
|
||||
|
||||
let { headers: requestHeaders } = headers;
|
||||
let { headers: payloadHeaders } = uploadHeaders;
|
||||
let allHeaders = [...payloadHeaders, ...requestHeaders];
|
||||
|
||||
let contentTypeHeader = allHeaders.find(e => {
|
||||
return e.name.toLowerCase() == "content-type";
|
||||
});
|
||||
|
||||
let contentTypeLongString = contentTypeHeader ? contentTypeHeader.value : "";
|
||||
|
||||
let contentType = yield getString(contentTypeLongString);
|
||||
|
||||
if (contentType.includes("x-www-form-urlencoded")) {
|
||||
let postDataLongString = postData.postData.text;
|
||||
let text = yield getString(postDataLongString);
|
||||
|
||||
for (let section of text.split(/\r\n|\r|\n/)) {
|
||||
// Before displaying it, make sure this section of the POST data
|
||||
// isn't a line containing upload stream headers.
|
||||
if (payloadHeaders.every(header => !section.startsWith(header.name))) {
|
||||
formDataSections.push(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formDataSections;
|
||||
});
|
||||
|
||||
/**
|
||||
* Form a data: URI given a mime type, encoding, and some text.
|
||||
*
|
||||
* @param {String} mimeType the mime type
|
||||
* @param {String} encoding the encoding to use; if not set, the
|
||||
* text will be base64-encoded.
|
||||
* @param {String} text the text of the URI.
|
||||
* @return {String} a data: URI
|
||||
*/
|
||||
exports.formDataURI = function (mimeType, encoding, text) {
|
||||
if (!encoding) {
|
||||
encoding = "base64";
|
||||
text = btoa(text);
|
||||
}
|
||||
return "data:" + mimeType + ";" + encoding + "," + text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Write out a list of headers into a chunk of text
|
||||
*
|
||||
* @param array headers
|
||||
* Array of headers info {name, value}
|
||||
* @return string text
|
||||
* List of headers in text format
|
||||
*/
|
||||
exports.writeHeaderText = function (headers) {
|
||||
return headers.map(({name, value}) => name + ": " + value).join("\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for getting an abbreviated string for a mime type.
|
||||
*
|
||||
* @param string mimeType
|
||||
* @return string
|
||||
*/
|
||||
exports.getAbbreviatedMimeType = function (mimeType) {
|
||||
if (!mimeType) {
|
||||
return "";
|
||||
}
|
||||
return (mimeType.split(";")[0].split("/")[1] || "").split("+")[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helpers for getting details about an nsIURL.
|
||||
*
|
||||
* @param nsIURL | string url
|
||||
* @return string
|
||||
*/
|
||||
exports.getUriNameWithQuery = function (url) {
|
||||
if (!(url instanceof Ci.nsIURL)) {
|
||||
url = NetworkHelper.nsIURL(url);
|
||||
}
|
||||
|
||||
let name = NetworkHelper.convertToUnicode(
|
||||
unescape(url.fileName || url.filePath || "/"));
|
||||
let query = NetworkHelper.convertToUnicode(unescape(url.query));
|
||||
|
||||
return name + (query ? "?" + query : "");
|
||||
};
|
||||
|
||||
exports.getUriHostPort = function (url) {
|
||||
if (!(url instanceof Ci.nsIURL)) {
|
||||
url = NetworkHelper.nsIURL(url);
|
||||
}
|
||||
return NetworkHelper.convertToUnicode(unescape(url.hostPort));
|
||||
};
|
||||
|
||||
exports.getUriHost = function (url) {
|
||||
return exports.getUriHostPort(url).replace(/:\d+$/, "");
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a nsIContentPolicy constant to a display string
|
||||
*/
|
||||
const LOAD_CAUSE_STRINGS = {
|
||||
[Ci.nsIContentPolicy.TYPE_INVALID]: "invalid",
|
||||
[Ci.nsIContentPolicy.TYPE_OTHER]: "other",
|
||||
[Ci.nsIContentPolicy.TYPE_SCRIPT]: "script",
|
||||
[Ci.nsIContentPolicy.TYPE_IMAGE]: "img",
|
||||
[Ci.nsIContentPolicy.TYPE_STYLESHEET]: "stylesheet",
|
||||
[Ci.nsIContentPolicy.TYPE_OBJECT]: "object",
|
||||
[Ci.nsIContentPolicy.TYPE_DOCUMENT]: "document",
|
||||
[Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "subdocument",
|
||||
[Ci.nsIContentPolicy.TYPE_REFRESH]: "refresh",
|
||||
[Ci.nsIContentPolicy.TYPE_XBL]: "xbl",
|
||||
[Ci.nsIContentPolicy.TYPE_PING]: "ping",
|
||||
[Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "xhr",
|
||||
[Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "objectSubdoc",
|
||||
[Ci.nsIContentPolicy.TYPE_DTD]: "dtd",
|
||||
[Ci.nsIContentPolicy.TYPE_FONT]: "font",
|
||||
[Ci.nsIContentPolicy.TYPE_MEDIA]: "media",
|
||||
[Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket",
|
||||
[Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp",
|
||||
[Ci.nsIContentPolicy.TYPE_XSLT]: "xslt",
|
||||
[Ci.nsIContentPolicy.TYPE_BEACON]: "beacon",
|
||||
[Ci.nsIContentPolicy.TYPE_FETCH]: "fetch",
|
||||
[Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset",
|
||||
[Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest"
|
||||
};
|
||||
|
||||
exports.loadCauseString = function (causeType) {
|
||||
return LOAD_CAUSE_STRINGS[causeType] || "unknown";
|
||||
};
|
||||
1649
devtools/client/netmonitor/requests-menu-view.js
Normal file
1649
devtools/client/netmonitor/requests-menu-view.js
Normal file
File diff suppressed because it is too large
Load diff
8
devtools/client/netmonitor/selectors/index.js
Normal file
8
devtools/client/netmonitor/selectors/index.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// selectors...
|
||||
};
|
||||
8
devtools/client/netmonitor/selectors/moz.build
Normal file
8
devtools/client/netmonitor/selectors/moz.build
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DevToolsModules(
|
||||
'index.js'
|
||||
)
|
||||
92
devtools/client/netmonitor/sort-predicates.js
Normal file
92
devtools/client/netmonitor/sort-predicates.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"use strict";
|
||||
|
||||
const { getAbbreviatedMimeType,
|
||||
getUriNameWithQuery,
|
||||
getUriHostPort,
|
||||
loadCauseString } = require("./request-utils");
|
||||
|
||||
/**
|
||||
* Predicates used when sorting items.
|
||||
*
|
||||
* @param object first
|
||||
* The first item used in the comparison.
|
||||
* @param object second
|
||||
* The second item used in the comparison.
|
||||
* @return number
|
||||
* <0 to sort first to a lower index than second
|
||||
* =0 to leave first and second unchanged with respect to each other
|
||||
* >0 to sort second to a lower index than first
|
||||
*/
|
||||
|
||||
function waterfall(first, second) {
|
||||
return first.startedMillis - second.startedMillis;
|
||||
}
|
||||
|
||||
function status(first, second) {
|
||||
return first.status == second.status
|
||||
? first.startedMillis - second.startedMillis
|
||||
: first.status - second.status;
|
||||
}
|
||||
|
||||
function method(first, second) {
|
||||
if (first.method == second.method) {
|
||||
return first.startedMillis - second.startedMillis;
|
||||
}
|
||||
return first.method > second.method ? 1 : -1;
|
||||
}
|
||||
|
||||
function file(first, second) {
|
||||
let firstUrl = getUriNameWithQuery(first.url).toLowerCase();
|
||||
let secondUrl = getUriNameWithQuery(second.url).toLowerCase();
|
||||
if (firstUrl == secondUrl) {
|
||||
return first.startedMillis - second.startedMillis;
|
||||
}
|
||||
return firstUrl > secondUrl ? 1 : -1;
|
||||
}
|
||||
|
||||
function domain(first, second) {
|
||||
let firstDomain = getUriHostPort(first.url).toLowerCase();
|
||||
let secondDomain = getUriHostPort(second.url).toLowerCase();
|
||||
if (firstDomain == secondDomain) {
|
||||
return first.startedMillis - second.startedMillis;
|
||||
}
|
||||
return firstDomain > secondDomain ? 1 : -1;
|
||||
}
|
||||
|
||||
function cause(first, second) {
|
||||
let firstCause = loadCauseString(first.cause.type);
|
||||
let secondCause = loadCauseString(second.cause.type);
|
||||
if (firstCause == secondCause) {
|
||||
return first.startedMillis - second.startedMillis;
|
||||
}
|
||||
return firstCause > secondCause ? 1 : -1;
|
||||
}
|
||||
|
||||
function type(first, second) {
|
||||
let firstType = getAbbreviatedMimeType(first.mimeType).toLowerCase();
|
||||
let secondType = getAbbreviatedMimeType(second.mimeType).toLowerCase();
|
||||
if (firstType == secondType) {
|
||||
return first.startedMillis - second.startedMillis;
|
||||
}
|
||||
return firstType > secondType ? 1 : -1;
|
||||
}
|
||||
|
||||
function transferred(first, second) {
|
||||
return first.transferredSize - second.transferredSize;
|
||||
}
|
||||
|
||||
function size(first, second) {
|
||||
return first.contentSize - second.contentSize;
|
||||
}
|
||||
|
||||
exports.Sorters = {
|
||||
status,
|
||||
method,
|
||||
file,
|
||||
domain,
|
||||
cause,
|
||||
type,
|
||||
transferred,
|
||||
size,
|
||||
waterfall,
|
||||
};
|
||||
13
devtools/client/netmonitor/store.js
Normal file
13
devtools/client/netmonitor/store.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const createStore = require("devtools/client/shared/redux/create-store");
|
||||
const reducers = require("./reducers/index");
|
||||
|
||||
function configureStore() {
|
||||
return createStore()(reducers);
|
||||
}
|
||||
|
||||
exports.configureStore = configureStore;
|
||||
6
devtools/client/netmonitor/test/.eslintrc.js
Normal file
6
devtools/client/netmonitor/test/.eslintrc.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../.eslintrc.mochitests.js"
|
||||
};
|
||||
156
devtools/client/netmonitor/test/browser.ini
Normal file
156
devtools/client/netmonitor/test/browser.ini
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
dropmarker.svg
|
||||
head.js
|
||||
html_cause-test-page.html
|
||||
html_content-type-test-page.html
|
||||
html_content-type-without-cache-test-page.html
|
||||
html_brotli-test-page.html
|
||||
html_image-tooltip-test-page.html
|
||||
html_cors-test-page.html
|
||||
html_custom-get-page.html
|
||||
html_cyrillic-test-page.html
|
||||
html_frame-test-page.html
|
||||
html_frame-subdocument.html
|
||||
html_filter-test-page.html
|
||||
html_infinite-get-page.html
|
||||
html_json-custom-mime-test-page.html
|
||||
html_json-long-test-page.html
|
||||
html_json-malformed-test-page.html
|
||||
html_json-text-mime-test-page.html
|
||||
html_jsonp-test-page.html
|
||||
html_navigate-test-page.html
|
||||
html_params-test-page.html
|
||||
html_post-data-test-page.html
|
||||
html_post-json-test-page.html
|
||||
html_post-raw-test-page.html
|
||||
html_post-raw-with-headers-test-page.html
|
||||
html_simple-test-page.html
|
||||
html_single-get-page.html
|
||||
html_send-beacon.html
|
||||
html_sorting-test-page.html
|
||||
html_statistics-test-page.html
|
||||
html_status-codes-test-page.html
|
||||
html_api-calls-test-page.html
|
||||
html_copy-as-curl.html
|
||||
html_curl-utils.html
|
||||
sjs_content-type-test-server.sjs
|
||||
sjs_cors-test-server.sjs
|
||||
sjs_https-redirect-test-server.sjs
|
||||
sjs_hsts-test-server.sjs
|
||||
sjs_simple-test-server.sjs
|
||||
sjs_sorting-test-server.sjs
|
||||
sjs_status-codes-test-server.sjs
|
||||
sjs_truncate-test-server.sjs
|
||||
test-image.png
|
||||
service-workers/status-codes.html
|
||||
service-workers/status-codes-service-worker.js
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
|
||||
[browser_net_aaa_leaktest.js]
|
||||
[browser_net_accessibility-01.js]
|
||||
[browser_net_accessibility-02.js]
|
||||
skip-if = (toolkit == "cocoa" && e10s) # bug 1252254
|
||||
[browser_net_api-calls.js]
|
||||
[browser_net_autoscroll.js]
|
||||
skip-if = true # Bug 1309191 - replace with rewritten version in React
|
||||
[browser_net_cached-status.js]
|
||||
[browser_net_cause.js]
|
||||
[browser_net_cause_redirect.js]
|
||||
[browser_net_service-worker-status.js]
|
||||
[browser_net_charts-01.js]
|
||||
[browser_net_charts-02.js]
|
||||
[browser_net_charts-03.js]
|
||||
[browser_net_charts-04.js]
|
||||
[browser_net_charts-05.js]
|
||||
[browser_net_charts-06.js]
|
||||
[browser_net_charts-07.js]
|
||||
[browser_net_clear.js]
|
||||
[browser_net_complex-params.js]
|
||||
[browser_net_content-type.js]
|
||||
[browser_net_brotli.js]
|
||||
[browser_net_curl-utils.js]
|
||||
[browser_net_copy_image_as_data_uri.js]
|
||||
subsuite = clipboard
|
||||
[browser_net_copy_svg_image_as_data_uri.js]
|
||||
subsuite = clipboard
|
||||
[browser_net_copy_url.js]
|
||||
subsuite = clipboard
|
||||
[browser_net_copy_params.js]
|
||||
subsuite = clipboard
|
||||
[browser_net_copy_response.js]
|
||||
subsuite = clipboard
|
||||
[browser_net_copy_headers.js]
|
||||
subsuite = clipboard
|
||||
[browser_net_copy_as_curl.js]
|
||||
subsuite = clipboard
|
||||
[browser_net_cors_requests.js]
|
||||
[browser_net_cyrillic-01.js]
|
||||
[browser_net_cyrillic-02.js]
|
||||
[browser_net_details-no-duplicated-content.js]
|
||||
skip-if = (os == 'linux' && e10s && debug) # Bug 1242204
|
||||
[browser_net_frame.js]
|
||||
[browser_net_filter-01.js]
|
||||
[browser_net_filter-02.js]
|
||||
[browser_net_filter-03.js]
|
||||
[browser_net_filter-04.js]
|
||||
[browser_net_footer-summary.js]
|
||||
[browser_net_html-preview.js]
|
||||
[browser_net_icon-preview.js]
|
||||
[browser_net_image-tooltip.js]
|
||||
[browser_net_json-long.js]
|
||||
[browser_net_json-malformed.js]
|
||||
[browser_net_json_custom_mime.js]
|
||||
[browser_net_json_text_mime.js]
|
||||
[browser_net_jsonp.js]
|
||||
[browser_net_large-response.js]
|
||||
[browser_net_leak_on_tab_close.js]
|
||||
[browser_net_open_request_in_tab.js]
|
||||
[browser_net_page-nav.js]
|
||||
[browser_net_pane-collapse.js]
|
||||
[browser_net_pane-toggle.js]
|
||||
[browser_net_post-data-01.js]
|
||||
[browser_net_post-data-02.js]
|
||||
[browser_net_post-data-03.js]
|
||||
[browser_net_post-data-04.js]
|
||||
[browser_net_prefs-and-l10n.js]
|
||||
[browser_net_prefs-reload.js]
|
||||
[browser_net_raw_headers.js]
|
||||
[browser_net_reload-button.js]
|
||||
[browser_net_reload-markers.js]
|
||||
[browser_net_req-resp-bodies.js]
|
||||
[browser_net_resend_cors.js]
|
||||
[browser_net_resend_headers.js]
|
||||
[browser_net_resend.js]
|
||||
[browser_net_security-details.js]
|
||||
[browser_net_security-error.js]
|
||||
[browser_net_security-icon-click.js]
|
||||
[browser_net_security-redirect.js]
|
||||
[browser_net_security-state.js]
|
||||
[browser_net_security-tab-deselect.js]
|
||||
[browser_net_security-tab-visibility.js]
|
||||
[browser_net_security-warnings.js]
|
||||
[browser_net_send-beacon.js]
|
||||
[browser_net_send-beacon-other-tab.js]
|
||||
[browser_net_simple-init.js]
|
||||
[browser_net_simple-request-data.js]
|
||||
skip-if = true # Bug 1258809
|
||||
[browser_net_simple-request-details.js]
|
||||
skip-if = true # Bug 1258809
|
||||
[browser_net_simple-request.js]
|
||||
[browser_net_sort-01.js]
|
||||
skip-if = (e10s && debug && os == 'mac') # Bug 1253037
|
||||
[browser_net_sort-02.js]
|
||||
[browser_net_sort-03.js]
|
||||
[browser_net_statistics-01.js]
|
||||
[browser_net_statistics-02.js]
|
||||
[browser_net_statistics-03.js]
|
||||
[browser_net_status-codes.js]
|
||||
[browser_net_streaming-response.js]
|
||||
[browser_net_throttle.js]
|
||||
[browser_net_timeline_ticks.js]
|
||||
[browser_net_timing-division.js]
|
||||
[browser_net_truncate.js]
|
||||
[browser_net_persistent_logs.js]
|
||||
28
devtools/client/netmonitor/test/browser_net_aaa_leaktest.js
Normal file
28
devtools/client/netmonitor/test/browser_net_aaa_leaktest.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the network monitor leaks on initialization and sudden destruction.
|
||||
* You can also use this initialization format as a template for other tests.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, NetMonitorView, NetMonitorController } = monitor.panelWin;
|
||||
let { RequestsMenu, NetworkDetails } = NetMonitorView;
|
||||
|
||||
ok(tab, "Should have a tab available.");
|
||||
ok(monitor, "Should have a network monitor pane available.");
|
||||
|
||||
ok(document, "Should have a document available.");
|
||||
ok(NetMonitorView, "Should have a NetMonitorView object available.");
|
||||
ok(NetMonitorController, "Should have a NetMonitorController object available.");
|
||||
ok(RequestsMenu, "Should have a RequestsMenu object available.");
|
||||
ok(NetworkDetails, "Should have a NetworkDetails object available.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if focus modifiers work for the SideMenuWidget.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
// It seems that this test may be slow on Ubuntu builds running on ec2.
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let count = 0;
|
||||
function check(selectedIndex, paneVisibility) {
|
||||
info("Performing check " + (count++) + ".");
|
||||
|
||||
is(RequestsMenu.selectedIndex, selectedIndex,
|
||||
"The selected item in the requests menu was incorrect.");
|
||||
is(NetMonitorView.detailsPaneHidden, !paneVisibility,
|
||||
"The network requests details pane visibility state was incorrect.");
|
||||
}
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 2);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests(2);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
check(-1, false);
|
||||
|
||||
RequestsMenu.focusLastVisibleItem();
|
||||
check(1, true);
|
||||
RequestsMenu.focusFirstVisibleItem();
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusNextItem();
|
||||
check(1, true);
|
||||
RequestsMenu.focusPrevItem();
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusItemAtDelta(+1);
|
||||
check(1, true);
|
||||
RequestsMenu.focusItemAtDelta(-1);
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusItemAtDelta(+10);
|
||||
check(1, true);
|
||||
RequestsMenu.focusItemAtDelta(-10);
|
||||
check(0, true);
|
||||
|
||||
wait = waitForNetworkEvents(monitor, 18);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests(18);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
RequestsMenu.focusLastVisibleItem();
|
||||
check(19, true);
|
||||
RequestsMenu.focusFirstVisibleItem();
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusNextItem();
|
||||
check(1, true);
|
||||
RequestsMenu.focusPrevItem();
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusItemAtDelta(+10);
|
||||
check(10, true);
|
||||
RequestsMenu.focusItemAtDelta(-10);
|
||||
check(0, true);
|
||||
|
||||
RequestsMenu.focusItemAtDelta(+100);
|
||||
check(19, true);
|
||||
RequestsMenu.focusItemAtDelta(-100);
|
||||
check(0, true);
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
130
devtools/client/netmonitor/test/browser_net_accessibility-02.js
Normal file
130
devtools/client/netmonitor/test/browser_net_accessibility-02.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if keyboard and mouse navigation works in the network requests menu.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
// It seems that this test may be slow on Ubuntu builds running on ec2.
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let { window, $, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let count = 0;
|
||||
function check(selectedIndex, paneVisibility) {
|
||||
info("Performing check " + (count++) + ".");
|
||||
|
||||
is(RequestsMenu.selectedIndex, selectedIndex,
|
||||
"The selected item in the requests menu was incorrect.");
|
||||
is(NetMonitorView.detailsPaneHidden, !paneVisibility,
|
||||
"The network requests details pane visibility state was incorrect.");
|
||||
}
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 2);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests(2);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
check(-1, false);
|
||||
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("END", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
|
||||
wait = waitForNetworkEvents(monitor, 18);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests(18);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(2, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(4, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(8, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(4, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
|
||||
EventUtils.sendKey("END", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("END", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("END", window);
|
||||
check(19, true);
|
||||
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(15, true);
|
||||
EventUtils.sendKey("PAGE_UP", window);
|
||||
check(11, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(10, true);
|
||||
EventUtils.sendKey("UP", window);
|
||||
check(9, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(13, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(17, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("PAGE_DOWN", window);
|
||||
check(19, true);
|
||||
|
||||
EventUtils.sendKey("HOME", window);
|
||||
check(0, true);
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(1, true);
|
||||
EventUtils.sendKey("END", window);
|
||||
check(19, true);
|
||||
EventUtils.sendKey("DOWN", window);
|
||||
check(19, true);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
check(-1, false);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $(".side-menu-widget-item"));
|
||||
check(0, true);
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
39
devtools/client/netmonitor/test/browser_net_api-calls.js
Normal file
39
devtools/client/netmonitor/test/browser_net_api-calls.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests whether API call URLs (without a filename) are correctly displayed
|
||||
* (including Unicode)
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(API_CALLS_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
const REQUEST_URIS = [
|
||||
"http://example.com/api/fileName.xml",
|
||||
"http://example.com/api/file%E2%98%A2.xml",
|
||||
"http://example.com/api/ascii/get/",
|
||||
"http://example.com/api/unicode/%E2%98%A2/",
|
||||
"http://example.com/api/search/?q=search%E2%98%A2"
|
||||
];
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 5);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
REQUEST_URIS.forEach(function (uri, index) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(index), "GET", uri);
|
||||
});
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
75
devtools/client/netmonitor/test/browser_net_autoscroll.js
Normal file
75
devtools/client/netmonitor/test/browser_net_autoscroll.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Bug 863102 - Automatically scroll down upon new network requests.
|
||||
*/
|
||||
add_task(function* () {
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let { monitor } = yield initNetMonitor(INFINITE_GET_URL);
|
||||
let win = monitor.panelWin;
|
||||
let topNode = win.document.getElementById("requests-menu-contents");
|
||||
let requestsContainer = topNode.getElementsByTagName("scrollbox")[0];
|
||||
ok(!!requestsContainer, "Container element exists as expected.");
|
||||
|
||||
// (1) Check that the scroll position is maintained at the bottom
|
||||
// when the requests overflow the vertical size of the container.
|
||||
yield waitForRequestsToOverflowContainer();
|
||||
yield waitForScroll();
|
||||
ok(scrolledToBottom(requestsContainer), "Scrolled to bottom on overflow.");
|
||||
|
||||
// (2) Now set the scroll position somewhere in the middle and check
|
||||
// that additional requests do not change the scroll position.
|
||||
let children = requestsContainer.childNodes;
|
||||
let middleNode = children.item(children.length / 2);
|
||||
middleNode.scrollIntoView();
|
||||
ok(!scrolledToBottom(requestsContainer), "Not scrolled to bottom.");
|
||||
// save for comparison later
|
||||
let scrollTop = requestsContainer.scrollTop;
|
||||
yield waitForNetworkEvents(monitor, 8);
|
||||
yield waitSomeTime();
|
||||
is(requestsContainer.scrollTop, scrollTop, "Did not scroll.");
|
||||
|
||||
// (3) Now set the scroll position back at the bottom and check that
|
||||
// additional requests *do* cause the container to scroll down.
|
||||
requestsContainer.scrollTop = requestsContainer.scrollHeight;
|
||||
ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
|
||||
yield waitForNetworkEvents(monitor, 8);
|
||||
yield waitForScroll();
|
||||
ok(scrolledToBottom(requestsContainer), "Still scrolled to bottom.");
|
||||
|
||||
// (4) Now select an item in the list and check that additional requests
|
||||
// do not change the scroll position.
|
||||
monitor.panelWin.NetMonitorView.RequestsMenu.selectedIndex = 0;
|
||||
yield waitForNetworkEvents(monitor, 8);
|
||||
yield waitSomeTime();
|
||||
is(requestsContainer.scrollTop, 0, "Did not scroll.");
|
||||
|
||||
// Done: clean up.
|
||||
yield teardown(monitor);
|
||||
|
||||
function* waitForRequestsToOverflowContainer() {
|
||||
while (true) {
|
||||
yield waitForNetworkEvents(monitor, 1);
|
||||
if (requestsContainer.scrollHeight > requestsContainer.clientHeight) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scrolledToBottom(element) {
|
||||
return element.scrollTop + element.clientHeight >= element.scrollHeight;
|
||||
}
|
||||
|
||||
function waitSomeTime() {
|
||||
// Wait to make sure no scrolls happen
|
||||
return wait(50);
|
||||
}
|
||||
|
||||
function waitForScroll() {
|
||||
return monitor._view.RequestsMenu.widget.once("scroll-to-bottom");
|
||||
}
|
||||
});
|
||||
91
devtools/client/netmonitor/test/browser_net_brotli.js
Normal file
91
devtools/client/netmonitor/test/browser_net_brotli.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const BROTLI_URL = HTTPS_EXAMPLE_URL + "html_brotli-test-page.html";
|
||||
const BROTLI_REQUESTS = 1;
|
||||
|
||||
/**
|
||||
* Test brotli encoded response is handled correctly on HTTPS.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(BROTLI_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Editor, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, BROTLI_REQUESTS);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", HTTPS_CONTENT_TYPE_SJS + "?fmt=br", {
|
||||
status: 200,
|
||||
statusText: "Connected",
|
||||
type: "plain",
|
||||
fullMimeType: "text/plain",
|
||||
transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 10),
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64),
|
||||
time: true
|
||||
});
|
||||
|
||||
let onEvent = waitForResponseBodyDisplayed();
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
yield onEvent;
|
||||
yield testResponseTab("br");
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function* testResponseTab(type) {
|
||||
let tabEl = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tabEl.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
function checkVisibility(box) {
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), box != "json",
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), box != "textarea",
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), box != "image",
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "br": {
|
||||
checkVisibility("textarea");
|
||||
|
||||
let expected = "X".repeat(64);
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
is(editor.getText(), expected,
|
||||
"The text shown in the source editor is incorrect for the brotli request.");
|
||||
is(editor.getMode(), Editor.modes.text,
|
||||
"The mode active in the source editor is incorrect for the brotli request.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function waitForResponseBodyDisplayed() {
|
||||
return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
}
|
||||
});
|
||||
111
devtools/client/netmonitor/test/browser_net_cached-status.js
Normal file
111
devtools/client/netmonitor/test/browser_net_cached-status.js
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if cached requests have the correct status code
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL, null, true);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu, NetworkDetails } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
NetworkDetails._params.lazyEmpty = false;
|
||||
|
||||
const REQUEST_DATA = [
|
||||
{
|
||||
method: "GET",
|
||||
uri: STATUS_CODES_SJS + "?sts=ok&cached",
|
||||
details: {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "plain",
|
||||
fullMimeType: "text/plain; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
uri: STATUS_CODES_SJS + "?sts=redirect&cached",
|
||||
details: {
|
||||
status: 301,
|
||||
statusText: "Moved Permanently",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
uri: "http://example.com/redirected",
|
||||
details: {
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
uri: STATUS_CODES_SJS + "?sts=ok&cached",
|
||||
details: {
|
||||
status: 200,
|
||||
statusText: "OK (cached)",
|
||||
displayedStatus: "cached",
|
||||
type: "plain",
|
||||
fullMimeType: "text/plain; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
uri: STATUS_CODES_SJS + "?sts=redirect&cached",
|
||||
details: {
|
||||
status: 301,
|
||||
statusText: "Moved Permanently (cached)",
|
||||
displayedStatus: "cached",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
}
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
uri: "http://example.com/redirected",
|
||||
details: {
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
info("Performing requests #1...");
|
||||
yield performRequestsAndWait();
|
||||
|
||||
info("Performing requests #2...");
|
||||
yield performRequestsAndWait();
|
||||
|
||||
let index = 0;
|
||||
for (let request of REQUEST_DATA) {
|
||||
let item = RequestsMenu.getItemAtIndex(index);
|
||||
|
||||
info("Verifying request #" + index);
|
||||
yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function* performRequestsAndWait() {
|
||||
let wait = waitForNetworkEvents(monitor, 3);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performCachedRequests();
|
||||
});
|
||||
yield wait;
|
||||
}
|
||||
});
|
||||
147
devtools/client/netmonitor/test/browser_net_cause.js
Normal file
147
devtools/client/netmonitor/test/browser_net_cause.js
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if request cause is reported correctly.
|
||||
*/
|
||||
|
||||
const CAUSE_FILE_NAME = "html_cause-test-page.html";
|
||||
const CAUSE_URL = EXAMPLE_URL + CAUSE_FILE_NAME;
|
||||
|
||||
const EXPECTED_REQUESTS = [
|
||||
{
|
||||
method: "GET",
|
||||
url: CAUSE_URL,
|
||||
causeType: "document",
|
||||
causeUri: "",
|
||||
// The document load has internal privileged JS code on the stack
|
||||
stack: true
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "stylesheet_request",
|
||||
causeType: "stylesheet",
|
||||
causeUri: CAUSE_URL,
|
||||
stack: false
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "img_request",
|
||||
causeType: "img",
|
||||
causeUri: CAUSE_URL,
|
||||
stack: false
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "xhr_request",
|
||||
causeType: "xhr",
|
||||
causeUri: CAUSE_URL,
|
||||
stack: [{ fn: "performXhrRequest", file: CAUSE_FILE_NAME, line: 22 }]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: CAUSE_URL,
|
||||
stack: [{ fn: "performFetchRequest", file: CAUSE_FILE_NAME, line: 26 }]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "promise_fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: CAUSE_URL,
|
||||
stack: [
|
||||
{ fn: "performPromiseFetchRequest", file: CAUSE_FILE_NAME, line: 38 },
|
||||
{ fn: null, file: CAUSE_FILE_NAME, line: 37, asyncCause: "promise callback" },
|
||||
]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "timeout_fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: CAUSE_URL,
|
||||
stack: [
|
||||
{ fn: "performTimeoutFetchRequest", file: CAUSE_FILE_NAME, line: 40 },
|
||||
{ fn: "performPromiseFetchRequest", file: CAUSE_FILE_NAME, line: 39,
|
||||
asyncCause: "setTimeout handler" },
|
||||
]
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
url: EXAMPLE_URL + "beacon_request",
|
||||
causeType: "beacon",
|
||||
causeUri: CAUSE_URL,
|
||||
stack: [{ fn: "performBeaconRequest", file: CAUSE_FILE_NAME, line: 30 }]
|
||||
},
|
||||
];
|
||||
|
||||
add_task(function* () {
|
||||
// Async stacks aren't on by default in all builds
|
||||
yield SpecialPowers.pushPrefEnv({ set: [["javascript.options.asyncstack", true]] });
|
||||
|
||||
// the initNetMonitor function clears the network request list after the
|
||||
// page is loaded. That's why we first load a bogus page from SIMPLE_URL,
|
||||
// and only then load the real thing from CAUSE_URL - we want to catch
|
||||
// all the requests the page is making, not only the XHRs.
|
||||
// We can't use about:blank here, because initNetMonitor checks that the
|
||||
// page has actually made at least one request.
|
||||
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
let { $, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
|
||||
tab.linkedBrowser.loadURI(CAUSE_URL);
|
||||
yield wait;
|
||||
|
||||
is(RequestsMenu.itemCount, EXPECTED_REQUESTS.length,
|
||||
"All the page events should be recorded.");
|
||||
|
||||
EXPECTED_REQUESTS.forEach((spec, i) => {
|
||||
let { method, url, causeType, causeUri, stack } = spec;
|
||||
|
||||
let requestItem = RequestsMenu.getItemAtIndex(i);
|
||||
verifyRequestItemTarget(requestItem,
|
||||
method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
|
||||
);
|
||||
|
||||
let { stacktrace } = requestItem.attachment.cause;
|
||||
let stackLen = stacktrace ? stacktrace.length : 0;
|
||||
|
||||
if (stack) {
|
||||
ok(stacktrace, `Request #${i} has a stacktrace`);
|
||||
ok(stackLen > 0,
|
||||
`Request #${i} (${causeType}) has a stacktrace with ${stackLen} items`);
|
||||
|
||||
// if "stack" is array, check the details about the top stack frames
|
||||
if (Array.isArray(stack)) {
|
||||
stack.forEach((frame, j) => {
|
||||
is(stacktrace[j].functionName, frame.fn,
|
||||
`Request #${i} has the correct function on JS stack frame #${j}`);
|
||||
is(stacktrace[j].filename.split("/").pop(), frame.file,
|
||||
`Request #${i} has the correct file on JS stack frame #${j}`);
|
||||
is(stacktrace[j].lineNumber, frame.line,
|
||||
`Request #${i} has the correct line number on JS stack frame #${j}`);
|
||||
is(stacktrace[j].asyncCause, frame.asyncCause,
|
||||
`Request #${i} has the correct async cause on JS stack frame #${j}`);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort the requests by cause and check the order
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-cause-button"));
|
||||
let expectedOrder = EXPECTED_REQUESTS.map(r => r.causeType).sort();
|
||||
expectedOrder.forEach((expectedCause, i) => {
|
||||
let { target } = RequestsMenu.getItemAtIndex(i);
|
||||
let causeLabel = target.querySelector(".requests-menu-cause-label");
|
||||
let cause = causeLabel.getAttribute("value");
|
||||
is(cause, expectedCause, `The request #${i} has the expected cause after sorting`);
|
||||
});
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if request JS stack is property reported if the request is internally
|
||||
* redirected without hitting the network (HSTS is one of such cases)
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
const EXPECTED_REQUESTS = [
|
||||
// Request to HTTP URL, redirects to HTTPS, has callstack
|
||||
{ status: 302, hasStack: true },
|
||||
// Serves HTTPS, sets the Strict-Transport-Security header, no stack
|
||||
{ status: 200, hasStack: false },
|
||||
// Second request to HTTP redirects to HTTPS internally
|
||||
{ status: 200, hasStack: true },
|
||||
];
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
|
||||
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
|
||||
yield performRequests(2, HSTS_SJS);
|
||||
yield wait;
|
||||
|
||||
EXPECTED_REQUESTS.forEach(({status, hasStack}, i) => {
|
||||
let { attachment } = RequestsMenu.getItemAtIndex(i);
|
||||
|
||||
is(attachment.status, status, `Request #${i} has the expected status`);
|
||||
|
||||
let { stacktrace } = attachment.cause;
|
||||
let stackLen = stacktrace ? stacktrace.length : 0;
|
||||
|
||||
if (hasStack) {
|
||||
ok(stacktrace, `Request #${i} has a stacktrace`);
|
||||
ok(stackLen > 0, `Request #${i} has a stacktrace with ${stackLen} items`);
|
||||
} else {
|
||||
is(stackLen, 0, `Request #${i} has an empty stacktrace`);
|
||||
}
|
||||
});
|
||||
|
||||
// Send a request to reset the HSTS policy to state before the test
|
||||
wait = waitForNetworkEvents(monitor, 1);
|
||||
yield performRequests(1, HSTS_SJS + "?reset");
|
||||
yield wait;
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function performRequests(count, url) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, { count, url }, function* (args) {
|
||||
content.wrappedJSObject.performRequests(args.count, args.url);
|
||||
});
|
||||
}
|
||||
});
|
||||
73
devtools/client/netmonitor/test/browser_net_charts-01.js
Normal file
73
devtools/client/netmonitor/test/browser_net_charts-01.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Makes sure Pie Charts have the right internal structure.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Chart } = monitor.panelWin;
|
||||
|
||||
let pie = Chart.Pie(document, {
|
||||
width: 100,
|
||||
height: 100,
|
||||
data: [{
|
||||
size: 1,
|
||||
label: "foo"
|
||||
}, {
|
||||
size: 2,
|
||||
label: "bar"
|
||||
}, {
|
||||
size: 3,
|
||||
label: "baz"
|
||||
}]
|
||||
});
|
||||
|
||||
let node = pie.node;
|
||||
let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
|
||||
let labels = node.querySelectorAll(".pie-chart-label");
|
||||
|
||||
ok(node.classList.contains("pie-chart-container") &&
|
||||
node.classList.contains("generic-chart-container"),
|
||||
"A pie chart container was created successfully.");
|
||||
|
||||
is(slices.length, 3,
|
||||
"There should be 3 pie chart slices created.");
|
||||
ok(slices[0].getAttribute("d").match(
|
||||
/\s*M 50,50 L 49\.\d+,97\.\d+ A 47\.5,47\.5 0 0 1 49\.\d+,2\.5\d* Z/),
|
||||
"The first slice has the correct data.");
|
||||
ok(slices[1].getAttribute("d").match(
|
||||
/\s*M 50,50 L 91\.\d+,26\.\d+ A 47\.5,47\.5 0 0 1 49\.\d+,97\.\d+ Z/),
|
||||
"The second slice has the correct data.");
|
||||
ok(slices[2].getAttribute("d").match(
|
||||
/\s*M 50,50 L 50\.\d+,2\.5\d* A 47\.5,47\.5 0 0 1 91\.\d+,26\.\d+ Z/),
|
||||
"The third slice has the correct data.");
|
||||
|
||||
ok(slices[0].hasAttribute("largest"),
|
||||
"The first slice should be the largest one.");
|
||||
ok(slices[2].hasAttribute("smallest"),
|
||||
"The third slice should be the smallest one.");
|
||||
|
||||
ok(slices[0].getAttribute("name"), "baz",
|
||||
"The first slice's name is correct.");
|
||||
ok(slices[1].getAttribute("name"), "bar",
|
||||
"The first slice's name is correct.");
|
||||
ok(slices[2].getAttribute("name"), "foo",
|
||||
"The first slice's name is correct.");
|
||||
|
||||
is(labels.length, 3,
|
||||
"There should be 3 pie chart labels created.");
|
||||
is(labels[0].textContent, "baz",
|
||||
"The first label's text is correct.");
|
||||
is(labels[1].textContent, "bar",
|
||||
"The first label's text is correct.");
|
||||
is(labels[2].textContent, "foo",
|
||||
"The first label's text is correct.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
49
devtools/client/netmonitor/test/browser_net_charts-02.js
Normal file
49
devtools/client/netmonitor/test/browser_net_charts-02.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Makes sure Pie Charts have the right internal structure when
|
||||
* initialized with empty data.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Chart } = monitor.panelWin;
|
||||
|
||||
let pie = Chart.Pie(document, {
|
||||
data: null,
|
||||
width: 100,
|
||||
height: 100
|
||||
});
|
||||
|
||||
let node = pie.node;
|
||||
let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
|
||||
let labels = node.querySelectorAll(".pie-chart-label");
|
||||
|
||||
ok(node.classList.contains("pie-chart-container") &&
|
||||
node.classList.contains("generic-chart-container"),
|
||||
"A pie chart container was created successfully.");
|
||||
|
||||
is(slices.length, 1, "There should be 1 pie chart slice created.");
|
||||
ok(slices[0].getAttribute("d").match(
|
||||
/\s*M 50,50 L 50\.\d+,2\.5\d* A 47\.5,47\.5 0 1 1 49\.\d+,2\.5\d* Z/),
|
||||
"The first slice has the correct data.");
|
||||
|
||||
ok(slices[0].hasAttribute("largest"),
|
||||
"The first slice should be the largest one.");
|
||||
ok(slices[0].hasAttribute("smallest"),
|
||||
"The first slice should also be the smallest one.");
|
||||
ok(slices[0].getAttribute("name"), L10N.getStr("pieChart.loading"),
|
||||
"The first slice's name is correct.");
|
||||
|
||||
is(labels.length, 1, "There should be 1 pie chart label created.");
|
||||
is(labels[0].textContent, "Loading", "The first label's text is correct.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
106
devtools/client/netmonitor/test/browser_net_charts-03.js
Normal file
106
devtools/client/netmonitor/test/browser_net_charts-03.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Makes sure Table Charts have the right internal structure.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Chart } = monitor.panelWin;
|
||||
|
||||
let table = Chart.Table(document, {
|
||||
title: "Table title",
|
||||
data: [{
|
||||
label1: 1,
|
||||
label2: 11.1
|
||||
}, {
|
||||
label1: 2,
|
||||
label2: 12.2
|
||||
}, {
|
||||
label1: 3,
|
||||
label2: 13.3
|
||||
}],
|
||||
strings: {
|
||||
label2: (value, index) => value + ["foo", "bar", "baz"][index]
|
||||
},
|
||||
totals: {
|
||||
label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
|
||||
label2: value => "World " + L10N.numberWithDecimals(value, 2)
|
||||
}
|
||||
});
|
||||
|
||||
let node = table.node;
|
||||
let title = node.querySelector(".table-chart-title");
|
||||
let grid = node.querySelector(".table-chart-grid");
|
||||
let totals = node.querySelector(".table-chart-totals");
|
||||
let rows = grid.querySelectorAll(".table-chart-row");
|
||||
let sums = node.querySelectorAll(".table-chart-summary-label");
|
||||
|
||||
ok(node.classList.contains("table-chart-container") &&
|
||||
node.classList.contains("generic-chart-container"),
|
||||
"A table chart container was created successfully.");
|
||||
|
||||
ok(title, "A title node was created successfully.");
|
||||
is(title.getAttribute("value"), "Table title",
|
||||
"The title node displays the correct text.");
|
||||
|
||||
is(rows.length, 3, "There should be 3 table chart rows created.");
|
||||
|
||||
ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||
"A colored blob exists for the firt row.");
|
||||
is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "label1",
|
||||
"The first column of the first row exists.");
|
||||
is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label2",
|
||||
"The second column of the first row exists.");
|
||||
is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "1",
|
||||
"The first column of the first row displays the correct text.");
|
||||
is(rows[0].querySelectorAll("label")[1].getAttribute("value"), "11.1foo",
|
||||
"The second column of the first row displays the correct text.");
|
||||
|
||||
ok(rows[1].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||
"A colored blob exists for the second row.");
|
||||
is(rows[1].querySelectorAll("label")[0].getAttribute("name"), "label1",
|
||||
"The first column of the second row exists.");
|
||||
is(rows[1].querySelectorAll("label")[1].getAttribute("name"), "label2",
|
||||
"The second column of the second row exists.");
|
||||
is(rows[1].querySelectorAll("label")[0].getAttribute("value"), "2",
|
||||
"The first column of the second row displays the correct text.");
|
||||
is(rows[1].querySelectorAll("label")[1].getAttribute("value"), "12.2bar",
|
||||
"The second column of the first row displays the correct text.");
|
||||
|
||||
ok(rows[2].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||
"A colored blob exists for the third row.");
|
||||
is(rows[2].querySelectorAll("label")[0].getAttribute("name"), "label1",
|
||||
"The first column of the third row exists.");
|
||||
is(rows[2].querySelectorAll("label")[1].getAttribute("name"), "label2",
|
||||
"The second column of the third row exists.");
|
||||
is(rows[2].querySelectorAll("label")[0].getAttribute("value"), "3",
|
||||
"The first column of the third row displays the correct text.");
|
||||
is(rows[2].querySelectorAll("label")[1].getAttribute("value"), "13.3baz",
|
||||
"The second column of the third row displays the correct text.");
|
||||
|
||||
is(sums.length, 2, "There should be 2 total summaries created.");
|
||||
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("name"),
|
||||
"label1",
|
||||
"The first sum's type is correct.");
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("value"),
|
||||
"Hello 6",
|
||||
"The first sum's value is correct.");
|
||||
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("name"),
|
||||
"label2",
|
||||
"The second sum's type is correct.");
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("value"),
|
||||
"World 36.60",
|
||||
"The second sum's value is correct.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
75
devtools/client/netmonitor/test/browser_net_charts-04.js
Normal file
75
devtools/client/netmonitor/test/browser_net_charts-04.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Makes sure Pie Charts have the right internal structure when
|
||||
* initialized with empty data.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Chart } = monitor.panelWin;
|
||||
|
||||
let table = Chart.Table(document, {
|
||||
title: "Table title",
|
||||
data: null,
|
||||
totals: {
|
||||
label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
|
||||
label2: value => "World " + L10N.numberWithDecimals(value, 2)
|
||||
}
|
||||
});
|
||||
|
||||
let node = table.node;
|
||||
let title = node.querySelector(".table-chart-title");
|
||||
let grid = node.querySelector(".table-chart-grid");
|
||||
let totals = node.querySelector(".table-chart-totals");
|
||||
let rows = grid.querySelectorAll(".table-chart-row");
|
||||
let sums = node.querySelectorAll(".table-chart-summary-label");
|
||||
|
||||
ok(node.classList.contains("table-chart-container") &&
|
||||
node.classList.contains("generic-chart-container"),
|
||||
"A table chart container was created successfully.");
|
||||
|
||||
ok(title, "A title node was created successfully.");
|
||||
is(title.getAttribute("value"), "Table title",
|
||||
"The title node displays the correct text.");
|
||||
|
||||
is(rows.length, 1, "There should be 1 table chart row created.");
|
||||
|
||||
ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||
"A colored blob exists for the firt row.");
|
||||
is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "size",
|
||||
"The first column of the first row exists.");
|
||||
is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label",
|
||||
"The second column of the first row exists.");
|
||||
is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "",
|
||||
"The first column of the first row displays the correct text.");
|
||||
is(rows[0].querySelectorAll("label")[1].getAttribute("value"),
|
||||
L10N.getStr("tableChart.loading"),
|
||||
"The second column of the first row displays the correct text.");
|
||||
|
||||
is(sums.length, 2,
|
||||
"There should be 2 total summaries created.");
|
||||
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("name"),
|
||||
"label1",
|
||||
"The first sum's type is correct.");
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("value"),
|
||||
"Hello 0",
|
||||
"The first sum's value is correct.");
|
||||
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("name"),
|
||||
"label2",
|
||||
"The second sum's type is correct.");
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("value"),
|
||||
"World 0",
|
||||
"The second sum's value is correct.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
61
devtools/client/netmonitor/test/browser_net_charts-05.js
Normal file
61
devtools/client/netmonitor/test/browser_net_charts-05.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Makes sure Pie+Table Charts have the right internal structure.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Chart } = monitor.panelWin;
|
||||
|
||||
let chart = Chart.PieTable(document, {
|
||||
title: "Table title",
|
||||
data: [{
|
||||
size: 1,
|
||||
label: 11.1
|
||||
}, {
|
||||
size: 2,
|
||||
label: 12.2
|
||||
}, {
|
||||
size: 3,
|
||||
label: 13.3
|
||||
}],
|
||||
strings: {
|
||||
label2: (value, index) => value + ["foo", "bar", "baz"][index]
|
||||
},
|
||||
totals: {
|
||||
size: value => "Hello " + L10N.numberWithDecimals(value, 2),
|
||||
label: value => "World " + L10N.numberWithDecimals(value, 2)
|
||||
}
|
||||
});
|
||||
|
||||
ok(chart.pie, "The pie chart proxy is accessible.");
|
||||
ok(chart.table, "The table chart proxy is accessible.");
|
||||
|
||||
let node = chart.node;
|
||||
let rows = node.querySelectorAll(".table-chart-row");
|
||||
let sums = node.querySelectorAll(".table-chart-summary-label");
|
||||
|
||||
ok(node.classList.contains("pie-table-chart-container"),
|
||||
"A pie+table chart container was created successfully.");
|
||||
|
||||
ok(node.querySelector(".table-chart-title"),
|
||||
"A title node was created successfully.");
|
||||
ok(node.querySelector(".pie-chart-container"),
|
||||
"A pie chart was created successfully.");
|
||||
ok(node.querySelector(".table-chart-container"),
|
||||
"A table chart was created successfully.");
|
||||
|
||||
is(rows.length, 3, "There should be 3 pie chart slices created.");
|
||||
is(rows.length, 3, "There should be 3 table chart rows created.");
|
||||
is(sums.length, 2, "There should be 2 total summaries created.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
47
devtools/client/netmonitor/test/browser_net_charts-06.js
Normal file
47
devtools/client/netmonitor/test/browser_net_charts-06.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Makes sure Pie Charts correctly handle empty source data.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Chart } = monitor.panelWin;
|
||||
|
||||
let pie = Chart.Pie(document, {
|
||||
data: [],
|
||||
width: 100,
|
||||
height: 100
|
||||
});
|
||||
|
||||
let node = pie.node;
|
||||
let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
|
||||
let labels = node.querySelectorAll(".pie-chart-label");
|
||||
|
||||
is(slices.length, 1,
|
||||
"There should be 1 pie chart slice created.");
|
||||
ok(slices[0].getAttribute("d").match(
|
||||
/\s*M 50,50 L 50\.\d+,2\.5\d* A 47\.5,47\.5 0 1 1 49\.\d+,2\.5\d* Z/),
|
||||
"The slice has the correct data.");
|
||||
|
||||
ok(slices[0].hasAttribute("largest"),
|
||||
"The slice should be the largest one.");
|
||||
ok(slices[0].hasAttribute("smallest"),
|
||||
"The slice should also be the smallest one.");
|
||||
ok(slices[0].getAttribute("name"), L10N.getStr("pieChart.unavailable"),
|
||||
"The slice's name is correct.");
|
||||
|
||||
is(labels.length, 1,
|
||||
"There should be 1 pie chart label created.");
|
||||
is(labels[0].textContent, "Empty",
|
||||
"The label's text is correct.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
63
devtools/client/netmonitor/test/browser_net_charts-07.js
Normal file
63
devtools/client/netmonitor/test/browser_net_charts-07.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Makes sure Table Charts correctly handle empty source data.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Chart } = monitor.panelWin;
|
||||
|
||||
let table = Chart.Table(document, {
|
||||
data: [],
|
||||
totals: {
|
||||
label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
|
||||
label2: value => "World " + L10N.numberWithDecimals(value, 2)
|
||||
}
|
||||
});
|
||||
|
||||
let node = table.node;
|
||||
let grid = node.querySelector(".table-chart-grid");
|
||||
let totals = node.querySelector(".table-chart-totals");
|
||||
let rows = grid.querySelectorAll(".table-chart-row");
|
||||
let sums = node.querySelectorAll(".table-chart-summary-label");
|
||||
|
||||
is(rows.length, 1, "There should be 1 table chart row created.");
|
||||
|
||||
ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"),
|
||||
"A colored blob exists for the firt row.");
|
||||
is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "size",
|
||||
"The first column of the first row exists.");
|
||||
is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label",
|
||||
"The second column of the first row exists.");
|
||||
is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "",
|
||||
"The first column of the first row displays the correct text.");
|
||||
is(rows[0].querySelectorAll("label")[1].getAttribute("value"),
|
||||
L10N.getStr("tableChart.unavailable"),
|
||||
"The second column of the first row displays the correct text.");
|
||||
|
||||
is(sums.length, 2, "There should be 2 total summaries created.");
|
||||
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("name"),
|
||||
"label1",
|
||||
"The first sum's type is correct.");
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("value"),
|
||||
"Hello 0",
|
||||
"The first sum's value is correct.");
|
||||
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("name"),
|
||||
"label2",
|
||||
"The second sum's type is correct.");
|
||||
is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("value"),
|
||||
"World 0",
|
||||
"The second sum's value is correct.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
77
devtools/client/netmonitor/test/browser_net_clear.js
Normal file
77
devtools/client/netmonitor/test/browser_net_clear.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the clear button empties the request menu.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
let detailsPane = $("#details-pane");
|
||||
let detailsPaneToggleButton = $("#details-pane-toggle");
|
||||
let clearButton = $("#requests-menu-clear-button");
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
// Make sure we start in a sane state
|
||||
assertNoRequestState(RequestsMenu, detailsPaneToggleButton);
|
||||
|
||||
// Load one request and assert it shows up in the list
|
||||
let networkEvent = monitor.panelWin.once(monitor.panelWin.EVENTS.NETWORK_EVENT);
|
||||
tab.linkedBrowser.reload();
|
||||
yield networkEvent;
|
||||
|
||||
assertSingleRequestState();
|
||||
|
||||
// Click clear and make sure the requests are gone
|
||||
EventUtils.sendMouseEvent({ type: "click" }, clearButton);
|
||||
assertNoRequestState();
|
||||
|
||||
// Load a second request and make sure they still show up
|
||||
networkEvent = monitor.panelWin.once(monitor.panelWin.EVENTS.NETWORK_EVENT);
|
||||
tab.linkedBrowser.reload();
|
||||
yield networkEvent;
|
||||
|
||||
assertSingleRequestState();
|
||||
|
||||
// Make sure we can now open the details pane
|
||||
NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
|
||||
ok(!detailsPane.classList.contains("pane-collapsed") &&
|
||||
!detailsPaneToggleButton.classList.contains("pane-collapsed"),
|
||||
"The details pane should be visible after clicking the toggle button.");
|
||||
|
||||
// Click clear and make sure the details pane closes
|
||||
EventUtils.sendMouseEvent({ type: "click" }, clearButton);
|
||||
assertNoRequestState();
|
||||
ok(detailsPane.classList.contains("pane-collapsed") &&
|
||||
detailsPaneToggleButton.classList.contains("pane-collapsed"),
|
||||
"The details pane should not be visible clicking 'clear'.");
|
||||
|
||||
return teardown(monitor);
|
||||
|
||||
/**
|
||||
* Asserts the state of the network monitor when one request has loaded
|
||||
*/
|
||||
function assertSingleRequestState() {
|
||||
is(RequestsMenu.itemCount, 1,
|
||||
"The request menu should have one item at this point.");
|
||||
is(detailsPaneToggleButton.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should be enabled after a request is made.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the state of the network monitor when no requests have loaded
|
||||
*/
|
||||
function assertNoRequestState() {
|
||||
is(RequestsMenu.itemCount, 0,
|
||||
"The request menu should be empty at this point.");
|
||||
is(detailsPaneToggleButton.hasAttribute("disabled"), true,
|
||||
"The pane toggle button should be disabled when the request menu is cleared.");
|
||||
}
|
||||
});
|
||||
195
devtools/client/netmonitor/test/browser_net_complex-params.js
Normal file
195
devtools/client/netmonitor/test/browser_net_complex-params.js
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests whether complex request params and payload sent via POST are
|
||||
* displayed correctly.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu, NetworkDetails } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
NetworkDetails._params.lazyEmpty = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1, 6);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
let onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[2]);
|
||||
yield onEvent;
|
||||
yield testParamsTab1("a", '""', '{ "foo": "bar" }', '""');
|
||||
|
||||
onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
|
||||
RequestsMenu.selectedIndex = 1;
|
||||
yield onEvent;
|
||||
yield testParamsTab1("a", '"b"', '{ "foo": "bar" }', '""');
|
||||
|
||||
onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
|
||||
RequestsMenu.selectedIndex = 2;
|
||||
yield onEvent;
|
||||
yield testParamsTab1("a", '"b"', "foo", '"bar"');
|
||||
|
||||
onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
|
||||
RequestsMenu.selectedIndex = 3;
|
||||
yield onEvent;
|
||||
yield testParamsTab2("a", '""', '{ "foo": "bar" }', "js");
|
||||
|
||||
onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
|
||||
RequestsMenu.selectedIndex = 4;
|
||||
yield onEvent;
|
||||
yield testParamsTab2("a", '"b"', '{ "foo": "bar" }', "js");
|
||||
|
||||
onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
|
||||
RequestsMenu.selectedIndex = 5;
|
||||
yield onEvent;
|
||||
yield testParamsTab2("a", '"b"', "?foo=bar", "text");
|
||||
|
||||
onEvent = monitor.panelWin.once(EVENTS.SIDEBAR_POPULATED);
|
||||
RequestsMenu.selectedIndex = 6;
|
||||
yield onEvent;
|
||||
yield testParamsTab3("a", '"b"');
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function testParamsTab1(queryStringParamName, queryStringParamValue,
|
||||
formDataParamName, formDataParamValue) {
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
|
||||
"The number of param scopes displayed in this tabpanel is incorrect.");
|
||||
is(tabpanel.querySelectorAll(".variable-or-property").length, 2,
|
||||
"The number of param values displayed in this tabpanel is incorrect.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
|
||||
is(tabpanel.querySelector("#request-params-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The request params box should not be hidden.");
|
||||
is(tabpanel.querySelector("#request-post-data-textarea-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The request post data textarea box should be hidden.");
|
||||
|
||||
let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
let formDataScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
|
||||
|
||||
is(paramsScope.querySelector(".name").getAttribute("value"),
|
||||
L10N.getStr("paramsQueryString"),
|
||||
"The params scope doesn't have the correct title.");
|
||||
is(formDataScope.querySelector(".name").getAttribute("value"),
|
||||
L10N.getStr("paramsFormData"),
|
||||
"The form data scope doesn't have the correct title.");
|
||||
|
||||
is(paramsScope.querySelectorAll(".variables-view-variable .name")[0]
|
||||
.getAttribute("value"),
|
||||
queryStringParamName,
|
||||
"The first query string param name was incorrect.");
|
||||
is(paramsScope.querySelectorAll(".variables-view-variable .value")[0]
|
||||
.getAttribute("value"),
|
||||
queryStringParamValue,
|
||||
"The first query string param value was incorrect.");
|
||||
|
||||
is(formDataScope.querySelectorAll(".variables-view-variable .name")[0]
|
||||
.getAttribute("value"),
|
||||
formDataParamName,
|
||||
"The first form data param name was incorrect.");
|
||||
is(formDataScope.querySelectorAll(".variables-view-variable .value")[0]
|
||||
.getAttribute("value"),
|
||||
formDataParamValue,
|
||||
"The first form data param value was incorrect.");
|
||||
}
|
||||
|
||||
function* testParamsTab2(queryStringParamName, queryStringParamValue,
|
||||
requestPayload, editorMode) {
|
||||
let isJSON = editorMode == "js";
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
|
||||
"The number of param scopes displayed in this tabpanel is incorrect.");
|
||||
is(tabpanel.querySelectorAll(".variable-or-property").length, isJSON ? 4 : 1,
|
||||
"The number of param values displayed in this tabpanel is incorrect.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
|
||||
is(tabpanel.querySelector("#request-params-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The request params box should not be hidden.");
|
||||
is(tabpanel.querySelector("#request-post-data-textarea-box")
|
||||
.hasAttribute("hidden"), isJSON,
|
||||
"The request post data textarea box should be hidden.");
|
||||
|
||||
let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
let payloadScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
|
||||
|
||||
is(paramsScope.querySelector(".name").getAttribute("value"),
|
||||
L10N.getStr("paramsQueryString"),
|
||||
"The params scope doesn't have the correct title.");
|
||||
is(payloadScope.querySelector(".name").getAttribute("value"),
|
||||
isJSON ? L10N.getStr("jsonScopeName") : L10N.getStr("paramsPostPayload"),
|
||||
"The request payload scope doesn't have the correct title.");
|
||||
|
||||
is(paramsScope.querySelectorAll(".variables-view-variable .name")[0]
|
||||
.getAttribute("value"),
|
||||
queryStringParamName,
|
||||
"The first query string param name was incorrect.");
|
||||
is(paramsScope.querySelectorAll(".variables-view-variable .value")[0]
|
||||
.getAttribute("value"),
|
||||
queryStringParamValue,
|
||||
"The first query string param value was incorrect.");
|
||||
|
||||
if (isJSON) {
|
||||
let requestPayloadObject = JSON.parse(requestPayload);
|
||||
let requestPairs = Object.keys(requestPayloadObject)
|
||||
.map(k => [k, requestPayloadObject[k]]);
|
||||
let displayedNames = payloadScope.querySelectorAll(
|
||||
".variables-view-property.variable-or-property .name");
|
||||
let displayedValues = payloadScope.querySelectorAll(
|
||||
".variables-view-property.variable-or-property .value");
|
||||
for (let i = 0; i < requestPairs.length; i++) {
|
||||
let [requestPayloadName, requestPayloadValue] = requestPairs[i];
|
||||
is(requestPayloadName, displayedNames[i].getAttribute("value"),
|
||||
"JSON property name " + i + " should be displayed correctly");
|
||||
is('"' + requestPayloadValue + '"', displayedValues[i].getAttribute("value"),
|
||||
"JSON property value " + i + " should be displayed correctly");
|
||||
}
|
||||
} else {
|
||||
let editor = yield NetMonitorView.editor("#request-post-data-textarea");
|
||||
is(editor.getText(), requestPayload,
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(editor.getMode(), Editor.modes[editorMode],
|
||||
"The mode active in the source editor is incorrect.");
|
||||
}
|
||||
}
|
||||
|
||||
function testParamsTab3(queryStringParamName, queryStringParamValue) {
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 0,
|
||||
"The number of param scopes displayed in this tabpanel is incorrect.");
|
||||
is(tabpanel.querySelectorAll(".variable-or-property").length, 0,
|
||||
"The number of param values displayed in this tabpanel is incorrect.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 1,
|
||||
"The empty notice should be displayed in this tabpanel.");
|
||||
|
||||
is(tabpanel.querySelector("#request-params-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The request params box should not be hidden.");
|
||||
is(tabpanel.querySelector("#request-post-data-textarea-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The request post data textarea box should be hidden.");
|
||||
}
|
||||
});
|
||||
255
devtools/client/netmonitor/test/browser_net_content-type.js
Normal file
255
devtools/client/netmonitor/test/browser_net_content-type.js
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if different response content types are handled correctly.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Editor, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=xml", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "xml",
|
||||
fullMimeType: "text/xml; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 42),
|
||||
time: true
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "css",
|
||||
fullMimeType: "text/css; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
|
||||
time: true
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "js",
|
||||
fullMimeType: "application/javascript; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
|
||||
time: true
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=json", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "json",
|
||||
fullMimeType: "application/json; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
|
||||
time: true
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=bogus", {
|
||||
status: 404,
|
||||
statusText: "Not Found",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24),
|
||||
time: true
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
|
||||
"GET", TEST_IMAGE, {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "png",
|
||||
fullMimeType: "image/png",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 580),
|
||||
time: true
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=gzip", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "plain",
|
||||
fullMimeType: "text/plain",
|
||||
transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
|
||||
time: true
|
||||
});
|
||||
|
||||
let onEvent = waitForResponseBodyDisplayed();
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
yield onEvent;
|
||||
yield testResponseTab("xml");
|
||||
|
||||
yield selectIndexAndWaitForTabUpdated(1);
|
||||
yield testResponseTab("css");
|
||||
|
||||
yield selectIndexAndWaitForTabUpdated(2);
|
||||
yield testResponseTab("js");
|
||||
|
||||
yield selectIndexAndWaitForTabUpdated(3);
|
||||
yield testResponseTab("json");
|
||||
|
||||
yield selectIndexAndWaitForTabUpdated(4);
|
||||
yield testResponseTab("html");
|
||||
|
||||
yield selectIndexAndWaitForTabUpdated(5);
|
||||
yield testResponseTab("png");
|
||||
|
||||
yield selectIndexAndWaitForTabUpdated(6);
|
||||
yield testResponseTab("gzip");
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function* testResponseTab(type) {
|
||||
let tabEl = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tabEl.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
function checkVisibility(box) {
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), box != "json",
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), box != "textarea",
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), box != "image",
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "xml": {
|
||||
checkVisibility("textarea");
|
||||
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
is(editor.getText(), "<label value='greeting'>Hello XML!</label>",
|
||||
"The text shown in the source editor is incorrect for the xml request.");
|
||||
is(editor.getMode(), Editor.modes.html,
|
||||
"The mode active in the source editor is incorrect for the xml request.");
|
||||
break;
|
||||
}
|
||||
case "css": {
|
||||
checkVisibility("textarea");
|
||||
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
is(editor.getText(), "body:pre { content: 'Hello CSS!' }",
|
||||
"The text shown in the source editor is incorrect for the xml request.");
|
||||
is(editor.getMode(), Editor.modes.css,
|
||||
"The mode active in the source editor is incorrect for the xml request.");
|
||||
break;
|
||||
}
|
||||
case "js": {
|
||||
checkVisibility("textarea");
|
||||
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
is(editor.getText(), "function() { return 'Hello JS!'; }",
|
||||
"The text shown in the source editor is incorrect for the xml request.");
|
||||
is(editor.getMode(), Editor.modes.js,
|
||||
"The mode active in the source editor is incorrect for the xml request.");
|
||||
break;
|
||||
}
|
||||
case "json": {
|
||||
checkVisibility("json");
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
|
||||
"There should be 1 json scope displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
|
||||
"There should be 2 json properties displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
|
||||
let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
|
||||
is(jsonScope.querySelector(".name").getAttribute("value"),
|
||||
L10N.getStr("jsonScopeName"),
|
||||
"The json scope doesn't have the correct title.");
|
||||
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
|
||||
.getAttribute("value"),
|
||||
"greeting", "The first json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
|
||||
.getAttribute("value"),
|
||||
"\"Hello JSON!\"", "The first json property value was incorrect.");
|
||||
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
|
||||
.getAttribute("value"),
|
||||
"__proto__", "The second json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
|
||||
.getAttribute("value"),
|
||||
"Object", "The second json property value was incorrect.");
|
||||
break;
|
||||
}
|
||||
case "html": {
|
||||
checkVisibility("textarea");
|
||||
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
is(editor.getText(), "<blink>Not Found</blink>",
|
||||
"The text shown in the source editor is incorrect for the xml request.");
|
||||
is(editor.getMode(), Editor.modes.html,
|
||||
"The mode active in the source editor is incorrect for the xml request.");
|
||||
break;
|
||||
}
|
||||
case "png": {
|
||||
checkVisibility("image");
|
||||
|
||||
let imageNode = tabpanel.querySelector("#response-content-image");
|
||||
yield once(imageNode, "load");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-image-name-value")
|
||||
.getAttribute("value"), "test-image.png",
|
||||
"The image name info isn't correct.");
|
||||
is(tabpanel.querySelector("#response-content-image-mime-value")
|
||||
.getAttribute("value"), "image/png",
|
||||
"The image mime info isn't correct.");
|
||||
is(tabpanel.querySelector("#response-content-image-dimensions-value")
|
||||
.getAttribute("value"), "16" + " \u00D7 " + "16",
|
||||
"The image dimensions info isn't correct.");
|
||||
break;
|
||||
}
|
||||
case "gzip": {
|
||||
checkVisibility("textarea");
|
||||
|
||||
let expected = new Array(1000).join("Hello gzip!");
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
is(editor.getText(), expected,
|
||||
"The text shown in the source editor is incorrect for the gzip request.");
|
||||
is(editor.getMode(), Editor.modes.text,
|
||||
"The mode active in the source editor is incorrect for the gzip request.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectIndexAndWaitForTabUpdated(index) {
|
||||
let onTabUpdated = monitor.panelWin.once(monitor.panelWin.EVENTS.TAB_UPDATED);
|
||||
RequestsMenu.selectedIndex = index;
|
||||
return onTabUpdated;
|
||||
}
|
||||
|
||||
function waitForResponseBodyDisplayed() {
|
||||
return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
}
|
||||
});
|
||||
88
devtools/client/netmonitor/test/browser_net_copy_as_curl.js
Normal file
88
devtools/client/netmonitor/test/browser_net_copy_as_curl.js
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if Copy as cURL works.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CURL_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
// Different quote chars are used for Windows and POSIX
|
||||
const QUOTE = Services.appinfo.OS == "WINNT" ? "\"" : "'";
|
||||
|
||||
// Quote a string, escape the quotes inside the string
|
||||
function quote(str) {
|
||||
return QUOTE + str.replace(new RegExp(QUOTE, "g"), `\\${QUOTE}`) + QUOTE;
|
||||
}
|
||||
|
||||
// Header param is formatted as -H "Header: value" or -H 'Header: value'
|
||||
function header(h) {
|
||||
return "-H " + quote(h);
|
||||
}
|
||||
|
||||
// Construct the expected command
|
||||
const EXPECTED_RESULT = [
|
||||
"curl " + quote(SIMPLE_SJS),
|
||||
"--compressed",
|
||||
header("Host: example.com"),
|
||||
header("User-Agent: " + navigator.userAgent),
|
||||
header("Accept: */*"),
|
||||
header("Accept-Language: " + navigator.language),
|
||||
header("X-Custom-Header-1: Custom value"),
|
||||
header("X-Custom-Header-2: 8.8.8.8"),
|
||||
header("X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"),
|
||||
header("Referer: " + CURL_URL),
|
||||
header("Connection: keep-alive"),
|
||||
header("Pragma: no-cache"),
|
||||
header("Cache-Control: no-cache")
|
||||
];
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
|
||||
content.wrappedJSObject.performRequest(url);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
let requestItem = RequestsMenu.getItemAtIndex(0);
|
||||
RequestsMenu.selectedItem = requestItem;
|
||||
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyAsCurl();
|
||||
}, function validate(result) {
|
||||
if (typeof result !== "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Different setups may produce the same command, but with the
|
||||
// parameters in a different order in the commandline (which is fine).
|
||||
// Here we confirm that the commands are the same even in that case.
|
||||
|
||||
// This monster regexp parses the command line into an array of arguments,
|
||||
// recognizing quoted args with matching quotes and escaped quotes inside:
|
||||
// [ "curl 'url'", "--standalone-arg", "-arg-with-quoted-string 'value\'s'" ]
|
||||
let matchRe = /[-A-Za-z1-9]+(?: ([\"'])(?:\\\1|.)*?\1)?/g;
|
||||
|
||||
let actual = result.match(matchRe);
|
||||
|
||||
// Must begin with the same "curl 'URL'" segment
|
||||
if (!actual || EXPECTED_RESULT[0] != actual[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must match each of the params in the middle (headers and --compressed)
|
||||
return EXPECTED_RESULT.length === actual.length &&
|
||||
EXPECTED_RESULT.every(param => actual.includes(param));
|
||||
});
|
||||
|
||||
info("Clipboard contains a cURL command for the currently selected item's url.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
72
devtools/client/netmonitor/test/browser_net_copy_headers.js
Normal file
72
devtools/client/netmonitor/test/browser_net_copy_headers.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if copying a request's request/response headers works.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
tab.linkedBrowser.reload();
|
||||
yield wait;
|
||||
|
||||
let requestItem = RequestsMenu.getItemAtIndex(0);
|
||||
RequestsMenu.selectedItem = requestItem;
|
||||
|
||||
let { method, httpVersion, status, statusText } = requestItem.attachment;
|
||||
|
||||
const EXPECTED_REQUEST_HEADERS = [
|
||||
`${method} ${SIMPLE_URL} ${httpVersion}`,
|
||||
"Host: example.com",
|
||||
"User-Agent: " + navigator.userAgent + "",
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Accept-Language: " + navigator.languages.join(",") + ";q=0.5",
|
||||
"Accept-Encoding: gzip, deflate",
|
||||
"Connection: keep-alive",
|
||||
"Upgrade-Insecure-Requests: 1",
|
||||
"Pragma: no-cache",
|
||||
"Cache-Control: no-cache"
|
||||
].join("\n");
|
||||
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyRequestHeaders();
|
||||
}, function validate(result) {
|
||||
// Sometimes, a "Cookie" header is left over from other tests. Remove it:
|
||||
result = String(result).replace(/Cookie: [^\n]+\n/, "");
|
||||
return result === EXPECTED_REQUEST_HEADERS;
|
||||
});
|
||||
info("Clipboard contains the currently selected item's request headers.");
|
||||
|
||||
const EXPECTED_RESPONSE_HEADERS = [
|
||||
`${httpVersion} ${status} ${statusText}`,
|
||||
"Last-Modified: Sun, 3 May 2015 11:11:11 GMT",
|
||||
"Content-Type: text/html",
|
||||
"Content-Length: 465",
|
||||
"Connection: close",
|
||||
"Server: httpd.js",
|
||||
"Date: Sun, 3 May 2015 11:11:11 GMT"
|
||||
].join("\n");
|
||||
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyResponseHeaders();
|
||||
}, function validate(result) {
|
||||
// Fake the "Last-Modified" and "Date" headers because they will vary:
|
||||
result = String(result)
|
||||
.replace(/Last-Modified: [^\n]+ GMT/, "Last-Modified: Sun, 3 May 2015 11:11:11 GMT")
|
||||
.replace(/Date: [^\n]+ GMT/, "Date: Sun, 3 May 2015 11:11:11 GMT");
|
||||
return result === EXPECTED_RESPONSE_HEADERS;
|
||||
});
|
||||
info("Clipboard contains the currently selected item's response headers.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if copying an image as data uri works.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
let requestItem = RequestsMenu.getItemAtIndex(5);
|
||||
RequestsMenu.selectedItem = requestItem;
|
||||
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyImageAsDataUri();
|
||||
}, TEST_IMAGE_DATA_URI);
|
||||
|
||||
ok(true, "Clipboard contains the currently selected image as data uri.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
98
devtools/client/netmonitor/test/browser_net_copy_params.js
Normal file
98
devtools/client/netmonitor/test/browser_net_copy_params.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests whether copying a request item's parameters works.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1, 6);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(0);
|
||||
yield testCopyUrlParamsHidden(false);
|
||||
yield testCopyUrlParams("a");
|
||||
yield testCopyPostDataHidden(false);
|
||||
yield testCopyPostData("{ \"foo\": \"bar\" }");
|
||||
|
||||
RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(1);
|
||||
yield testCopyUrlParamsHidden(false);
|
||||
yield testCopyUrlParams("a=b");
|
||||
yield testCopyPostDataHidden(false);
|
||||
yield testCopyPostData("{ \"foo\": \"bar\" }");
|
||||
|
||||
RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(2);
|
||||
yield testCopyUrlParamsHidden(false);
|
||||
yield testCopyUrlParams("a=b");
|
||||
yield testCopyPostDataHidden(false);
|
||||
yield testCopyPostData("foo=bar");
|
||||
|
||||
RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(3);
|
||||
yield testCopyUrlParamsHidden(false);
|
||||
yield testCopyUrlParams("a");
|
||||
yield testCopyPostDataHidden(false);
|
||||
yield testCopyPostData("{ \"foo\": \"bar\" }");
|
||||
|
||||
RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(4);
|
||||
yield testCopyUrlParamsHidden(false);
|
||||
yield testCopyUrlParams("a=b");
|
||||
yield testCopyPostDataHidden(false);
|
||||
yield testCopyPostData("{ \"foo\": \"bar\" }");
|
||||
|
||||
RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(5);
|
||||
yield testCopyUrlParamsHidden(false);
|
||||
yield testCopyUrlParams("a=b");
|
||||
yield testCopyPostDataHidden(false);
|
||||
yield testCopyPostData("?foo=bar");
|
||||
|
||||
RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(6);
|
||||
yield testCopyUrlParamsHidden(true);
|
||||
yield testCopyPostDataHidden(true);
|
||||
|
||||
return teardown(monitor);
|
||||
|
||||
function testCopyUrlParamsHidden(hidden) {
|
||||
let allMenuItems = openContextMenuAndGetAllItems(NetMonitorView);
|
||||
let copyUrlParamsNode = allMenuItems.find(item =>
|
||||
item.id === "request-menu-context-copy-url-params");
|
||||
is(copyUrlParamsNode.visible, !hidden,
|
||||
"The \"Copy URL Parameters\" context menu item should" + (hidden ? " " : " not ") +
|
||||
"be hidden.");
|
||||
}
|
||||
|
||||
function* testCopyUrlParams(queryString) {
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyUrlParams();
|
||||
}, queryString);
|
||||
ok(true, "The url query string copied from the selected item is correct.");
|
||||
}
|
||||
|
||||
function testCopyPostDataHidden(hidden) {
|
||||
let allMenuItems = openContextMenuAndGetAllItems(NetMonitorView);
|
||||
let copyPostDataNode = allMenuItems.find(item =>
|
||||
item.id === "request-menu-context-copy-post-data");
|
||||
is(copyPostDataNode.visible, !hidden,
|
||||
"The \"Copy POST Data\" context menu item should" + (hidden ? " " : " not ") +
|
||||
"be hidden.");
|
||||
}
|
||||
|
||||
function* testCopyPostData(postData) {
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyPostData();
|
||||
}, postData);
|
||||
ok(true, "The post data string copied from the selected item is correct.");
|
||||
}
|
||||
});
|
||||
35
devtools/client/netmonitor/test/browser_net_copy_response.js
Normal file
35
devtools/client/netmonitor/test/browser_net_copy_response.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if copying a request's response works.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
const EXPECTED_RESULT = '{ "greeting": "Hello JSON!" }';
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
let requestItem = RequestsMenu.getItemAtIndex(3);
|
||||
RequestsMenu.selectedItem = requestItem;
|
||||
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyResponse();
|
||||
}, EXPECTED_RESULT);
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if copying an image as data uri works.
|
||||
*/
|
||||
|
||||
const SVG_URL = EXAMPLE_URL + "dropmarker.svg";
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CURL_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, SVG_URL, function* (url) {
|
||||
content.wrappedJSObject.performRequest(url);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
let requestItem = RequestsMenu.getItemAtIndex(0);
|
||||
RequestsMenu.selectedItem = requestItem;
|
||||
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyImageAsDataUri();
|
||||
}, function check(text) {
|
||||
return text.startsWith("data:") && !/undefined/.test(text);
|
||||
});
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
31
devtools/client/netmonitor/test/browser_net_copy_url.js
Normal file
31
devtools/client/netmonitor/test/browser_net_copy_url.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if copying a request's url works.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests(1);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
let requestItem = RequestsMenu.getItemAtIndex(0);
|
||||
RequestsMenu.selectedItem = requestItem;
|
||||
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyUrl();
|
||||
}, requestItem.attachment.url);
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
33
devtools/client/netmonitor/test/browser_net_cors_requests.js
Normal file
33
devtools/client/netmonitor/test/browser_net_cors_requests.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test that CORS preflight requests are displayed by network monitor
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CORS_URL);
|
||||
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1, 1);
|
||||
|
||||
info("Performing a CORS request");
|
||||
let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
|
||||
yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
|
||||
content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
|
||||
});
|
||||
|
||||
info("Waiting until the requests appear in netmonitor");
|
||||
yield wait;
|
||||
|
||||
info("Checking the preflight and flight methods");
|
||||
["OPTIONS", "POST"].forEach((method, i) => {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i), method, requestUrl);
|
||||
});
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
228
devtools/client/netmonitor/test/browser_net_curl-utils.js
Normal file
228
devtools/client/netmonitor/test/browser_net_curl-utils.js
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests Curl Utils functionality.
|
||||
*/
|
||||
|
||||
const { CurlUtils } = require("devtools/client/shared/curl");
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CURL_UTILS_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView, gNetwork } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1, 3);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
|
||||
content.wrappedJSObject.performRequests(url);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
let requests = {
|
||||
get: RequestsMenu.getItemAtIndex(0),
|
||||
post: RequestsMenu.getItemAtIndex(1),
|
||||
multipart: RequestsMenu.getItemAtIndex(2),
|
||||
multipartForm: RequestsMenu.getItemAtIndex(3)
|
||||
};
|
||||
|
||||
let data = yield createCurlData(requests.get.attachment, gNetwork);
|
||||
testFindHeader(data);
|
||||
|
||||
data = yield createCurlData(requests.post.attachment, gNetwork);
|
||||
testIsUrlEncodedRequest(data);
|
||||
testWritePostDataTextParams(data);
|
||||
|
||||
data = yield createCurlData(requests.multipart.attachment, gNetwork);
|
||||
testIsMultipartRequest(data);
|
||||
testGetMultipartBoundary(data);
|
||||
testRemoveBinaryDataFromMultipartText(data);
|
||||
|
||||
data = yield createCurlData(requests.multipartForm.attachment, gNetwork);
|
||||
testGetHeadersFromMultipartText(data);
|
||||
|
||||
if (Services.appinfo.OS != "WINNT") {
|
||||
testEscapeStringPosix();
|
||||
} else {
|
||||
testEscapeStringWin();
|
||||
}
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
||||
function testIsUrlEncodedRequest(data) {
|
||||
let isUrlEncoded = CurlUtils.isUrlEncodedRequest(data);
|
||||
ok(isUrlEncoded, "Should return true for url encoded requests.");
|
||||
}
|
||||
|
||||
function testIsMultipartRequest(data) {
|
||||
let isMultipart = CurlUtils.isMultipartRequest(data);
|
||||
ok(isMultipart, "Should return true for multipart/form-data requests.");
|
||||
}
|
||||
|
||||
function testFindHeader(data) {
|
||||
let headers = data.headers;
|
||||
let hostName = CurlUtils.findHeader(headers, "Host");
|
||||
let requestedWithLowerCased = CurlUtils.findHeader(headers, "x-requested-with");
|
||||
let doesNotExist = CurlUtils.findHeader(headers, "X-Does-Not-Exist");
|
||||
|
||||
is(hostName, "example.com",
|
||||
"Header with name 'Host' should be found in the request array.");
|
||||
is(requestedWithLowerCased, "XMLHttpRequest",
|
||||
"The search should be case insensitive.");
|
||||
is(doesNotExist, null,
|
||||
"Should return null when a header is not found.");
|
||||
}
|
||||
|
||||
function testWritePostDataTextParams(data) {
|
||||
let params = CurlUtils.writePostDataTextParams(data.postDataText);
|
||||
is(params, "param1=value1¶m2=value2¶m3=value3",
|
||||
"Should return a serialized representation of the request parameters");
|
||||
}
|
||||
|
||||
function testGetMultipartBoundary(data) {
|
||||
let boundary = CurlUtils.getMultipartBoundary(data);
|
||||
ok(/-{3,}\w+/.test(boundary),
|
||||
"A boundary string should be found in a multipart request.");
|
||||
}
|
||||
|
||||
function testRemoveBinaryDataFromMultipartText(data) {
|
||||
let generatedBoundary = CurlUtils.getMultipartBoundary(data);
|
||||
let text = data.postDataText;
|
||||
let binaryRemoved =
|
||||
CurlUtils.removeBinaryDataFromMultipartText(text, generatedBoundary);
|
||||
let boundary = "--" + generatedBoundary;
|
||||
|
||||
const EXPECTED_POSIX_RESULT = [
|
||||
"$'",
|
||||
boundary,
|
||||
"\\r\\n\\r\\n",
|
||||
"Content-Disposition: form-data; name=\"param1\"",
|
||||
"\\r\\n\\r\\n",
|
||||
"value1",
|
||||
"\\r\\n",
|
||||
boundary,
|
||||
"\\r\\n\\r\\n",
|
||||
"Content-Disposition: form-data; name=\"file\"; filename=\"filename.png\"",
|
||||
"\\r\\n",
|
||||
"Content-Type: image/png",
|
||||
"\\r\\n\\r\\n",
|
||||
boundary + "--",
|
||||
"\\r\\n",
|
||||
"'"
|
||||
].join("");
|
||||
|
||||
const EXPECTED_WIN_RESULT = [
|
||||
'"' + boundary + '"^',
|
||||
"\u000d\u000A\u000d\u000A",
|
||||
'"Content-Disposition: form-data; name=""param1"""^',
|
||||
"\u000d\u000A\u000d\u000A",
|
||||
'"value1"^',
|
||||
"\u000d\u000A",
|
||||
'"' + boundary + '"^',
|
||||
"\u000d\u000A\u000d\u000A",
|
||||
'"Content-Disposition: form-data; name=""file""; filename=""filename.png"""^',
|
||||
"\u000d\u000A",
|
||||
'"Content-Type: image/png"^',
|
||||
"\u000d\u000A\u000d\u000A",
|
||||
'"' + boundary + '--"^',
|
||||
"\u000d\u000A",
|
||||
'""'
|
||||
].join("");
|
||||
|
||||
if (Services.appinfo.OS != "WINNT") {
|
||||
is(CurlUtils.escapeStringPosix(binaryRemoved), EXPECTED_POSIX_RESULT,
|
||||
"The mulitpart request payload should not contain binary data.");
|
||||
} else {
|
||||
is(CurlUtils.escapeStringWin(binaryRemoved), EXPECTED_WIN_RESULT,
|
||||
"WinNT: The mulitpart request payload should not contain binary data.");
|
||||
}
|
||||
}
|
||||
|
||||
function testGetHeadersFromMultipartText(data) {
|
||||
let headers = CurlUtils.getHeadersFromMultipartText(data.postDataText);
|
||||
|
||||
ok(Array.isArray(headers), "Should return an array.");
|
||||
ok(headers.length > 0, "There should exist at least one request header.");
|
||||
is(headers[0].name, "Content-Type", "The first header name should be 'Content-Type'.");
|
||||
}
|
||||
|
||||
function testEscapeStringPosix() {
|
||||
let surroundedWithQuotes = "A simple string";
|
||||
is(CurlUtils.escapeStringPosix(surroundedWithQuotes), "'A simple string'",
|
||||
"The string should be surrounded with single quotes.");
|
||||
|
||||
let singleQuotes = "It's unusual to put crickets in your coffee.";
|
||||
is(CurlUtils.escapeStringPosix(singleQuotes),
|
||||
"$'It\\'s unusual to put crickets in your coffee.'",
|
||||
"Single quotes should be escaped.");
|
||||
|
||||
let newLines = "Line 1\r\nLine 2\u000d\u000ALine3";
|
||||
is(CurlUtils.escapeStringPosix(newLines), "$'Line 1\\r\\nLine 2\\r\\nLine3'",
|
||||
"Newlines should be escaped.");
|
||||
|
||||
let controlChars = "\u0007 \u0009 \u000C \u001B";
|
||||
is(CurlUtils.escapeStringPosix(controlChars), "$'\\x07 \\x09 \\x0c \\x1b'",
|
||||
"Control characters should be escaped.");
|
||||
|
||||
let extendedAsciiChars = "æ ø ü ß ö é";
|
||||
is(CurlUtils.escapeStringPosix(extendedAsciiChars),
|
||||
"$'\\xc3\\xa6 \\xc3\\xb8 \\xc3\\xbc \\xc3\\x9f \\xc3\\xb6 \\xc3\\xa9'",
|
||||
"Character codes outside of the decimal range 32 - 126 should be escaped.");
|
||||
}
|
||||
|
||||
function testEscapeStringWin() {
|
||||
let surroundedWithDoubleQuotes = "A simple string";
|
||||
is(CurlUtils.escapeStringWin(surroundedWithDoubleQuotes), '"A simple string"',
|
||||
"The string should be surrounded with double quotes.");
|
||||
|
||||
let doubleQuotes = "Quote: \"Time is an illusion. Lunchtime doubly so.\"";
|
||||
is(CurlUtils.escapeStringWin(doubleQuotes),
|
||||
'"Quote: ""Time is an illusion. Lunchtime doubly so."""',
|
||||
"Double quotes should be escaped.");
|
||||
|
||||
let percentSigns = "%AppData%";
|
||||
is(CurlUtils.escapeStringWin(percentSigns), '""%"AppData"%""',
|
||||
"Percent signs should be escaped.");
|
||||
|
||||
let backslashes = "\\A simple string\\";
|
||||
is(CurlUtils.escapeStringWin(backslashes), '"\\\\A simple string\\\\"',
|
||||
"Backslashes should be escaped.");
|
||||
|
||||
let newLines = "line1\r\nline2\r\nline3";
|
||||
is(CurlUtils.escapeStringWin(newLines),
|
||||
'"line1"^\u000d\u000A"line2"^\u000d\u000A"line3"',
|
||||
"Newlines should be escaped.");
|
||||
}
|
||||
|
||||
function* createCurlData(selected, network, controller) {
|
||||
let { url, method, httpVersion } = selected;
|
||||
|
||||
// Create a sanitized object for the Curl command generator.
|
||||
let data = {
|
||||
url,
|
||||
method,
|
||||
headers: [],
|
||||
httpVersion,
|
||||
postDataText: null
|
||||
};
|
||||
|
||||
// Fetch header values.
|
||||
for (let { name, value } of selected.requestHeaders.headers) {
|
||||
let text = yield network.getString(value);
|
||||
data.headers.push({ name: name, value: text });
|
||||
}
|
||||
|
||||
// Fetch the request payload.
|
||||
if (selected.requestPostData) {
|
||||
let postData = selected.requestPostData.postData.text;
|
||||
data.postDataText = yield network.getString(postData);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
45
devtools/client/netmonitor/test/browser_net_cyrillic-01.js
Normal file
45
devtools/client/netmonitor/test/browser_net_cyrillic-01.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if cyrillic text is rendered correctly in the source editor.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=txt", {
|
||||
status: 200,
|
||||
statusText: "DA DA DA"
|
||||
});
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
yield monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
// u044F = я
|
||||
is(editor.getText().indexOf("\u044F"), 26,
|
||||
"The text shown in the source editor is correct.");
|
||||
is(editor.getMode(), Editor.modes.text,
|
||||
"The mode active in the source editor is correct.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
44
devtools/client/netmonitor/test/browser_net_cyrillic-02.js
Normal file
44
devtools/client/netmonitor/test/browser_net_cyrillic-02.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if cyrillic text is rendered correctly in the source editor
|
||||
* when loaded directly from an HTML page.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
tab.linkedBrowser.reload();
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CYRILLIC_URL, {
|
||||
status: 200,
|
||||
statusText: "OK"
|
||||
});
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
|
||||
yield monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
// u044F = я
|
||||
is(editor.getText().indexOf("\u044F"), 486,
|
||||
"The text shown in the source editor is correct.");
|
||||
is(editor.getMode(), Editor.modes.html,
|
||||
"The mode active in the source editor is correct.");
|
||||
|
||||
return teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// A test to ensure that the content in details pane is not duplicated.
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
|
||||
let panel = monitor.panelWin;
|
||||
let { NetMonitorView, EVENTS } = panel;
|
||||
let { RequestsMenu, NetworkDetails } = NetMonitorView;
|
||||
|
||||
const COOKIE_UNIQUE_PATH = "/do-not-use-in-other-tests-using-cookies";
|
||||
|
||||
let TEST_CASES = [
|
||||
{
|
||||
desc: "Test headers tab",
|
||||
pageURI: CUSTOM_GET_URL,
|
||||
requestURI: null,
|
||||
isPost: false,
|
||||
tabIndex: 0,
|
||||
variablesView: NetworkDetails._headers,
|
||||
expectedScopeLength: 2,
|
||||
},
|
||||
{
|
||||
desc: "Test cookies tab",
|
||||
pageURI: CUSTOM_GET_URL,
|
||||
requestURI: COOKIE_UNIQUE_PATH,
|
||||
isPost: false,
|
||||
tabIndex: 1,
|
||||
variablesView: NetworkDetails._cookies,
|
||||
expectedScopeLength: 1,
|
||||
},
|
||||
{
|
||||
desc: "Test params tab",
|
||||
pageURI: POST_RAW_URL,
|
||||
requestURI: null,
|
||||
isPost: true,
|
||||
tabIndex: 2,
|
||||
variablesView: NetworkDetails._params,
|
||||
expectedScopeLength: 1,
|
||||
},
|
||||
];
|
||||
|
||||
info("Adding a cookie for the \"Cookie\" tab test");
|
||||
yield setDocCookie("a=b; path=" + COOKIE_UNIQUE_PATH);
|
||||
|
||||
info("Running tests");
|
||||
for (let spec of TEST_CASES) {
|
||||
yield runTestCase(spec);
|
||||
}
|
||||
|
||||
// Remove the cookie. If an error occurs the path of the cookie ensures it
|
||||
// doesn't mess with the other tests.
|
||||
info("Removing the added cookie.");
|
||||
yield setDocCookie(
|
||||
"a=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + COOKIE_UNIQUE_PATH);
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
/**
|
||||
* Set a content document cookie
|
||||
*/
|
||||
function setDocCookie(cookie) {
|
||||
return ContentTask.spawn(tab.linkedBrowser, cookie, function* (cookieArg) {
|
||||
content.document.cookie = cookieArg;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper that handles the execution of each case.
|
||||
*/
|
||||
function* runTestCase(spec) {
|
||||
info("Running case: " + spec.desc);
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
tab.linkedBrowser.loadURI(spec.pageURI);
|
||||
yield wait;
|
||||
|
||||
RequestsMenu.clear();
|
||||
yield waitForFinalDetailTabUpdate(spec.tabIndex, spec.isPost, spec.requestURI);
|
||||
|
||||
is(spec.variablesView._store.length, spec.expectedScopeLength,
|
||||
"View contains " + spec.expectedScopeLength + " scope headers");
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper that prepares the variables view for the actual testing. It
|
||||
* - selects the correct tab
|
||||
* - performs the specified request to specified URI
|
||||
* - opens the details view
|
||||
* - waits for the final update to happen
|
||||
*/
|
||||
function* waitForFinalDetailTabUpdate(tabIndex, isPost, uri) {
|
||||
let onNetworkEvent = panel.once(EVENTS.NETWORK_EVENT);
|
||||
let onDetailsPopulated = panel.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
|
||||
let onRequestFinished = isPost ?
|
||||
waitForNetworkEvents(monitor, 0, 1) :
|
||||
waitForNetworkEvents(monitor, 1);
|
||||
|
||||
info("Performing a request");
|
||||
yield ContentTask.spawn(tab.linkedBrowser, uri, function* (url) {
|
||||
content.wrappedJSObject.performRequests(1, url);
|
||||
});
|
||||
|
||||
info("Waiting for NETWORK_EVENT");
|
||||
yield onNetworkEvent;
|
||||
|
||||
if (!RequestsMenu.getItemAtIndex(0)) {
|
||||
info("Waiting for the request to be added to the view");
|
||||
yield monitor.panelWin.once(EVENTS.REQUEST_ADDED);
|
||||
}
|
||||
|
||||
ok(true, "Received NETWORK_EVENT. Selecting the item.");
|
||||
let item = RequestsMenu.getItemAtIndex(0);
|
||||
RequestsMenu.selectedItem = item;
|
||||
|
||||
info("Item selected. Waiting for NETWORKDETAILSVIEW_POPULATED");
|
||||
yield onDetailsPopulated;
|
||||
|
||||
info("Received populated event. Selecting tab at index " + tabIndex);
|
||||
NetworkDetails.widget.selectedIndex = tabIndex;
|
||||
|
||||
info("Waiting for request to finish.");
|
||||
yield onRequestFinished;
|
||||
|
||||
ok(true, "Request finished.");
|
||||
|
||||
/**
|
||||
* Because this test uses lazy updates there's four scenarios to consider:
|
||||
* #1: Everything is updated and test is ready to continue.
|
||||
* #2: There's updates that are waiting to be flushed.
|
||||
* #3: Updates are flushed but the tab update is still running.
|
||||
* #4: There's pending updates and a tab update is still running.
|
||||
*
|
||||
* For case #1 there's not going to be a TAB_UPDATED event so don't wait for
|
||||
* it (bug 1106181).
|
||||
*
|
||||
* For cases #2 and #3 it's enough to wait for one TAB_UPDATED event as for
|
||||
* - case #2 the next flush will perform the final update and single
|
||||
* TAB_UPDATED event is emitted.
|
||||
* - case #3 the running update is the final update that'll emit one
|
||||
* TAB_UPDATED event.
|
||||
*
|
||||
* For case #4 we must wait for the updates to be flushed before we can
|
||||
* start waiting for TAB_UPDATED event or we'll continue the test right
|
||||
* after the pending update finishes.
|
||||
*/
|
||||
let hasQueuedUpdates = RequestsMenu._updateQueue.length !== 0;
|
||||
let hasRunningTabUpdate = NetworkDetails._viewState.updating[tabIndex];
|
||||
|
||||
if (hasQueuedUpdates || hasRunningTabUpdate) {
|
||||
info("There's pending updates - waiting for them to finish.");
|
||||
info(" hasQueuedUpdates: " + hasQueuedUpdates);
|
||||
info(" hasRunningTabUpdate: " + hasRunningTabUpdate);
|
||||
|
||||
if (hasQueuedUpdates && hasRunningTabUpdate) {
|
||||
info("Waiting for updates to be flushed.");
|
||||
// _flushRequests calls .populate which emits the following event
|
||||
yield panel.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
|
||||
|
||||
info("Requests flushed.");
|
||||
}
|
||||
|
||||
info("Waiting for final tab update.");
|
||||
yield waitFor(panel, EVENTS.TAB_UPDATED);
|
||||
}
|
||||
|
||||
info("All updates completed.");
|
||||
}
|
||||
});
|
||||
264
devtools/client/netmonitor/test/browser_net_filter-01.js
Normal file
264
devtools/client/netmonitor/test/browser_net_filter-01.js
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test if filtering items in the network table works correctly.
|
||||
*/
|
||||
const BASIC_REQUESTS = [
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined&text=Sample" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=css&text=sample" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=js&text=sample" },
|
||||
];
|
||||
|
||||
const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=font" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=image" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=audio" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=video" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
|
||||
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
|
||||
]);
|
||||
|
||||
add_task(function* () {
|
||||
let Actions = require("devtools/client/netmonitor/actions/index");
|
||||
let { monitor } = yield initNetMonitor(FILTERING_URL);
|
||||
let { gStore } = monitor.panelWin;
|
||||
|
||||
function setFreetextFilter(value) {
|
||||
gStore.dispatch(Actions.setFilterText(value));
|
||||
}
|
||||
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 9);
|
||||
loadCommonFrameScript();
|
||||
yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
yield wait;
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should be a selected item in the requests menu.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be selected in the requests menu.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
|
||||
// First test with single filters...
|
||||
testFilterButtons(monitor, "all");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testFilterButtons(monitor, "html");
|
||||
testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
// Reset filters
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
testFilterButtons(monitor, "css");
|
||||
testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
|
||||
testFilterButtons(monitor, "js");
|
||||
testContents([0, 0, 1, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-xhr-button"));
|
||||
testFilterButtons(monitor, "xhr");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 0]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-fonts-button"));
|
||||
testFilterButtons(monitor, "fonts");
|
||||
testContents([0, 0, 0, 1, 0, 0, 0, 0, 0]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-images-button"));
|
||||
testFilterButtons(monitor, "images");
|
||||
testContents([0, 0, 0, 0, 1, 0, 0, 0, 0]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-media-button"));
|
||||
testFilterButtons(monitor, "media");
|
||||
testContents([0, 0, 0, 0, 0, 1, 1, 0, 0]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
|
||||
testFilterButtons(monitor, "flash");
|
||||
testContents([0, 0, 0, 0, 0, 0, 0, 1, 0]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
|
||||
testFilterButtons(monitor, "ws");
|
||||
testContents([0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
testFilterButtons(monitor, "all");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
|
||||
// Text in filter box that matches nothing should hide all.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
setFreetextFilter("foobar");
|
||||
testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
// Text in filter box that matches should filter out everything else.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
setFreetextFilter("sample");
|
||||
testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
// Text in filter box that matches should filter out everything else.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
setFreetextFilter("SAMPLE");
|
||||
testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
// Test negative filtering (only show unmatched items)
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
setFreetextFilter("-sample");
|
||||
testContents([0, 0, 0, 1, 1, 1, 1, 1, 1]);
|
||||
|
||||
// ...then combine multiple filters together.
|
||||
|
||||
// Enable filtering for html and css; should show request of both type.
|
||||
setFreetextFilter("");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
// Html and css filter enabled and text filter should show just the html and css match.
|
||||
// Should not show both the items matching the button plus the items matching the text.
|
||||
setFreetextFilter("sample");
|
||||
testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
|
||||
setFreetextFilter("");
|
||||
testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0]);
|
||||
testContents([1, 1, 0, 0, 0, 0, 0, 1, 0]);
|
||||
|
||||
// Disable some filters. Only one left active.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
|
||||
testFilterButtons(monitor, "html");
|
||||
testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
// Disable last active filter. Should toggle to all.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testFilterButtons(monitor, "all");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
|
||||
// Enable few filters and click on all. Only "all" should be checked.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
|
||||
testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 1]);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
testFilterButtons(monitor, "all");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function testContents(visibility) {
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should still be a selected item after filtering.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be still selected after filtering.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should still be visible after filtering.");
|
||||
|
||||
is(RequestsMenu.items.length, visibility.length,
|
||||
"There should be a specific amount of items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, visibility.filter(e => e).length,
|
||||
"There should be a specific amount of visbile items in the requests menu.");
|
||||
|
||||
for (let i = 0; i < visibility.length; i++) {
|
||||
is(RequestsMenu.getItemAtIndex(i).target.hidden, !visibility[i],
|
||||
"The item at index " + i + " doesn't have the correct hidden state.");
|
||||
}
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "css",
|
||||
fullMimeType: "text/css; charset=utf-8"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "js",
|
||||
fullMimeType: "application/javascript; charset=utf-8"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=font", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "woff",
|
||||
fullMimeType: "font/woff"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=image", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "png",
|
||||
fullMimeType: "image/png"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=audio", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "ogg",
|
||||
fullMimeType: "audio/ogg"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=video", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "webm",
|
||||
fullMimeType: "video/webm"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(7),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=flash", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "x-shockwave-flash",
|
||||
fullMimeType: "application/x-shockwave-flash"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(8),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=ws", {
|
||||
fuzzyUrl: true,
|
||||
status: 101,
|
||||
statusText: "Switching Protocols",
|
||||
});
|
||||
}
|
||||
});
|
||||
200
devtools/client/netmonitor/test/browser_net_filter-02.js
Normal file
200
devtools/client/netmonitor/test/browser_net_filter-02.js
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test if filtering items in the network table works correctly with new requests.
|
||||
*/
|
||||
|
||||
const BASIC_REQUESTS = [
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=css" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=js" },
|
||||
];
|
||||
|
||||
const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=font" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=image" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=audio" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=video" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
|
||||
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
|
||||
]);
|
||||
|
||||
add_task(function* () {
|
||||
let { monitor } = yield initNetMonitor(FILTERING_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
// It seems that this test may be slow on Ubuntu builds running on ec2.
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let { $, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 9);
|
||||
loadCommonFrameScript();
|
||||
yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
yield wait;
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should be a selected item in the requests menu.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be selected in the requests menu.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
|
||||
testFilterButtons(monitor, "all");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
|
||||
info("Testing html filtering.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testFilterButtons(monitor, "html");
|
||||
testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
info("Performing more requests.");
|
||||
wait = waitForNetworkEvents(monitor, 9);
|
||||
yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
yield wait;
|
||||
|
||||
info("Testing html filtering again.");
|
||||
testFilterButtons(monitor, "html");
|
||||
testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
info("Performing more requests.");
|
||||
wait = waitForNetworkEvents(monitor, 9);
|
||||
yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
yield wait;
|
||||
|
||||
info("Testing html filtering again.");
|
||||
testFilterButtons(monitor, "html");
|
||||
testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
info("Resetting filters.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
testFilterButtons(monitor, "all");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function testContents(visibility) {
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should still be a selected item after filtering.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be still selected after filtering.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should still be visible after filtering.");
|
||||
|
||||
is(RequestsMenu.items.length, visibility.length,
|
||||
"There should be a specific amount of items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, visibility.filter(e => e).length,
|
||||
"There should be a specific amount of visbile items in the requests menu.");
|
||||
|
||||
for (let i = 0; i < visibility.length; i++) {
|
||||
is(RequestsMenu.getItemAtIndex(i).target.hidden, !visibility[i],
|
||||
"The item at index " + i + " doesn't have the correct hidden state.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 1; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "css",
|
||||
fullMimeType: "text/css; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 2; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "js",
|
||||
fullMimeType: "application/javascript; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 3; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=font", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "woff",
|
||||
fullMimeType: "font/woff"
|
||||
});
|
||||
}
|
||||
for (let i = 4; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=image", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "png",
|
||||
fullMimeType: "image/png"
|
||||
});
|
||||
}
|
||||
for (let i = 5; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=audio", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "ogg",
|
||||
fullMimeType: "audio/ogg"
|
||||
});
|
||||
}
|
||||
for (let i = 6; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=video", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "webm",
|
||||
fullMimeType: "video/webm"
|
||||
});
|
||||
}
|
||||
for (let i = 7; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=flash", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "x-shockwave-flash",
|
||||
fullMimeType: "application/x-shockwave-flash"
|
||||
});
|
||||
}
|
||||
for (let i = 8; i < visibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=ws", {
|
||||
fuzzyUrl: true,
|
||||
status: 101,
|
||||
statusText: "Switching Protocols"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
185
devtools/client/netmonitor/test/browser_net_filter-03.js
Normal file
185
devtools/client/netmonitor/test/browser_net_filter-03.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test if filtering items in the network table works correctly with new requests
|
||||
* and while sorting is enabled.
|
||||
*/
|
||||
const BASIC_REQUESTS = [
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=css" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=js" },
|
||||
];
|
||||
|
||||
const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=font" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=image" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=audio" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=video" },
|
||||
]);
|
||||
|
||||
add_task(function* () {
|
||||
let { monitor } = yield initNetMonitor(FILTERING_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
// It seems that this test may be slow on Ubuntu builds running on ec2.
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let { $, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
// The test assumes that the first HTML request here has a longer response
|
||||
// body than the other HTML requests performed later during the test.
|
||||
let requests = Cu.cloneInto(REQUESTS_WITH_MEDIA, {});
|
||||
let newres = "res=<p>" + new Array(10).join(Math.random(10)) + "</p>";
|
||||
requests[0].url = requests[0].url.replace("res=undefined", newres);
|
||||
|
||||
loadCommonFrameScript();
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 7);
|
||||
yield performRequestsInContent(requests);
|
||||
yield wait;
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should be a selected item in the requests menu.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be selected in the requests menu.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
|
||||
testFilterButtons(monitor, "all");
|
||||
testContents([0, 1, 2, 3, 4, 5, 6], 7, 0);
|
||||
|
||||
info("Sorting by size, ascending.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
|
||||
testFilterButtons(monitor, "all");
|
||||
testContents([6, 4, 5, 0, 1, 2, 3], 7, 6);
|
||||
|
||||
info("Testing html filtering.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testFilterButtons(monitor, "html");
|
||||
testContents([6, 4, 5, 0, 1, 2, 3], 1, 6);
|
||||
|
||||
info("Performing more requests.");
|
||||
wait = waitForNetworkEvents(monitor, 7);
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA);
|
||||
yield wait;
|
||||
|
||||
info("Testing html filtering again.");
|
||||
resetSorting();
|
||||
testFilterButtons(monitor, "html");
|
||||
testContents([8, 13, 9, 11, 10, 12, 0, 4, 1, 5, 2, 6, 3, 7], 2, 13);
|
||||
|
||||
info("Performing more requests.");
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA);
|
||||
yield waitForNetworkEvents(monitor, 7);
|
||||
|
||||
info("Testing html filtering again.");
|
||||
resetSorting();
|
||||
testFilterButtons(monitor, "html");
|
||||
testContents([12, 13, 20, 14, 16, 18, 15, 17, 19, 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11],
|
||||
3, 20);
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function resetSorting() {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
|
||||
}
|
||||
|
||||
function testContents(order, visible, selection) {
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should still be a selected item after filtering.");
|
||||
is(RequestsMenu.selectedIndex, selection,
|
||||
"The first item should be still selected after filtering.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should still be visible after filtering.");
|
||||
|
||||
is(RequestsMenu.items.length, order.length,
|
||||
"There should be a specific amount of items in the requests menu.");
|
||||
is(RequestsMenu.visibleItems.length, visible,
|
||||
"There should be a specific amount of visbile items in the requests menu.");
|
||||
|
||||
for (let i = 0; i < order.length; i++) {
|
||||
is(RequestsMenu.getItemAtIndex(i), RequestsMenu.items[i],
|
||||
"The requests menu items aren't ordered correctly. Misplaced item " + i + ".");
|
||||
}
|
||||
|
||||
for (let i = 0, len = order.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "html",
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = order.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "css",
|
||||
fullMimeType: "text/css; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = order.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 2]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "js",
|
||||
fullMimeType: "application/javascript; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = order.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 3]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=font", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "woff",
|
||||
fullMimeType: "font/woff"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = order.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 4]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=image", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "png",
|
||||
fullMimeType: "image/png"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = order.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 5]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=audio", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "ogg",
|
||||
fullMimeType: "audio/ogg"
|
||||
});
|
||||
}
|
||||
for (let i = 0, len = order.length / 7; i < len; i++) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 6]),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=video", {
|
||||
fuzzyUrl: true,
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "webm",
|
||||
fullMimeType: "video/webm"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
63
devtools/client/netmonitor/test/browser_net_filter-04.js
Normal file
63
devtools/client/netmonitor/test/browser_net_filter-04.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if invalid filter types are sanitized when loaded from the preferences.
|
||||
*/
|
||||
|
||||
const BASIC_REQUESTS = [
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=css" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=js" },
|
||||
];
|
||||
|
||||
const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=font" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=image" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=audio" },
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=video" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
|
||||
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
|
||||
]);
|
||||
|
||||
add_task(function* () {
|
||||
Services.prefs.setCharPref("devtools.netmonitor.filters", '["js", "bogus"]');
|
||||
|
||||
let { monitor } = yield initNetMonitor(FILTERING_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { Prefs, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
is(Prefs.filters.length, 2,
|
||||
"All filter types were loaded as an array from the preferences.");
|
||||
is(Prefs.filters[0], "js",
|
||||
"The first filter type is correct.");
|
||||
is(Prefs.filters[1], "bogus",
|
||||
"The second filter type is invalid, but loaded anyway.");
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 9);
|
||||
loadCommonFrameScript();
|
||||
yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
yield wait;
|
||||
|
||||
testFilterButtons(monitor, "js");
|
||||
ok(true, "Only the correct filter type was taken into consideration.");
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
let filters = Services.prefs.getCharPref("devtools.netmonitor.filters");
|
||||
is(filters, '["js"]',
|
||||
"The bogus filter type was ignored and removed from the preferences.");
|
||||
});
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test if the summary text displayed in the network requests menu footer
|
||||
* is correct.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
requestLongerTimeout(2);
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
let { PluralForm } = require("devtools/shared/plural-form");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(FILTERING_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
testStatus();
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
info(`Performing requests in batch #${i}`);
|
||||
let wait = waitForNetworkEvents(monitor, 8);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests('{ "getMedia": true, "getFlash": true }');
|
||||
});
|
||||
yield wait;
|
||||
|
||||
testStatus();
|
||||
|
||||
let buttons = ["html", "css", "js", "xhr", "fonts", "images", "media", "flash"];
|
||||
for (let button of buttons) {
|
||||
let buttonEl = $(`#requests-menu-filter-${button}-button`);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, buttonEl);
|
||||
testStatus();
|
||||
}
|
||||
}
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function testStatus() {
|
||||
let summary = $("#requests-menu-network-summary-button");
|
||||
let value = summary.getAttribute("label");
|
||||
info("Current summary: " + value);
|
||||
|
||||
let visibleItems = RequestsMenu.visibleItems;
|
||||
let visibleRequestsCount = visibleItems.length;
|
||||
let totalRequestsCount = RequestsMenu.itemCount;
|
||||
info("Current requests: " + visibleRequestsCount + " of " + totalRequestsCount + ".");
|
||||
|
||||
if (!totalRequestsCount || !visibleRequestsCount) {
|
||||
is(value, L10N.getStr("networkMenu.empty"),
|
||||
"The current summary text is incorrect, expected an 'empty' label.");
|
||||
return;
|
||||
}
|
||||
|
||||
let totalBytes = RequestsMenu._getTotalBytesOfRequests(visibleItems);
|
||||
let totalMillis =
|
||||
RequestsMenu._getNewestRequest(visibleItems).attachment.endedMillis -
|
||||
RequestsMenu._getOldestRequest(visibleItems).attachment.startedMillis;
|
||||
|
||||
info("Computed total bytes: " + totalBytes);
|
||||
info("Computed total millis: " + totalMillis);
|
||||
|
||||
is(value, PluralForm.get(visibleRequestsCount, L10N.getStr("networkMenu.summary"))
|
||||
.replace("#1", visibleRequestsCount)
|
||||
.replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024, 2))
|
||||
.replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000, 2))
|
||||
, "The current summary text is incorrect.");
|
||||
}
|
||||
});
|
||||
221
devtools/client/netmonitor/test/browser_net_frame.js
Normal file
221
devtools/client/netmonitor/test/browser_net_frame.js
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests for all expected requests when an iframe is loading a subdocument.
|
||||
*/
|
||||
|
||||
const TOP_FILE_NAME = "html_frame-test-page.html";
|
||||
const SUB_FILE_NAME = "html_frame-subdocument.html";
|
||||
const TOP_URL = EXAMPLE_URL + TOP_FILE_NAME;
|
||||
const SUB_URL = EXAMPLE_URL + SUB_FILE_NAME;
|
||||
|
||||
const EXPECTED_REQUESTS_TOP = [
|
||||
{
|
||||
method: "GET",
|
||||
url: TOP_URL,
|
||||
causeType: "document",
|
||||
causeUri: "",
|
||||
stack: true
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "stylesheet_request",
|
||||
causeType: "stylesheet",
|
||||
causeUri: TOP_URL,
|
||||
stack: false
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "img_request",
|
||||
causeType: "img",
|
||||
causeUri: TOP_URL,
|
||||
stack: false
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "xhr_request",
|
||||
causeType: "xhr",
|
||||
causeUri: TOP_URL,
|
||||
stack: [{ fn: "performXhrRequest", file: TOP_FILE_NAME, line: 23 }]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: TOP_URL,
|
||||
stack: [{ fn: "performFetchRequest", file: TOP_FILE_NAME, line: 27 }]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "promise_fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: TOP_URL,
|
||||
stack: [
|
||||
{ fn: "performPromiseFetchRequest", file: TOP_FILE_NAME, line: 39 },
|
||||
{ fn: null, file: TOP_FILE_NAME, line: 38, asyncCause: "promise callback" },
|
||||
]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "timeout_fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: TOP_URL,
|
||||
stack: [
|
||||
{ fn: "performTimeoutFetchRequest", file: TOP_FILE_NAME, line: 41 },
|
||||
{ fn: "performPromiseFetchRequest", file: TOP_FILE_NAME, line: 40,
|
||||
asyncCause: "setTimeout handler" },
|
||||
]
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
url: EXAMPLE_URL + "beacon_request",
|
||||
causeType: "beacon",
|
||||
causeUri: TOP_URL,
|
||||
stack: [{ fn: "performBeaconRequest", file: TOP_FILE_NAME, line: 31 }]
|
||||
},
|
||||
];
|
||||
|
||||
const EXPECTED_REQUESTS_SUB = [
|
||||
{
|
||||
method: "GET",
|
||||
url: SUB_URL,
|
||||
causeType: "subdocument",
|
||||
causeUri: TOP_URL,
|
||||
stack: false
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "stylesheet_request",
|
||||
causeType: "stylesheet",
|
||||
causeUri: SUB_URL,
|
||||
stack: false
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "img_request",
|
||||
causeType: "img",
|
||||
causeUri: SUB_URL,
|
||||
stack: false
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "xhr_request",
|
||||
causeType: "xhr",
|
||||
causeUri: SUB_URL,
|
||||
stack: [{ fn: "performXhrRequest", file: SUB_FILE_NAME, line: 22 }]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: SUB_URL,
|
||||
stack: [{ fn: "performFetchRequest", file: SUB_FILE_NAME, line: 26 }]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "promise_fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: SUB_URL,
|
||||
stack: [
|
||||
{ fn: "performPromiseFetchRequest", file: SUB_FILE_NAME, line: 38 },
|
||||
{ fn: null, file: SUB_FILE_NAME, line: 37, asyncCause: "promise callback" },
|
||||
]
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
url: EXAMPLE_URL + "timeout_fetch_request",
|
||||
causeType: "fetch",
|
||||
causeUri: SUB_URL,
|
||||
stack: [
|
||||
{ fn: "performTimeoutFetchRequest", file: SUB_FILE_NAME, line: 40 },
|
||||
{ fn: "performPromiseFetchRequest", file: SUB_FILE_NAME, line: 39,
|
||||
asyncCause: "setTimeout handler" },
|
||||
]
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
url: EXAMPLE_URL + "beacon_request",
|
||||
causeType: "beacon",
|
||||
causeUri: SUB_URL,
|
||||
stack: [{ fn: "performBeaconRequest", file: SUB_FILE_NAME, line: 30 }]
|
||||
},
|
||||
];
|
||||
|
||||
const REQUEST_COUNT = EXPECTED_REQUESTS_TOP.length + EXPECTED_REQUESTS_SUB.length;
|
||||
|
||||
add_task(function* () {
|
||||
// Async stacks aren't on by default in all builds
|
||||
yield SpecialPowers.pushPrefEnv({ set: [["javascript.options.asyncstack", true]] });
|
||||
|
||||
// the initNetMonitor function clears the network request list after the
|
||||
// page is loaded. That's why we first load a bogus page from SIMPLE_URL,
|
||||
// and only then load the real thing from TOP_URL - we want to catch
|
||||
// all the requests the page is making, not only the XHRs.
|
||||
// We can't use about:blank here, because initNetMonitor checks that the
|
||||
// page has actually made at least one request.
|
||||
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
tab.linkedBrowser.loadURI(TOP_URL, null, null);
|
||||
|
||||
yield waitForNetworkEvents(monitor, REQUEST_COUNT);
|
||||
|
||||
is(RequestsMenu.itemCount, REQUEST_COUNT,
|
||||
"All the page events should be recorded.");
|
||||
|
||||
// While there is a defined order for requests in each document separately, the requests
|
||||
// from different documents may interleave in various ways that change per test run, so
|
||||
// there is not a single order when considering all the requests together.
|
||||
let currentTop = 0;
|
||||
let currentSub = 0;
|
||||
for (let i = 0; i < REQUEST_COUNT; i++) {
|
||||
let requestItem = RequestsMenu.getItemAtIndex(i);
|
||||
|
||||
let itemUrl = requestItem.attachment.url;
|
||||
let itemCauseUri = requestItem.target.querySelector(".requests-menu-cause-label")
|
||||
.getAttribute("tooltiptext");
|
||||
let spec;
|
||||
if (itemUrl == SUB_URL || itemCauseUri == SUB_URL) {
|
||||
spec = EXPECTED_REQUESTS_SUB[currentSub++];
|
||||
} else {
|
||||
spec = EXPECTED_REQUESTS_TOP[currentTop++];
|
||||
}
|
||||
let { method, url, causeType, causeUri, stack } = spec;
|
||||
|
||||
verifyRequestItemTarget(requestItem,
|
||||
method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
|
||||
);
|
||||
|
||||
let { stacktrace } = requestItem.attachment.cause;
|
||||
let stackLen = stacktrace ? stacktrace.length : 0;
|
||||
|
||||
if (stack) {
|
||||
ok(stacktrace, `Request #${i} has a stacktrace`);
|
||||
ok(stackLen > 0,
|
||||
`Request #${i} (${causeType}) has a stacktrace with ${stackLen} items`);
|
||||
|
||||
// if "stack" is array, check the details about the top stack frames
|
||||
if (Array.isArray(stack)) {
|
||||
stack.forEach((frame, j) => {
|
||||
is(stacktrace[j].functionName, frame.fn,
|
||||
`Request #${i} has the correct function on JS stack frame #${j}`);
|
||||
is(stacktrace[j].filename.split("/").pop(), frame.file,
|
||||
`Request #${i} has the correct file on JS stack frame #${j}`);
|
||||
is(stacktrace[j].lineNumber, frame.line,
|
||||
`Request #${i} has the correct line number on JS stack frame #${j}`);
|
||||
is(stacktrace[j].asyncCause, frame.asyncCause,
|
||||
`Request #${i} has the correct async cause on JS stack frame #${j}`);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`);
|
||||
}
|
||||
}
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
62
devtools/client/netmonitor/test/browser_net_html-preview.js
Normal file
62
devtools/client/netmonitor/test/browser_net_html-preview.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if html responses show and properly populate a "Preview" tab.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, document, EVENTS, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 6);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
|
||||
is($("#event-details-pane").selectedIndex, 0,
|
||||
"The first tab in the details pane should be selected.");
|
||||
is($("#preview-tab").hidden, true,
|
||||
"The preview tab should be hidden for non html responses.");
|
||||
is($("#preview-tabpanel").hidden, false,
|
||||
"The preview tabpanel is not hidden for non html responses.");
|
||||
|
||||
RequestsMenu.selectedIndex = 4;
|
||||
NetMonitorView.toggleDetailsPane({ visible: true, animated: false }, 6);
|
||||
|
||||
is($("#event-details-pane").selectedIndex, 6,
|
||||
"The sixth tab in the details pane should be selected.");
|
||||
is($("#preview-tab").hidden, false,
|
||||
"The preview tab should be visible now.");
|
||||
|
||||
yield monitor.panelWin.once(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
|
||||
let iframe = $("#response-preview");
|
||||
ok(iframe,
|
||||
"There should be a response preview iframe available.");
|
||||
ok(iframe.contentDocument,
|
||||
"The iframe's content document should be available.");
|
||||
is(iframe.contentDocument.querySelector("blink").textContent, "Not Found",
|
||||
"The iframe's content document should be loaded and correct.");
|
||||
|
||||
RequestsMenu.selectedIndex = 5;
|
||||
|
||||
is($("#event-details-pane").selectedIndex, 0,
|
||||
"The first tab in the details pane should be selected again.");
|
||||
is($("#preview-tab").hidden, true,
|
||||
"The preview tab should be hidden again for non html responses.");
|
||||
is($("#preview-tabpanel").hidden, false,
|
||||
"The preview tabpanel is not hidden again for non html responses.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
71
devtools/client/netmonitor/test/browser_net_icon-preview.js
Normal file
71
devtools/client/netmonitor/test/browser_net_icon-preview.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if image responses show a thumbnail in the requests menu.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let Actions = require("devtools/client/netmonitor/actions/index");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, $all, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController,
|
||||
gStore } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
let wait = waitForEvents();
|
||||
yield performRequests();
|
||||
yield wait;
|
||||
|
||||
info("Checking the image thumbnail when all items are shown.");
|
||||
checkImageThumbnail();
|
||||
|
||||
RequestsMenu.sortBy("size");
|
||||
info("Checking the image thumbnail when all items are sorted.");
|
||||
checkImageThumbnail();
|
||||
|
||||
gStore.dispatch(Actions.toggleFilterType("images"));
|
||||
info("Checking the image thumbnail when only images are shown.");
|
||||
checkImageThumbnail();
|
||||
|
||||
info("Reloading the debuggee and performing all requests again...");
|
||||
wait = waitForEvents();
|
||||
yield reloadAndPerformRequests();
|
||||
yield wait;
|
||||
|
||||
info("Checking the image thumbnail after a reload.");
|
||||
checkImageThumbnail();
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function waitForEvents() {
|
||||
return promise.all([
|
||||
waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS),
|
||||
monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
|
||||
]);
|
||||
}
|
||||
|
||||
function performRequests() {
|
||||
return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
}
|
||||
|
||||
function* reloadAndPerformRequests() {
|
||||
yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
|
||||
yield performRequests();
|
||||
}
|
||||
|
||||
function checkImageThumbnail() {
|
||||
is($all(".requests-menu-icon[type=thumbnail]").length, 1,
|
||||
"There should be only one image request with a thumbnail displayed.");
|
||||
is($(".requests-menu-icon[type=thumbnail]").src, TEST_IMAGE_DATA_URI,
|
||||
"The image requests-menu-icon thumbnail is displayed correctly.");
|
||||
is($(".requests-menu-icon[type=thumbnail]").hidden, false,
|
||||
"The image requests-menu-icon thumbnail should not be hidden.");
|
||||
}
|
||||
});
|
||||
101
devtools/client/netmonitor/test/browser_net_image-tooltip.js
Normal file
101
devtools/client/netmonitor/test/browser_net_image-tooltip.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const IMAGE_TOOLTIP_URL = EXAMPLE_URL + "html_image-tooltip-test-page.html";
|
||||
const IMAGE_TOOLTIP_REQUESTS = 1;
|
||||
|
||||
/**
|
||||
* Tests if image responses show a popup in the requests menu when hovered.
|
||||
*/
|
||||
add_task(function* test() {
|
||||
let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController } =
|
||||
monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
RequestsMenu.lazyUpdate = true;
|
||||
|
||||
let onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS);
|
||||
let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
|
||||
|
||||
yield performRequests();
|
||||
yield onEvents;
|
||||
yield onThumbnail;
|
||||
|
||||
info("Checking the image thumbnail after a few requests were made...");
|
||||
yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[0]);
|
||||
|
||||
// Hide tooltip before next test, to avoid the situation that tooltip covers
|
||||
// the icon for the request of the next test.
|
||||
info("Checking the image thumbnail gets hidden...");
|
||||
yield hideTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[0]);
|
||||
|
||||
// +1 extra document reload
|
||||
onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS + 1);
|
||||
onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
|
||||
|
||||
info("Reloading the debuggee and performing all requests again...");
|
||||
yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
|
||||
yield performRequests();
|
||||
yield onEvents;
|
||||
yield onThumbnail;
|
||||
|
||||
info("Checking the image thumbnail after a reload.");
|
||||
yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[1]);
|
||||
|
||||
info("Checking if the image thumbnail is hidden when mouse leaves the menu widget");
|
||||
let requestsMenuEl = $("#requests-menu-contents");
|
||||
let onHidden = RequestsMenu.tooltip.once("hidden");
|
||||
EventUtils.synthesizeMouse(requestsMenuEl, 0, 0, {type: "mouseout"}, monitor.panelWin);
|
||||
yield onHidden;
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function performRequests() {
|
||||
return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a tooltip on the {requestItem} and verify that it was displayed
|
||||
* with the expected content.
|
||||
*/
|
||||
function* showTooltipAndVerify(tooltip, requestItem) {
|
||||
let anchor = $(".requests-menu-file", requestItem.target);
|
||||
yield showTooltipOn(tooltip, anchor);
|
||||
|
||||
info("Tooltip was successfully opened for the image request.");
|
||||
is(tooltip.panel.querySelector("img").src, TEST_IMAGE_DATA_URI,
|
||||
"The tooltip's image content is displayed correctly.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a tooltip over an element by sending mousemove event.
|
||||
* @return a promise that resolves when the tooltip is shown
|
||||
*/
|
||||
function showTooltipOn(tooltip, element) {
|
||||
let onShown = tooltip.once("shown");
|
||||
let win = element.ownerDocument.defaultView;
|
||||
EventUtils.synthesizeMouseAtCenter(element, {type: "mousemove"}, win);
|
||||
return onShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a tooltip on the {requestItem} and verify that it was closed.
|
||||
*/
|
||||
function* hideTooltipAndVerify(tooltip, requestItem) {
|
||||
// Hovering method hides tooltip.
|
||||
let anchor = $(".requests-menu-method", requestItem.target);
|
||||
|
||||
let onHidden = tooltip.once("hidden");
|
||||
let win = anchor.ownerDocument.defaultView;
|
||||
EventUtils.synthesizeMouseAtCenter(anchor, {type: "mousemove"}, win);
|
||||
yield onHidden;
|
||||
|
||||
info("Tooltip was successfully closed.");
|
||||
}
|
||||
});
|
||||
98
devtools/client/netmonitor/test/browser_net_json-long.js
Normal file
98
devtools/client/netmonitor/test/browser_net_json-long.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if very long JSON responses are handled correctly.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
// This is receiving over 80 KB of json and will populate over 6000 items
|
||||
// in a variables view instance. Debug builds are slow.
|
||||
requestLongerTimeout(4);
|
||||
|
||||
let { document, EVENTS, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=json-long", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "json",
|
||||
fullMimeType: "text/json; charset=utf-8",
|
||||
size: L10N.getFormatStr("networkMenu.sizeKB",
|
||||
L10N.numberWithDecimals(85975 / 1024, 2)),
|
||||
time: true
|
||||
});
|
||||
|
||||
let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
yield onEvent;
|
||||
|
||||
testResponseTab();
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function testResponseTab() {
|
||||
let tabEl = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tabEl.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
|
||||
"There should be 1 json scope displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-property").length, 6143,
|
||||
"There should be 6143 json properties displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
|
||||
let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
let names = ".variables-view-property > .title > .name";
|
||||
let values = ".variables-view-property > .title > .value";
|
||||
|
||||
is(jsonScope.querySelector(".name").getAttribute("value"),
|
||||
L10N.getStr("jsonScopeName"),
|
||||
"The json scope doesn't have the correct title.");
|
||||
|
||||
is(jsonScope.querySelectorAll(names)[0].getAttribute("value"),
|
||||
"0", "The first json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(values)[0].getAttribute("value"),
|
||||
"Object", "The first json property value was incorrect.");
|
||||
|
||||
is(jsonScope.querySelectorAll(names)[1].getAttribute("value"),
|
||||
"greeting", "The second json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(values)[1].getAttribute("value"),
|
||||
"\"Hello long string JSON!\"", "The second json property value was incorrect.");
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if malformed JSON responses are handled correctly.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(JSON_MALFORMED_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=json-malformed", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "json",
|
||||
fullMimeType: "text/json; charset=utf-8"
|
||||
});
|
||||
|
||||
let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
yield onEvent;
|
||||
|
||||
let tabEl = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tabEl.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.getAttribute("value"),
|
||||
"SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
|
||||
" at line 1 column 40 of the JSON data",
|
||||
"The response info header doesn't have the intended value attribute.");
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.getAttribute("tooltiptext"),
|
||||
"SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
|
||||
" at line 1 column 40 of the JSON data",
|
||||
"The response info header doesn't have the intended tooltiptext attribute.");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
is(editor.getText(), "{ \"greeting\": \"Hello malformed JSON!\" },",
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(editor.getMode(), Editor.modes.js,
|
||||
"The mode active in the source editor is incorrect.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if JSON responses with unusal/custom MIME types are handled correctly.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(JSON_CUSTOM_MIME_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, EVENTS, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=json-custom-mime", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "x-bigcorp-json",
|
||||
fullMimeType: "text/x-bigcorp-json; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
|
||||
time: true
|
||||
});
|
||||
|
||||
let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
yield onEvent;
|
||||
|
||||
testResponseTab();
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function testResponseTab() {
|
||||
let tabEl = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tabEl.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
|
||||
"There should be 1 json scope displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
|
||||
"There should be 2 json properties displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
|
||||
let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
|
||||
.getAttribute("value"),
|
||||
"greeting", "The first json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
|
||||
.getAttribute("value"),
|
||||
"\"Hello oddly-named JSON!\"", "The first json property value was incorrect.");
|
||||
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
|
||||
.getAttribute("value"),
|
||||
"__proto__", "The second json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
|
||||
.getAttribute("value"),
|
||||
"Object", "The second json property value was incorrect.");
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if JSON responses with unusal/custom MIME types are handled correctly.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(JSON_TEXT_MIME_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, EVENTS, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=json-text-mime", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "plain",
|
||||
fullMimeType: "text/plain; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
|
||||
time: true
|
||||
});
|
||||
|
||||
let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
yield onEvent;
|
||||
|
||||
testResponseTab();
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function testResponseTab() {
|
||||
let tabEl = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tabEl.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
|
||||
"There should be 1 json scope displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
|
||||
"There should be 2 json properties displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
|
||||
let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
|
||||
.getAttribute("value"),
|
||||
"greeting", "The first json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
|
||||
.getAttribute("value"),
|
||||
"\"Hello third-party JSON!\"", "The first json property value was incorrect.");
|
||||
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
|
||||
.getAttribute("value"),
|
||||
"__proto__", "The second json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
|
||||
.getAttribute("value"),
|
||||
"Object", "The second json property value was incorrect.");
|
||||
}
|
||||
});
|
||||
111
devtools/client/netmonitor/test/browser_net_jsonp.js
Normal file
111
devtools/client/netmonitor/test/browser_net_jsonp.js
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if JSONP responses are handled correctly.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { L10N } = require("devtools/client/netmonitor/l10n");
|
||||
|
||||
let { tab, monitor } = yield initNetMonitor(JSONP_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, EVENTS, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu, NetworkDetails } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
NetworkDetails._json.lazyEmpty = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 2);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests();
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=jsonp&jsonp=$_0123Fun", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "json",
|
||||
fullMimeType: "text/json; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
|
||||
time: true
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=jsonp2&jsonp=$_4567Sad", {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
type: "json",
|
||||
fullMimeType: "text/json; charset=utf-8",
|
||||
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 54),
|
||||
time: true
|
||||
});
|
||||
|
||||
let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
yield onEvent;
|
||||
|
||||
testResponseTab("$_0123Fun", "\"Hello JSONP!\"");
|
||||
|
||||
onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
RequestsMenu.selectedIndex = 1;
|
||||
yield onEvent;
|
||||
|
||||
testResponseTab("$_4567Sad", "\"Hello weird JSONP!\"");
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
function testResponseTab(func, greeting) {
|
||||
let tabEl = document.querySelectorAll("#details-pane tab")[3];
|
||||
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
|
||||
|
||||
is(tabEl.getAttribute("selected"), "true",
|
||||
"The response tab in the network details pane should be selected.");
|
||||
|
||||
is(tabpanel.querySelector("#response-content-info-header")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response info header doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-json-box")
|
||||
.hasAttribute("hidden"), false,
|
||||
"The response content json box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-textarea-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content textarea box doesn't have the intended visibility.");
|
||||
is(tabpanel.querySelector("#response-content-image-box")
|
||||
.hasAttribute("hidden"), true,
|
||||
"The response content image box doesn't have the intended visibility.");
|
||||
|
||||
is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
|
||||
"There should be 1 json scope displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
|
||||
"There should be 2 json properties displayed in this tabpanel.");
|
||||
is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
|
||||
"The empty notice should not be displayed in this tabpanel.");
|
||||
|
||||
let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
|
||||
|
||||
is(jsonScope.querySelector(".name").getAttribute("value"),
|
||||
L10N.getFormatStr("jsonpScopeName", func),
|
||||
"The json scope doesn't have the correct title.");
|
||||
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
|
||||
.getAttribute("value"),
|
||||
"greeting", "The first json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
|
||||
.getAttribute("value"),
|
||||
greeting, "The first json property value was incorrect.");
|
||||
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
|
||||
.getAttribute("value"),
|
||||
"__proto__", "The second json property name was incorrect.");
|
||||
is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
|
||||
.getAttribute("value"),
|
||||
"Object", "The second json property value was incorrect.");
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if very large response contents are just displayed as plain text.
|
||||
*/
|
||||
|
||||
const HTML_LONG_URL = CONTENT_TYPE_SJS + "?fmt=html-long";
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
// This test could potentially be slow because over 100 KB of stuff
|
||||
// is going to be requested and displayed in the source editor.
|
||||
requestLongerTimeout(2);
|
||||
|
||||
let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, HTML_LONG_URL, function* (url) {
|
||||
content.wrappedJSObject.performRequests(1, url);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html-long", {
|
||||
status: 200,
|
||||
statusText: "OK"
|
||||
});
|
||||
|
||||
let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.getElementById("details-pane-toggle"));
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" },
|
||||
document.querySelectorAll("#details-pane tab")[3]);
|
||||
yield onEvent;
|
||||
|
||||
let editor = yield NetMonitorView.editor("#response-content-textarea");
|
||||
ok(editor.getText().match(/^<p>/),
|
||||
"The text shown in the source editor is incorrect.");
|
||||
is(editor.getMode(), Editor.modes.text,
|
||||
"The mode active in the source editor is incorrect.");
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
// This test uses a lot of memory, so force a GC to help fragmentation.
|
||||
info("Forcing GC after netmonitor test.");
|
||||
Cu.forceGC();
|
||||
});
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests that netmonitor doesn't leak windows on parent-side pages (bug 1285638)
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
// Tell initNetMonitor to enable cache. Otherwise it will assert that there were more
|
||||
// than zero network requests during the page load. But when loading about:config,
|
||||
// there are none.
|
||||
let { monitor } = yield initNetMonitor("about:config", null, true);
|
||||
ok(monitor, "The network monitor was opened");
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if Open in new tab works.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
|
||||
info("Starting test...");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let wait = waitForNetworkEvents(monitor, 1);
|
||||
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
|
||||
content.wrappedJSObject.performRequests(1);
|
||||
});
|
||||
yield wait;
|
||||
|
||||
let requestItem = RequestsMenu.getItemAtIndex(0);
|
||||
RequestsMenu.selectedItem = requestItem;
|
||||
|
||||
let onTabOpen = once(gBrowser.tabContainer, "TabOpen", false);
|
||||
RequestsMenu.contextMenu.openRequestInTab();
|
||||
yield onTabOpen;
|
||||
|
||||
ok(true, "A new tab has been opened");
|
||||
|
||||
yield teardown(monitor);
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
69
devtools/client/netmonitor/test/browser_net_page-nav.js
Normal file
69
devtools/client/netmonitor/test/browser_net_page-nav.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if page navigation ("close", "navigate", etc.) triggers an appropriate
|
||||
* action in the network monitor.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { EVENTS } = monitor.panelWin;
|
||||
|
||||
yield testNavigate();
|
||||
yield testNavigateBack();
|
||||
yield testClose();
|
||||
|
||||
function* testNavigate() {
|
||||
info("Navigating forward...");
|
||||
|
||||
let onWillNav = monitor.panelWin.once(EVENTS.TARGET_WILL_NAVIGATE);
|
||||
let onDidNav = monitor.panelWin.once(EVENTS.TARGET_DID_NAVIGATE);
|
||||
|
||||
tab.linkedBrowser.loadURI(NAVIGATE_URL);
|
||||
yield onWillNav;
|
||||
|
||||
is(tab.linkedBrowser.currentURI.spec, SIMPLE_URL,
|
||||
"Target started navigating to the correct location.");
|
||||
|
||||
yield onDidNav;
|
||||
is(tab.linkedBrowser.currentURI.spec, NAVIGATE_URL,
|
||||
"Target finished navigating to the correct location.");
|
||||
}
|
||||
|
||||
function* testNavigateBack() {
|
||||
info("Navigating backward...");
|
||||
|
||||
let onWillNav = monitor.panelWin.once(EVENTS.TARGET_WILL_NAVIGATE);
|
||||
let onDidNav = monitor.panelWin.once(EVENTS.TARGET_DID_NAVIGATE);
|
||||
|
||||
tab.linkedBrowser.loadURI(SIMPLE_URL);
|
||||
yield onWillNav;
|
||||
|
||||
is(tab.linkedBrowser.currentURI.spec, NAVIGATE_URL,
|
||||
"Target started navigating back to the previous location.");
|
||||
|
||||
yield onDidNav;
|
||||
is(tab.linkedBrowser.currentURI.spec, SIMPLE_URL,
|
||||
"Target finished navigating back to the previous location.");
|
||||
}
|
||||
|
||||
function* testClose() {
|
||||
info("Closing...");
|
||||
|
||||
let onDestroyed = monitor.once("destroyed");
|
||||
removeTab(tab);
|
||||
yield onDestroyed;
|
||||
|
||||
ok(!monitor._controller.client,
|
||||
"There shouldn't be a client available after destruction.");
|
||||
ok(!monitor._controller.tabClient,
|
||||
"There shouldn't be a tabClient available after destruction.");
|
||||
ok(!monitor._controller.webConsoleClient,
|
||||
"There shouldn't be a webConsoleClient available after destruction.");
|
||||
}
|
||||
});
|
||||
72
devtools/client/netmonitor/test/browser_net_pane-collapse.js
Normal file
72
devtools/client/netmonitor/test/browser_net_pane-collapse.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the network monitor panes collapse properly.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { document, Prefs, NetMonitorView } = monitor.panelWin;
|
||||
let detailsPane = document.getElementById("details-pane");
|
||||
let detailsPaneToggleButton = document.getElementById("details-pane-toggle");
|
||||
|
||||
ok(detailsPane.classList.contains("pane-collapsed") &&
|
||||
detailsPaneToggleButton.classList.contains("pane-collapsed"),
|
||||
"The details pane should initially be hidden.");
|
||||
|
||||
NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
|
||||
|
||||
let width = ~~(detailsPane.getAttribute("width"));
|
||||
is(width, Prefs.networkDetailsWidth,
|
||||
"The details pane has an incorrect width.");
|
||||
is(detailsPane.style.marginLeft, "0px",
|
||||
"The details pane has an incorrect left margin.");
|
||||
is(detailsPane.style.marginRight, "0px",
|
||||
"The details pane has an incorrect right margin.");
|
||||
ok(!detailsPane.hasAttribute("animated"),
|
||||
"The details pane has an incorrect animated attribute.");
|
||||
ok(!detailsPane.classList.contains("pane-collapsed") &&
|
||||
!detailsPaneToggleButton.classList.contains("pane-collapsed"),
|
||||
"The details pane should at this point be visible.");
|
||||
|
||||
// Trigger reflow to make sure the UI is in required state.
|
||||
document.documentElement.getBoundingClientRect();
|
||||
|
||||
NetMonitorView.toggleDetailsPane({ visible: false, animated: true });
|
||||
|
||||
yield once(detailsPane, "transitionend");
|
||||
|
||||
let margin = -(width + 1) + "px";
|
||||
is(width, Prefs.networkDetailsWidth,
|
||||
"The details pane has an incorrect width after collapsing.");
|
||||
is(detailsPane.style.marginLeft, margin,
|
||||
"The details pane has an incorrect left margin after collapsing.");
|
||||
is(detailsPane.style.marginRight, margin,
|
||||
"The details pane has an incorrect right margin after collapsing.");
|
||||
ok(!detailsPane.hasAttribute("animated"),
|
||||
"The details pane has an incorrect attribute after an animated collapsing.");
|
||||
ok(detailsPane.classList.contains("pane-collapsed") &&
|
||||
detailsPaneToggleButton.classList.contains("pane-collapsed"),
|
||||
"The details pane should not be visible after collapsing.");
|
||||
|
||||
NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
|
||||
|
||||
is(width, Prefs.networkDetailsWidth,
|
||||
"The details pane has an incorrect width after uncollapsing.");
|
||||
is(detailsPane.style.marginLeft, "0px",
|
||||
"The details pane has an incorrect left margin after uncollapsing.");
|
||||
is(detailsPane.style.marginRight, "0px",
|
||||
"The details pane has an incorrect right margin after uncollapsing.");
|
||||
ok(!detailsPane.hasAttribute("animated"),
|
||||
"The details pane has an incorrect attribute after an unanimated uncollapsing.");
|
||||
ok(!detailsPane.classList.contains("pane-collapsed") &&
|
||||
!detailsPaneToggleButton.classList.contains("pane-collapsed"),
|
||||
"The details pane should be visible again after uncollapsing.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
74
devtools/client/netmonitor/test/browser_net_pane-toggle.js
Normal file
74
devtools/client/netmonitor/test/browser_net_pane-toggle.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if toggling the details pane works as expected.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { $, NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
let { NETWORK_EVENT, TAB_UPDATED } = monitor.panelWin.EVENTS;
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
let toggleButton = $("#details-pane-toggle");
|
||||
|
||||
is(toggleButton.hasAttribute("disabled"), true,
|
||||
"The pane toggle button should be disabled when the frontend is opened.");
|
||||
is(toggleButton.classList.contains("pane-collapsed"), true,
|
||||
"The pane toggle button should indicate that the details pane is " +
|
||||
"collapsed when the frontend is opened.");
|
||||
is(NetMonitorView.detailsPaneHidden, true,
|
||||
"The details pane should be hidden when the frontend is opened.");
|
||||
is(RequestsMenu.selectedItem, null,
|
||||
"There should be no selected item in the requests menu.");
|
||||
|
||||
let networkEvent = monitor.panelWin.once(NETWORK_EVENT);
|
||||
tab.linkedBrowser.reload();
|
||||
yield networkEvent;
|
||||
|
||||
is(toggleButton.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should be enabled after the first request.");
|
||||
is(toggleButton.classList.contains("pane-collapsed"), true,
|
||||
"The pane toggle button should still indicate that the details pane is " +
|
||||
"collapsed after the first request.");
|
||||
is(NetMonitorView.detailsPaneHidden, true,
|
||||
"The details pane should still be hidden after the first request.");
|
||||
is(RequestsMenu.selectedItem, null,
|
||||
"There should still be no selected item in the requests menu.");
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, toggleButton);
|
||||
|
||||
yield monitor.panelWin.once(TAB_UPDATED);
|
||||
|
||||
is(toggleButton.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should still be enabled after being pressed.");
|
||||
is(toggleButton.classList.contains("pane-collapsed"), false,
|
||||
"The pane toggle button should now indicate that the details pane is " +
|
||||
"not collapsed anymore after being pressed.");
|
||||
is(NetMonitorView.detailsPaneHidden, false,
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
"There should be a selected item in the requests menu.");
|
||||
is(RequestsMenu.selectedIndex, 0,
|
||||
"The first item should be selected in the requests menu.");
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, toggleButton);
|
||||
|
||||
is(toggleButton.hasAttribute("disabled"), false,
|
||||
"The pane toggle button should still be enabled after being pressed again.");
|
||||
is(toggleButton.classList.contains("pane-collapsed"), true,
|
||||
"The pane toggle button should now indicate that the details pane is " +
|
||||
"collapsed after being pressed again.");
|
||||
is(NetMonitorView.detailsPaneHidden, true,
|
||||
"The details pane should now be hidden after the toggle button was pressed again.");
|
||||
is(RequestsMenu.selectedItem, null,
|
||||
"There should now be no selected item in the requests menu.");
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Tests if the network monitor leaks on initialization and sudden destruction.
|
||||
* You can also use this initialization format as a template for other tests.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, monitor } = yield initNetMonitor(SINGLE_GET_URL);
|
||||
info("Starting test... ");
|
||||
|
||||
let { NetMonitorView } = monitor.panelWin;
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
|
||||
Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
|
||||
|
||||
yield reloadAndWait();
|
||||
|
||||
is(RequestsMenu.itemCount, 2,
|
||||
"The request menu should have two items at this point.");
|
||||
|
||||
yield reloadAndWait();
|
||||
|
||||
// Since the reload clears the log, we still expect two requests in the log
|
||||
is(RequestsMenu.itemCount, 2,
|
||||
"The request menu should still have two items at this point.");
|
||||
|
||||
// Now we toggle the persistence logs on
|
||||
Services.prefs.setBoolPref("devtools.webconsole.persistlog", true);
|
||||
|
||||
yield reloadAndWait();
|
||||
|
||||
// Since we togged the persistence logs, we expect four items after the reload
|
||||
is(RequestsMenu.itemCount, 4,
|
||||
"The request menu should now have four items at this point.");
|
||||
|
||||
Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
|
||||
return teardown(monitor);
|
||||
|
||||
/**
|
||||
* Reload the page and wait for 2 GET requests. Race-free.
|
||||
*/
|
||||
function reloadAndWait() {
|
||||
let wait = waitForNetworkEvents(monitor, 2);
|
||||
tab.linkedBrowser.reload();
|
||||
return wait;
|
||||
}
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue