forked from mirrors/gecko-dev
MozReview-Commit-ID: IMu1sJLxQYy --HG-- extra : rebase_source : aa5a6f1a67ad0d4d83bcb0308d3dd6fe0de0ee0f
197 lines
5.8 KiB
JavaScript
197 lines
5.8 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* globals document */
|
|
|
|
"use strict";
|
|
|
|
const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
|
|
const { div, button } = DOM;
|
|
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
|
const { L10N } = require("../l10n");
|
|
const { getWaterfallScale } = require("../selectors/index");
|
|
const Actions = require("../actions/index");
|
|
const WaterfallBackground = require("../waterfall-background");
|
|
|
|
// ms
|
|
const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
|
|
// px
|
|
const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60;
|
|
|
|
const REQUEST_TIME_DECIMALS = 2;
|
|
|
|
const HEADERS = [
|
|
{ name: "status", label: "status3" },
|
|
{ name: "method" },
|
|
{ name: "file", boxName: "icon-and-file" },
|
|
{ name: "domain", boxName: "security-and-domain" },
|
|
{ name: "cause" },
|
|
{ name: "type" },
|
|
{ name: "transferred" },
|
|
{ name: "size" },
|
|
{ name: "waterfall" }
|
|
];
|
|
|
|
/**
|
|
* Render the request list header with sorting arrows for columns.
|
|
* Displays tick marks in the waterfall column header.
|
|
* Also draws the waterfall background canvas and updates it when needed.
|
|
*/
|
|
const RequestListHeader = createClass({
|
|
displayName: "RequestListHeader",
|
|
|
|
propTypes: {
|
|
sort: PropTypes.object,
|
|
scale: PropTypes.number,
|
|
waterfallWidth: PropTypes.number,
|
|
onHeaderClick: PropTypes.func.isRequired,
|
|
},
|
|
|
|
componentDidMount() {
|
|
this.background = new WaterfallBackground(document);
|
|
this.background.draw(this.props);
|
|
},
|
|
|
|
componentDidUpdate() {
|
|
this.background.draw(this.props);
|
|
},
|
|
|
|
componentWillUnmount() {
|
|
this.background.destroy();
|
|
this.background = null;
|
|
},
|
|
|
|
render() {
|
|
const { sort, scale, waterfallWidth, onHeaderClick } = this.props;
|
|
|
|
return div(
|
|
{ id: "requests-menu-toolbar", className: "devtools-toolbar" },
|
|
div({ id: "toolbar-labels" },
|
|
HEADERS.map(header => {
|
|
const name = header.name;
|
|
const boxName = header.boxName || name;
|
|
const label = L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
|
|
|
|
let sorted, sortedTitle;
|
|
const active = sort.type == name ? true : undefined;
|
|
if (active) {
|
|
sorted = sort.ascending ? "ascending" : "descending";
|
|
sortedTitle = L10N.getStr(sort.ascending
|
|
? "networkMenu.sortedAsc"
|
|
: "networkMenu.sortedDesc");
|
|
}
|
|
|
|
return div(
|
|
{
|
|
id: `requests-menu-${boxName}-header-box`,
|
|
key: name,
|
|
className: `requests-menu-header requests-menu-${boxName}`,
|
|
// Used to style the next column.
|
|
"data-active": active,
|
|
},
|
|
button(
|
|
{
|
|
id: `requests-menu-${name}-button`,
|
|
className: `requests-menu-header-button requests-menu-${name}`,
|
|
"data-sorted": sorted,
|
|
title: sortedTitle,
|
|
onClick: () => onHeaderClick(name),
|
|
},
|
|
name == "waterfall" ? WaterfallLabel(waterfallWidth, scale, label)
|
|
: div({ className: "button-text" }, label),
|
|
div({ className: "button-icon" })
|
|
)
|
|
);
|
|
})
|
|
)
|
|
);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Build the waterfall header - timing tick marks with the right spacing
|
|
*/
|
|
function waterfallDivisionLabels(waterfallWidth, scale) {
|
|
let labels = [];
|
|
|
|
// Build new millisecond tick labels...
|
|
let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
|
|
let scaledStep = scale * timingStep;
|
|
|
|
// Ignore any divisions that would end up being too close to each other.
|
|
while (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
|
|
scaledStep *= 2;
|
|
}
|
|
|
|
// Insert one label for each division on the current scale.
|
|
for (let x = 0; x < waterfallWidth; x += scaledStep) {
|
|
let millisecondTime = x / scale;
|
|
|
|
let normalizedTime = millisecondTime;
|
|
let divisionScale = "millisecond";
|
|
|
|
// If the division is greater than 1 minute.
|
|
if (normalizedTime > 60000) {
|
|
normalizedTime /= 60000;
|
|
divisionScale = "minute";
|
|
} else if (normalizedTime > 1000) {
|
|
// If the division is greater than 1 second.
|
|
normalizedTime /= 1000;
|
|
divisionScale = "second";
|
|
}
|
|
|
|
// Showing too many decimals is bad UX.
|
|
if (divisionScale == "millisecond") {
|
|
normalizedTime |= 0;
|
|
} else {
|
|
normalizedTime = L10N.numberWithDecimals(normalizedTime, REQUEST_TIME_DECIMALS);
|
|
}
|
|
|
|
let width = (x + scaledStep | 0) - (x | 0);
|
|
// Adjust the first marker for the borders
|
|
if (x == 0) {
|
|
width -= 2;
|
|
}
|
|
// Last marker doesn't need a width specified at all
|
|
if (x + scaledStep >= waterfallWidth) {
|
|
width = undefined;
|
|
}
|
|
|
|
labels.push(div(
|
|
{
|
|
key: labels.length,
|
|
className: "requests-menu-timings-division",
|
|
"data-division-scale": divisionScale,
|
|
style: { width }
|
|
},
|
|
L10N.getFormatStr("networkMenu." + divisionScale, normalizedTime)
|
|
));
|
|
}
|
|
|
|
return labels;
|
|
}
|
|
|
|
function WaterfallLabel(waterfallWidth, scale, label) {
|
|
let className = "button-text requests-menu-waterfall-label-wrapper";
|
|
|
|
if (scale != null) {
|
|
label = waterfallDivisionLabels(waterfallWidth, scale);
|
|
className += " requests-menu-waterfall-visible";
|
|
}
|
|
|
|
return div({ className }, label);
|
|
}
|
|
|
|
module.exports = connect(
|
|
state => ({
|
|
sort: state.sort,
|
|
scale: getWaterfallScale(state),
|
|
waterfallWidth: state.ui.waterfallWidth,
|
|
firstRequestStartedMillis: state.requests.firstStartedMillis,
|
|
timingMarkers: state.timingMarkers,
|
|
}),
|
|
dispatch => ({
|
|
onHeaderClick: type => dispatch(Actions.sortBy(type)),
|
|
})
|
|
)(RequestListHeader);
|