chrome-devtools-frontend
Version:
Chrome DevTools UI
263 lines (229 loc) • 13.4 kB
text/typescript
// Copyright 2023 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 {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js';
import {TraceLoader} from '../../../testing/TraceLoader.js';
import * as Trace from '../trace.js';
describeWithEnvironment('InitiatorsHandler', () => {
beforeEach(() => {
Trace.Handlers.ModelHandlers.Initiators.reset();
});
it('for an UpdateLayoutTree event it sets the initiator to the previous ScheduledStyleRecalculation event',
async function() {
const traceEvents = await TraceLoader.rawEvents(this, 'web-dev-with-commit.json.gz');
for (const event of traceEvents) {
Trace.Handlers.ModelHandlers.Initiators.handleEvent(event);
}
await Trace.Handlers.ModelHandlers.Initiators.finalize();
const data = Trace.Handlers.ModelHandlers.Initiators.data();
const updateLayoutTreeEvent = traceEvents.find(event => {
return Trace.Types.Events.isUpdateLayoutTree(event) && event.ts === 122411039965;
});
if (!updateLayoutTreeEvent || !Trace.Types.Events.isUpdateLayoutTree(updateLayoutTreeEvent)) {
throw new Error('Could not find layout tree event.');
}
const initiator = data.eventToInitiator.get(updateLayoutTreeEvent);
if (!initiator) {
throw new Error('Did not find expected initiator for updateLayoutTreeEvent');
}
assert.isTrue(Trace.Types.Events.isScheduleStyleRecalculation(initiator));
assert.strictEqual(updateLayoutTreeEvent.args.beginData?.frame, '25D2F12F1818C70B5BD4325CC9ACD8FF');
assert.strictEqual(updateLayoutTreeEvent.args.beginData?.frame, initiator.args?.data?.frame);
});
it('for a Layout event it sets the initiator to the last InvalidateLayout event on that frame', async function() {
const traceEvents = await TraceLoader.rawEvents(this, 'web-dev-with-commit.json.gz');
for (const event of traceEvents) {
Trace.Handlers.ModelHandlers.Initiators.handleEvent(event);
}
await Trace.Handlers.ModelHandlers.Initiators.finalize();
const data = Trace.Handlers.ModelHandlers.Initiators.data();
const layoutEvent = traceEvents.find(event => {
return Trace.Types.Events.isLayout(event) && event.ts === 122411039994;
});
if (!layoutEvent || !Trace.Types.Events.isLayout(layoutEvent)) {
throw new Error('Could not find layout event.');
}
const initiator = data.eventToInitiator.get(layoutEvent);
if (!initiator) {
throw new Error('Did not find expected initiator for LayoutEvent');
}
assert.isTrue(Trace.Types.Events.isInvalidateLayout(initiator));
assert.strictEqual(initiator.ts, 122411036517);
});
it('for a Layout event it sets the initiator to the last ScheduledStyleRecalculation if it occurred before the InvalidateLayout event',
async function() {
const traceEvents = await TraceLoader.rawEvents(this, 'web-dev-with-commit.json.gz');
for (const event of traceEvents) {
Trace.Handlers.ModelHandlers.Initiators.handleEvent(event);
}
await Trace.Handlers.ModelHandlers.Initiators.finalize();
const data = Trace.Handlers.ModelHandlers.Initiators.data();
const layoutEvent = traceEvents.find(event => {
return Trace.Types.Events.isLayout(event) && event.ts === 122411054960;
});
if (!layoutEvent || !Trace.Types.Events.isLayout(layoutEvent)) {
throw new Error('Could not find layout event.');
}
const initiator = data.eventToInitiator.get(layoutEvent);
if (!initiator) {
throw new Error('Did not find expected initiator for LayoutEvent');
}
assert.isTrue(Trace.Types.Events.isScheduleStyleRecalculation(initiator));
assert.strictEqual(initiator.ts, 122411054482);
});
it('sets an initiator relationship between a requestAnimationFrame and the scheduled FunctionCall', async function() {
const {parsedTrace} = await TraceLoader.traceEngine(this, 'async-js-calls.json.gz');
const requestAnimationFrameCall = parsedTrace.Renderer.allTraceEntries.find(
e => Trace.Types.Events.isProfileCall(e) && e.callFrame.functionName === 'requestAnimationFrame');
if (!requestAnimationFrameCall) {
throw new Error('Could not find requestAnimationFrame call');
}
const functionCallEvent = parsedTrace.Renderer.allTraceEntries.find(
e => Trace.Types.Events.isFunctionCall(e) && e.ts > requestAnimationFrameCall.ts);
if (!functionCallEvent) {
throw new Error('Could not find FunctionCall event');
}
assert.strictEqual(parsedTrace.Initiators.eventToInitiator.get(functionCallEvent), requestAnimationFrameCall);
assert.deepEqual(parsedTrace.Initiators.initiatorToEvents.get(requestAnimationFrameCall), [functionCallEvent]);
});
it('sets an initiator relationship between a setTimeout and the scheduled FunctionCall', async function() {
const {parsedTrace} = await TraceLoader.traceEngine(this, 'async-js-calls.json.gz');
const setTimeoutCall =
parsedTrace.Renderer.allTraceEntries
.filter(e => Trace.Types.Events.isProfileCall(e) && e.callFrame.functionName === 'setTimeout')
.at(-1);
if (!setTimeoutCall) {
throw new Error('Could not find setTimeout call');
}
const functionCallEvent = parsedTrace.Renderer.allTraceEntries.find(
e => Trace.Types.Events.isFunctionCall(e) && e.ts > setTimeoutCall.ts);
if (!functionCallEvent) {
throw new Error('Could not find FunctionCall event');
}
assert.strictEqual(parsedTrace.Initiators.eventToInitiator.get(functionCallEvent), setTimeoutCall);
assert.deepEqual(parsedTrace.Initiators.initiatorToEvents.get(setTimeoutCall), [functionCallEvent]);
});
it('sets an initiator relationship between a requestIdleCallback and the scheduled FunctionCall', async function() {
const {parsedTrace} = await TraceLoader.traceEngine(this, 'async-js-calls.json.gz');
const requestIdleCallback = parsedTrace.Renderer.allTraceEntries.find(
e => Trace.Types.Events.isProfileCall(e) && e.callFrame.functionName === 'requestIdleCallback');
if (!requestIdleCallback) {
throw new Error('Could not find requestIdleCallback call');
}
const functionCallEvent = parsedTrace.Renderer.allTraceEntries.find(
e => Trace.Types.Events.isFunctionCall(e) && e.ts > requestIdleCallback.ts);
if (!functionCallEvent) {
throw new Error('Could not find FunctionCall event');
}
assert.strictEqual(parsedTrace.Initiators.eventToInitiator.get(functionCallEvent), requestIdleCallback);
assert.deepEqual(parsedTrace.Initiators.initiatorToEvents.get(requestIdleCallback), [functionCallEvent]);
});
it('sets an initiator relationship between a console.createTask and the scheduled task.run', async function() {
const {parsedTrace} = await TraceLoader.traceEngine(this, 'async-js-calls.json.gz');
const schedulerFuntion = parsedTrace.Renderer.allTraceEntries.find(
e => Trace.Types.Events.isProfileCall(e) && e.callFrame.functionName === 'startExample');
if (!schedulerFuntion) {
throw new Error('Could not find scheduler function call');
}
const consoleRunTask = parsedTrace.Renderer.allTraceEntries.find(
e => Trace.Types.Events.isConsoleRunTask(e) && e.ts > schedulerFuntion.ts);
assert.exists(consoleRunTask);
assert.strictEqual(parsedTrace.Initiators.eventToInitiator.get(consoleRunTask), schedulerFuntion);
assert.deepEqual(parsedTrace.Initiators.initiatorToEvents.get(schedulerFuntion), [consoleRunTask]);
});
it('for a WebSocketSendHandshakeRequest the initiator is the WebSocketCreate event', async function() {
const traceEvents = await TraceLoader.rawEvents(this, 'web-sockets.json.gz');
for (const event of traceEvents) {
Trace.Handlers.ModelHandlers.Initiators.handleEvent(event);
}
await Trace.Handlers.ModelHandlers.Initiators.finalize();
const data = Trace.Handlers.ModelHandlers.Initiators.data();
const webSocketCreateEvent = traceEvents.find(Trace.Types.Events.isWebSocketCreate);
if (!webSocketCreateEvent) {
throw new Error('Could not fnd WebSocketCreateEvent');
}
const webSocketSendHandshakeRequestEvent = traceEvents.find(Trace.Types.Events.isWebSocketSendHandshakeRequest);
if (!webSocketSendHandshakeRequestEvent) {
throw new Error('Could not find WebSocketSendHandshakeRequest');
}
assert.strictEqual(data.eventToInitiator.get(webSocketSendHandshakeRequestEvent), webSocketCreateEvent);
});
it('for a WebSocketReceiveHandshakeResponse the initiator is the WebSocketCreate event', async function() {
const traceEvents = await TraceLoader.rawEvents(this, 'web-sockets.json.gz');
for (const event of traceEvents) {
Trace.Handlers.ModelHandlers.Initiators.handleEvent(event);
}
await Trace.Handlers.ModelHandlers.Initiators.finalize();
const data = Trace.Handlers.ModelHandlers.Initiators.data();
const webSocketCreateEvent = traceEvents.find(Trace.Types.Events.isWebSocketCreate);
if (!webSocketCreateEvent) {
throw new Error('Could not fnd WebSocketCreateEvent');
}
const webSocketReceieveHandshakeResponseEvent =
traceEvents.find(Trace.Types.Events.isWebSocketReceiveHandshakeResponse);
if (!webSocketReceieveHandshakeResponseEvent) {
throw new Error('Could not find WebSocketReceiveHandshakeResponse event');
}
const webSocketSendHandshakeRequestEvent = traceEvents.find(Trace.Types.Events.isWebSocketSendHandshakeRequest);
if (!webSocketSendHandshakeRequestEvent) {
throw new Error('Could not find WebSocketSendHandshakeRequest');
}
assert.strictEqual(data.eventToInitiator.get(webSocketReceieveHandshakeResponseEvent), webSocketCreateEvent);
assert.deepEqual(
data.initiatorToEvents.get(webSocketCreateEvent),
[webSocketSendHandshakeRequestEvent, webSocketReceieveHandshakeResponseEvent]);
});
it('for a PostMessage Handler event the initiator is the PostMessage Dispatch event', async function() {
const traceEvents = await TraceLoader.rawEvents(this, 'postmessage-initiators.json.gz');
for (const event of traceEvents) {
Trace.Handlers.ModelHandlers.Flows.handleEvent(event);
Trace.Handlers.ModelHandlers.Initiators.handleEvent(event);
}
await Trace.Handlers.ModelHandlers.Flows.finalize();
await Trace.Handlers.ModelHandlers.Initiators.finalize();
const data = Trace.Handlers.ModelHandlers.Initiators.data();
const schedulePostMessageEvent = traceEvents.find(Trace.Types.Events.isSchedulePostMessage);
if (!schedulePostMessageEvent) {
throw new Error('Could not find schedulePostMessageEvent event');
}
const handlePostMessageEvent = traceEvents.find(Trace.Types.Events.isHandlePostMessage);
if (!handlePostMessageEvent) {
throw new Error('Could not find handlePostMessageEvent event');
}
assert.strictEqual(data.eventToInitiator.get(handlePostMessageEvent), schedulePostMessageEvent);
assert.deepEqual(data.initiatorToEvents.get(schedulePostMessageEvent), [handlePostMessageEvent]);
});
it('pairs the postTask-scheduled tasks with their scheduling initiators', async function() {
const traceEvents = await TraceLoader.rawEvents(this, 'scheduler-post-task.json.gz');
for (const event of traceEvents) {
Trace.Handlers.ModelHandlers.Initiators.handleEvent(event);
}
await Trace.Handlers.ModelHandlers.Initiators.finalize();
const data = Trace.Handlers.ModelHandlers.Initiators.data();
const scheduleEvents = traceEvents.filter(Trace.Types.Events.isSchedulePostTaskCallback);
assert.isNotEmpty(scheduleEvents, 'Could not find SchedulePostTaskCallback events');
const runEvents = traceEvents.filter(Trace.Types.Events.isRunPostTaskCallback);
assert.isNotEmpty(runEvents, 'Could not find RunPostTaskCallback events');
const cancelEvents = traceEvents.filter(Trace.Types.Events.isAbortPostTaskCallback);
assert.isNotEmpty(cancelEvents, 'Could not find AbortPostTaskCallback events');
assert.containsAllKeys(data.initiatorToEvents, scheduleEvents, 'Not all schedule events in initiators');
// All end events have a SchedulePostTaskCallback initiator.
for (const endEvent of [...runEvents, ...cancelEvents]) {
const initiator = data.eventToInitiator.get(endEvent);
assert.exists(initiator);
assert(Trace.Types.Events.isSchedulePostTaskCallback(initiator));
assert.strictEqual(endEvent.args.data.taskId, initiator.args.data.taskId);
assert(data.initiatorToEvents.get(initiator)?.includes(endEvent));
}
// There is one task that cancels itself while running, so it has both run and cancel events.
const doubleEvents = scheduleEvents.some(scheduleEvent => {
const endEvents = data.initiatorToEvents.get(scheduleEvent);
if (!endEvents || endEvents.length < 2) {
return false;
}
return endEvents.some(Trace.Types.Events.isRunPostTaskCallback) &&
endEvents.some(Trace.Types.Events.isAbortPostTaskCallback);
});
assert(doubleEvents, 'initiator with both run and cancel initiated events not found');
});
});