chore(tracing): simplify resource treatment (#6571)

This commit is contained in:
Pavel Feldman 2021-05-13 20:41:32 -07:00 committed by GitHub
parent 9b0aeeffae
commit 7b844c5fab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 148 additions and 259 deletions

View File

@ -56,13 +56,11 @@ export class Dialog extends SdkObject {
assert(!this._handled, 'Cannot accept dialog which is already handled!');
this._handled = true;
await this._onHandle(true, promptText);
this._page.emit(Page.Events.InternalDialogClosed, this);
}
async dismiss() {
assert(!this._handled, 'Cannot dismiss dialog which is already handled!');
this._handled = true;
await this._onHandle(false);
this._page.emit(Page.Events.InternalDialogClosed, this);
}
}

View File

@ -97,7 +97,6 @@ export class Page extends SdkObject {
Crash: 'crash',
Console: 'console',
Dialog: 'dialog',
InternalDialogClosed: 'internaldialogclosed',
Download: 'download',
FileChooser: 'filechooser',
DOMContentLoaded: 'domcontentloaded',

View File

@ -66,35 +66,6 @@ export type FrameSnapshotTraceEvent = {
snapshot: FrameSnapshot,
};
export type DialogOpenedEvent = {
timestamp: number,
type: 'dialog-opened',
pageId: string,
dialogType: string,
message?: string,
};
export type DialogClosedEvent = {
timestamp: number,
type: 'dialog-closed',
pageId: string,
dialogType: string,
};
export type NavigationEvent = {
timestamp: number,
type: 'navigation',
pageId: string,
url: string,
sameDocument: boolean,
};
export type LoadEvent = {
timestamp: number,
type: 'load',
pageId: string,
};
export type TraceEvent =
ContextCreatedTraceEvent |
PageCreatedTraceEvent |
@ -102,8 +73,4 @@ export type TraceEvent =
ScreencastFrameTraceEvent |
ActionTraceEvent |
ResourceSnapshotTraceEvent |
FrameSnapshotTraceEvent |
DialogOpenedEvent |
DialogClosedEvent |
NavigationEvent |
LoadEvent;
FrameSnapshotTraceEvent;

View File

@ -21,9 +21,7 @@ import yazl from 'yazl';
import { calculateSha1, createGuid, mkdirIfNeeded, monotonicTime } from '../../../utils/utils';
import { Artifact } from '../../artifact';
import { BrowserContext } from '../../browserContext';
import { Dialog } from '../../dialog';
import { ElementHandle } from '../../dom';
import { Frame, NavigationEvent } from '../../frames';
import { helper, RegisteredListener } from '../../helper';
import { CallMetadata, InstrumentationListener, SdkObject } from '../../instrumentation';
import { Page } from '../../page';
@ -188,51 +186,6 @@ export class Tracing implements InstrumentationListener {
page.setScreencastOptions({ width: 800, height: 600, quality: 90 });
this._eventListeners.push(
helper.addEventListener(page, Page.Events.Dialog, (dialog: Dialog) => {
const event: trace.DialogOpenedEvent = {
timestamp: monotonicTime(),
type: 'dialog-opened',
pageId,
dialogType: dialog.type(),
message: dialog.message(),
};
this._appendTraceEvent(event);
}),
helper.addEventListener(page, Page.Events.InternalDialogClosed, (dialog: Dialog) => {
const event: trace.DialogClosedEvent = {
timestamp: monotonicTime(),
type: 'dialog-closed',
pageId,
dialogType: dialog.type(),
};
this._appendTraceEvent(event);
}),
helper.addEventListener(page.mainFrame(), Frame.Events.Navigation, (navigationEvent: NavigationEvent) => {
if (page.mainFrame().url() === 'about:blank')
return;
const event: trace.NavigationEvent = {
timestamp: monotonicTime(),
type: 'navigation',
pageId,
url: navigationEvent.url,
sameDocument: !navigationEvent.newDocument,
};
this._appendTraceEvent(event);
}),
helper.addEventListener(page, Page.Events.Load, () => {
if (page.mainFrame().url() === 'about:blank')
return;
const event: trace.LoadEvent = {
timestamp: monotonicTime(),
type: 'load',
pageId,
};
this._appendTraceEvent(event);
}),
helper.addEventListener(page, Page.Events.ScreencastFrame, params => {
const sha1 = calculateSha1(createGuid()); // no need to compute sha1 for screenshots
const event: trace.ScreencastFrameTraceEvent = {
@ -248,7 +201,6 @@ export class Tracing implements InstrumentationListener {
await fsWriteFileAsync(path.join(this._resourcesDir!, sha1), params.buffer).catch(() => {});
});
}),
helper.addEventListener(page, Page.Events.Close, () => {
const event: trace.PageDestroyedTraceEvent = {
timestamp: monotonicTime(),

View File

@ -34,18 +34,10 @@ export class TraceModel {
appendEvents(events: trace.TraceEvent[], snapshotStorage: SnapshotStorage) {
for (const event of events)
this.appendEvent(event);
const actions: ActionEntry[] = [];
const actions: trace.ActionTraceEvent[] = [];
for (const page of this.contextEntry!.pages)
actions.push(...page.actions);
const resources = snapshotStorage.resources().reverse();
actions.reverse();
for (const action of actions) {
while (resources.length && resources[0].timestamp > action.timestamp)
action.resources.push(resources.shift()!);
action.resources.reverse();
}
this.contextEntry!.resources = snapshotStorage.resources();
}
appendEvent(event: trace.TraceEvent) {
@ -56,6 +48,7 @@ export class TraceModel {
endTime: Number.MIN_VALUE,
created: event,
pages: [],
resources: []
};
break;
}
@ -64,7 +57,7 @@ export class TraceModel {
created: event,
destroyed: undefined as any,
actions: [],
interestingEvents: [],
events: [],
screencastFrames: [],
};
this.pageEntries.set(event.pageId, pageEntry);
@ -82,20 +75,14 @@ export class TraceModel {
case 'action': {
const metadata = event.metadata;
const pageEntry = this.pageEntries.get(metadata.pageId!)!;
const action: ActionEntry = {
actionId: metadata.id,
resources: [],
...event,
};
pageEntry.actions.push(action);
pageEntry.actions.push(event);
break;
}
case 'dialog-opened':
case 'dialog-closed':
case 'navigation':
case 'load': {
const pageEntry = this.pageEntries.get(event.pageId)!;
pageEntry.interestingEvents.push(event);
case 'event': {
const metadata = event.metadata;
const pageEntry = this.pageEntries.get(metadata.pageId!);
if (pageEntry)
pageEntry.events.push(event);
break;
}
case 'resource-snapshot':
@ -105,8 +92,10 @@ export class TraceModel {
this._snapshotStorage.addFrameSnapshot(event.snapshot);
break;
}
this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.timestamp);
this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.timestamp);
if (event.type === 'action' || event.type === 'event') {
this.contextEntry!.startTime = Math.min(this.contextEntry!.startTime, event.metadata.startTime);
this.contextEntry!.endTime = Math.max(this.contextEntry!.endTime, event.metadata.endTime);
}
}
}
@ -115,15 +104,14 @@ export type ContextEntry = {
endTime: number;
created: trace.ContextCreatedTraceEvent;
pages: PageEntry[];
resources: ResourceSnapshot[];
}
export type InterestingPageEvent = trace.DialogOpenedEvent | trace.DialogClosedEvent | trace.NavigationEvent | trace.LoadEvent;
export type PageEntry = {
created: trace.PageCreatedTraceEvent;
destroyed: trace.PageDestroyedTraceEvent;
actions: ActionEntry[];
interestingEvents: InterestingPageEvent[];
actions: trace.ActionTraceEvent[];
events: trace.ActionTraceEvent[];
screencastFrames: {
sha1: string,
timestamp: number,
@ -132,11 +120,6 @@ export type PageEntry = {
}[]
}
export type ActionEntry = trace.ActionTraceEvent & {
actionId: string;
resources: ResourceSnapshot[]
};
export class PersistentSnapshotStorage extends BaseSnapshotStorage {
private _resourcesDir: string;

View File

@ -14,17 +14,17 @@
limitations under the License.
*/
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
import './actionList.css';
import './tabbedPane.css';
import * as React from 'react';
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
export interface ActionListProps {
actions: ActionEntry[],
selectedAction: ActionEntry | undefined,
highlightedAction: ActionEntry | undefined,
onSelected: (action: ActionEntry) => void,
onHighlighted: (action: ActionEntry | undefined) => void,
actions: ActionTraceEvent[],
selectedAction: ActionTraceEvent | undefined,
highlightedAction: ActionTraceEvent | undefined,
onSelected: (action: ActionTraceEvent) => void,
onHighlighted: (action: ActionTraceEvent | undefined) => void,
}
export const ActionList: React.FC<ActionListProps> = ({
@ -68,16 +68,16 @@ export const ActionList: React.FC<ActionListProps> = ({
}}
ref={actionListRef}
>
{actions.map(actionEntry => {
const { metadata, actionId } = actionEntry;
const selectedSuffix = actionEntry === selectedAction ? ' selected' : '';
const highlightedSuffix = actionEntry === highlightedAction ? ' highlighted' : '';
{actions.map(action => {
const { metadata } = action;
const selectedSuffix = action === selectedAction ? ' selected' : '';
const highlightedSuffix = action === highlightedAction ? ' highlighted' : '';
return <div
className={'action-entry' + selectedSuffix + highlightedSuffix}
key={actionId}
onClick={() => onSelected(actionEntry)}
onMouseEnter={() => onHighlighted(actionEntry)}
onMouseLeave={() => (highlightedAction === actionEntry) && onHighlighted(undefined)}
key={metadata.id}
onClick={() => onSelected(action)}
onMouseEnter={() => onHighlighted(action)}
onMouseLeave={() => (highlightedAction === action) && onHighlighted(undefined)}
>
<div className={'action-error codicon codicon-issues'} hidden={!metadata.error} />
<div className='action-title'>{metadata.apiName || metadata.method}</div>

View File

@ -14,18 +14,18 @@
* limitations under the License.
*/
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
import * as React from 'react';
import './logsTab.css';
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
export const LogsTab: React.FunctionComponent<{
actionEntry: ActionEntry | undefined,
}> = ({ actionEntry }) => {
action: ActionTraceEvent | undefined,
}> = ({ action }) => {
let logs: string[] = [];
if (actionEntry) {
logs = actionEntry.metadata.log || [];
if (actionEntry.metadata.error)
logs = [actionEntry.metadata.error, ...logs];
if (action) {
logs = action.metadata.log || [];
if (action.metadata.error)
logs = [action.metadata.error, ...logs];
}
return <div className='logs-tab'>{
logs.map((logLine, index) => {

View File

@ -14,18 +14,25 @@
* limitations under the License.
*/
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
import { ContextEntry } from '../../../server/trace/viewer/traceModel';
import './networkTab.css';
import * as React from 'react';
import { NetworkResourceDetails } from './networkResourceDetails';
import { ResourceSnapshot } from '../../../server/snapshot/snapshotTypes';
export const NetworkTab: React.FunctionComponent<{
actionEntry: ActionEntry | undefined,
}> = ({ actionEntry }) => {
context: ContextEntry,
action: ActionTraceEvent | undefined,
nextAction: ActionTraceEvent | undefined,
}> = ({ context, action, nextAction }) => {
const [selected, setSelected] = React.useState(0);
const resources: ResourceSnapshot[] = context.resources.filter(resource => {
return action && resource.timestamp > action.metadata.startTime && (!nextAction || resource.timestamp < nextAction.metadata.startTime);
});
return <div className='network-tab'>{
(actionEntry ? actionEntry.resources : []).map((resource, index) => {
resources.map((resource, index) => {
return <NetworkResourceDetails resource={resource} key={index} index={index} selected={selected === index} setSelected={setSelected} />;
})
}</div>;

View File

@ -14,23 +14,23 @@
* limitations under the License.
*/
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
import { Size } from '../geometry';
import './snapshotTab.css';
import './tabbedPane.css';
import * as React from 'react';
import { useMeasure } from './helpers';
import type { Point } from '../../../common/types';
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
export const SnapshotTab: React.FunctionComponent<{
actionEntry: ActionEntry | undefined,
action: ActionTraceEvent | undefined,
snapshotSize: Size,
}> = ({ actionEntry, snapshotSize }) => {
}> = ({ action, snapshotSize }) => {
const [measure, ref] = useMeasure<HTMLDivElement>();
let [snapshotIndex, setSnapshotIndex] = React.useState(0);
const snapshotMap = new Map<string, { title: string, snapshotName: string }>();
for (const snapshot of actionEntry?.metadata.snapshots || [])
for (const snapshot of action?.metadata.snapshots || [])
snapshotMap.set(snapshot.title, snapshot);
const actionSnapshot = snapshotMap.get('action') || snapshotMap.get('after');
const snapshots = [actionSnapshot ? { ...actionSnapshot, title: 'action' } : undefined, snapshotMap.get('before'), snapshotMap.get('after')].filter(Boolean) as { title: string, snapshotName: string }[];
@ -44,12 +44,12 @@ export const SnapshotTab: React.FunctionComponent<{
return;
let snapshotUri = undefined;
let point: Point | undefined = undefined;
if (actionEntry) {
if (action) {
const snapshot = snapshots[snapshotIndex];
if (snapshot && snapshot.snapshotName) {
snapshotUri = `${actionEntry.metadata.pageId}?name=${snapshot.snapshotName}`;
snapshotUri = `${action.metadata.pageId}?name=${snapshot.snapshotName}`;
if (snapshot.snapshotName.includes('action'))
point = actionEntry.metadata.point;
point = action.metadata.point;
}
}
const snapshotUrl = snapshotUri ? `${window.location.origin}/snapshot/${snapshotUri}` : 'data:text/html,<body style="background: #ddd"></body>';
@ -57,7 +57,7 @@ export const SnapshotTab: React.FunctionComponent<{
(iframeRef.current.contentWindow as any).showSnapshot(snapshotUrl, { point });
} catch (e) {
}
}, [actionEntry, snapshotIndex]);
}, [action, snapshotIndex]);
const scale = Math.min(measure.width / snapshotSize.width, measure.height / snapshotSize.height);
const scaledSize = {

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
import * as React from 'react';
import { useAsyncMemo } from './helpers';
import './sourceTab.css';
@ -23,6 +22,7 @@ import { StackFrame } from '../../../common/types';
import { Source as SourceView } from '../../components/source';
import { StackTraceView } from './stackTrace';
import { SplitView } from '../../components/splitView';
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
type StackInfo = string | {
frames: StackFrame[];
@ -30,22 +30,22 @@ type StackInfo = string | {
};
export const SourceTab: React.FunctionComponent<{
actionEntry: ActionEntry | undefined,
}> = ({ actionEntry }) => {
const [lastAction, setLastAction] = React.useState<ActionEntry | undefined>();
action: ActionTraceEvent | undefined,
}> = ({ action }) => {
const [lastAction, setLastAction] = React.useState<ActionTraceEvent | undefined>();
const [selectedFrame, setSelectedFrame] = React.useState<number>(0);
const [needReveal, setNeedReveal] = React.useState<boolean>(false);
if (lastAction !== actionEntry) {
setLastAction(actionEntry);
if (lastAction !== action) {
setLastAction(action);
setSelectedFrame(0);
setNeedReveal(true);
}
const stackInfo = React.useMemo<StackInfo>(() => {
if (!actionEntry)
if (!action)
return '';
const { metadata } = actionEntry;
const { metadata } = action;
if (!metadata.stack)
return '';
const frames = metadata.stack;
@ -53,7 +53,7 @@ export const SourceTab: React.FunctionComponent<{
frames,
fileContent: new Map(),
};
}, [actionEntry]);
}, [action]);
const content = useAsyncMemo<string>(async () => {
let value: string;
@ -80,6 +80,6 @@ export const SourceTab: React.FunctionComponent<{
return <SplitView sidebarSize={100} orientation='vertical'>
<SourceView text={content} language='javascript' highlight={[{ line: targetLine, type: 'running' }]} revealLine={targetLine}></SourceView>
<StackTraceView actionEntry={actionEntry} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame}></StackTraceView>
<StackTraceView action={action} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame}></StackTraceView>
</SplitView>;
};

View File

@ -14,16 +14,16 @@
* limitations under the License.
*/
import { ActionEntry } from '../../../server/trace/viewer/traceModel';
import * as React from 'react';
import './stackTrace.css';
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
export const StackTraceView: React.FunctionComponent<{
actionEntry: ActionEntry | undefined,
action: ActionTraceEvent | undefined,
selectedFrame: number,
setSelectedFrame: (index: number) => void
}> = ({ actionEntry, setSelectedFrame, selectedFrame }) => {
const frames = actionEntry?.metadata.stack || [];
}> = ({ action, setSelectedFrame, selectedFrame }) => {
const frames = action?.metadata.stack || [];
return <div className='stack-trace'>{
frames.map((frame, index) => {
return <div

View File

@ -71,10 +71,9 @@
position: absolute;
height: 9px;
top: 11px;
background-color: red;
border-radius: 2px;
min-width: 3px;
--action-color: 'transparent';
--action-color: gray;
background-color: var(--action-color);
}
@ -83,46 +82,54 @@
box-shadow: 0 0 0 1px var(--action-color);
}
.timeline-bar.click,
.timeline-bar.dblclick,
.timeline-bar.hover,
.timeline-bar.check,
.timeline-bar.uncheck,
.timeline-bar.tap {
.timeline-bar.frame_click,
.timeline-bar.frame_dblclick,
.timeline-bar.frame_hover,
.timeline-bar.frame_check,
.timeline-bar.frame_uncheck,
.timeline-bar.frame_tap {
--action-color: var(--green);
}
.timeline-bar.fill,
.timeline-bar.press,
.timeline-bar.type,
.timeline-bar.selectOption,
.timeline-bar.setInputFiles {
.timeline-bar.page_load,
.timeline-bar.page_domcontentloaded,
.timeline-bar.frame_fill,
.timeline-bar.frame_press,
.timeline-bar.frame_type,
.timeline-bar.frame_selectoption,
.timeline-bar.frame_setinputfiles {
--action-color: var(--orange);
}
.timeline-bar.goto,
.timeline-bar.setContent,
.timeline-bar.goBack,
.timeline-bar.goForward,
.timeline-bar.frame_loadstate {
display: none;
}
.timeline-bar.frame_goto,
.timeline-bar.frame_setcontent,
.timeline-bar.frame_goback,
.timeline-bar.frame_goforward,
.timeline-bar.reload {
--action-color: var(--blue);
}
.timeline-bar.evaluateExpression {
.timeline-bar.frame_evaluateexpression {
--action-color: var(--yellow);
}
.timeline-bar.dialog {
top: 22px;
.timeline-bar.frame_dialog {
--action-color: var(--transparent-blue);
}
.timeline-bar.navigation {
top: 22px;
.timeline-bar.frame_navigated {
--action-color: var(--blue);
}
.timeline-bar.waitForEventInfo {
.timeline-bar.event {
top: 22px;
}
.timeline-bar.frame_waitforeventinfo {
bottom: inherit;
top: 0;
--action-color: var(--gray);

View File

@ -15,7 +15,8 @@
limitations under the License.
*/
import { ContextEntry, InterestingPageEvent, ActionEntry, trace } from '../../../server/trace/viewer/traceModel';
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
import { ContextEntry } from '../../../server/trace/viewer/traceModel';
import './timeline.css';
import { Boundaries } from '../geometry';
import * as React from 'react';
@ -24,24 +25,24 @@ import { msToString } from '../../uiUtils';
import { FilmStrip } from './filmStrip';
type TimelineBar = {
entry?: ActionEntry;
event?: InterestingPageEvent;
action?: ActionTraceEvent;
event?: ActionTraceEvent;
leftPosition: number;
rightPosition: number;
leftTime: number;
rightTime: number;
type: string;
label: string;
priority: number;
className: string;
};
export const Timeline: React.FunctionComponent<{
context: ContextEntry,
boundaries: Boundaries,
selectedAction: ActionEntry | undefined,
highlightedAction: ActionEntry | undefined,
onSelected: (action: ActionEntry) => void,
onHighlighted: (action: ActionEntry | undefined) => void,
selectedAction: ActionTraceEvent | undefined,
highlightedAction: ActionTraceEvent | undefined,
onSelected: (action: ActionTraceEvent) => void,
onHighlighted: (action: ActionTraceEvent | undefined) => void,
}> = ({ context, boundaries, selectedAction, highlightedAction, onSelected, onHighlighted }) => {
const [measure, ref] = useMeasure<HTMLDivElement>();
const [previewPoint, setPreviewPoint] = React.useState<{ x: number, clientY: number } | undefined>();
@ -61,64 +62,36 @@ export const Timeline: React.FunctionComponent<{
if (entry.metadata.method === 'goto')
detail = entry.metadata.params.url || '';
bars.push({
entry,
action: entry,
leftTime: entry.metadata.startTime,
rightTime: entry.metadata.endTime,
leftPosition: timeToPosition(measure.width, boundaries, entry.metadata.startTime),
rightPosition: timeToPosition(measure.width, boundaries, entry.metadata.endTime),
label: entry.metadata.apiName + ' ' + detail,
type: entry.metadata.method,
priority: 0,
type: entry.metadata.type + '.' + entry.metadata.method,
className: `${entry.metadata.type}_${entry.metadata.method}`.toLowerCase()
});
}
let lastDialogOpened: trace.DialogOpenedEvent | undefined;
for (const event of page.interestingEvents) {
if (event.type === 'dialog-opened') {
lastDialogOpened = event;
continue;
}
if (event.type === 'dialog-closed' && lastDialogOpened) {
bars.push({
event,
leftTime: lastDialogOpened.timestamp,
rightTime: event.timestamp,
leftPosition: timeToPosition(measure.width, boundaries, lastDialogOpened.timestamp),
rightPosition: timeToPosition(measure.width, boundaries, event.timestamp),
label: lastDialogOpened.message ? `${event.dialogType} "${lastDialogOpened.message}"` : event.dialogType,
type: 'dialog',
priority: -1,
});
} else if (event.type === 'navigation') {
bars.push({
event,
leftTime: event.timestamp,
rightTime: event.timestamp,
leftPosition: timeToPosition(measure.width, boundaries, event.timestamp),
rightPosition: timeToPosition(measure.width, boundaries, event.timestamp),
label: `navigated to ${event.url}`,
type: event.type,
priority: 1,
});
} else if (event.type === 'load') {
bars.push({
event,
leftTime: event.timestamp,
rightTime: event.timestamp,
leftPosition: timeToPosition(measure.width, boundaries, event.timestamp),
rightPosition: timeToPosition(measure.width, boundaries, event.timestamp),
label: `load`,
type: event.type,
priority: 1,
});
}
for (const event of page.events) {
const startTime = event.metadata.startTime;
bars.push({
event,
leftTime: startTime,
rightTime: startTime,
leftPosition: timeToPosition(measure.width, boundaries, startTime),
rightPosition: timeToPosition(measure.width, boundaries, startTime),
label: event.metadata.method,
type: event.metadata.type + '.' + event.metadata.method,
className: `${event.metadata.type}_${event.metadata.method}`.toLowerCase()
});
}
}
bars.sort((a, b) => a.priority - b.priority);
return bars;
}, [context, boundaries, measure.width]);
const hoveredBar = hoveredBarIndex !== undefined ? bars[hoveredBarIndex] : undefined;
let targetBar: TimelineBar | undefined = bars.find(bar => bar.entry === (highlightedAction || selectedAction));
let targetBar: TimelineBar | undefined = bars.find(bar => bar.action === (highlightedAction || selectedAction));
targetBar = hoveredBar || targetBar;
const findHoveredBarIndex = (x: number) => {
@ -150,7 +123,7 @@ export const Timeline: React.FunctionComponent<{
setPreviewPoint({ x, clientY: event.clientY });
setHoveredBarIndex(index);
if (typeof index === 'number')
onHighlighted(bars[index].entry);
onHighlighted(bars[index].action);
};
const onMouseLeave = () => {
@ -167,7 +140,7 @@ export const Timeline: React.FunctionComponent<{
const index = findHoveredBarIndex(x);
if (index === undefined)
return;
const entry = bars[index].entry;
const entry = bars[index].action;
if (entry)
onSelected(entry);
};
@ -183,7 +156,7 @@ export const Timeline: React.FunctionComponent<{
<div className='timeline-lane timeline-labels'>{
bars.map((bar, index) => {
return <div key={index}
className={'timeline-label ' + bar.type + (targetBar === bar ? ' selected' : '')}
className={'timeline-label ' + bar.className + (targetBar === bar ? ' selected' : '')}
style={{
left: bar.leftPosition + 'px',
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',
@ -196,7 +169,7 @@ export const Timeline: React.FunctionComponent<{
<div className='timeline-lane timeline-bars'>{
bars.map((bar, index) => {
return <div key={index}
className={'timeline-bar ' + bar.type + (targetBar === bar ? ' selected' : '')}
className={'timeline-bar ' + (bar.action ? 'action ' : '') + (bar.event ? 'event ' : '') + bar.className + (targetBar === bar ? ' selected' : '')}
style={{
left: bar.leftPosition + 'px',
width: Math.max(1, bar.rightPosition - bar.leftPosition) + 'px',

View File

@ -14,7 +14,8 @@
limitations under the License.
*/
import { ActionEntry, ContextEntry } from '../../../server/trace/viewer/traceModel';
import { ActionTraceEvent } from '../../../server/trace/common/traceEvents';
import { ContextEntry } from '../../../server/trace/viewer/traceModel';
import { ActionList } from './actionList';
import { TabbedPane } from './tabbedPane';
import { Timeline } from './timeline';
@ -33,20 +34,21 @@ export const Workbench: React.FunctionComponent<{
debugNames: string[],
}> = ({ debugNames }) => {
const [debugName, setDebugName] = React.useState(debugNames[0]);
const [selectedAction, setSelectedAction] = React.useState<ActionEntry | undefined>();
const [highlightedAction, setHighlightedAction] = React.useState<ActionEntry | undefined>();
const [selectedAction, setSelectedAction] = React.useState<ActionTraceEvent | undefined>();
const [highlightedAction, setHighlightedAction] = React.useState<ActionTraceEvent | undefined>();
let context = useAsyncMemo(async () => {
return (await fetch(`/context/${debugName}`).then(response => response.json())) as ContextEntry;
}, [debugName], emptyContext);
const actions = React.useMemo(() => {
const actions: ActionEntry[] = [];
const { actions, nextAction } = React.useMemo(() => {
const actions: ActionTraceEvent[] = [];
for (const page of context.pages)
actions.push(...page.actions);
actions.sort((a, b) => a.timestamp - b.timestamp);
return actions;
}, [context]);
const nextAction = selectedAction ? actions[actions.indexOf(selectedAction) + 1] : undefined;
return { actions, nextAction };
}, [context, selectedAction]);
const snapshotSize = context.created.viewportSize || { width: 1280, height: 720 };
const boundaries = { minimum: context.startTime, maximum: context.endTime };
@ -77,11 +79,11 @@ export const Workbench: React.FunctionComponent<{
</div>
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
<SplitView sidebarSize={300} orientation='horizontal'>
<SnapshotTab actionEntry={selectedAction} snapshotSize={snapshotSize} />
<SnapshotTab action={selectedAction} snapshotSize={snapshotSize} />
<TabbedPane tabs={[
{ id: 'logs', title: 'Log', render: () => <LogsTab actionEntry={selectedAction} /> },
{ id: 'source', title: 'Source', render: () => <SourceTab actionEntry={selectedAction} /> },
{ id: 'network', title: 'Network', render: () => <NetworkTab actionEntry={selectedAction} /> },
{ id: 'logs', title: 'Log', render: () => <LogsTab action={selectedAction} /> },
{ id: 'source', title: 'Source', render: () => <SourceTab action={selectedAction} /> },
{ id: 'network', title: 'Network', render: () => <NetworkTab context={context} action={selectedAction} nextAction={nextAction}/> },
]}/>
</SplitView>
<ActionList
@ -110,5 +112,6 @@ const emptyContext: ContextEntry = {
viewportSize: { width: 1280, height: 800 },
debugName: '<empty>',
},
pages: []
pages: [],
resources: []
};

View File

@ -147,7 +147,7 @@ DEPS['src/cli/driver.ts'] = DEPS['src/inprocess.ts'] = DEPS['src/browserServerIm
// Tracing is a client/server plugin, nothing should depend on it.
DEPS['src/web/recorder/'] = ['src/common/', 'src/web/', 'src/web/components/', 'src/server/supplements/recorder/recorderTypes.ts'];
DEPS['src/web/traceViewer/'] = ['src/common/', 'src/web/'];
DEPS['src/web/traceViewer/ui/'] = ['src/common/', 'src/web/traceViewer/', 'src/web/', 'src/server/trace/viewer/', 'src/server/trace/', 'src/server/trace/common/', 'src/server/snapshot/snapshotTypes.ts'];
DEPS['src/web/traceViewer/ui/'] = ['src/common/', 'src/web/traceViewer/', 'src/web/', 'src/server/trace/viewer/', 'src/server/trace/', 'src/server/trace/common/', 'src/server/snapshot/snapshotTypes.ts', 'src/protocol/channels.ts'];
// The service is a cross-cutting feature, and so it depends on a bunch of things.
DEPS['src/remote/'] = ['src/client/', 'src/debug/', 'src/dispatchers/', 'src/server/', 'src/server/supplements/', 'src/server/electron/', 'src/server/trace/'];