Bug 1356126 - Move column react component to separate files;r=rickychien

MozReview-Commit-ID: HdLcKOHW0ag

--HG--
extra : rebase_source : b76852580f453cc45c3e408ec9827f870347d2b7
This commit is contained in:
Fred Lin 2017-04-13 14:45:06 +08:00
parent 06d3c649d5
commit 0a273175e9
14 changed files with 659 additions and 459 deletions

View File

@ -12,6 +12,17 @@ DevToolsModules(
'network-details-panel.js',
'params-panel.js',
'properties-view.js',
'request-list-column-cause.js',
'request-list-column-content-size.js',
'request-list-column-domain.js',
'request-list-column-file.js',
'request-list-column-method.js',
'request-list-column-protocol.js',
'request-list-column-remote-ip.js',
'request-list-column-status.js',
'request-list-column-transferred-size.js',
'request-list-column-type.js',
'request-list-column-waterfall.js',
'request-list-content.js',
'request-list-empty-notice.js',
'request-list-header.js',

View File

@ -0,0 +1,62 @@
/* 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 { div, span } = DOM;
const RequestListColumnCause = createClass({
displayName: "RequestListColumnCause",
propTypes: {
item: PropTypes.object.isRequired,
onCauseBadgeClick: PropTypes.func.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.cause !== nextProps.item.cause;
},
render() {
const {
item,
onCauseBadgeClick,
} = this.props;
const { cause } = item;
let causeType = "";
let causeUri = undefined;
let causeHasStack = false;
if (cause) {
// Legacy server might send a numeric value. Display it as "unknown"
causeType = typeof cause.type === "string" ? cause.type : "unknown";
causeUri = cause.loadingDocumentUri;
causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
}
return (
div({
className: "requests-list-subitem requests-list-cause",
title: causeUri,
},
span({
className: "requests-list-cause-stack",
hidden: !causeHasStack,
onClick: onCauseBadgeClick,
}, "JS"),
span({ className: "subitem-label" }, causeType),
)
);
}
});
module.exports = RequestListColumnCause;

View File

@ -0,0 +1,46 @@
/* 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 { getFormattedSize } = require("../utils/format-utils");
const { div, span } = DOM;
const RequestListColumnContentSize = createClass({
displayName: "RequestListColumnContentSize",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.contentSize !== nextProps.item.contentSize;
},
render() {
const { contentSize } = this.props.item;
let text;
if (typeof contentSize == "number") {
text = getFormattedSize(contentSize);
}
return (
div({
className: "requests-list-subitem subitem-label requests-list-size",
title: text,
},
span({ className: "subitem-label" }, text),
)
);
}
});
module.exports = RequestListColumnContentSize;

View File

@ -0,0 +1,64 @@
/* 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 { L10N } = require("../utils/l10n");
const { propertiesEqual } = require("../utils/request-utils");
const { div, span } = DOM;
const UPDATED_DOMAIN_PROPS = [
"urlDetails",
"remoteAddress",
"securityState",
];
const RequestListColumnDomain = createClass({
displayName: "RequestListColumnDomain",
propTypes: {
item: PropTypes.object.isRequired,
onSecurityIconClick: PropTypes.func.isRequired,
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_DOMAIN_PROPS, this.props.item, nextProps.item);
},
render() {
const { item, onSecurityIconClick } = this.props;
const { urlDetails, remoteAddress, securityState } = item;
let iconClassList = ["requests-security-state-icon"];
let iconTitle;
if (urlDetails.isLocal) {
iconClassList.push("security-state-local");
iconTitle = L10N.getStr("netmonitor.security.state.secure");
} else if (securityState) {
iconClassList.push(`security-state-${securityState}`);
iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
}
let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : "");
return (
div({ className: "requests-list-subitem requests-list-security-and-domain" },
div({
className: iconClassList.join(" "),
title: iconTitle,
onClick: onSecurityIconClick,
}),
span({ className: "subitem-label requests-list-domain", title }, urlDetails.host),
)
);
}
});
module.exports = RequestListColumnDomain;

View File

@ -0,0 +1,54 @@
/* 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 { propertiesEqual } = require("../utils/request-utils");
const { div, img } = DOM;
const UPDATED_FILE_PROPS = [
"urlDetails",
"responseContentDataUri",
];
const RequestListColumnFile = createClass({
displayName: "RequestListColumnFile",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_FILE_PROPS, this.props.item, nextProps.item);
},
render() {
const { urlDetails, responseContentDataUri } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-icon-and-file" },
img({
className: "requests-list-icon",
src: responseContentDataUri,
hidden: !responseContentDataUri,
"data-type": responseContentDataUri ? "thumbnail" : undefined,
}),
div({
className: "subitem-label requests-list-file",
title: urlDetails.unicodeUrl,
},
urlDetails.baseNameWithQuery,
),
)
);
}
});
module.exports = RequestListColumnFile;

View File

@ -0,0 +1,36 @@
/* 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 { div, span } = DOM;
const RequestListColumnMethod = createClass({
displayName: "RequestListColumnMethod",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.method !== nextProps.item.method;
},
render() {
const { method } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-method-box" },
span({ className: "subitem-label requests-list-method" }, method)
)
);
}
});
module.exports = RequestListColumnMethod;

View File

@ -0,0 +1,36 @@
/* 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 { div, span } = DOM;
const RequestListColumnProtocol = createClass({
displayName: "RequestListColumnProtocol",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.httpVersion !== nextProps.item.httpVersion;
},
render() {
const { httpVersion } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-protocol" },
span({ className: "subitem-label", title: httpVersion }, httpVersion),
)
);
}
});
module.exports = RequestListColumnProtocol;

View File

@ -0,0 +1,38 @@
/* 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 { div, span } = DOM;
const RequestListColumnRemoteIP = createClass({
displayName: "RequestListColumnRemoteIP",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.remoteAddress !== nextProps.item.remoteAddress;
},
render() {
const { remoteAddress, remotePort } = this.props.item;
let remoteSummary = remoteAddress ? `${remoteAddress}:${remotePort}` : "";
return (
div({ className: "requests-list-subitem requests-list-remoteip" },
span({ className: "subitem-label", title: remoteSummary }, remoteSummary),
)
);
}
});
module.exports = RequestListColumnRemoteIP;

View File

@ -0,0 +1,68 @@
/* 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 { propertiesEqual } = require("../utils/request-utils");
const { div, span } = DOM;
const UPDATED_STATUS_PROPS = [
"fromCache",
"fromServiceWorker",
"status",
"statusText",
];
const RequestListColumnStatus = createClass({
displayName: "RequestListColumnStatus",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_STATUS_PROPS, this.props.item, nextProps.item);
},
render() {
const { status, statusText, fromCache, fromServiceWorker } = this.props.item;
let code, title;
if (status) {
if (fromCache) {
code = "cached";
} else if (fromServiceWorker) {
code = "service worker";
} else {
code = status;
}
if (statusText) {
title = `${status} ${statusText}`;
if (fromCache) {
title += " (cached)";
}
if (fromServiceWorker) {
title += " (service worker)";
}
}
}
return (
div({ className: "requests-list-subitem requests-list-status", title },
div({ className: "requests-list-status-icon", "data-code": code }),
span({ className: "subitem-label requests-list-status-code" }, status)
)
);
}
});
module.exports = RequestListColumnStatus;

View File

@ -0,0 +1,63 @@
/* 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 { getFormattedSize } = require("../utils/format-utils");
const { L10N } = require("../utils/l10n");
const { propertiesEqual } = require("../utils/request-utils");
const { div, span } = DOM;
const UPDATED_TRANSFERRED_PROPS = [
"transferredSize",
"fromCache",
"fromServiceWorker",
];
const RequestListColumnTransferredSize = createClass({
displayName: "RequestListColumnTransferredSize",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_TRANSFERRED_PROPS, this.props.item, nextProps.item);
},
render() {
const { transferredSize, fromCache, fromServiceWorker, status } = this.props.item;
let text;
let className = "subitem-label";
if (fromCache || status === "304") {
text = L10N.getStr("networkMenu.sizeCached");
className += " theme-comment";
} else if (fromServiceWorker) {
text = L10N.getStr("networkMenu.sizeServiceWorker");
className += " theme-comment";
} else if (typeof transferredSize == "number") {
text = getFormattedSize(transferredSize);
} else if (transferredSize === null) {
text = L10N.getStr("networkMenu.sizeUnavailable");
}
return (
div({
className: "requests-list-subitem requests-list-transferred",
title: text,
},
span({ className }, text),
)
);
}
});
module.exports = RequestListColumnTransferredSize;

View File

@ -0,0 +1,52 @@
/* 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 { getAbbreviatedMimeType } = require("../utils/request-utils");
const { div, span } = DOM;
const CONTENT_MIME_TYPE_ABBREVIATIONS = {
"ecmascript": "js",
"javascript": "js",
"x-javascript": "js"
};
const RequestListColumnType = createClass({
displayName: "RequestListColumnType",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.mimeType !== nextProps.item.mimeType;
},
render() {
const { mimeType } = this.props.item;
let abbrevType;
if (mimeType) {
abbrevType = getAbbreviatedMimeType(mimeType);
abbrevType = CONTENT_MIME_TYPE_ABBREVIATIONS[abbrevType] || abbrevType;
}
return (
div({
className: "requests-list-subitem requests-list-type",
title: mimeType,
},
span({ className: "subitem-label" }, abbrevType),
)
);
}
});
module.exports = RequestListColumnType;

View File

@ -0,0 +1,95 @@
/* 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 { L10N } = require("../utils/l10n");
const { propertiesEqual } = require("../utils/request-utils");
const { div } = DOM;
const UPDATED_WATERFALL_PROPS = [
"eventTimings",
"totalTime",
"fromCache",
"fromServiceWorker",
];
const RequestListColumnWaterfall = createClass({
displayName: "RequestListColumnWaterfall",
propTypes: {
firstRequestStartedMillis: PropTypes.number.isRequired,
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis ||
!propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item);
},
render() {
const { item, firstRequestStartedMillis } = this.props;
return (
div({ className: "requests-list-subitem requests-list-waterfall" },
div({
className: "requests-list-timings",
style: {
paddingInlineStart: `${item.startedMillis - firstRequestStartedMillis}px`,
},
},
timingBoxes(item),
)
)
);
}
});
// List of properties of the timing info we want to create boxes for
const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
function timingBoxes(item) {
const { eventTimings, totalTime, fromCache, fromServiceWorker } = item;
let boxes = [];
if (fromCache || fromServiceWorker) {
return boxes;
}
if (eventTimings) {
// Add a set of boxes representing timing information.
for (let key of TIMING_KEYS) {
let width = eventTimings.timings[key];
// Don't render anything if it surely won't be visible.
// One millisecond == one unscaled pixel.
if (width > 0) {
boxes.push(div({
key,
className: "requests-list-timings-box " + key,
style: { width }
}));
}
}
}
if (typeof totalTime === "number") {
let text = L10N.getFormatStr("networkMenu.totalMS", totalTime);
boxes.push(div({
key: "total",
className: "requests-list-timings-total",
title: text
}, text));
}
return boxes;
}
module.exports = RequestListColumnWaterfall;

View File

@ -12,18 +12,22 @@ const {
} = require("devtools/client/shared/vendor/react");
const I = require("devtools/client/shared/vendor/immutable");
const { getFormattedSize } = require("../utils/format-utils");
const { L10N } = require("../utils/l10n");
const { getAbbreviatedMimeType } = require("../utils/request-utils");
const { propertiesEqual } = require("../utils/request-utils");
const { div, img, span } = DOM;
// Components
const RequestListColumnCause = createFactory(require("./request-list-column-cause"));
const RequestListColumnContentSize = createFactory(require("./request-list-column-content-size"));
const RequestListColumnDomain = createFactory(require("./request-list-column-domain"));
const RequestListColumnFile = createFactory(require("./request-list-column-file"));
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 RequestListColumnStatus = createFactory(require("./request-list-column-status"));
const RequestListColumnTransferredSize = createFactory(require("./request-list-column-transferred-size"));
const RequestListColumnType = createFactory(require("./request-list-column-type"));
const RequestListColumnWaterfall = createFactory(require("./request-list-column-waterfall"));
/**
* Compare two objects on a subset of their properties
*/
function propertiesEqual(props, item1, item2) {
return item1 === item2 || props.every(p => item1[p] === item2[p]);
}
const { div } = DOM;
/**
* Used by shouldComponentUpdate: compare two items, and compare only properties
@ -131,458 +135,21 @@ const RequestListItem = createClass({
onContextMenu,
onMouseDown,
},
columns.get("status") && StatusColumn({ item }),
columns.get("method") && MethodColumn({ item }),
columns.get("file") && FileColumn({ item }),
columns.get("protocol") && ProtocolColumn({ item }),
columns.get("domain") && DomainColumn({ item, onSecurityIconClick }),
columns.get("remoteip") && RemoteIPColumn({ item }),
columns.get("cause") && CauseColumn({ item, onCauseBadgeClick }),
columns.get("type") && TypeColumn({ item }),
columns.get("transferred") && TransferredSizeColumn({ item }),
columns.get("contentSize") && ContentSizeColumn({ item }),
columns.get("waterfall") && WaterfallColumn({ item, firstRequestStartedMillis }),
columns.get("status") && RequestListColumnStatus({ item }),
columns.get("method") && RequestListColumnMethod({ item }),
columns.get("file") && RequestListColumnFile({ item }),
columns.get("protocol") && RequestListColumnProtocol({ item }),
columns.get("domain") && RequestListColumnDomain({ item, onSecurityIconClick }),
columns.get("remoteip") && RequestListColumnRemoteIP({ item }),
columns.get("cause") && RequestListColumnCause({ item, onCauseBadgeClick }),
columns.get("type") && RequestListColumnType({ item }),
columns.get("transferred") && RequestListColumnTransferredSize({ item }),
columns.get("contentSize") && RequestListColumnContentSize({ item }),
columns.get("waterfall") &&
RequestListColumnWaterfall({ item, firstRequestStartedMillis }),
)
);
}
});
const UPDATED_STATUS_PROPS = [
"status",
"statusText",
"fromCache",
"fromServiceWorker",
];
const StatusColumn = createFactory(createClass({
displayName: "StatusColumn",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_STATUS_PROPS, this.props.item, nextProps.item);
},
render() {
const { status, statusText, fromCache, fromServiceWorker } = this.props.item;
let code, title;
if (status) {
if (fromCache) {
code = "cached";
} else if (fromServiceWorker) {
code = "service worker";
} else {
code = status;
}
if (statusText) {
title = `${status} ${statusText}`;
if (fromCache) {
title += " (cached)";
}
if (fromServiceWorker) {
title += " (service worker)";
}
}
}
return (
div({ className: "requests-list-subitem requests-list-status", title },
div({ className: "requests-list-status-icon", "data-code": code }),
span({ className: "subitem-label requests-list-status-code" }, status)
)
);
}
}));
const MethodColumn = createFactory(createClass({
displayName: "MethodColumn",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.method !== nextProps.item.method;
},
render() {
const { method } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-method-box" },
span({ className: "subitem-label requests-list-method" }, method)
)
);
}
}));
const UPDATED_FILE_PROPS = [
"urlDetails",
"responseContentDataUri",
];
const FileColumn = createFactory(createClass({
displayName: "FileColumn",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_FILE_PROPS, this.props.item, nextProps.item);
},
render() {
const { urlDetails, responseContentDataUri } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-icon-and-file" },
img({
className: "requests-list-icon",
src: responseContentDataUri,
hidden: !responseContentDataUri,
"data-type": responseContentDataUri ? "thumbnail" : undefined,
}),
div({
className: "subitem-label requests-list-file",
title: urlDetails.unicodeUrl,
},
urlDetails.baseNameWithQuery,
),
)
);
}
}));
const ProtocolColumn = createFactory(createClass({
displayName: "Protocol",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.httpVersion !== nextProps.item.httpVersion;
},
render() {
const { httpVersion } = this.props.item;
return (
div({ className: "requests-list-subitem requests-list-protocol" },
span({ className: "subitem-label", title: httpVersion }, httpVersion),
)
);
}
}));
const UPDATED_DOMAIN_PROPS = [
"urlDetails",
"remoteAddress",
"securityState",
];
const DomainColumn = createFactory(createClass({
displayName: "DomainColumn",
propTypes: {
item: PropTypes.object.isRequired,
onSecurityIconClick: PropTypes.func.isRequired,
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_DOMAIN_PROPS, this.props.item, nextProps.item);
},
render() {
const { item, onSecurityIconClick } = this.props;
const { urlDetails, remoteAddress, securityState } = item;
let iconClassList = ["requests-security-state-icon"];
let iconTitle;
if (urlDetails.isLocal) {
iconClassList.push("security-state-local");
iconTitle = L10N.getStr("netmonitor.security.state.secure");
} else if (securityState) {
iconClassList.push(`security-state-${securityState}`);
iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
}
let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : "");
return (
div({ className: "requests-list-subitem requests-list-security-and-domain" },
div({
className: iconClassList.join(" "),
title: iconTitle,
onClick: onSecurityIconClick,
}),
span({ className: "subitem-label requests-list-domain", title }, urlDetails.host),
)
);
}
}));
const RemoteIPColumn = createFactory(createClass({
displayName: "RemoteIP",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.remoteAddress !== nextProps.item.remoteAddress;
},
render() {
const { remoteAddress, remotePort } = this.props.item;
let remoteSummary = remoteAddress ? `${remoteAddress}:${remotePort}` : "";
return (
div({ className: "requests-list-subitem requests-list-remoteip" },
span({ className: "subitem-label", title: remoteSummary }, remoteSummary),
)
);
}
}));
const CauseColumn = createFactory(createClass({
displayName: "CauseColumn",
propTypes: {
item: PropTypes.object.isRequired,
onCauseBadgeClick: PropTypes.func.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.cause !== nextProps.item.cause;
},
render() {
const {
item,
onCauseBadgeClick,
} = this.props;
const { cause } = item;
let causeType = "";
let causeUri = undefined;
let causeHasStack = false;
if (cause) {
// Legacy server might send a numeric value. Display it as "unknown"
causeType = typeof cause.type === "string" ? cause.type : "unknown";
causeUri = cause.loadingDocumentUri;
causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
}
return (
div({
className: "requests-list-subitem requests-list-cause",
title: causeUri,
},
span({
className: "requests-list-cause-stack",
hidden: !causeHasStack,
onClick: onCauseBadgeClick,
}, "JS"),
span({ className: "subitem-label" }, causeType),
)
);
}
}));
const CONTENT_MIME_TYPE_ABBREVIATIONS = {
"ecmascript": "js",
"javascript": "js",
"x-javascript": "js"
};
const TypeColumn = createFactory(createClass({
displayName: "TypeColumn",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.mimeType !== nextProps.item.mimeType;
},
render() {
const { mimeType } = this.props.item;
let abbrevType;
if (mimeType) {
abbrevType = getAbbreviatedMimeType(mimeType);
abbrevType = CONTENT_MIME_TYPE_ABBREVIATIONS[abbrevType] || abbrevType;
}
return (
div({
className: "requests-list-subitem requests-list-type",
title: mimeType,
},
span({ className: "subitem-label" }, abbrevType),
)
);
}
}));
const UPDATED_TRANSFERRED_PROPS = [
"transferredSize",
"fromCache",
"fromServiceWorker",
];
const TransferredSizeColumn = createFactory(createClass({
displayName: "TransferredSizeColumn",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return !propertiesEqual(UPDATED_TRANSFERRED_PROPS, this.props.item, nextProps.item);
},
render() {
const { transferredSize, fromCache, fromServiceWorker, status } = this.props.item;
let text;
let className = "subitem-label";
if (fromCache || status === "304") {
text = L10N.getStr("networkMenu.sizeCached");
className += " theme-comment";
} else if (fromServiceWorker) {
text = L10N.getStr("networkMenu.sizeServiceWorker");
className += " theme-comment";
} else if (typeof transferredSize == "number") {
text = getFormattedSize(transferredSize);
} else if (transferredSize === null) {
text = L10N.getStr("networkMenu.sizeUnavailable");
}
return (
div({
className: "requests-list-subitem requests-list-transferred",
title: text,
},
span({ className }, text),
)
);
}
}));
const ContentSizeColumn = createFactory(createClass({
displayName: "ContentSizeColumn",
propTypes: {
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.item.contentSize !== nextProps.item.contentSize;
},
render() {
const { contentSize } = this.props.item;
let text;
if (typeof contentSize == "number") {
text = getFormattedSize(contentSize);
}
return (
div({
className: "requests-list-subitem subitem-label requests-list-size",
title: text,
},
span({ className: "subitem-label" }, text),
)
);
}
}));
const UPDATED_WATERFALL_PROPS = [
"eventTimings",
"totalTime",
"fromCache",
"fromServiceWorker",
];
const WaterfallColumn = createFactory(createClass({
displayName: "WaterfallColumn",
propTypes: {
firstRequestStartedMillis: PropTypes.number.isRequired,
item: PropTypes.object.isRequired,
},
shouldComponentUpdate(nextProps) {
return this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis ||
!propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item);
},
render() {
const { item, firstRequestStartedMillis } = this.props;
return (
div({ className: "requests-list-subitem requests-list-waterfall" },
div({
className: "requests-list-timings",
style: {
paddingInlineStart: `${item.startedMillis - firstRequestStartedMillis}px`,
},
},
timingBoxes(item),
)
)
);
}
}));
// List of properties of the timing info we want to create boxes for
const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
function timingBoxes(item) {
const { eventTimings, totalTime, fromCache, fromServiceWorker } = item;
let boxes = [];
if (fromCache || fromServiceWorker) {
return boxes;
}
if (eventTimings) {
// Add a set of boxes representing timing information.
for (let key of TIMING_KEYS) {
let width = eventTimings.timings[key];
// Don't render anything if it surely won't be visible.
// One millisecond == one unscaled pixel.
if (width > 0) {
boxes.push(div({
key,
className: "requests-list-timings-box " + key,
style: { width }
}));
}
}
}
if (typeof totalTime === "number") {
let text = L10N.getFormatStr("networkMenu.totalMS", totalTime);
boxes.push(div({
key: "total",
className: "requests-list-timings-total",
title: text
}, text));
}
return boxes;
}
module.exports = RequestListItem;

View File

@ -274,6 +274,13 @@ function ipToLong(ip) {
}, 0);
}
/**
* Compare two objects on a subset of their properties
*/
function propertiesEqual(props, item1, item2) {
return item1 === item2 || props.every(p => item1[p] === item2[p]);
}
module.exports = {
getFormDataSections,
fetchHeaders,
@ -289,5 +296,6 @@ module.exports = {
getUrlDetails,
parseQueryString,
parseFormData,
propertiesEqual,
ipToLong,
};