chrome-devtools-frontend
Version:
Chrome DevTools UI
640 lines (560 loc) • 25.3 kB
text/typescript
// 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 '../../core/common/common.js';
import networkWaterfallColumnStyles from './networkWaterfallColumn.css.js';
import type * as SDK from '../../core/sdk/sdk.js';
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
import * as Coordinator from '../../ui/components/render_coordinator/render_coordinator.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
import {type NetworkNode} from './NetworkDataGridNode.js';
import {RequestTimeRangeNameToColor} from './NetworkOverview.js';
import {type Label, type NetworkTimeCalculator} from './NetworkTimeCalculator.js';
import {RequestTimeRangeNames, RequestTimingView, type RequestTimeRange} from './RequestTimingView.js';
import networkingTimingTableStyles from './networkTimingTable.css.js';
const BAR_SPACING = 1;
const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance();
export class NetworkWaterfallColumn extends UI.Widget.VBox {
private canvas: HTMLCanvasElement;
private canvasPosition: DOMRect;
private readonly leftPadding: number;
private readonly fontSize: number;
private rightPadding: number;
private scrollTop: number;
private headerHeight: number;
private calculator: NetworkTimeCalculator;
private rawRowHeight: number;
private rowHeight: number;
private offsetWidth: number;
private offsetHeight: number;
private startTime: number;
private endTime: number;
private readonly popoverHelper: UI.PopoverHelper.PopoverHelper;
private nodes: NetworkNode[];
private hoveredNode: NetworkNode|null;
private eventDividers: Map<string, number[]>;
private readonly styleForTimeRangeName: Map<RequestTimeRangeNames, _LayerStyle>;
private readonly styleForWaitingResourceType: Map<Common.ResourceType.ResourceType, _LayerStyle>;
private readonly styleForDownloadingResourceType: Map<Common.ResourceType.ResourceType, _LayerStyle>;
private readonly wiskerStyle: _LayerStyle;
private readonly hoverDetailsStyle: _LayerStyle;
private readonly pathForStyle: Map<_LayerStyle, Path2D>;
private textLayers: _TextLayer[];
constructor(calculator: NetworkTimeCalculator) {
// TODO(allada) Make this a shadowDOM when the NetworkWaterfallColumn gets moved into NetworkLogViewColumns.
super(false);
this.canvas = (this.contentElement.createChild('canvas') as HTMLCanvasElement);
this.canvas.tabIndex = -1;
this.setDefaultFocusedElement(this.canvas);
this.canvasPosition = this.canvas.getBoundingClientRect();
this.leftPadding = 5;
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);
this.nodes = [];
this.hoveredNode = null;
this.eventDividers = new Map();
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();
this.styleForWaitingResourceType = resourceStyleTuple[0];
this.styleForDownloadingResourceType = resourceStyleTuple[1];
const baseLineColor = ThemeSupport.ThemeSupport.instance().getComputedValue('--color-text-disabled');
this.wiskerStyle = {borderColor: baseLineColor, lineWidth: 1, fillStyle: undefined};
this.hoverDetailsStyle = {fillStyle: baseLineColor, lineWidth: 1, borderColor: baseLineColor};
this.pathForStyle = new Map();
this.textLayers = [];
}
private static buildRequestTimeRangeStyle(): Map<RequestTimeRangeNames, _LayerStyle> {
const types = RequestTimeRangeNames;
const styleMap = new Map<RequestTimeRangeNames, _LayerStyle>();
styleMap.set(types.Connecting, {fillStyle: RequestTimeRangeNameToColor[types.Connecting]});
styleMap.set(types.SSL, {fillStyle: RequestTimeRangeNameToColor[types.SSL]});
styleMap.set(types.DNS, {fillStyle: RequestTimeRangeNameToColor[types.DNS]});
styleMap.set(types.Proxy, {fillStyle: RequestTimeRangeNameToColor[types.Proxy]});
styleMap.set(types.Blocking, {fillStyle: RequestTimeRangeNameToColor[types.Blocking]});
styleMap.set(types.Push, {fillStyle: RequestTimeRangeNameToColor[types.Push]});
styleMap.set(
types.Queueing,
{fillStyle: RequestTimeRangeNameToColor[types.Queueing], lineWidth: 2, borderColor: 'lightgrey'});
// This ensures we always show at least 2 px for a request.
styleMap.set(types.Receiving, {
fillStyle: RequestTimeRangeNameToColor[types.Receiving],
lineWidth: 2,
borderColor: '#03A9F4',
});
styleMap.set(types.Waiting, {fillStyle: RequestTimeRangeNameToColor[types.Waiting]});
styleMap.set(types.ReceivingPush, {fillStyle: RequestTimeRangeNameToColor[types.ReceivingPush]});
styleMap.set(types.ServiceWorker, {fillStyle: RequestTimeRangeNameToColor[types.ServiceWorker]});
styleMap.set(
types.ServiceWorkerPreparation, {fillStyle: RequestTimeRangeNameToColor[types.ServiceWorkerPreparation]});
styleMap.set(types.ServiceWorkerRespondWith, {
fillStyle: RequestTimeRangeNameToColor[types.ServiceWorkerRespondWith],
});
return styleMap;
}
private static buildResourceTypeStyle(): Map<Common.ResourceType.ResourceType, _LayerStyle>[] {
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<Common.ResourceType.ResourceType, _LayerStyle>();
const downloadingStyleMap = new Map<Common.ResourceType.ResourceType, _LayerStyle>();
for (const resourceType of Object.values(Common.ResourceType.resourceTypes)) {
let color = baseResourceTypeColors.get(resourceType.name());
if (!color) {
color = baseResourceTypeColors.get('other');
}
const borderColor = toBorderColor((color as string));
waitingStyleMap.set(
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// @ts-expect-error
resourceType, {fillStyle: toWaitingColor((color as string)), lineWidth: 1, borderColor: borderColor});
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// @ts-expect-error
downloadingStyleMap.set(resourceType, {fillStyle: color, lineWidth: 1, borderColor: borderColor});
}
return [waitingStyleMap, downloadingStyleMap];
function toBorderColor(color: string): string|null {
const parsedColor = Common.Color.parse(color)?.as(Common.Color.Format.HSL);
if (!parsedColor) {
return '';
}
let {s, l} = parsedColor;
s /= 2;
l -= Math.min(l, 0.2);
return new Common.Color.HSL(parsedColor.h, s, l, parsedColor.alpha).asString();
}
function toWaitingColor(color: string): string|null {
const parsedColor = Common.Color.parse(color)?.as(Common.Color.Format.HSL);
if (!parsedColor) {
return '';
}
let {l} = parsedColor;
l *= 1.1;
return new Common.Color.HSL(parsedColor.h, parsedColor.s, l, parsedColor.alpha).asString();
}
}
private resetPaths(): void {
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(): void {
this.popoverHelper.hidePopover();
}
override wasShown(): void {
this.update();
this.registerCSSFiles([networkWaterfallColumnStyles]);
}
private onMouseMove(event: MouseEvent): void {
this.setHoveredNode(this.getNodeFromPoint(event.offsetX, event.offsetY), event.shiftKey);
}
private onClick(event: MouseEvent): void {
const handled = this.setSelectedNode(this.getNodeFromPoint(event.offsetX, event.offsetY));
if (handled) {
event.consume(true);
}
}
private getPopoverRequest(event: MouseEvent): UI.PopoverHelper.PopoverRequest|null {
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((range as RequestTimeRange).start);
end = this.timeToPosition((range as RequestTimeRange).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((range as RequestTimeRange).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,
show: (popover: UI.GlassPane.GlassPane): Promise<true> => {
const content =
RequestTimingView.createTimingTable((request as SDK.NetworkRequest.NetworkRequest), this.calculator);
popover.registerCSSFiles([networkingTimingTableStyles]);
popover.contentElement.appendChild(content);
return Promise.resolve(true);
},
hide: undefined,
};
}
private setHoveredNode(node: NetworkNode|null, highlightInitiatorChain: boolean): void {
if (this.hoveredNode) {
this.hoveredNode.setHovered(false, false);
}
this.hoveredNode = node;
if (this.hoveredNode) {
this.hoveredNode.setHovered(true, highlightInitiatorChain);
}
}
private setSelectedNode(node: NetworkNode|null): boolean {
if (node && node.dataGrid) {
node.select();
node.dataGrid.element.focus();
return true;
}
return false;
}
setRowHeight(height: number): void {
this.rawRowHeight = height;
this.updateRowHeight();
}
private updateRowHeight(): void {
this.rowHeight = Math.round(this.rawRowHeight * window.devicePixelRatio) / window.devicePixelRatio;
}
setHeaderHeight(height: number): void {
this.headerHeight = height;
}
setRightPadding(padding: number): void {
this.rightPadding = padding;
this.calculateCanvasSize();
}
setCalculator(calculator: NetworkTimeCalculator): void {
this.calculator = calculator;
}
getNodeFromPoint(x: number, y: number): NetworkNode|null {
if (y <= this.headerHeight) {
return null;
}
return this.nodes[Math.floor((this.scrollTop + y - this.headerHeight) / this.rowHeight)];
}
scheduleDraw(): void {
void coordinator.write(() => this.update());
}
update(scrollTop?: number, eventDividers?: Map<string, number[]>, nodes?: NetworkNode[]): void {
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;
}
this.startTime = this.calculator.minimumBoundary();
this.endTime = this.calculator.maximumBoundary();
this.resetCanvas();
this.resetPaths();
this.textLayers = [];
this.draw();
}
private resetCanvas(): void {
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(): void {
super.onResize();
this.updateRowHeight();
this.calculateCanvasSize();
this.scheduleDraw();
}
private calculateCanvasSize(): void {
this.offsetWidth = this.contentElement.offsetWidth - this.rightPadding;
this.offsetHeight = this.contentElement.offsetHeight;
this.calculator.setDisplayWidth(this.offsetWidth);
this.canvasPosition = this.canvas.getBoundingClientRect();
}
private timeToPosition(time: number): number {
const availableWidth = this.offsetWidth - this.leftPadding;
const timeToPixel = availableWidth / (this.endTime - this.startTime);
return Math.floor(this.leftPadding + (time - this.startTime) * timeToPixel);
}
private didDrawForTest(): void {
}
private draw(): void {
const useTimingBars = !Common.Settings.Settings.instance().moduleSetting('networkColorCodeResourceTypes').get() &&
!this.calculator.startAtZero;
const nodes = this.nodes;
const context = (this.canvas.getContext('2d') as CanvasRenderingContext2D | null);
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);
let drawNodes: NetworkNode[] = [];
if (node.hasChildren() && !node.expanded) {
drawNodes = (node.flatChildren() as NetworkNode[]);
}
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, useTimingBars);
context.save();
context.fillStyle = ThemeSupport.ThemeSupport.instance().getComputedValue('--color-text-disabled');
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.save();
context.scale(window.devicePixelRatio, window.devicePixelRatio);
context.clearRect(this.offsetWidth - freeZoneAtRight, 0, freeZoneAtRight, this.headerHeight);
context.restore();
this.didDrawForTest();
}
private drawLayers(context: CanvasRenderingContext2D, useTimingBars: boolean): void {
for (const entry of this.pathForStyle) {
const style = (entry[0] as _LayerStyle);
const path = (entry[1] as Path2D);
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 =
useTimingBars ? ThemeSupport.ThemeSupport.instance().getComputedValue(style.fillStyle) : style.fillStyle;
context.fill(path);
}
context.restore();
}
}
private drawEventDividers(context: CanvasRenderingContext2D): void {
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();
}
private getBarHeight(type?: RequestTimeRangeNames): number {
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;
}
}
private getSimplifiedBarRange(request: SDK.NetworkRequest.NetworkRequest, borderOffset: number): {
start: number,
mid: number,
end: number,
} {
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,
};
}
private buildSimplifiedBarLayers(context: CanvasRenderingContext2D, node: NetworkNode, y: number): void {
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 = (this.styleForWaitingResourceType.get(request.resourceType()) as _LayerStyle);
const waitingPath = (this.pathForStyle.get(waitingStyle) as Path2D);
waitingPath.rect(ranges.start, y, ranges.mid - ranges.start, height - borderWidth);
const barWidth = Math.max(2, ranges.end - ranges.mid);
const downloadingStyle = (this.styleForDownloadingResourceType.get(request.resourceType()) as _LayerStyle);
const downloadingPath = (this.pathForStyle.get(downloadingStyle) as Path2D);
downloadingPath.rect(ranges.mid, y, barWidth, height - borderWidth);
let labels: Label|null = 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 = (this.pathForStyle.get(this.hoverDetailsStyle) as Path2D);
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 =
(RequestTimingView.calculateRequestTimeRanges(request, 0)
.find(data => data.name === RequestTimeRangeNames.Total) as RequestTimeRange);
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 = (this.pathForStyle.get(this.wiskerStyle) as Path2D);
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);
}
}
}
private buildTimingBarLayers(node: NetworkNode, y: number): void {
const request = node.request();
if (!request) {
return;
}
const ranges = RequestTimingView.calculateRequestTimeRanges(request, 0);
let index = 0;
for (const range of ranges) {
if (range.name === RequestTimeRangeNames.Total || range.name === RequestTimeRangeNames.Sending ||
range.end - range.start === 0) {
continue;
}
const style = (this.styleForTimeRangeName.get(range.name) as _LayerStyle);
const path = (this.pathForStyle.get(style) as Path2D);
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 + (index * BAR_SPACING), middleBarY, end - start, height - lineWidth);
index++;
}
}
private decorateRow(context: CanvasRenderingContext2D, node: NetworkNode, y: number): void {
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();
}
}
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface _TextLayer {
x: number;
y: number;
text: string;
}
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/naming-convention
export interface _LayerStyle {
fillStyle?: string;
lineWidth?: number;
borderColor?: string;
}