Bug 1360495 - Add response header columns in NetMonitor. r=Honza

--HG--
extra : rebase_source : eed0d9916453a3a5d2711026beaa9ce9085ae86a
This commit is contained in:
Michael Brennan 2017-06-29 05:11:00 -04:00
Родитель 021ccf02e1
Коммит 1a57567f72
21 изменённых файлов: 180 добавлений и 42 удалений

Просмотреть файл

@ -640,6 +640,11 @@ netmonitor.toolbar.resetColumns=Reset Columns
# displayed in the network table header context menu for the timing submenu
netmonitor.toolbar.timings=Timings
# LOCALIZATION NOTE (netmonitor.toolbar.responseHeaders): This is the
# label displayed in the network table header context menu for the
# response headers submenu.
netmonitor.toolbar.responseHeaders=Response Headers
# LOCALIZATION NOTE (netmonitor.summary.url): This is the label displayed
# in the network details headers tab identifying the URL.
netmonitor.summary.url=Request URL:

Просмотреть файл

@ -23,9 +23,9 @@ EventEmitter.decorate(window);
pref("devtools.netmonitor.enabled", true);
pref("devtools.netmonitor.filters", "[\"all\"]");
pref("devtools.netmonitor.hiddenColumns",
"[\"cookies\",\"duration\",\"endTime\",\"latency\"," +
"\"protocol\",\"remoteip\",\"responseTime\",\"scheme\",\"setCookies\",\"startTime\"]"
pref("devtools.netmonitor.visibleColumns",
"[\"status\",\"method\",\"file\",\"domain\",\"cause\"," +
"\"type\",\"transferred\",\"contentSize\",\"waterfall\"]"
);
pref("devtools.netmonitor.panes-network-details-width", 550);
pref("devtools.netmonitor.panes-network-details-height", 450);

Просмотреть файл

@ -489,6 +489,9 @@ body,
width: 8%;
}
.requests-list-response-header {
width: 13%;
}
.requests-list-domain.requests-list-column {
text-align: start;

Просмотреть файл

@ -23,6 +23,7 @@ DevToolsModules(
'request-list-column-method.js',
'request-list-column-protocol.js',
'request-list-column-remote-ip.js',
'request-list-column-response-header.js',
'request-list-column-response-time.js',
'request-list-column-scheme.js',
'request-list-column-set-cookies.js',

Просмотреть файл

@ -0,0 +1,47 @@
/* 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 {
createClass,
DOM,
PropTypes,
} = require("devtools/client/shared/vendor/react");
const { getResponseHeader } = require("../utils/request-utils");
const { div } = DOM;
/**
* Renders a response header column in the requests list. The actual
* header to show is passed as a prop.
*/
const RequestListColumnResponseHeader = createClass({
displayName: "RequestListColumnResponseHeader",
propTypes: {
item: PropTypes.object.isRequired,
header: PropTypes.string.isRequired,
},
shouldComponentUpdate(nextProps) {
const currHeader = getResponseHeader(this.props.item, this.props.header);
const nextHeader = getResponseHeader(nextProps.item, nextProps.header);
return currHeader !== nextHeader;
},
render() {
let header = getResponseHeader(this.props.item, this.props.header);
return (
div({
className: "requests-list-column requests-list-response-header",
title: header
},
header
)
);
}
});
module.exports = RequestListColumnResponseHeader;

Просмотреть файл

@ -89,7 +89,8 @@ const RequestListHeader = createClass({
HEADERS.filter((header) => columns.get(header.name)).map((header) => {
let name = header.name;
let boxName = header.boxName || name;
let label = L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
let label = header.noLocalization
? name : L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
let sorted, sortedTitle;
let active = sort.type == name ? true : undefined;

Просмотреть файл

@ -12,6 +12,7 @@ const {
} = require("devtools/client/shared/vendor/react");
const I = require("devtools/client/shared/vendor/immutable");
const { propertiesEqual } = require("../utils/request-utils");
const { RESPONSE_HEADERS } = require("../constants");
// Components
const RequestListColumnCause = createFactory(require("./request-list-column-cause"));
@ -25,6 +26,7 @@ const RequestListColumnLatency = createFactory(require("./request-list-column-la
const RequestListColumnMethod = createFactory(require("./request-list-column-method"));
const RequestListColumnProtocol = createFactory(require("./request-list-column-protocol"));
const RequestListColumnRemoteIP = createFactory(require("./request-list-column-remote-ip"));
const RequestListColumnResponseHeader = createFactory(require("./request-list-column-response-header"));
const RequestListColumnResponseTime = createFactory(require("./request-list-column-response-time"));
const RequestListColumnScheme = createFactory(require("./request-list-column-scheme"));
const RequestListColumnSetCookies = createFactory(require("./request-list-column-set-cookies"));
@ -163,6 +165,9 @@ const RequestListItem = createClass({
RequestListColumnResponseTime({ item, firstRequestStartedMillis }),
columns.get("duration") && RequestListColumnDuration({ item }),
columns.get("latency") && RequestListColumnLatency({ item }),
...RESPONSE_HEADERS.filter(header => columns.get(header)).map(
header => RequestListColumnResponseHeader({ item, header }),
),
columns.get("waterfall") &&
RequestListColumnWaterfall({ item, firstRequestStartedMillis,
onWaterfallMouseDown }),

Просмотреть файл

@ -93,6 +93,18 @@ const EVENTS = {
CONNECTED: "connected",
};
const RESPONSE_HEADERS = [
"Cache-Control",
"Connection",
"Content-Encoding",
"Content-Length",
"ETag",
"Keep-Alive",
"Last-Modified",
"Server",
"Vary"
];
const HEADERS = [
{
name: "status",
@ -180,6 +192,13 @@ const HEADERS = [
canFilter: false,
subMenu: "timings",
},
...RESPONSE_HEADERS
.map(header => ({
name: header,
canFilter: false,
subMenu: "responseHeaders",
noLocalization: true
})),
{
name: "waterfall",
canFilter: false,
@ -225,6 +244,7 @@ const general = {
EVENTS,
FILTER_SEARCH_DELAY: 200,
HEADERS,
RESPONSE_HEADERS,
FILTER_FLAGS,
SOURCE_EDITOR_SYNTAX_HIGHLIGHT_MAX_SIZE: 51200, // 50 KB in bytes
REQUESTS_WATERFALL,

Просмотреть файл

@ -32,11 +32,11 @@ function prefsMiddleware(store) {
break;
case TOGGLE_COLUMN:
case RESET_COLUMNS:
let hiddenColumns = [...store.getState().ui.columns]
.filter(([column, shown]) => !shown)
let visibleColumns = [...store.getState().ui.columns]
.filter(([column, shown]) => shown)
.map(([column, shown]) => column);
Services.prefs.setCharPref(
"devtools.netmonitor.hiddenColumns", JSON.stringify(hiddenColumns));
"devtools.netmonitor.visibleColumns", JSON.stringify(visibleColumns));
break;
}
return res;

Просмотреть файл

@ -11,6 +11,7 @@ const {
OPEN_STATISTICS,
REMOVE_SELECTED_CUSTOM_REQUEST,
RESET_COLUMNS,
RESPONSE_HEADERS,
SELECT_DETAILS_PANEL_TAB,
SEND_CUSTOM_REQUEST,
SELECT_REQUEST,
@ -18,7 +19,7 @@ const {
WATERFALL_RESIZE,
} = require("../constants");
const Columns = I.Record({
const cols = {
status: true,
method: true,
file: true,
@ -38,7 +39,13 @@ const Columns = I.Record({
duration: false,
latency: false,
waterfall: true,
});
};
const Columns = I.Record(
Object.assign(
cols,
RESPONSE_HEADERS.reduce((acc, header) => Object.assign(acc, { [header]: false }), {})
)
);
const UI = I.Record({
columns: new Columns(),

Просмотреть файл

@ -16,6 +16,10 @@ const subMenuMap = HEADERS
.filter((header) => header.hasOwnProperty("subMenu"))
.reduce((acc, { name, subMenu }) => Object.assign(acc, { [name]: subMenu }), {});
const nonLocalizedHeaders = HEADERS
.filter((header) => header.hasOwnProperty("noLocalization"))
.map((header) => header.name);
class RequestListHeaderContextMenu {
constructor({ toggleColumn, resetColumns }) {
this.toggleColumn = toggleColumn;
@ -37,13 +41,16 @@ class RequestListHeaderContextMenu {
*/
open(event = {}) {
let menu = [];
let subMenu = { timings: [] };
let subMenu = { timings: [], responseHeaders: [] };
let onlyOneColumn = this.visibleColumns.length === 1;
for (let [column, shown] of this.columns) {
let label = nonLocalizedHeaders.includes(column)
? stringMap[column] || column
: L10N.getStr(`netmonitor.toolbar.${stringMap[column] || column}`);
let entry = {
id: `request-list-header-${column}-toggle`,
label: L10N.getStr(`netmonitor.toolbar.${stringMap[column] || column}`),
label,
type: "checkbox",
checked: shown,
click: () => this.toggleColumn(column),
@ -60,6 +67,10 @@ class RequestListHeaderContextMenu {
label: L10N.getStr("netmonitor.toolbar.timings"),
submenu: subMenu.timings,
});
menu.push({
label: L10N.getStr("netmonitor.toolbar.responseHeaders"),
submenu: subMenu.responseHeaders,
});
menu.push({ type: "separator" });
menu.push({

Просмотреть файл

@ -32,11 +32,11 @@ function configureStore() {
});
let columns = new Columns();
let hiddenColumns = getPref("devtools.netmonitor.hiddenColumns");
let visibleColumns = getPref("devtools.netmonitor.visibleColumns");
for (let [col] of columns) {
columns = columns.withMutations((state) => {
state.set(col, !hiddenColumns.includes(col));
state.set(col, visibleColumns.includes(col));
});
}

Просмотреть файл

@ -12,6 +12,6 @@ const { PrefsHelper } = require("devtools/client/shared/prefs");
exports.Prefs = new PrefsHelper("devtools.netmonitor", {
networkDetailsWidth: ["Int", "panes-network-details-width"],
networkDetailsHeight: ["Int", "panes-network-details-height"],
hiddenColumns: ["Json", "hiddenColumns"],
visibleColumns: ["Json", "visibleColumns"],
filters: ["Json", "filters"]
});

Просмотреть файл

@ -356,6 +356,24 @@ function getFormattedProtocol(item) {
return protocol.join("+");
}
/**
* Get the value of a particular response header, or null if not
* present.
*/
function getResponseHeader(item, header) {
let { responseHeaders } = item;
if (!responseHeaders || !responseHeaders.headers.length) {
return null;
}
header = header.toLowerCase();
for (let responseHeader of responseHeaders.headers) {
if (responseHeader.name.toLowerCase() == header) {
return responseHeader.value;
}
}
return null;
}
module.exports = {
getFormDataSections,
fetchHeaders,
@ -365,6 +383,7 @@ module.exports = {
getAbbreviatedMimeType,
getEndTime,
getFormattedProtocol,
getResponseHeader,
getResponseTime,
getStartTime,
getUrlBaseName,

Просмотреть файл

@ -8,9 +8,11 @@ const {
getAbbreviatedMimeType,
getEndTime,
getResponseTime,
getResponseHeader,
getStartTime,
ipToLong,
} = require("./request-utils");
const { RESPONSE_HEADERS } = require("../constants");
/**
* Predicates used when sorting items.
@ -92,6 +94,16 @@ function latency(first, second) {
return result || waterfall(first, second);
}
function compareHeader(header, first, second) {
const result = compareValues(getResponseHeader(first, header) || "",
getResponseHeader(second, header) || "");
return result || waterfall(first, second);
}
const responseHeaders = RESPONSE_HEADERS
.reduce((acc, header) => Object.assign(
acc, {[header]: (first, second) => compareHeader(header, first, second)}), {});
function domain(first, second) {
const firstDomain = first.urlDetails.host.toLowerCase();
const secondDomain = second.urlDetails.host.toLowerCase();
@ -150,7 +162,7 @@ function contentSize(first, second) {
return result || waterfall(first, second);
}
exports.Sorters = {
const sorters = {
status,
method,
file,
@ -171,3 +183,4 @@ exports.Sorters = {
latency,
waterfall,
};
exports.Sorters = Object.assign(sorters, responseHeaders);

Просмотреть файл

@ -18,6 +18,9 @@ add_task(function* () {
.filter(([_, visible]) => visible);
if (visibleColumns.length === 1) {
if (!shown) {
continue;
}
yield testLastMenuItem(column);
break;
}

Просмотреть файл

@ -4,38 +4,36 @@
"use strict";
/**
* Tests if hidden columns are properly saved
* Tests if visible columns are properly saved
*/
add_task(function* () {
Services.prefs.setCharPref("devtools.netmonitor.hiddenColumns",
'["status", "contentSize"]');
Services.prefs.setCharPref("devtools.netmonitor.visibleColumns",
'["status", "contentSize", "waterfall"]');
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
let { document, parent } = monitor.panelWin;
ok(!document.querySelector("#requests-list-status-button"),
"Status column should be hidden");
ok(!document.querySelector("#requests-list-contentSize-button"),
"Content size column should be hidden");
yield showColumn("status");
yield showColumn("contentSize");
ok(!Services.prefs.getCharPref("devtools.netmonitor.hiddenColumns").includes("status"),
"Pref should be synced for status");
ok(!Services.prefs.getCharPref("devtools.netmonitor.hiddenColumns")
.includes("contentSize"), "Pref should be synced for contentSize");
ok(document.querySelector("#requests-list-status-button"),
"Status column should be shown");
ok(document.querySelector("#requests-list-contentSize-button"),
"Content size column should be shown");
yield hideColumn("status");
yield hideColumn("contentSize");
ok(Services.prefs.getCharPref("devtools.netmonitor.hiddenColumns").includes("status"),
"Pref should be synced for status");
ok(!Services.prefs.getCharPref("devtools.netmonitor.visibleColumns").includes("status"),
"Pref should be synced for status");
ok(!Services.prefs.getCharPref("devtools.netmonitor.visibleColumns")
.includes("contentSize"), "Pref should be synced for contentSize");
yield showColumn("status");
ok(Services.prefs.getCharPref("devtools.netmonitor.visibleColumns").includes("status"),
"Pref should be synced for status");
function* hideColumn(column) {
info(`Clicking context-menu item for ${column}`);
EventUtils.sendMouseEvent({ type: "contextmenu" },

Просмотреть файл

@ -14,7 +14,7 @@ add_task(function* () {
let { document, parent, windowRequire } = monitor.panelWin;
let { Prefs } = windowRequire("devtools/client/netmonitor/src/utils/prefs");
let prefBefore = Prefs.hiddenColumns;
let prefBefore = Prefs.visibleColumns;
hideColumn("status");
hideColumn("waterfall");
@ -24,7 +24,7 @@ add_task(function* () {
parent.document.querySelector("#request-list-header-reset-columns").click();
is(JSON.stringify(prefBefore), JSON.stringify(Prefs.hiddenColumns),
is(JSON.stringify(prefBefore), JSON.stringify(Prefs.visibleColumns),
"Reset columns item should reset columns pref");
function* hideColumn(column) {

Просмотреть файл

@ -8,11 +8,10 @@
*/
add_task(function* () {
// Hide file, protocol, remoteip columns to make sure timing division
// can render properly
// Make sure timing division can render properly
Services.prefs.setCharPref(
"devtools.netmonitor.hiddenColumns",
"[\"file\",\"protocol\",\"remoteip\"]"
"devtools.netmonitor.visibleColumns",
"[\"waterfall\"]"
);
let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);

Просмотреть файл

@ -92,8 +92,14 @@ Services.prefs.setBoolPref("devtools.debugger.log", false);
// Always reset some prefs to their original values after the test finishes.
const gDefaultFilters = Services.prefs.getCharPref("devtools.netmonitor.filters");
// Reveal all hidden columns for test
Services.prefs.setCharPref("devtools.netmonitor.hiddenColumns", "[]");
// Reveal many columns for test
Services.prefs.setCharPref(
"devtools.netmonitor.visibleColumns",
"[\"cause\",\"contentSize\",\"cookies\",\"domain\",\"duration\"," +
"\"endTime\",\"file\",\"latency\",\"method\",\"protocol\"," +
"\"remoteip\",\"responseTime\",\"scheme\",\"setCookies\"," +
"\"startTime\",\"status\",\"transferred\",\"type\",\"waterfall\"]"
);
registerCleanupFunction(() => {
info("finish() was called, cleaning up...");

Просмотреть файл

@ -160,8 +160,8 @@ pref("devtools.netmonitor.enabled", true);
pref("devtools.netmonitor.panes-network-details-width", 550);
pref("devtools.netmonitor.panes-network-details-height", 450);
pref("devtools.netmonitor.filters", "[\"all\"]");
pref("devtools.netmonitor.hiddenColumns",
"[\"cookies\",\"duration\",\"endTime\",\"latency\",\"protocol\",\"remoteip\",\"responseTime\",\"scheme\",\"setCookies\",\"startTime\"]"
pref("devtools.netmonitor.visibleColumns",
"[\"status\",\"method\",\"file\",\"domain\",\"cause\",\"type\",\"transferred\",\"contentSize\",\"waterfall\"]"
);
// The default Network monitor HAR export setting