mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-01 01:38:07 +08:00
This CL batches up changes to multiple CSS files where they had either no colors to change, or one very easy color to change. Bug: 1152736 Change-Id: I061ebd909539dfaca7e8c3a70b0e97f0a0ba5f07 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2567115 Auto-Submit: Jack Franklin <jacktfranklin@chromium.org> Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
700 lines
24 KiB
JavaScript
700 lines
24 KiB
JavaScript
// Copyright 2016 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import * as Common from '../common/common.js';
|
|
import * as PerfUI from '../perf_ui/perf_ui.js';
|
|
import * as SDK from '../sdk/sdk.js'; // eslint-disable-line no-unused-vars
|
|
import * as ThemeSupport from '../theme_support/theme_support.js';
|
|
import * as UI from '../ui/ui.js';
|
|
|
|
import {NetworkNode} from './NetworkDataGridNode.js'; // eslint-disable-line no-unused-vars
|
|
import {NetworkTimeCalculator} from './NetworkTimeCalculator.js'; // eslint-disable-line no-unused-vars
|
|
import {RequestTimeRange, RequestTimeRangeNames, RequestTimingView} from './RequestTimingView.js'; // eslint-disable-line no-unused-vars
|
|
|
|
export class NetworkWaterfallColumn extends UI.Widget.VBox {
|
|
/**
|
|
* @param {!NetworkTimeCalculator} calculator
|
|
*/
|
|
constructor(calculator) {
|
|
// TODO(allada) Make this a shadowDOM when the NetworkWaterfallColumn gets moved into NetworkLogViewColumns.
|
|
super(false);
|
|
this.registerRequiredCSS('network/networkWaterfallColumn.css', {enableLegacyPatching: false});
|
|
|
|
/** @type {!HTMLCanvasElement} */
|
|
this._canvas = /** @type {!HTMLCanvasElement} */ (this.contentElement.createChild('canvas'));
|
|
this._canvas.tabIndex = -1;
|
|
this.setDefaultFocusedElement(this._canvas);
|
|
this._canvasPosition = this._canvas.getBoundingClientRect();
|
|
|
|
/** @const */
|
|
this._leftPadding = 5;
|
|
/** @const */
|
|
this._fontSize = 10;
|
|
|
|
this._rightPadding = 0;
|
|
this._scrollTop = 0;
|
|
|
|
this._headerHeight = 0;
|
|
this._calculator = calculator;
|
|
|
|
// this._rawRowHeight captures model height (41 or 21px),
|
|
// this._rowHeight is computed height of the row in CSS pixels, can be 20.8 for zoomed-in content.
|
|
this._rawRowHeight = 0;
|
|
this._rowHeight = 0;
|
|
|
|
this._offsetWidth = 0;
|
|
this._offsetHeight = 0;
|
|
this._startTime = this._calculator.minimumBoundary();
|
|
this._endTime = this._calculator.maximumBoundary();
|
|
|
|
this._popoverHelper = new UI.PopoverHelper.PopoverHelper(this.element, this._getPopoverRequest.bind(this));
|
|
this._popoverHelper.setHasPadding(true);
|
|
this._popoverHelper.setTimeout(300, 300);
|
|
|
|
/** @type {!Array<!NetworkNode>} */
|
|
this._nodes = [];
|
|
|
|
/** @type {?NetworkNode} */
|
|
this._hoveredNode = null;
|
|
|
|
/** @type {!Map<string, !Array<number>>} */
|
|
this._eventDividers = new Map();
|
|
|
|
/** @type {(number|undefined)} */
|
|
this._updateRequestID;
|
|
|
|
this.element.addEventListener('mousemove', this._onMouseMove.bind(this), true);
|
|
this.element.addEventListener('mouseleave', event => this._setHoveredNode(null, false), true);
|
|
this.element.addEventListener('click', this._onClick.bind(this), true);
|
|
|
|
this._styleForTimeRangeName = NetworkWaterfallColumn._buildRequestTimeRangeStyle();
|
|
|
|
const resourceStyleTuple = NetworkWaterfallColumn._buildResourceTypeStyle();
|
|
/** @type {!Map<!Common.ResourceType.ResourceType, !_LayerStyle>} */
|
|
this._styleForWaitingResourceType = resourceStyleTuple[0];
|
|
/** @type {!Map<!Common.ResourceType.ResourceType, !_LayerStyle>} */
|
|
this._styleForDownloadingResourceType = resourceStyleTuple[1];
|
|
|
|
const baseLineColor =
|
|
ThemeSupport.ThemeSupport.instance().patchColorText('#a5a5a5', ThemeSupport.ThemeSupport.ColorUsage.Foreground);
|
|
/** @type {!_LayerStyle} */
|
|
this._wiskerStyle = {borderColor: baseLineColor, lineWidth: 1, fillStyle: undefined};
|
|
/** @type {!_LayerStyle} */
|
|
this._hoverDetailsStyle = {fillStyle: baseLineColor, lineWidth: 1, borderColor: baseLineColor};
|
|
|
|
/** @type {!Map<!_LayerStyle, !Path2D>} */
|
|
this._pathForStyle = new Map();
|
|
/** @type {!Array<!_TextLayer>} */
|
|
this._textLayers = [];
|
|
}
|
|
|
|
/**
|
|
* @return {!Map<!RequestTimeRangeNames, !_LayerStyle>}
|
|
*/
|
|
static _buildRequestTimeRangeStyle() {
|
|
const types = RequestTimeRangeNames;
|
|
const styleMap = new Map();
|
|
styleMap.set(types.Connecting, {fillStyle: '#FF9800'});
|
|
styleMap.set(types.SSL, {fillStyle: '#9C27B0'});
|
|
styleMap.set(types.DNS, {fillStyle: '#009688'});
|
|
styleMap.set(types.Proxy, {fillStyle: '#A1887F'});
|
|
styleMap.set(types.Blocking, {fillStyle: '#AAAAAA'});
|
|
styleMap.set(types.Push, {fillStyle: '#8CDBff'});
|
|
styleMap.set(types.Queueing, {fillStyle: 'white', lineWidth: 2, borderColor: 'lightgrey'});
|
|
// This ensures we always show at least 2 px for a request.
|
|
styleMap.set(types.Receiving, {fillStyle: '#03A9F4', lineWidth: 2, borderColor: '#03A9F4'});
|
|
styleMap.set(types.Waiting, {fillStyle: '#00C853'});
|
|
styleMap.set(types.ReceivingPush, {fillStyle: '#03A9F4'});
|
|
styleMap.set(types.ServiceWorker, {fillStyle: 'orange'});
|
|
styleMap.set(types.ServiceWorkerPreparation, {fillStyle: 'orange'});
|
|
styleMap.set(types.ServiceWorkerRespondWith, {fillStyle: '#A8A3FF'});
|
|
return styleMap;
|
|
}
|
|
|
|
/**
|
|
* @return {!Array<!Map<!Common.ResourceType.ResourceType, !_LayerStyle>>}
|
|
*/
|
|
static _buildResourceTypeStyle() {
|
|
const baseResourceTypeColors = new Map([
|
|
['document', 'hsl(215, 100%, 80%)'],
|
|
['font', 'hsl(8, 100%, 80%)'],
|
|
['media', 'hsl(90, 50%, 80%)'],
|
|
['image', 'hsl(90, 50%, 80%)'],
|
|
['script', 'hsl(31, 100%, 80%)'],
|
|
['stylesheet', 'hsl(272, 64%, 80%)'],
|
|
['texttrack', 'hsl(8, 100%, 80%)'],
|
|
['websocket', 'hsl(0, 0%, 95%)'],
|
|
['xhr', 'hsl(53, 100%, 80%)'],
|
|
['fetch', 'hsl(53, 100%, 80%)'],
|
|
['other', 'hsl(0, 0%, 95%)'],
|
|
]);
|
|
const waitingStyleMap = new Map();
|
|
const downloadingStyleMap = new Map();
|
|
|
|
for (const resourceType of Object.values(Common.ResourceType.resourceTypes)) {
|
|
let color = baseResourceTypeColors.get(resourceType.name());
|
|
if (!color) {
|
|
color = baseResourceTypeColors.get('other');
|
|
}
|
|
const borderColor = toBorderColor(/** @type {string} */ (color));
|
|
|
|
waitingStyleMap.set(
|
|
resourceType,
|
|
{fillStyle: toWaitingColor(/** @type {string} */ (color)), lineWidth: 1, borderColor: borderColor});
|
|
downloadingStyleMap.set(resourceType, {fillStyle: color, lineWidth: 1, borderColor: borderColor});
|
|
}
|
|
return [waitingStyleMap, downloadingStyleMap];
|
|
|
|
/**
|
|
* @param {string} color
|
|
*/
|
|
function toBorderColor(color) {
|
|
const parsedColor = Common.Color.Color.parse(color);
|
|
if (!parsedColor) {
|
|
return '';
|
|
}
|
|
const hsla = parsedColor.hsla();
|
|
hsla[1] /= 2;
|
|
hsla[2] -= Math.min(hsla[2], 0.2);
|
|
return parsedColor.asString(null);
|
|
}
|
|
|
|
/**
|
|
* @param {string} color
|
|
*/
|
|
function toWaitingColor(color) {
|
|
const parsedColor = Common.Color.Color.parse(color);
|
|
if (!parsedColor) {
|
|
return '';
|
|
}
|
|
const hsla = parsedColor.hsla();
|
|
hsla[2] *= 1.1;
|
|
return parsedColor.asString(null);
|
|
}
|
|
}
|
|
|
|
_resetPaths() {
|
|
this._pathForStyle.clear();
|
|
this._pathForStyle.set(this._wiskerStyle, new Path2D());
|
|
this._styleForTimeRangeName.forEach(style => this._pathForStyle.set(style, new Path2D()));
|
|
this._styleForWaitingResourceType.forEach(style => this._pathForStyle.set(style, new Path2D()));
|
|
this._styleForDownloadingResourceType.forEach(style => this._pathForStyle.set(style, new Path2D()));
|
|
this._pathForStyle.set(this._hoverDetailsStyle, new Path2D());
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
willHide() {
|
|
this._popoverHelper.hidePopover();
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
wasShown() {
|
|
this.update();
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_onMouseMove(/** @type {!MouseEvent} */ event) {
|
|
this._setHoveredNode(this.getNodeFromPoint(event.offsetX, event.offsetY), event.shiftKey);
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_onClick(/** @type {!MouseEvent} */ event) {
|
|
const handled = this._setSelectedNode(this.getNodeFromPoint(event.offsetX, event.offsetY));
|
|
if (handled) {
|
|
event.consume(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!MouseEvent} event
|
|
* @return {?UI.PopoverHelper.PopoverRequest}
|
|
*/
|
|
_getPopoverRequest(event) {
|
|
if (!this._hoveredNode) {
|
|
return null;
|
|
}
|
|
const request = this._hoveredNode.request();
|
|
if (!request) {
|
|
return null;
|
|
}
|
|
const useTimingBars = !Common.Settings.Settings.instance().moduleSetting('networkColorCodeResourceTypes').get() &&
|
|
!this._calculator.startAtZero;
|
|
let range;
|
|
let start;
|
|
let end;
|
|
if (useTimingBars) {
|
|
range = RequestTimingView.calculateRequestTimeRanges(request, 0)
|
|
.find(data => data.name === RequestTimeRangeNames.Total);
|
|
start = this._timeToPosition(/** @type {!RequestTimeRange} */ (range).start);
|
|
end = this._timeToPosition(/** @type {!RequestTimeRange} */ (range).end);
|
|
} else {
|
|
range = this._getSimplifiedBarRange(request, 0);
|
|
start = range.start;
|
|
end = range.end;
|
|
}
|
|
|
|
if (end - start < 50) {
|
|
const halfWidth = (end - start) / 2;
|
|
start = start + halfWidth - 25;
|
|
end = end - halfWidth + 25;
|
|
}
|
|
|
|
if (event.clientX < this._canvasPosition.left + start || event.clientX > this._canvasPosition.left + end) {
|
|
return null;
|
|
}
|
|
|
|
const rowIndex = this._nodes.findIndex(node => node.hovered());
|
|
const barHeight = this._getBarHeight(/** @type {!RequestTimeRange} */ (range).name);
|
|
const y = this._headerHeight + (this._rowHeight * rowIndex - this._scrollTop) + ((this._rowHeight - barHeight) / 2);
|
|
|
|
if (event.clientY < this._canvasPosition.top + y || event.clientY > this._canvasPosition.top + y + barHeight) {
|
|
return null;
|
|
}
|
|
|
|
const anchorBox = this.element.boxInWindow();
|
|
anchorBox.x += start;
|
|
anchorBox.y += y;
|
|
anchorBox.width = end - start;
|
|
anchorBox.height = barHeight;
|
|
|
|
return {
|
|
box: anchorBox,
|
|
/** @param {!UI.GlassPane.GlassPane} popover */
|
|
show: popover => {
|
|
const content = RequestTimingView.createTimingTable(
|
|
/** @type {!SDK.NetworkRequest.NetworkRequest} */ (request), this._calculator);
|
|
popover.contentElement.appendChild(content);
|
|
return Promise.resolve(true);
|
|
},
|
|
hide: undefined
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {?NetworkNode} node
|
|
* @param {boolean} highlightInitiatorChain
|
|
*/
|
|
_setHoveredNode(node, highlightInitiatorChain) {
|
|
if (this._hoveredNode) {
|
|
this._hoveredNode.setHovered(false, false);
|
|
}
|
|
this._hoveredNode = node;
|
|
if (this._hoveredNode) {
|
|
this._hoveredNode.setHovered(true, highlightInitiatorChain);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {?NetworkNode} node
|
|
* @returns {boolean}
|
|
*/
|
|
_setSelectedNode(node) {
|
|
if (node && node.dataGrid) {
|
|
node.select();
|
|
node.dataGrid.element.focus();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param {number} height
|
|
*/
|
|
setRowHeight(height) {
|
|
this._rawRowHeight = height;
|
|
this._updateRowHeight();
|
|
}
|
|
|
|
_updateRowHeight() {
|
|
this._rowHeight = Math.round(this._rawRowHeight * window.devicePixelRatio) / window.devicePixelRatio;
|
|
}
|
|
|
|
/**
|
|
* @param {number} height
|
|
*/
|
|
setHeaderHeight(height) {
|
|
this._headerHeight = height;
|
|
}
|
|
|
|
/**
|
|
* @param {number} padding
|
|
*/
|
|
setRightPadding(padding) {
|
|
this._rightPadding = padding;
|
|
this._calculateCanvasSize();
|
|
}
|
|
|
|
/**
|
|
* @param {!NetworkTimeCalculator} calculator
|
|
*/
|
|
setCalculator(calculator) {
|
|
this._calculator = calculator;
|
|
}
|
|
|
|
/**
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @return {?NetworkNode}
|
|
*/
|
|
getNodeFromPoint(x, y) {
|
|
if (y <= this._headerHeight) {
|
|
return null;
|
|
}
|
|
return this._nodes[Math.floor((this._scrollTop + y - this._headerHeight) / this._rowHeight)];
|
|
}
|
|
|
|
scheduleDraw() {
|
|
if (this._updateRequestID) {
|
|
return;
|
|
}
|
|
this._updateRequestID = this.element.window().requestAnimationFrame(() => this.update());
|
|
}
|
|
|
|
/**
|
|
* @param {number=} scrollTop
|
|
* @param {!Map<string, !Array<number>>=} eventDividers
|
|
* @param {!Array<!NetworkNode>=} nodes
|
|
*/
|
|
update(scrollTop, eventDividers, nodes) {
|
|
if (scrollTop !== undefined && this._scrollTop !== scrollTop) {
|
|
this._popoverHelper.hidePopover();
|
|
this._scrollTop = scrollTop;
|
|
}
|
|
if (nodes) {
|
|
this._nodes = nodes;
|
|
this._calculateCanvasSize();
|
|
}
|
|
if (eventDividers !== undefined) {
|
|
this._eventDividers = eventDividers;
|
|
}
|
|
if (this._updateRequestID) {
|
|
this.element.window().cancelAnimationFrame(this._updateRequestID);
|
|
delete this._updateRequestID;
|
|
}
|
|
|
|
this._startTime = this._calculator.minimumBoundary();
|
|
this._endTime = this._calculator.maximumBoundary();
|
|
this._resetCanvas();
|
|
this._resetPaths();
|
|
this._textLayers = [];
|
|
this._draw();
|
|
}
|
|
|
|
_resetCanvas() {
|
|
const ratio = window.devicePixelRatio;
|
|
this._canvas.width = this._offsetWidth * ratio;
|
|
this._canvas.height = this._offsetHeight * ratio;
|
|
this._canvas.style.width = this._offsetWidth + 'px';
|
|
this._canvas.style.height = this._offsetHeight + 'px';
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
onResize() {
|
|
super.onResize();
|
|
this._updateRowHeight();
|
|
this._calculateCanvasSize();
|
|
this.scheduleDraw();
|
|
}
|
|
|
|
_calculateCanvasSize() {
|
|
this._offsetWidth = this.contentElement.offsetWidth - this._rightPadding;
|
|
this._offsetHeight = this.contentElement.offsetHeight;
|
|
this._calculator.setDisplayWidth(this._offsetWidth);
|
|
this._canvasPosition = this._canvas.getBoundingClientRect();
|
|
}
|
|
|
|
/**
|
|
* @param {number} time
|
|
* @return {number}
|
|
*/
|
|
_timeToPosition(time) {
|
|
const availableWidth = this._offsetWidth - this._leftPadding;
|
|
const timeToPixel = availableWidth / (this._endTime - this._startTime);
|
|
return Math.floor(this._leftPadding + (time - this._startTime) * timeToPixel);
|
|
}
|
|
|
|
_didDrawForTest() {
|
|
}
|
|
|
|
_draw() {
|
|
const useTimingBars = !Common.Settings.Settings.instance().moduleSetting('networkColorCodeResourceTypes').get() &&
|
|
!this._calculator.startAtZero;
|
|
const nodes = this._nodes;
|
|
/** @type {?CanvasRenderingContext2D} */
|
|
const context = /** @type {?CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
|
|
if (!context) {
|
|
return;
|
|
}
|
|
context.save();
|
|
context.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
context.translate(0, this._headerHeight);
|
|
context.rect(0, 0, this._offsetWidth, this._offsetHeight);
|
|
context.clip();
|
|
const firstRequestIndex = Math.floor(this._scrollTop / this._rowHeight);
|
|
const lastRequestIndex =
|
|
Math.min(nodes.length, firstRequestIndex + Math.ceil(this._offsetHeight / this._rowHeight));
|
|
for (let i = firstRequestIndex; i < lastRequestIndex; i++) {
|
|
const rowOffset = this._rowHeight * i;
|
|
const node = nodes[i];
|
|
this._decorateRow(context, node, rowOffset - this._scrollTop);
|
|
/** @type {!Array<!NetworkNode>} */
|
|
let drawNodes = [];
|
|
if (node.hasChildren() && !node.expanded) {
|
|
drawNodes = /** @type {!Array<!NetworkNode>} */ (node.flatChildren());
|
|
}
|
|
drawNodes.push(node);
|
|
for (const drawNode of drawNodes) {
|
|
if (useTimingBars) {
|
|
this._buildTimingBarLayers(drawNode, rowOffset - this._scrollTop);
|
|
} else {
|
|
this._buildSimplifiedBarLayers(context, drawNode, rowOffset - this._scrollTop);
|
|
}
|
|
}
|
|
}
|
|
this._drawLayers(context);
|
|
|
|
context.save();
|
|
context.fillStyle =
|
|
ThemeSupport.ThemeSupport.instance().patchColorText('#888', ThemeSupport.ThemeSupport.ColorUsage.Foreground);
|
|
for (const textData of this._textLayers) {
|
|
context.fillText(textData.text, textData.x, textData.y);
|
|
}
|
|
context.restore();
|
|
|
|
this._drawEventDividers(context);
|
|
context.restore();
|
|
|
|
const freeZoneAtLeft = 75;
|
|
const freeZoneAtRight = 18;
|
|
const dividersData = PerfUI.TimelineGrid.TimelineGrid.calculateGridOffsets(this._calculator);
|
|
PerfUI.TimelineGrid.TimelineGrid.drawCanvasGrid(context, dividersData);
|
|
PerfUI.TimelineGrid.TimelineGrid.drawCanvasHeaders(
|
|
context, dividersData, time => this._calculator.formatValue(time, dividersData.precision), this._fontSize,
|
|
this._headerHeight, freeZoneAtLeft);
|
|
context.clearRect(this._offsetWidth - freeZoneAtRight, 0, freeZoneAtRight, this._headerHeight);
|
|
this._didDrawForTest();
|
|
}
|
|
|
|
/**
|
|
* @param {!CanvasRenderingContext2D} context
|
|
*/
|
|
_drawLayers(context) {
|
|
for (const entry of this._pathForStyle) {
|
|
const style = /** @type {!_LayerStyle} */ (entry[0]);
|
|
const path = /** @type {!Path2D} */ (entry[1]);
|
|
context.save();
|
|
context.beginPath();
|
|
if (style.lineWidth) {
|
|
context.lineWidth = style.lineWidth;
|
|
if (style.borderColor) {
|
|
context.strokeStyle = style.borderColor;
|
|
}
|
|
context.stroke(path);
|
|
}
|
|
if (style.fillStyle) {
|
|
context.fillStyle = style.fillStyle;
|
|
context.fill(path);
|
|
}
|
|
context.restore();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!CanvasRenderingContext2D} context
|
|
*/
|
|
_drawEventDividers(context) {
|
|
context.save();
|
|
context.lineWidth = 1;
|
|
for (const color of this._eventDividers.keys()) {
|
|
context.strokeStyle = color;
|
|
for (const time of this._eventDividers.get(color) || []) {
|
|
context.beginPath();
|
|
const x = this._timeToPosition(time);
|
|
context.moveTo(x, 0);
|
|
context.lineTo(x, this._offsetHeight);
|
|
}
|
|
context.stroke();
|
|
}
|
|
context.restore();
|
|
}
|
|
|
|
/**
|
|
* @param {!RequestTimeRangeNames=} type
|
|
* @return {number}
|
|
*/
|
|
_getBarHeight(type) {
|
|
const types = RequestTimeRangeNames;
|
|
switch (type) {
|
|
case types.Connecting:
|
|
case types.SSL:
|
|
case types.DNS:
|
|
case types.Proxy:
|
|
case types.Blocking:
|
|
case types.Push:
|
|
case types.Queueing:
|
|
return 7;
|
|
default:
|
|
return 13;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.NetworkRequest.NetworkRequest} request
|
|
* @param {number} borderOffset
|
|
* @return {!{start: number, mid: number, end: number}}
|
|
*/
|
|
_getSimplifiedBarRange(request, borderOffset) {
|
|
const drawWidth = this._offsetWidth - this._leftPadding;
|
|
const percentages = this._calculator.computeBarGraphPercentages(request);
|
|
return {
|
|
start: this._leftPadding + Math.floor((percentages.start / 100) * drawWidth) + borderOffset,
|
|
mid: this._leftPadding + Math.floor((percentages.middle / 100) * drawWidth) + borderOffset,
|
|
end: this._leftPadding + Math.floor((percentages.end / 100) * drawWidth) + borderOffset
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {!CanvasRenderingContext2D} context
|
|
* @param {!NetworkNode} node
|
|
* @param {number} y
|
|
*/
|
|
_buildSimplifiedBarLayers(context, node, y) {
|
|
const request = node.request();
|
|
if (!request) {
|
|
return;
|
|
}
|
|
const borderWidth = 1;
|
|
const borderOffset = borderWidth % 2 === 0 ? 0 : 0.5;
|
|
|
|
const ranges = this._getSimplifiedBarRange(request, borderOffset);
|
|
const height = this._getBarHeight();
|
|
y += Math.floor(this._rowHeight / 2 - height / 2 + borderWidth) - borderWidth / 2;
|
|
|
|
const waitingStyle = /** @type {!_LayerStyle} */ (this._styleForWaitingResourceType.get(request.resourceType()));
|
|
const waitingPath = /** @type {!Path2D} */ (this._pathForStyle.get(waitingStyle));
|
|
waitingPath.rect(ranges.start, y, ranges.mid - ranges.start, height - borderWidth);
|
|
|
|
const barWidth = Math.max(2, ranges.end - ranges.mid);
|
|
const downloadingStyle =
|
|
/** @type {!_LayerStyle} */ (this._styleForDownloadingResourceType.get(request.resourceType()));
|
|
const downloadingPath = /** @type {!Path2D} */ (this._pathForStyle.get(downloadingStyle));
|
|
downloadingPath.rect(ranges.mid, y, barWidth, height - borderWidth);
|
|
|
|
/** @type {?{left: string, right: string, tooltip: (string|undefined)}} */
|
|
let labels = null;
|
|
if (node.hovered()) {
|
|
labels = this._calculator.computeBarGraphLabels(request);
|
|
const barDotLineLength = 10;
|
|
const leftLabelWidth = context.measureText(labels.left).width;
|
|
const rightLabelWidth = context.measureText(labels.right).width;
|
|
const hoverLinePath = /** @type {!Path2D} */ (this._pathForStyle.get(this._hoverDetailsStyle));
|
|
|
|
if (leftLabelWidth < ranges.mid - ranges.start) {
|
|
const midBarX = ranges.start + (ranges.mid - ranges.start - leftLabelWidth) / 2;
|
|
this._textLayers.push({text: labels.left, x: midBarX, y: y + this._fontSize});
|
|
} else if (barDotLineLength + leftLabelWidth + this._leftPadding < ranges.start) {
|
|
this._textLayers.push(
|
|
{text: labels.left, x: ranges.start - leftLabelWidth - barDotLineLength - 1, y: y + this._fontSize});
|
|
hoverLinePath.moveTo(ranges.start - barDotLineLength, y + Math.floor(height / 2));
|
|
hoverLinePath.arc(ranges.start, y + Math.floor(height / 2), 2, 0, 2 * Math.PI);
|
|
hoverLinePath.moveTo(ranges.start - barDotLineLength, y + Math.floor(height / 2));
|
|
hoverLinePath.lineTo(ranges.start, y + Math.floor(height / 2));
|
|
}
|
|
|
|
const endX = ranges.mid + barWidth + borderOffset;
|
|
if (rightLabelWidth < endX - ranges.mid) {
|
|
const midBarX = ranges.mid + (endX - ranges.mid - rightLabelWidth) / 2;
|
|
this._textLayers.push({text: labels.right, x: midBarX, y: y + this._fontSize});
|
|
} else if (endX + barDotLineLength + rightLabelWidth < this._offsetWidth - this._leftPadding) {
|
|
this._textLayers.push({text: labels.right, x: endX + barDotLineLength + 1, y: y + this._fontSize});
|
|
hoverLinePath.moveTo(endX, y + Math.floor(height / 2));
|
|
hoverLinePath.arc(endX, y + Math.floor(height / 2), 2, 0, 2 * Math.PI);
|
|
hoverLinePath.moveTo(endX, y + Math.floor(height / 2));
|
|
hoverLinePath.lineTo(endX + barDotLineLength, y + Math.floor(height / 2));
|
|
}
|
|
}
|
|
|
|
if (!this._calculator.startAtZero) {
|
|
const queueingRange =
|
|
/** @type {!RequestTimeRange} */ (RequestTimingView.calculateRequestTimeRanges(request, 0)
|
|
.find(data => data.name === RequestTimeRangeNames.Total));
|
|
const leftLabelWidth = labels ? context.measureText(labels.left).width : 0;
|
|
const leftTextPlacedInBar = leftLabelWidth < ranges.mid - ranges.start;
|
|
const wiskerTextPadding = 13;
|
|
const textOffset = (labels && !leftTextPlacedInBar) ? leftLabelWidth + wiskerTextPadding : 0;
|
|
const queueingStart = this._timeToPosition(queueingRange.start);
|
|
if (ranges.start - textOffset > queueingStart) {
|
|
const wiskerPath = /** @type {!Path2D} */ (this._pathForStyle.get(this._wiskerStyle));
|
|
wiskerPath.moveTo(queueingStart, y + Math.floor(height / 2));
|
|
wiskerPath.lineTo(ranges.start - textOffset, y + Math.floor(height / 2));
|
|
|
|
// TODO(allada) This needs to be floored.
|
|
const wiskerHeight = height / 2;
|
|
wiskerPath.moveTo(queueingStart + borderOffset, y + wiskerHeight / 2);
|
|
wiskerPath.lineTo(queueingStart + borderOffset, y + height - wiskerHeight / 2 - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!NetworkNode} node
|
|
* @param {number} y
|
|
*/
|
|
_buildTimingBarLayers(node, y) {
|
|
const request = node.request();
|
|
if (!request) {
|
|
return;
|
|
}
|
|
const ranges = RequestTimingView.calculateRequestTimeRanges(request, 0);
|
|
for (const range of ranges) {
|
|
if (range.name === RequestTimeRangeNames.Total || range.name === RequestTimeRangeNames.Sending ||
|
|
range.end - range.start === 0) {
|
|
continue;
|
|
}
|
|
|
|
const style = /** @type {!_LayerStyle} */ (this._styleForTimeRangeName.get(range.name));
|
|
const path = /** @type {!Path2D} */ (this._pathForStyle.get(style));
|
|
const lineWidth = style.lineWidth || 0;
|
|
const height = this._getBarHeight(range.name);
|
|
const middleBarY = y + Math.floor(this._rowHeight / 2 - height / 2) + lineWidth / 2;
|
|
const start = this._timeToPosition(range.start);
|
|
const end = this._timeToPosition(range.end);
|
|
path.rect(start, middleBarY, end - start, height - lineWidth);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!CanvasRenderingContext2D} context
|
|
* @param {!NetworkNode} node
|
|
* @param {number} y
|
|
*/
|
|
_decorateRow(context, node, y) {
|
|
const nodeBgColorId = node.backgroundColor();
|
|
context.save();
|
|
context.beginPath();
|
|
context.fillStyle = ThemeSupport.ThemeSupport.instance().getComputedValue(nodeBgColorId);
|
|
context.rect(0, y, this._offsetWidth, this._rowHeight);
|
|
context.fill();
|
|
context.restore();
|
|
}
|
|
}
|
|
|
|
/** @typedef {!{x: number, y: number, text: string}} */
|
|
// @ts-ignore typedef
|
|
export let _TextLayer;
|
|
|
|
/** @typedef {!{fillStyle: (string|undefined), lineWidth: (number|undefined), borderColor: (string|undefined)}} */
|
|
// @ts-ignore typedef
|
|
export let _LayerStyle;
|