mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 05:35:05 +08:00
Show the navigation time mark on the timeline. Change-Id: I1d8d832f40b17094f84ef1b1ae366beabe6016ec Reviewed-on: https://chromium-review.googlesource.com/1036619 Reviewed-by: Dmitry Gozman <dgozman@chromium.org> Commit-Queue: Alexei Filippov <alph@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#555208} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: 3423647773befa7ffab8395f6edc6d7e3890ca16
2394 lines
94 KiB
JavaScript
2394 lines
94 KiB
JavaScript
/*
|
||
* Copyright (C) 2013 Google Inc. All rights reserved.
|
||
* Copyright (C) 2012 Intel Inc. All rights reserved.
|
||
*
|
||
* Redistribution and use in source and binary forms, with or without
|
||
* modification, are permitted provided that the following conditions are
|
||
* met:
|
||
*
|
||
* * Redistributions of source code must retain the above copyright
|
||
* notice, this list of conditions and the following disclaimer.
|
||
* * Redistributions in binary form must reproduce the above
|
||
* copyright notice, this list of conditions and the following disclaimer
|
||
* in the documentation and/or other materials provided with the
|
||
* distribution.
|
||
* * Neither the name of Google Inc. nor the names of its
|
||
* contributors may be used to endorse or promote products derived from
|
||
* this software without specific prior written permission.
|
||
*
|
||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
*/
|
||
|
||
/**
|
||
* @unrestricted
|
||
*/
|
||
Timeline.TimelineUIUtils = class {
|
||
/**
|
||
* @return {!Object.<string, !Timeline.TimelineRecordStyle>}
|
||
*/
|
||
static _initEventStyles() {
|
||
if (Timeline.TimelineUIUtils._eventStylesMap)
|
||
return Timeline.TimelineUIUtils._eventStylesMap;
|
||
|
||
const recordTypes = TimelineModel.TimelineModel.RecordType;
|
||
const categories = Timeline.TimelineUIUtils.categories();
|
||
|
||
const eventStyles = {};
|
||
eventStyles[recordTypes.Task] = new Timeline.TimelineRecordStyle(Common.UIString('Task'), categories['other']);
|
||
eventStyles[recordTypes.Program] = new Timeline.TimelineRecordStyle(Common.UIString('Other'), categories['other']);
|
||
eventStyles[recordTypes.Animation] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Animation'), categories['rendering']);
|
||
eventStyles[recordTypes.EventDispatch] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Event'), categories['scripting']);
|
||
eventStyles[recordTypes.RequestMainThreadFrame] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Request Main Thread Frame'), categories['rendering'], true);
|
||
eventStyles[recordTypes.BeginFrame] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Frame Start'), categories['rendering'], true);
|
||
eventStyles[recordTypes.BeginMainThreadFrame] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Frame Start (main thread)'), categories['rendering'], true);
|
||
eventStyles[recordTypes.DrawFrame] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Draw Frame'), categories['rendering'], true);
|
||
eventStyles[recordTypes.HitTest] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Hit Test'), categories['rendering']);
|
||
eventStyles[recordTypes.ScheduleStyleRecalculation] = new Timeline.TimelineRecordStyle(
|
||
Common.UIString('Schedule Style Recalculation'), categories['rendering'], true);
|
||
eventStyles[recordTypes.RecalculateStyles] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Recalculate Style'), categories['rendering']);
|
||
eventStyles[recordTypes.UpdateLayoutTree] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Recalculate Style'), categories['rendering']);
|
||
eventStyles[recordTypes.InvalidateLayout] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Invalidate Layout'), categories['rendering'], true);
|
||
eventStyles[recordTypes.Layout] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Layout'), categories['rendering']);
|
||
eventStyles[recordTypes.PaintSetup] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Paint Setup'), categories['painting']);
|
||
eventStyles[recordTypes.PaintImage] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Paint Image'), categories['painting'], true);
|
||
eventStyles[recordTypes.UpdateLayer] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Update Layer'), categories['painting'], true);
|
||
eventStyles[recordTypes.UpdateLayerTree] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Update Layer Tree'), categories['rendering']);
|
||
eventStyles[recordTypes.Paint] = new Timeline.TimelineRecordStyle(Common.UIString('Paint'), categories['painting']);
|
||
eventStyles[recordTypes.RasterTask] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Rasterize Paint'), categories['painting']);
|
||
eventStyles[recordTypes.ScrollLayer] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Scroll'), categories['rendering']);
|
||
eventStyles[recordTypes.CompositeLayers] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Composite Layers'), categories['painting']);
|
||
eventStyles[recordTypes.ParseHTML] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Parse HTML'), categories['loading']);
|
||
eventStyles[recordTypes.ParseAuthorStyleSheet] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Parse Stylesheet'), categories['loading']);
|
||
eventStyles[recordTypes.TimerInstall] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Install Timer'), categories['scripting']);
|
||
eventStyles[recordTypes.TimerRemove] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Remove Timer'), categories['scripting']);
|
||
eventStyles[recordTypes.TimerFire] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Timer Fired'), categories['scripting']);
|
||
eventStyles[recordTypes.XHRReadyStateChange] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('XHR Ready State Change'), categories['scripting']);
|
||
eventStyles[recordTypes.XHRLoad] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('XHR Load'), categories['scripting']);
|
||
eventStyles[recordTypes.CompileScript] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Compile Script'), categories['scripting']);
|
||
eventStyles[recordTypes.EvaluateScript] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Evaluate Script'), categories['scripting']);
|
||
eventStyles[recordTypes.CompileModule] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Compile Module'), categories['scripting']);
|
||
eventStyles[recordTypes.EvaluateModule] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Evaluate Module'), categories['scripting']);
|
||
eventStyles[recordTypes.ParseScriptOnBackground] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Parse Script'), categories['scripting']);
|
||
eventStyles[recordTypes.FrameStartedLoading] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Frame Started Loading'), categories['loading'], true);
|
||
eventStyles[recordTypes.MarkLoad] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Load event'), categories['scripting'], true);
|
||
eventStyles[recordTypes.MarkDOMContent] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('DOMContentLoaded event'), categories['scripting'], true);
|
||
eventStyles[recordTypes.MarkFirstPaint] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('First paint'), categories['painting'], true);
|
||
eventStyles[recordTypes.MarkFCP] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('First contentful paint'), categories['rendering'], true);
|
||
eventStyles[recordTypes.MarkFMP] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('First meaningful paint'), categories['rendering'], true);
|
||
eventStyles[recordTypes.MarkFMPCandidate] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Meaningful paint candidate'), categories['rendering'], true);
|
||
eventStyles[recordTypes.TimeStamp] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Timestamp'), categories['scripting']);
|
||
eventStyles[recordTypes.ConsoleTime] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Console Time'), categories['scripting']);
|
||
eventStyles[recordTypes.UserTiming] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('User Timing'), categories['scripting']);
|
||
eventStyles[recordTypes.ResourceSendRequest] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Send Request'), categories['loading']);
|
||
eventStyles[recordTypes.ResourceReceiveResponse] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Receive Response'), categories['loading']);
|
||
eventStyles[recordTypes.ResourceFinish] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Finish Loading'), categories['loading']);
|
||
eventStyles[recordTypes.ResourceReceivedData] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Receive Data'), categories['loading']);
|
||
eventStyles[recordTypes.RunMicrotasks] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Run Microtasks'), categories['scripting']);
|
||
eventStyles[recordTypes.FunctionCall] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Function Call'), categories['scripting']);
|
||
eventStyles[recordTypes.GCEvent] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('GC Event'), categories['scripting']);
|
||
eventStyles[recordTypes.MajorGC] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Major GC'), categories['scripting']);
|
||
eventStyles[recordTypes.MinorGC] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Minor GC'), categories['scripting']);
|
||
eventStyles[recordTypes.JSFrame] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('JS Frame'), categories['scripting']);
|
||
eventStyles[recordTypes.RequestAnimationFrame] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Request Animation Frame'), categories['scripting']);
|
||
eventStyles[recordTypes.CancelAnimationFrame] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Cancel Animation Frame'), categories['scripting']);
|
||
eventStyles[recordTypes.FireAnimationFrame] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Animation Frame Fired'), categories['scripting']);
|
||
eventStyles[recordTypes.RequestIdleCallback] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Request Idle Callback'), categories['scripting']);
|
||
eventStyles[recordTypes.CancelIdleCallback] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Cancel Idle Callback'), categories['scripting']);
|
||
eventStyles[recordTypes.FireIdleCallback] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Fire Idle Callback'), categories['scripting']);
|
||
eventStyles[recordTypes.WebSocketCreate] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Create WebSocket'), categories['scripting']);
|
||
eventStyles[recordTypes.WebSocketSendHandshakeRequest] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Send WebSocket Handshake'), categories['scripting']);
|
||
eventStyles[recordTypes.WebSocketReceiveHandshakeResponse] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Receive WebSocket Handshake'), categories['scripting']);
|
||
eventStyles[recordTypes.WebSocketDestroy] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Destroy WebSocket'), categories['scripting']);
|
||
eventStyles[recordTypes.EmbedderCallback] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Embedder Callback'), categories['scripting']);
|
||
eventStyles[recordTypes.DecodeImage] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Image Decode'), categories['painting']);
|
||
eventStyles[recordTypes.ResizeImage] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Image Resize'), categories['painting']);
|
||
eventStyles[recordTypes.GPUTask] = new Timeline.TimelineRecordStyle(Common.UIString('GPU'), categories['gpu']);
|
||
eventStyles[recordTypes.LatencyInfo] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Input Latency'), categories['scripting']);
|
||
|
||
eventStyles[recordTypes.GCIdleLazySweep] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('DOM GC'), categories['scripting']);
|
||
eventStyles[recordTypes.GCCompleteSweep] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('DOM GC'), categories['scripting']);
|
||
eventStyles[recordTypes.GCCollectGarbage] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('DOM GC'), categories['scripting']);
|
||
|
||
eventStyles[recordTypes.CryptoDoEncrypt] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Encrypt'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoEncryptReply] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Encrypt Reply'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoDecrypt] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Decrypt'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoDecryptReply] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Decrypt Reply'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoDigest] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Digest'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoDigestReply] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Digest Reply'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoSign] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Sign'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoSignReply] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Sign Reply'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoVerify] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Verify'), categories['scripting']);
|
||
eventStyles[recordTypes.CryptoDoVerifyReply] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Verify Reply'), categories['scripting']);
|
||
|
||
eventStyles[recordTypes.AsyncTask] =
|
||
new Timeline.TimelineRecordStyle(Common.UIString('Async Task'), categories['async']);
|
||
|
||
Timeline.TimelineUIUtils._eventStylesMap = eventStyles;
|
||
return eventStyles;
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.TimelineIRModel.InputEvents} inputEventType
|
||
* @return {?string}
|
||
*/
|
||
static inputEventDisplayName(inputEventType) {
|
||
if (!Timeline.TimelineUIUtils._inputEventToDisplayName) {
|
||
const inputEvent = TimelineModel.TimelineIRModel.InputEvents;
|
||
|
||
/** @type {!Map<!TimelineModel.TimelineIRModel.InputEvents, string>} */
|
||
Timeline.TimelineUIUtils._inputEventToDisplayName = new Map([
|
||
[inputEvent.Char, Common.UIString('Key Character')],
|
||
[inputEvent.KeyDown, Common.UIString('Key Down')],
|
||
[inputEvent.KeyDownRaw, Common.UIString('Key Down')],
|
||
[inputEvent.KeyUp, Common.UIString('Key Up')],
|
||
[inputEvent.Click, Common.UIString('Click')],
|
||
[inputEvent.ContextMenu, Common.UIString('Context Menu')],
|
||
[inputEvent.MouseDown, Common.UIString('Mouse Down')],
|
||
[inputEvent.MouseMove, Common.UIString('Mouse Move')],
|
||
[inputEvent.MouseUp, Common.UIString('Mouse Up')],
|
||
[inputEvent.MouseWheel, Common.UIString('Mouse Wheel')],
|
||
[inputEvent.ScrollBegin, Common.UIString('Scroll Begin')],
|
||
[inputEvent.ScrollEnd, Common.UIString('Scroll End')],
|
||
[inputEvent.ScrollUpdate, Common.UIString('Scroll Update')],
|
||
[inputEvent.FlingStart, Common.UIString('Fling Start')],
|
||
[inputEvent.FlingCancel, Common.UIString('Fling Halt')],
|
||
[inputEvent.Tap, Common.UIString('Tap')],
|
||
[inputEvent.TapCancel, Common.UIString('Tap Halt')],
|
||
[inputEvent.ShowPress, Common.UIString('Tap Begin')],
|
||
[inputEvent.TapDown, Common.UIString('Tap Down')],
|
||
[inputEvent.TouchCancel, Common.UIString('Touch Cancel')],
|
||
[inputEvent.TouchEnd, Common.UIString('Touch End')],
|
||
[inputEvent.TouchMove, Common.UIString('Touch Move')],
|
||
[inputEvent.TouchStart, Common.UIString('Touch Start')],
|
||
[inputEvent.PinchBegin, Common.UIString('Pinch Begin')],
|
||
[inputEvent.PinchEnd, Common.UIString('Pinch End')],
|
||
[inputEvent.PinchUpdate, Common.UIString('Pinch Update')]
|
||
]);
|
||
}
|
||
return Timeline.TimelineUIUtils._inputEventToDisplayName.get(inputEventType) || null;
|
||
}
|
||
|
||
/**
|
||
* @param {!Protocol.Runtime.CallFrame} frame
|
||
* @return {string}
|
||
*/
|
||
static frameDisplayName(frame) {
|
||
if (!TimelineModel.TimelineJSProfileProcessor.isNativeRuntimeFrame(frame))
|
||
return UI.beautifyFunctionName(frame.functionName);
|
||
const nativeGroup = TimelineModel.TimelineJSProfileProcessor.nativeGroup(frame.functionName);
|
||
const groups = TimelineModel.TimelineJSProfileProcessor.NativeGroups;
|
||
switch (nativeGroup) {
|
||
case groups.Compile:
|
||
return Common.UIString('Compile');
|
||
case groups.Parse:
|
||
return Common.UIString('Parse');
|
||
}
|
||
return frame.functionName;
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} traceEvent
|
||
* @param {!RegExp} regExp
|
||
* @return {boolean}
|
||
*/
|
||
static testContentMatching(traceEvent, regExp) {
|
||
const title = Timeline.TimelineUIUtils.eventStyle(traceEvent).title;
|
||
const tokens = [title];
|
||
const url = TimelineModel.TimelineData.forEvent(traceEvent).url;
|
||
if (url)
|
||
tokens.push(url);
|
||
appendObjectProperties(traceEvent.args, 2);
|
||
return regExp.test(tokens.join('|'));
|
||
|
||
/**
|
||
* @param {!Object} object
|
||
* @param {number} depth
|
||
*/
|
||
function appendObjectProperties(object, depth) {
|
||
if (!depth)
|
||
return;
|
||
for (const key in object) {
|
||
const value = object[key];
|
||
const type = typeof value;
|
||
if (type === 'string')
|
||
tokens.push(value);
|
||
else if (type === 'number')
|
||
tokens.push(String(value));
|
||
else if (type === 'object')
|
||
appendObjectProperties(value, depth - 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @return {?string}
|
||
*/
|
||
static eventURL(event) {
|
||
const data = event.args['data'] || event.args['beginData'];
|
||
const url = data && data.url;
|
||
if (url)
|
||
return url;
|
||
const stackTrace = data && data['stackTrace'];
|
||
const frame =
|
||
stackTrace && stackTrace.length && stackTrace[0] || TimelineModel.TimelineData.forEvent(event).topFrame();
|
||
return frame && frame.url || null;
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @return {!{title: string, category: !Timeline.TimelineCategory}}
|
||
*/
|
||
static eventStyle(event) {
|
||
const eventStyles = Timeline.TimelineUIUtils._initEventStyles();
|
||
if (event.hasCategory(TimelineModel.TimelineModel.Category.Console) ||
|
||
event.hasCategory(TimelineModel.TimelineModel.Category.UserTiming))
|
||
return {title: event.name, category: Timeline.TimelineUIUtils.categories()['scripting']};
|
||
|
||
if (event.hasCategory(TimelineModel.TimelineModel.Category.LatencyInfo)) {
|
||
/** @const */
|
||
const prefix = 'InputLatency::';
|
||
const inputEventType = event.name.startsWith(prefix) ? event.name.substr(prefix.length) : event.name;
|
||
const displayName = Timeline.TimelineUIUtils.inputEventDisplayName(
|
||
/** @type {!TimelineModel.TimelineIRModel.InputEvents} */ (inputEventType));
|
||
return {title: displayName || inputEventType, category: Timeline.TimelineUIUtils.categories()['scripting']};
|
||
}
|
||
let result = eventStyles[event.name];
|
||
if (!result) {
|
||
result = new Timeline.TimelineRecordStyle(event.name, Timeline.TimelineUIUtils.categories()['other'], true);
|
||
eventStyles[event.name] = result;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @return {string}
|
||
*/
|
||
static eventColor(event) {
|
||
if (event.name === TimelineModel.TimelineModel.RecordType.JSFrame) {
|
||
const frame = event.args['data'];
|
||
if (Timeline.TimelineUIUtils.isUserFrame(frame))
|
||
return Timeline.TimelineUIUtils.colorForId(frame.url);
|
||
}
|
||
return Timeline.TimelineUIUtils.eventStyle(event).category.color;
|
||
}
|
||
|
||
/**
|
||
* @param {!ProductRegistry.Registry} productRegistry
|
||
* @param {!TimelineModel.TimelineModel} model
|
||
* @param {!Map<string, string>} urlToColorCache
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @return {string}
|
||
*/
|
||
static eventColorByProduct(productRegistry, model, urlToColorCache, event) {
|
||
const url = Timeline.TimelineUIUtils.eventURL(event) || '';
|
||
let color = urlToColorCache.get(url);
|
||
if (color)
|
||
return color;
|
||
const defaultColor = '#f2ecdc';
|
||
const parsedURL = url.asParsedURL();
|
||
if (!parsedURL)
|
||
return defaultColor;
|
||
let name = productRegistry && productRegistry.nameForUrl(parsedURL);
|
||
if (!name) {
|
||
name = parsedURL.host;
|
||
const rootFrames = model.rootFrames();
|
||
if (rootFrames.some(pageFrame => new Common.ParsedURL(pageFrame.url).host === name))
|
||
color = defaultColor;
|
||
}
|
||
if (!color)
|
||
color = name ? ProductRegistry.BadgePool.colorForEntryName(name) : defaultColor;
|
||
urlToColorCache.set(url, color);
|
||
return color;
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @return {string}
|
||
*/
|
||
static eventTitle(event) {
|
||
const recordType = TimelineModel.TimelineModel.RecordType;
|
||
const eventData = event.args['data'];
|
||
if (event.name === recordType.JSFrame)
|
||
return Timeline.TimelineUIUtils.frameDisplayName(eventData);
|
||
const title = Timeline.TimelineUIUtils.eventStyle(event).title;
|
||
if (event.hasCategory(TimelineModel.TimelineModel.Category.Console))
|
||
return title;
|
||
if (event.name === recordType.TimeStamp)
|
||
return Common.UIString('%s: %s', title, eventData['message']);
|
||
if (event.name === recordType.Animation && eventData && eventData['name'])
|
||
return Common.UIString('%s: %s', title, eventData['name']);
|
||
return title;
|
||
}
|
||
|
||
/**
|
||
* !Map<!TimelineModel.TimelineIRModel.Phases, !{color: string, label: string}>
|
||
*/
|
||
static _interactionPhaseStyles() {
|
||
let map = Timeline.TimelineUIUtils._interactionPhaseStylesMap;
|
||
if (!map) {
|
||
map = new Map([
|
||
[TimelineModel.TimelineIRModel.Phases.Idle, {color: 'white', label: 'Idle'}],
|
||
[
|
||
TimelineModel.TimelineIRModel.Phases.Response,
|
||
{color: 'hsl(43, 83%, 64%)', label: Common.UIString('Response')}
|
||
],
|
||
[TimelineModel.TimelineIRModel.Phases.Scroll, {color: 'hsl(256, 67%, 70%)', label: Common.UIString('Scroll')}],
|
||
[TimelineModel.TimelineIRModel.Phases.Fling, {color: 'hsl(256, 67%, 70%)', label: Common.UIString('Fling')}],
|
||
[TimelineModel.TimelineIRModel.Phases.Drag, {color: 'hsl(256, 67%, 70%)', label: Common.UIString('Drag')}],
|
||
[
|
||
TimelineModel.TimelineIRModel.Phases.Animation,
|
||
{color: 'hsl(256, 67%, 70%)', label: Common.UIString('Animation')}
|
||
],
|
||
[
|
||
TimelineModel.TimelineIRModel.Phases.Uncategorized,
|
||
{color: 'hsl(0, 0%, 87%)', label: Common.UIString('Uncategorized')}
|
||
]
|
||
]);
|
||
Timeline.TimelineUIUtils._interactionPhaseStylesMap = map;
|
||
}
|
||
return map;
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.TimelineIRModel.Phases} phase
|
||
* @return {string}
|
||
*/
|
||
static interactionPhaseColor(phase) {
|
||
return Timeline.TimelineUIUtils._interactionPhaseStyles().get(phase).color;
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.TimelineIRModel.Phases} phase
|
||
* @return {string}
|
||
*/
|
||
static interactionPhaseLabel(phase) {
|
||
return Timeline.TimelineUIUtils._interactionPhaseStyles().get(phase).label;
|
||
}
|
||
|
||
/**
|
||
* @param {!Protocol.Runtime.CallFrame} frame
|
||
* @return {boolean}
|
||
*/
|
||
static isUserFrame(frame) {
|
||
return frame.scriptId !== '0' && !(frame.url && frame.url.startsWith('native '));
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.TimelineModel.NetworkRequest} request
|
||
* @return {!Timeline.TimelineUIUtils.NetworkCategory}
|
||
*/
|
||
static networkRequestCategory(request) {
|
||
const categories = Timeline.TimelineUIUtils.NetworkCategory;
|
||
switch (request.mimeType) {
|
||
case 'text/html':
|
||
return categories.HTML;
|
||
case 'application/javascript':
|
||
case 'application/x-javascript':
|
||
case 'text/javascript':
|
||
return categories.Script;
|
||
case 'text/css':
|
||
return categories.Style;
|
||
case 'audio/ogg':
|
||
case 'image/gif':
|
||
case 'image/jpeg':
|
||
case 'image/png':
|
||
case 'image/svg+xml':
|
||
case 'image/webp':
|
||
case 'image/x-icon':
|
||
case 'font/opentype':
|
||
case 'font/woff2':
|
||
case 'application/font-woff':
|
||
return categories.Media;
|
||
default:
|
||
return categories.Other;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!Timeline.TimelineUIUtils.NetworkCategory} category
|
||
* @return {string}
|
||
*/
|
||
static networkCategoryColor(category) {
|
||
const categories = Timeline.TimelineUIUtils.NetworkCategory;
|
||
switch (category) {
|
||
case categories.HTML:
|
||
return 'hsl(214, 67%, 66%)';
|
||
case categories.Script:
|
||
return 'hsl(43, 83%, 64%)';
|
||
case categories.Style:
|
||
return 'hsl(256, 67%, 70%)';
|
||
case categories.Media:
|
||
return 'hsl(109, 33%, 55%)';
|
||
default:
|
||
return 'hsl(0, 0%, 70%)';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {?SDK.Target} target
|
||
* @return {?string}
|
||
*/
|
||
static buildDetailsTextForTraceEvent(event, target) {
|
||
const recordType = TimelineModel.TimelineModel.RecordType;
|
||
let detailsText;
|
||
const eventData = event.args['data'];
|
||
switch (event.name) {
|
||
case recordType.GCEvent:
|
||
case recordType.MajorGC:
|
||
case recordType.MinorGC: {
|
||
const delta = event.args['usedHeapSizeBefore'] - event.args['usedHeapSizeAfter'];
|
||
detailsText = Common.UIString('%s collected', Number.bytesToString(delta));
|
||
break;
|
||
}
|
||
case recordType.FunctionCall:
|
||
if (eventData) {
|
||
detailsText =
|
||
linkifyLocationAsText(eventData['scriptId'], eventData['lineNumber'], eventData['columnNumber']);
|
||
}
|
||
break;
|
||
case recordType.JSFrame:
|
||
detailsText = Timeline.TimelineUIUtils.frameDisplayName(eventData);
|
||
break;
|
||
case recordType.EventDispatch:
|
||
detailsText = eventData ? eventData['type'] : null;
|
||
break;
|
||
case recordType.Paint: {
|
||
const width = Timeline.TimelineUIUtils.quadWidth(eventData.clip);
|
||
const height = Timeline.TimelineUIUtils.quadHeight(eventData.clip);
|
||
if (width && height)
|
||
detailsText = Common.UIString('%d\xa0\u00d7\xa0%d', width, height);
|
||
break;
|
||
}
|
||
case recordType.ParseHTML: {
|
||
const endLine = event.args['endData'] && event.args['endData']['endLine'];
|
||
const url = Bindings.displayNameForURL(event.args['beginData']['url']);
|
||
detailsText = Common.UIString(
|
||
'%s [%s\u2026%s]', url, event.args['beginData']['startLine'] + 1, endLine >= 0 ? endLine + 1 : '');
|
||
break;
|
||
}
|
||
case recordType.CompileModule:
|
||
detailsText = Bindings.displayNameForURL(event.args['fileName']);
|
||
break;
|
||
case recordType.CompileScript:
|
||
case recordType.EvaluateScript: {
|
||
const url = eventData && eventData['url'];
|
||
if (url)
|
||
detailsText = Bindings.displayNameForURL(url) + ':' + (eventData['lineNumber'] + 1);
|
||
break;
|
||
}
|
||
case recordType.ParseScriptOnBackground:
|
||
case recordType.XHRReadyStateChange:
|
||
case recordType.XHRLoad: {
|
||
const url = eventData['url'];
|
||
if (url)
|
||
detailsText = Bindings.displayNameForURL(url);
|
||
break;
|
||
}
|
||
case recordType.TimeStamp:
|
||
detailsText = eventData['message'];
|
||
break;
|
||
|
||
case recordType.WebSocketCreate:
|
||
case recordType.WebSocketSendHandshakeRequest:
|
||
case recordType.WebSocketReceiveHandshakeResponse:
|
||
case recordType.WebSocketDestroy:
|
||
case recordType.ResourceSendRequest:
|
||
case recordType.ResourceReceivedData:
|
||
case recordType.ResourceReceiveResponse:
|
||
case recordType.ResourceFinish:
|
||
case recordType.PaintImage:
|
||
case recordType.DecodeImage:
|
||
case recordType.ResizeImage:
|
||
case recordType.DecodeLazyPixelRef: {
|
||
const url = TimelineModel.TimelineData.forEvent(event).url;
|
||
if (url)
|
||
detailsText = Bindings.displayNameForURL(url);
|
||
break;
|
||
}
|
||
|
||
case recordType.EmbedderCallback:
|
||
detailsText = eventData['callbackName'];
|
||
break;
|
||
|
||
case recordType.Animation:
|
||
detailsText = eventData && eventData['name'];
|
||
break;
|
||
|
||
case recordType.GCIdleLazySweep:
|
||
detailsText = Common.UIString('idle sweep');
|
||
break;
|
||
|
||
case recordType.GCCompleteSweep:
|
||
detailsText = Common.UIString('complete sweep');
|
||
break;
|
||
|
||
case recordType.GCCollectGarbage:
|
||
detailsText = Common.UIString('collect');
|
||
break;
|
||
|
||
case recordType.AsyncTask:
|
||
detailsText = eventData ? eventData['name'] : null;
|
||
break;
|
||
|
||
default:
|
||
if (event.hasCategory(TimelineModel.TimelineModel.Category.Console))
|
||
detailsText = null;
|
||
else
|
||
detailsText = linkifyTopCallFrameAsText();
|
||
break;
|
||
}
|
||
|
||
return detailsText;
|
||
|
||
/**
|
||
* @param {string} scriptId
|
||
* @param {number} lineNumber
|
||
* @param {number} columnNumber
|
||
* @return {?string}
|
||
*/
|
||
function linkifyLocationAsText(scriptId, lineNumber, columnNumber) {
|
||
const debuggerModel = target ? target.model(SDK.DebuggerModel) : null;
|
||
if (!target || target.isDisposed() || !scriptId || !debuggerModel)
|
||
return null;
|
||
const rawLocation = debuggerModel.createRawLocationByScriptId(scriptId, lineNumber, columnNumber);
|
||
if (!rawLocation)
|
||
return null;
|
||
const uiLocation = Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(rawLocation);
|
||
return uiLocation ? uiLocation.linkText() : null;
|
||
}
|
||
|
||
/**
|
||
* @return {?string}
|
||
*/
|
||
function linkifyTopCallFrameAsText() {
|
||
const frame = TimelineModel.TimelineData.forEvent(event).topFrame();
|
||
if (!frame)
|
||
return null;
|
||
let text = linkifyLocationAsText(frame.scriptId, frame.lineNumber, frame.columnNumber);
|
||
if (!text) {
|
||
text = frame.url;
|
||
if (typeof frame.lineNumber === 'number')
|
||
text += ':' + (frame.lineNumber + 1);
|
||
}
|
||
return text;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {?SDK.Target} target
|
||
* @param {!Components.Linkifier} linkifier
|
||
* @return {?Node}
|
||
*/
|
||
static buildDetailsNodeForTraceEvent(event, target, linkifier) {
|
||
const recordType = TimelineModel.TimelineModel.RecordType;
|
||
let details = null;
|
||
let detailsText;
|
||
const eventData = event.args['data'];
|
||
switch (event.name) {
|
||
case recordType.GCEvent:
|
||
case recordType.MajorGC:
|
||
case recordType.MinorGC:
|
||
case recordType.EventDispatch:
|
||
case recordType.Paint:
|
||
case recordType.Animation:
|
||
case recordType.EmbedderCallback:
|
||
case recordType.ParseHTML:
|
||
case recordType.WebSocketCreate:
|
||
case recordType.WebSocketSendHandshakeRequest:
|
||
case recordType.WebSocketReceiveHandshakeResponse:
|
||
case recordType.WebSocketDestroy:
|
||
case recordType.GCIdleLazySweep:
|
||
case recordType.GCCompleteSweep:
|
||
case recordType.GCCollectGarbage:
|
||
detailsText = Timeline.TimelineUIUtils.buildDetailsTextForTraceEvent(event, target);
|
||
break;
|
||
case recordType.PaintImage:
|
||
case recordType.DecodeImage:
|
||
case recordType.ResizeImage:
|
||
case recordType.DecodeLazyPixelRef:
|
||
case recordType.XHRReadyStateChange:
|
||
case recordType.XHRLoad:
|
||
case recordType.ResourceSendRequest:
|
||
case recordType.ResourceReceivedData:
|
||
case recordType.ResourceReceiveResponse:
|
||
case recordType.ResourceFinish: {
|
||
const url = TimelineModel.TimelineData.forEvent(event).url;
|
||
if (url)
|
||
details = Components.Linkifier.linkifyURL(url);
|
||
break;
|
||
}
|
||
case recordType.FunctionCall:
|
||
case recordType.JSFrame:
|
||
details = createElement('span');
|
||
details.createTextChild(Timeline.TimelineUIUtils.frameDisplayName(eventData));
|
||
const location = linkifyLocation(
|
||
eventData['scriptId'], eventData['url'], eventData['lineNumber'], eventData['columnNumber']);
|
||
if (location) {
|
||
details.createTextChild(' @ ');
|
||
details.appendChild(location);
|
||
}
|
||
break;
|
||
case recordType.CompileModule:
|
||
details = linkifyLocation('', event.args['fileName'], 0, 0);
|
||
break;
|
||
case recordType.CompileScript:
|
||
case recordType.EvaluateScript: {
|
||
const url = eventData['url'];
|
||
if (url)
|
||
details = linkifyLocation('', url, eventData['lineNumber'], 0);
|
||
break;
|
||
}
|
||
case recordType.ParseScriptOnBackground: {
|
||
const url = eventData['url'];
|
||
if (url)
|
||
details = linkifyLocation('', url, 0, 0);
|
||
break;
|
||
}
|
||
default:
|
||
if (event.hasCategory(TimelineModel.TimelineModel.Category.Console))
|
||
detailsText = null;
|
||
else
|
||
details = linkifyTopCallFrame();
|
||
break;
|
||
}
|
||
|
||
if (!details && detailsText)
|
||
details = createTextNode(detailsText);
|
||
return details;
|
||
|
||
/**
|
||
* @param {string} scriptId
|
||
* @param {string} url
|
||
* @param {number} lineNumber
|
||
* @param {number=} columnNumber
|
||
* @return {?Element}
|
||
*/
|
||
function linkifyLocation(scriptId, url, lineNumber, columnNumber) {
|
||
return linkifier.linkifyScriptLocation(target, scriptId, url, lineNumber, columnNumber, 'timeline-details');
|
||
}
|
||
|
||
/**
|
||
* @return {?Element}
|
||
*/
|
||
function linkifyTopCallFrame() {
|
||
const frame = TimelineModel.TimelineData.forEvent(event).topFrame();
|
||
return frame ? linkifier.maybeLinkifyConsoleCallFrame(target, frame, 'timeline-details') : null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {!TimelineModel.TimelineModel} model
|
||
* @param {!Components.Linkifier} linkifier
|
||
* @param {!ProductRegistry.BadgePool} badgePool
|
||
* @param {boolean} detailed
|
||
* @return {!Promise<!DocumentFragment>}
|
||
*/
|
||
static async buildTraceEventDetails(event, model, linkifier, badgePool, detailed) {
|
||
const maybeTarget = model.targetByEvent(event);
|
||
/** @type {?Map<number, ?SDK.DOMNode>} */
|
||
let relatedNodesMap = null;
|
||
if (maybeTarget) {
|
||
const target = /** @type {!SDK.Target} */ (maybeTarget);
|
||
if (typeof event[Timeline.TimelineUIUtils._previewElementSymbol] === 'undefined') {
|
||
let previewElement = null;
|
||
const url = TimelineModel.TimelineData.forEvent(event).url;
|
||
if (url)
|
||
previewElement = await BrowserComponents.ImagePreview.build(target, url, false);
|
||
else if (TimelineModel.TimelineData.forEvent(event).picture)
|
||
previewElement = await Timeline.TimelineUIUtils.buildPicturePreviewContent(event, target);
|
||
event[Timeline.TimelineUIUtils._previewElementSymbol] = previewElement;
|
||
}
|
||
|
||
/** @type {!Set<number>} */
|
||
const nodeIdsToResolve = new Set();
|
||
const timelineData = TimelineModel.TimelineData.forEvent(event);
|
||
if (timelineData.backendNodeId)
|
||
nodeIdsToResolve.add(timelineData.backendNodeId);
|
||
const invalidationTrackingEvents = TimelineModel.InvalidationTracker.invalidationEventsFor(event);
|
||
if (invalidationTrackingEvents)
|
||
Timeline.TimelineUIUtils._collectInvalidationNodeIds(nodeIdsToResolve, invalidationTrackingEvents);
|
||
if (nodeIdsToResolve.size) {
|
||
const domModel = target.model(SDK.DOMModel);
|
||
if (domModel)
|
||
relatedNodesMap = await domModel.pushNodesByBackendIdsToFrontend(nodeIdsToResolve);
|
||
}
|
||
}
|
||
|
||
const recordTypes = TimelineModel.TimelineModel.RecordType;
|
||
// This message may vary per event.name;
|
||
let relatedNodeLabel;
|
||
|
||
const contentHelper = new Timeline.TimelineDetailsContentHelper(model.targetByEvent(event), linkifier);
|
||
contentHelper.addSection(
|
||
Timeline.TimelineUIUtils.eventTitle(event), Timeline.TimelineUIUtils.eventStyle(event).category.color);
|
||
|
||
const eventData = event.args['data'];
|
||
const timelineData = TimelineModel.TimelineData.forEvent(event);
|
||
const initiator = timelineData.initiator();
|
||
let url = null;
|
||
|
||
if (timelineData.warning)
|
||
contentHelper.appendWarningRow(event);
|
||
if (event.name === recordTypes.JSFrame && eventData['deoptReason'])
|
||
contentHelper.appendWarningRow(event, TimelineModel.TimelineModel.WarningType.V8Deopt);
|
||
|
||
if (detailed) {
|
||
contentHelper.appendTextRow(Common.UIString('Total Time'), Number.millisToString(event.duration || 0, true));
|
||
contentHelper.appendTextRow(Common.UIString('Self Time'), Number.millisToString(event.selfTime, true));
|
||
}
|
||
|
||
if (model.isGenericTrace()) {
|
||
for (const key in event.args) {
|
||
try {
|
||
contentHelper.appendTextRow(key, JSON.stringify(event.args[key]));
|
||
} catch (e) {
|
||
contentHelper.appendTextRow(key, `<${typeof event.args[key]}>`);
|
||
}
|
||
}
|
||
return contentHelper.fragment;
|
||
}
|
||
|
||
switch (event.name) {
|
||
case recordTypes.GCEvent:
|
||
case recordTypes.MajorGC:
|
||
case recordTypes.MinorGC:
|
||
const delta = event.args['usedHeapSizeBefore'] - event.args['usedHeapSizeAfter'];
|
||
contentHelper.appendTextRow(Common.UIString('Collected'), Number.bytesToString(delta));
|
||
break;
|
||
case recordTypes.JSFrame:
|
||
case recordTypes.FunctionCall:
|
||
const detailsNode =
|
||
Timeline.TimelineUIUtils.buildDetailsNodeForTraceEvent(event, model.targetByEvent(event), linkifier);
|
||
if (detailsNode)
|
||
contentHelper.appendElementRow(Common.UIString('Function'), detailsNode);
|
||
break;
|
||
case recordTypes.TimerFire:
|
||
case recordTypes.TimerInstall:
|
||
case recordTypes.TimerRemove:
|
||
contentHelper.appendTextRow(Common.UIString('Timer ID'), eventData['timerId']);
|
||
if (event.name === recordTypes.TimerInstall) {
|
||
contentHelper.appendTextRow(Common.UIString('Timeout'), Number.millisToString(eventData['timeout']));
|
||
contentHelper.appendTextRow(Common.UIString('Repeats'), !eventData['singleShot']);
|
||
}
|
||
break;
|
||
case recordTypes.FireAnimationFrame:
|
||
contentHelper.appendTextRow(Common.UIString('Callback ID'), eventData['id']);
|
||
break;
|
||
case recordTypes.ResourceSendRequest:
|
||
case recordTypes.ResourceReceiveResponse:
|
||
case recordTypes.ResourceReceivedData:
|
||
case recordTypes.ResourceFinish:
|
||
url = timelineData.url;
|
||
if (url)
|
||
contentHelper.appendElementRow(Common.UIString('Resource'), Components.Linkifier.linkifyURL(url));
|
||
if (eventData['requestMethod'])
|
||
contentHelper.appendTextRow(Common.UIString('Request Method'), eventData['requestMethod']);
|
||
if (typeof eventData['statusCode'] === 'number')
|
||
contentHelper.appendTextRow(Common.UIString('Status Code'), eventData['statusCode']);
|
||
if (eventData['mimeType'])
|
||
contentHelper.appendTextRow(Common.UIString('MIME Type'), eventData['mimeType']);
|
||
if ('priority' in eventData) {
|
||
const priority = PerfUI.uiLabelForNetworkPriority(eventData['priority']);
|
||
contentHelper.appendTextRow(Common.UIString('Priority'), priority);
|
||
}
|
||
if (eventData['encodedDataLength']) {
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Encoded Data'), Common.UIString('%d Bytes', eventData['encodedDataLength']));
|
||
}
|
||
if (eventData['decodedBodyLength']) {
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Decoded Body'), Common.UIString('%d Bytes', eventData['decodedBodyLength']));
|
||
}
|
||
break;
|
||
case recordTypes.CompileModule:
|
||
contentHelper.appendLocationRow(Common.UIString('Module'), event.args['fileName'], 0);
|
||
break;
|
||
case recordTypes.CompileScript:
|
||
url = eventData && eventData['url'];
|
||
if (url) {
|
||
contentHelper.appendLocationRow(
|
||
Common.UIString('Script'), url, eventData['lineNumber'], eventData['columnNumber']);
|
||
}
|
||
contentHelper.appendTextRow(Common.UIString('Streamed'), Common.UIString('%s', eventData['streamed']));
|
||
const cacheProduceOptions = eventData && eventData['cacheProduceOptions'];
|
||
if (cacheProduceOptions) {
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Cache Produce Options'), Common.UIString('%s', cacheProduceOptions));
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Produced Cache Size'), Common.UIString('%d', eventData['producedCacheSize']));
|
||
}
|
||
const cacheConsumeOptions = eventData && eventData['cacheConsumeOptions'];
|
||
if (cacheConsumeOptions) {
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Cache Consume Options'), Common.UIString('%s', cacheConsumeOptions));
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Consumed Cache Size'), Common.UIString('%d', eventData['consumedCacheSize']));
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Cache Rejected'), Common.UIString('%s', eventData['cacheRejected']));
|
||
}
|
||
break;
|
||
case recordTypes.EvaluateScript:
|
||
url = eventData && eventData['url'];
|
||
if (url) {
|
||
contentHelper.appendLocationRow(
|
||
Common.UIString('Script'), url, eventData['lineNumber'], eventData['columnNumber']);
|
||
}
|
||
break;
|
||
case recordTypes.Paint:
|
||
const clip = eventData['clip'];
|
||
contentHelper.appendTextRow(Common.UIString('Location'), Common.UIString('(%d, %d)', clip[0], clip[1]));
|
||
const clipWidth = Timeline.TimelineUIUtils.quadWidth(clip);
|
||
const clipHeight = Timeline.TimelineUIUtils.quadHeight(clip);
|
||
contentHelper.appendTextRow(Common.UIString('Dimensions'), Common.UIString('%d × %d', clipWidth, clipHeight));
|
||
// Fall-through intended.
|
||
|
||
case recordTypes.PaintSetup:
|
||
case recordTypes.Rasterize:
|
||
case recordTypes.ScrollLayer:
|
||
relatedNodeLabel = Common.UIString('Layer Root');
|
||
break;
|
||
case recordTypes.PaintImage:
|
||
case recordTypes.DecodeLazyPixelRef:
|
||
case recordTypes.DecodeImage:
|
||
case recordTypes.ResizeImage:
|
||
case recordTypes.DrawLazyPixelRef:
|
||
relatedNodeLabel = Common.UIString('Owner Element');
|
||
url = timelineData.url;
|
||
if (url)
|
||
contentHelper.appendElementRow(Common.UIString('Image URL'), Components.Linkifier.linkifyURL(url));
|
||
break;
|
||
case recordTypes.ParseAuthorStyleSheet:
|
||
url = eventData['styleSheetUrl'];
|
||
if (url)
|
||
contentHelper.appendElementRow(Common.UIString('Stylesheet URL'), Components.Linkifier.linkifyURL(url));
|
||
break;
|
||
case recordTypes.UpdateLayoutTree: // We don't want to see default details.
|
||
case recordTypes.RecalculateStyles:
|
||
contentHelper.appendTextRow(Common.UIString('Elements Affected'), event.args['elementCount']);
|
||
break;
|
||
case recordTypes.Layout:
|
||
const beginData = event.args['beginData'];
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Nodes That Need Layout'),
|
||
Common.UIString('%s of %s', beginData['dirtyObjects'], beginData['totalObjects']));
|
||
relatedNodeLabel = Common.UIString('Layout root');
|
||
break;
|
||
case recordTypes.ConsoleTime:
|
||
contentHelper.appendTextRow(Common.UIString('Message'), event.name);
|
||
break;
|
||
case recordTypes.WebSocketCreate:
|
||
case recordTypes.WebSocketSendHandshakeRequest:
|
||
case recordTypes.WebSocketReceiveHandshakeResponse:
|
||
case recordTypes.WebSocketDestroy:
|
||
const initiatorData = initiator ? initiator.args['data'] : eventData;
|
||
if (typeof initiatorData['webSocketURL'] !== 'undefined')
|
||
contentHelper.appendTextRow(Common.UIString('URL'), initiatorData['webSocketURL']);
|
||
if (typeof initiatorData['webSocketProtocol'] !== 'undefined')
|
||
contentHelper.appendTextRow(Common.UIString('WebSocket Protocol'), initiatorData['webSocketProtocol']);
|
||
if (typeof eventData['message'] !== 'undefined')
|
||
contentHelper.appendTextRow(Common.UIString('Message'), eventData['message']);
|
||
break;
|
||
case recordTypes.EmbedderCallback:
|
||
contentHelper.appendTextRow(Common.UIString('Callback Function'), eventData['callbackName']);
|
||
break;
|
||
case recordTypes.Animation:
|
||
if (event.phase === SDK.TracingModel.Phase.NestableAsyncInstant)
|
||
contentHelper.appendTextRow(Common.UIString('State'), eventData['state']);
|
||
break;
|
||
case recordTypes.ParseHTML: {
|
||
const beginData = event.args['beginData'];
|
||
const startLine = beginData['startLine'] - 1;
|
||
const endLine = event.args['endData'] ? event.args['endData']['endLine'] - 1 : undefined;
|
||
url = beginData['url'];
|
||
if (url)
|
||
contentHelper.appendLocationRange(Common.UIString('Range'), url, startLine, endLine);
|
||
break;
|
||
}
|
||
|
||
case recordTypes.FireIdleCallback:
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Allotted Time'), Number.millisToString(eventData['allottedMilliseconds']));
|
||
contentHelper.appendTextRow(Common.UIString('Invoked by Timeout'), eventData['timedOut']);
|
||
// Fall-through intended.
|
||
|
||
case recordTypes.RequestIdleCallback:
|
||
case recordTypes.CancelIdleCallback:
|
||
contentHelper.appendTextRow(Common.UIString('Callback ID'), eventData['id']);
|
||
break;
|
||
case recordTypes.EventDispatch:
|
||
contentHelper.appendTextRow(Common.UIString('Type'), eventData['type']);
|
||
break;
|
||
|
||
default: {
|
||
const detailsNode =
|
||
Timeline.TimelineUIUtils.buildDetailsNodeForTraceEvent(event, model.targetByEvent(event), linkifier);
|
||
if (detailsNode)
|
||
contentHelper.appendElementRow(Common.UIString('Details'), detailsNode);
|
||
break;
|
||
}
|
||
}
|
||
|
||
Timeline.TimelineUIUtils._maybeAppendProductToDetails(
|
||
contentHelper, badgePool, url || eventData && eventData['url']);
|
||
|
||
if (timelineData.timeWaitingForMainThread) {
|
||
contentHelper.appendTextRow(
|
||
Common.UIString('Time Waiting for Main Thread'),
|
||
Number.millisToString(timelineData.timeWaitingForMainThread, true));
|
||
}
|
||
|
||
const relatedNode = relatedNodesMap && relatedNodesMap.get(timelineData.backendNodeId);
|
||
if (relatedNode) {
|
||
const nodeSpan = await Common.Linkifier.linkify(relatedNode);
|
||
contentHelper.appendElementRow(relatedNodeLabel || Common.UIString('Related Node'), nodeSpan);
|
||
}
|
||
|
||
if (event[Timeline.TimelineUIUtils._previewElementSymbol]) {
|
||
contentHelper.addSection(Common.UIString('Preview'));
|
||
contentHelper.appendElementRow('', event[Timeline.TimelineUIUtils._previewElementSymbol]);
|
||
}
|
||
|
||
if (initiator || timelineData.stackTraceForSelfOrInitiator() ||
|
||
TimelineModel.InvalidationTracker.invalidationEventsFor(event))
|
||
Timeline.TimelineUIUtils._generateCauses(event, model.targetByEvent(event), relatedNodesMap, contentHelper);
|
||
|
||
const stats = {};
|
||
const showPieChart = detailed && Timeline.TimelineUIUtils._aggregatedStatsForTraceEvent(stats, model, event);
|
||
if (showPieChart) {
|
||
contentHelper.addSection(Common.UIString('Aggregated Time'));
|
||
const pieChart = Timeline.TimelineUIUtils.generatePieChart(
|
||
stats, Timeline.TimelineUIUtils.eventStyle(event).category, event.selfTime);
|
||
contentHelper.appendElementRow('', pieChart);
|
||
}
|
||
|
||
return contentHelper.fragment;
|
||
}
|
||
|
||
/**
|
||
* @param {!Timeline.TimelineDetailsContentHelper} contentHelper
|
||
* @param {!ProductRegistry.BadgePool} badgePool
|
||
* @param {?string} url
|
||
*/
|
||
static _maybeAppendProductToDetails(contentHelper, badgePool, url) {
|
||
const parsedURL = url ? url.asParsedURL() : null;
|
||
if (parsedURL)
|
||
contentHelper.appendElementRow('', badgePool.badgeForURL(parsedURL));
|
||
}
|
||
|
||
/**
|
||
* @param {!Array<!SDK.TracingModel.Event>} events
|
||
* @param {number} startTime
|
||
* @param {number} endTime
|
||
* @return {!Object<string, number>}
|
||
*/
|
||
static statsForTimeRange(events, startTime, endTime) {
|
||
if (!events.length)
|
||
return {'idle': endTime - startTime};
|
||
const symbol = Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol;
|
||
Timeline.TimelineUIUtils._buildRangeStatsCacheIfNeeded(events);
|
||
|
||
const before = findCachedStatsAfterTime(startTime);
|
||
const statsBefore =
|
||
subtractStats(before.stats, Timeline.TimelineUIUtils._slowStatsForTimeRange(events, startTime, before.time));
|
||
|
||
const after = findCachedStatsAfterTime(endTime);
|
||
const statsAfter =
|
||
subtractStats(after.stats, Timeline.TimelineUIUtils._slowStatsForTimeRange(events, endTime, after.time));
|
||
|
||
const aggregatedStats = subtractStats(statsAfter, statsBefore);
|
||
const aggregatedTotal = Object.values(aggregatedStats).reduce((a, b) => a + b, 0);
|
||
aggregatedStats['idle'] = Math.max(0, endTime - startTime - aggregatedTotal);
|
||
return aggregatedStats;
|
||
|
||
/**
|
||
* @param {number} atTime
|
||
* @return {!{time: number, stats: !Object<string, number>}}
|
||
*/
|
||
function findCachedStatsAfterTime(atTime) {
|
||
let index = events.lowerBound(atTime, (time, event) => time - (event.endTime || event.startTime));
|
||
while (index < events.length && !events[index][symbol])
|
||
index++;
|
||
if (index === events.length) {
|
||
const lastEvent = events.peekLast();
|
||
return {time: lastEvent.endTime || lastEvent.startTime, stats: events[symbol]};
|
||
}
|
||
const event = events[index];
|
||
return {time: event.endTime || event.startTime, stats: event[symbol]};
|
||
}
|
||
|
||
/**
|
||
* @param {!Object<string, number>} a
|
||
* @param {!Object<string, number>} b
|
||
* @return {!Object<string, number>}
|
||
*/
|
||
function subtractStats(a, b) {
|
||
const result = Object.assign({}, a);
|
||
for (const key in b)
|
||
result[key] -= b[key];
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!Array<!SDK.TracingModel.Event>} events
|
||
* @param {number} startTime
|
||
* @param {number} endTime
|
||
*/
|
||
static _slowStatsForTimeRange(events, startTime, endTime) {
|
||
/** @type {!Object<string, number>} */
|
||
const stats = {};
|
||
const ownTimes = [];
|
||
|
||
TimelineModel.TimelineModel.forEachEvent(
|
||
events, onStartEvent, onEndEvent, undefined, startTime, endTime, Timeline.TimelineUIUtils._filterForStats());
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} e
|
||
*/
|
||
function onStartEvent(e) {
|
||
const duration = Math.min(e.endTime, endTime) - Math.max(e.startTime, startTime);
|
||
if (ownTimes.length)
|
||
ownTimes[ownTimes.length - 1] -= duration;
|
||
ownTimes.push(duration);
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} e
|
||
*/
|
||
function onEndEvent(e) {
|
||
const category = Timeline.TimelineUIUtils.eventStyle(e).category.name;
|
||
stats[category] = (stats[category] || 0) + ownTimes.pop();
|
||
}
|
||
return stats;
|
||
}
|
||
|
||
/**
|
||
* @return {function(!SDK.TracingModel.Event):boolean}
|
||
*/
|
||
static _filterForStats() {
|
||
const visibleEventsFilter = Timeline.TimelineUIUtils.visibleEventsFilter();
|
||
return event => visibleEventsFilter.accept(event) || SDK.TracingModel.isTopLevelEvent(event);
|
||
}
|
||
|
||
/**
|
||
* @param {!Array<!SDK.TracingModel.Event>} events
|
||
*/
|
||
static _buildRangeStatsCacheIfNeeded(events) {
|
||
if (events[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol])
|
||
return;
|
||
|
||
const aggregatedStats = {};
|
||
const ownTimes = [];
|
||
TimelineModel.TimelineModel.forEachEvent(
|
||
events, onStartEvent, onEndEvent, undefined, undefined, undefined, Timeline.TimelineUIUtils._filterForStats());
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} e
|
||
*/
|
||
function onStartEvent(e) {
|
||
if (ownTimes.length)
|
||
ownTimes[ownTimes.length - 1] -= e.duration;
|
||
ownTimes.push(e.duration);
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} e
|
||
*/
|
||
function onEndEvent(e) {
|
||
const category = Timeline.TimelineUIUtils.eventStyle(e).category.name;
|
||
aggregatedStats[category] = (aggregatedStats[category] || 0) + ownTimes.pop();
|
||
if (!ownTimes.length)
|
||
e[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol] = Object.assign({}, aggregatedStats);
|
||
}
|
||
|
||
const obj = /** @type {!Object} */ (events);
|
||
obj[Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol] = Object.assign({}, aggregatedStats);
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.TimelineModel.NetworkRequest} request
|
||
* @param {!TimelineModel.TimelineModel} model
|
||
* @param {!Components.Linkifier} linkifier
|
||
* @param {!ProductRegistry.BadgePool} badgePool
|
||
* @return {!Promise<!DocumentFragment>}
|
||
*/
|
||
static async buildNetworkRequestDetails(request, model, linkifier, badgePool) {
|
||
const target = model.targetByEvent(request.children[0]);
|
||
const contentHelper = new Timeline.TimelineDetailsContentHelper(target, linkifier);
|
||
const category = Timeline.TimelineUIUtils.networkRequestCategory(request);
|
||
const color = Timeline.TimelineUIUtils.networkCategoryColor(category);
|
||
contentHelper.addSection(Common.UIString('Network request'), color);
|
||
|
||
const duration = request.endTime - (request.startTime || -Infinity);
|
||
if (request.url)
|
||
contentHelper.appendElementRow(Common.UIString('URL'), Components.Linkifier.linkifyURL(request.url));
|
||
Timeline.TimelineUIUtils._maybeAppendProductToDetails(contentHelper, badgePool, request.url);
|
||
if (isFinite(duration))
|
||
contentHelper.appendTextRow(Common.UIString('Duration'), Number.millisToString(duration, true));
|
||
if (request.requestMethod)
|
||
contentHelper.appendTextRow(Common.UIString('Request Method'), request.requestMethod);
|
||
if (typeof request.priority === 'string') {
|
||
const priority =
|
||
PerfUI.uiLabelForNetworkPriority(/** @type {!Protocol.Network.ResourcePriority} */ (request.priority));
|
||
contentHelper.appendTextRow(Common.UIString('Priority'), priority);
|
||
}
|
||
if (request.mimeType)
|
||
contentHelper.appendTextRow(Common.UIString('Mime Type'), request.mimeType);
|
||
let lengthText = '';
|
||
if (request.fromCache)
|
||
lengthText += Common.UIString(' (from cache)');
|
||
if (request.fromServiceWorker)
|
||
lengthText += Common.UIString(' (from service worker)');
|
||
if (request.encodedDataLength || !lengthText)
|
||
lengthText = `${Number.bytesToString(request.encodedDataLength)}${lengthText}`;
|
||
contentHelper.appendTextRow(Common.UIString('Encoded Data'), lengthText);
|
||
if (request.decodedBodyLength)
|
||
contentHelper.appendTextRow(Common.UIString('Decoded Body'), Number.bytesToString(request.decodedBodyLength));
|
||
const title = Common.UIString('Initiator');
|
||
const sendRequest = request.children[0];
|
||
const topFrame = TimelineModel.TimelineData.forEvent(sendRequest).topFrame();
|
||
if (topFrame) {
|
||
const link = linkifier.maybeLinkifyConsoleCallFrame(target, topFrame);
|
||
if (link)
|
||
contentHelper.appendElementRow(title, link);
|
||
} else {
|
||
const initiator = TimelineModel.TimelineData.forEvent(sendRequest).initiator();
|
||
if (initiator) {
|
||
const initiatorURL = TimelineModel.TimelineData.forEvent(initiator).url;
|
||
if (initiatorURL) {
|
||
const link = linkifier.maybeLinkifyScriptLocation(target, null, initiatorURL, 0);
|
||
if (link)
|
||
contentHelper.appendElementRow(title, link);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!request.previewElement && request.url && target)
|
||
request.previewElement = await BrowserComponents.ImagePreview.build(target, request.url, false);
|
||
if (request.previewElement)
|
||
contentHelper.appendElementRow(Common.UIString('Preview'), request.previewElement);
|
||
return contentHelper.fragment;
|
||
}
|
||
|
||
/**
|
||
* @param {!Array<!Protocol.Runtime.CallFrame>} callFrames
|
||
* @return {!Protocol.Runtime.StackTrace}
|
||
*/
|
||
static _stackTraceFromCallFrames(callFrames) {
|
||
return /** @type {!Protocol.Runtime.StackTrace} */ ({callFrames: callFrames});
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {?SDK.Target} target
|
||
* @param {?Map<number, ?SDK.DOMNode>} relatedNodesMap
|
||
* @param {!Timeline.TimelineDetailsContentHelper} contentHelper
|
||
*/
|
||
static _generateCauses(event, target, relatedNodesMap, contentHelper) {
|
||
const recordTypes = TimelineModel.TimelineModel.RecordType;
|
||
|
||
let callSiteStackLabel;
|
||
let stackLabel;
|
||
|
||
switch (event.name) {
|
||
case recordTypes.TimerFire:
|
||
callSiteStackLabel = Common.UIString('Timer Installed');
|
||
break;
|
||
case recordTypes.FireAnimationFrame:
|
||
callSiteStackLabel = Common.UIString('Animation Frame Requested');
|
||
break;
|
||
case recordTypes.FireIdleCallback:
|
||
callSiteStackLabel = Common.UIString('Idle Callback Requested');
|
||
break;
|
||
case recordTypes.UpdateLayoutTree:
|
||
case recordTypes.RecalculateStyles:
|
||
stackLabel = Common.UIString('Recalculation Forced');
|
||
break;
|
||
case recordTypes.Layout:
|
||
callSiteStackLabel = Common.UIString('First Layout Invalidation');
|
||
stackLabel = Common.UIString('Layout Forced');
|
||
break;
|
||
}
|
||
|
||
const timelineData = TimelineModel.TimelineData.forEvent(event);
|
||
// Direct cause.
|
||
if (timelineData.stackTrace && timelineData.stackTrace.length) {
|
||
contentHelper.addSection(Common.UIString('Call Stacks'));
|
||
contentHelper.appendStackTrace(
|
||
stackLabel || Common.UIString('Stack Trace'),
|
||
Timeline.TimelineUIUtils._stackTraceFromCallFrames(timelineData.stackTrace));
|
||
}
|
||
|
||
const initiator = TimelineModel.TimelineData.forEvent(event).initiator();
|
||
// Indirect causes.
|
||
if (TimelineModel.InvalidationTracker.invalidationEventsFor(event) && target) {
|
||
// Full invalidation tracking (experimental).
|
||
contentHelper.addSection(Common.UIString('Invalidations'));
|
||
Timeline.TimelineUIUtils._generateInvalidations(event, target, relatedNodesMap, contentHelper);
|
||
} else if (initiator) { // Partial invalidation tracking.
|
||
const delay = event.startTime - initiator.startTime;
|
||
contentHelper.appendTextRow(Common.UIString('Pending for'), Number.preciseMillisToString(delay, 1));
|
||
|
||
const link = createElementWithClass('span', 'devtools-link');
|
||
link.textContent = Common.UIString('reveal');
|
||
link.addEventListener('click', () => {
|
||
Timeline.TimelinePanel.instance().select(
|
||
Timeline.TimelineSelection.fromTraceEvent(/** @type {!SDK.TracingModel.Event} */ (initiator)));
|
||
});
|
||
contentHelper.appendElementRow(Common.UIString('Initiator'), link);
|
||
|
||
const initiatorStackTrace = TimelineModel.TimelineData.forEvent(initiator).stackTrace;
|
||
if (initiatorStackTrace) {
|
||
contentHelper.appendStackTrace(
|
||
callSiteStackLabel || Common.UIString('First Invalidated'),
|
||
Timeline.TimelineUIUtils._stackTraceFromCallFrames(initiatorStackTrace));
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {!SDK.Target} target
|
||
* @param {?Map<number, ?SDK.DOMNode>} relatedNodesMap
|
||
* @param {!Timeline.TimelineDetailsContentHelper} contentHelper
|
||
*/
|
||
static _generateInvalidations(event, target, relatedNodesMap, contentHelper) {
|
||
const invalidationTrackingEvents = TimelineModel.InvalidationTracker.invalidationEventsFor(event);
|
||
const invalidations = {};
|
||
invalidationTrackingEvents.forEach(function(invalidation) {
|
||
if (!invalidations[invalidation.type])
|
||
invalidations[invalidation.type] = [invalidation];
|
||
else
|
||
invalidations[invalidation.type].push(invalidation);
|
||
});
|
||
|
||
Object.keys(invalidations).forEach(function(type) {
|
||
Timeline.TimelineUIUtils._generateInvalidationsForType(
|
||
type, target, invalidations[type], relatedNodesMap, contentHelper);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param {string} type
|
||
* @param {!SDK.Target} target
|
||
* @param {!Array.<!TimelineModel.InvalidationTrackingEvent>} invalidations
|
||
* @param {?Map<number, ?SDK.DOMNode>} relatedNodesMap
|
||
* @param {!Timeline.TimelineDetailsContentHelper} contentHelper
|
||
*/
|
||
static _generateInvalidationsForType(type, target, invalidations, relatedNodesMap, contentHelper) {
|
||
let title;
|
||
switch (type) {
|
||
case TimelineModel.TimelineModel.RecordType.StyleRecalcInvalidationTracking:
|
||
title = Common.UIString('Style Invalidations');
|
||
break;
|
||
case TimelineModel.TimelineModel.RecordType.LayoutInvalidationTracking:
|
||
title = Common.UIString('Layout Invalidations');
|
||
break;
|
||
default:
|
||
title = Common.UIString('Other Invalidations');
|
||
break;
|
||
}
|
||
|
||
const invalidationsTreeOutline = new UI.TreeOutlineInShadow();
|
||
invalidationsTreeOutline.registerRequiredCSS('timeline/invalidationsTree.css');
|
||
invalidationsTreeOutline.element.classList.add('invalidations-tree');
|
||
|
||
const invalidationGroups = groupInvalidationsByCause(invalidations);
|
||
invalidationGroups.forEach(function(group) {
|
||
const groupElement =
|
||
new Timeline.TimelineUIUtils.InvalidationsGroupElement(target, relatedNodesMap, contentHelper, group);
|
||
invalidationsTreeOutline.appendChild(groupElement);
|
||
});
|
||
contentHelper.appendElementRow(title, invalidationsTreeOutline.element, false, true);
|
||
|
||
/**
|
||
* @param {!Array<!TimelineModel.InvalidationTrackingEvent>} invalidations
|
||
* @return {!Array<!Array<!TimelineModel.InvalidationTrackingEvent>>}
|
||
*/
|
||
function groupInvalidationsByCause(invalidations) {
|
||
/** @type {!Map<string, !Array<!TimelineModel.InvalidationTrackingEvent>>} */
|
||
const causeToInvalidationMap = new Map();
|
||
for (let index = 0; index < invalidations.length; index++) {
|
||
const invalidation = invalidations[index];
|
||
let causeKey = '';
|
||
if (invalidation.cause.reason)
|
||
causeKey += invalidation.cause.reason + '.';
|
||
if (invalidation.cause.stackTrace) {
|
||
invalidation.cause.stackTrace.forEach(function(stackFrame) {
|
||
causeKey += stackFrame['functionName'] + '.';
|
||
causeKey += stackFrame['scriptId'] + '.';
|
||
causeKey += stackFrame['url'] + '.';
|
||
causeKey += stackFrame['lineNumber'] + '.';
|
||
causeKey += stackFrame['columnNumber'] + '.';
|
||
});
|
||
}
|
||
|
||
if (causeToInvalidationMap.has(causeKey))
|
||
causeToInvalidationMap.get(causeKey).push(invalidation);
|
||
else
|
||
causeToInvalidationMap.set(causeKey, [invalidation]);
|
||
}
|
||
return causeToInvalidationMap.valuesArray();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!Set<number>} nodeIds
|
||
* @param {!Array<!TimelineModel.InvalidationTrackingEvent>} invalidations
|
||
*/
|
||
static _collectInvalidationNodeIds(nodeIds, invalidations) {
|
||
nodeIds.addAll(invalidations.map(invalidation => invalidation.nodeId).filter(id => id));
|
||
}
|
||
|
||
/**
|
||
* @param {!Object} total
|
||
* @param {!TimelineModel.TimelineModel} model
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @return {boolean}
|
||
*/
|
||
static _aggregatedStatsForTraceEvent(total, model, event) {
|
||
const events = model.inspectedTargetEvents();
|
||
/**
|
||
* @param {number} startTime
|
||
* @param {!SDK.TracingModel.Event} e
|
||
* @return {number}
|
||
*/
|
||
function eventComparator(startTime, e) {
|
||
return startTime - e.startTime;
|
||
}
|
||
const index = events.binaryIndexOf(event.startTime, eventComparator);
|
||
// Not a main thread event?
|
||
if (index < 0)
|
||
return false;
|
||
let hasChildren = false;
|
||
const endTime = event.endTime;
|
||
if (endTime) {
|
||
for (let i = index; i < events.length; i++) {
|
||
const nextEvent = events[i];
|
||
if (nextEvent.startTime >= endTime)
|
||
break;
|
||
if (!nextEvent.selfTime)
|
||
continue;
|
||
if (nextEvent.thread !== event.thread)
|
||
continue;
|
||
if (i > index)
|
||
hasChildren = true;
|
||
const categoryName = Timeline.TimelineUIUtils.eventStyle(nextEvent).category.name;
|
||
total[categoryName] = (total[categoryName] || 0) + nextEvent.selfTime;
|
||
}
|
||
}
|
||
if (SDK.TracingModel.isAsyncPhase(event.phase)) {
|
||
if (event.endTime) {
|
||
let aggregatedTotal = 0;
|
||
for (const categoryName in total)
|
||
aggregatedTotal += total[categoryName];
|
||
total['idle'] = Math.max(0, event.endTime - event.startTime - aggregatedTotal);
|
||
}
|
||
return false;
|
||
}
|
||
return hasChildren;
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {!SDK.Target} target
|
||
* @return {!Promise<?Element>}
|
||
*/
|
||
static async buildPicturePreviewContent(event, target) {
|
||
const snapshotWithRect = await new TimelineModel.LayerPaintEvent(event, target).snapshotPromise();
|
||
if (!snapshotWithRect)
|
||
return null;
|
||
const imageURLPromise = snapshotWithRect.snapshot.replay();
|
||
snapshotWithRect.snapshot.release();
|
||
const imageURL = await imageURLPromise;
|
||
if (!imageURL)
|
||
return null;
|
||
const container = createElement('div');
|
||
UI.appendStyle(container, 'browser_components/imagePreview.css');
|
||
container.classList.add('image-preview-container', 'vbox', 'link');
|
||
const img = container.createChild('img');
|
||
img.src = imageURL;
|
||
const paintProfilerButton = container.createChild('a');
|
||
paintProfilerButton.textContent = Common.UIString('Paint Profiler');
|
||
container.addEventListener(
|
||
'click', () => Timeline.TimelinePanel.instance().select(Timeline.TimelineSelection.fromTraceEvent(event)),
|
||
false);
|
||
return container;
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {number} zeroTime
|
||
* @return {!Element}
|
||
*/
|
||
static createEventDivider(event, zeroTime) {
|
||
const eventDivider = createElementWithClass('div', 'resources-event-divider');
|
||
const startTime = Number.millisToString(event.startTime - zeroTime);
|
||
eventDivider.title = Common.UIString('%s at %s', Timeline.TimelineUIUtils.eventTitle(event), startTime);
|
||
const style = Timeline.TimelineUIUtils.markerStyleForEvent(event);
|
||
if (style.tall)
|
||
eventDivider.style.backgroundColor = style.color;
|
||
return eventDivider;
|
||
}
|
||
|
||
/**
|
||
* @return {!Array.<string>}
|
||
*/
|
||
static _visibleTypes() {
|
||
const eventStyles = Timeline.TimelineUIUtils._initEventStyles();
|
||
const result = [];
|
||
for (const name in eventStyles) {
|
||
if (!eventStyles[name].hidden)
|
||
result.push(name);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @return {!TimelineModel.TimelineModelFilter}
|
||
*/
|
||
static visibleEventsFilter() {
|
||
return new TimelineModel.TimelineVisibleEventsFilter(Timeline.TimelineUIUtils._visibleTypes());
|
||
}
|
||
|
||
/**
|
||
* @return {!TimelineModel.TimelineModelFilter}
|
||
*/
|
||
static paintEventsFilter() {
|
||
const recordTypes = TimelineModel.TimelineModel.RecordType;
|
||
return new TimelineModel.TimelineInvisibleEventsFilter(
|
||
!Runtime.experiments.isEnabled('timelinePaintTimingMarkers') ?
|
||
[recordTypes.MarkFCP, recordTypes.MarkFMP, recordTypes.MarkFMPCandidate] :
|
||
[]);
|
||
}
|
||
|
||
/**
|
||
* @return {!Object.<string, !Timeline.TimelineCategory>}
|
||
*/
|
||
static categories() {
|
||
if (Timeline.TimelineUIUtils._categories)
|
||
return Timeline.TimelineUIUtils._categories;
|
||
Timeline.TimelineUIUtils._categories = {
|
||
loading: new Timeline.TimelineCategory(
|
||
'loading', Common.UIString('Loading'), true, 'hsl(214, 67%, 74%)', 'hsl(214, 67%, 66%)'),
|
||
scripting: new Timeline.TimelineCategory(
|
||
'scripting', Common.UIString('Scripting'), true, 'hsl(43, 83%, 72%)', 'hsl(43, 83%, 64%) '),
|
||
rendering: new Timeline.TimelineCategory(
|
||
'rendering', Common.UIString('Rendering'), true, 'hsl(256, 67%, 76%)', 'hsl(256, 67%, 70%)'),
|
||
painting: new Timeline.TimelineCategory(
|
||
'painting', Common.UIString('Painting'), true, 'hsl(109, 33%, 64%)', 'hsl(109, 33%, 55%)'),
|
||
gpu: new Timeline.TimelineCategory(
|
||
'gpu', Common.UIString('GPU'), false, 'hsl(109, 33%, 64%)', 'hsl(109, 33%, 55%)'),
|
||
async: new Timeline.TimelineCategory(
|
||
'async', Common.UIString('Async'), false, 'hsl(0, 100%, 50%)', 'hsl(0, 100%, 40%)'),
|
||
other:
|
||
new Timeline.TimelineCategory('other', Common.UIString('Other'), false, 'hsl(0, 0%, 87%)', 'hsl(0, 0%, 79%)'),
|
||
idle: new Timeline.TimelineCategory('idle', Common.UIString('Idle'), false, 'hsl(0, 0%, 98%)', 'hsl(0, 0%, 98%)')
|
||
};
|
||
return Timeline.TimelineUIUtils._categories;
|
||
}
|
||
|
||
/**
|
||
* @param {!Object} aggregatedStats
|
||
* @param {!Timeline.TimelineCategory=} selfCategory
|
||
* @param {number=} selfTime
|
||
* @return {!Element}
|
||
*/
|
||
static generatePieChart(aggregatedStats, selfCategory, selfTime) {
|
||
let total = 0;
|
||
for (const categoryName in aggregatedStats)
|
||
total += aggregatedStats[categoryName];
|
||
|
||
const element = createElementWithClass('div', 'timeline-details-view-pie-chart-wrapper hbox');
|
||
const pieChart = new PerfUI.PieChart(110, value => Number.preciseMillisToString(value), true);
|
||
pieChart.element.classList.add('timeline-details-view-pie-chart');
|
||
pieChart.setTotal(total);
|
||
const pieChartContainer = element.createChild('div', 'vbox');
|
||
pieChartContainer.appendChild(pieChart.element);
|
||
const footerElement = element.createChild('div', 'timeline-aggregated-info-legend');
|
||
|
||
/**
|
||
* @param {string} name
|
||
* @param {string} title
|
||
* @param {number} value
|
||
* @param {string} color
|
||
*/
|
||
function appendLegendRow(name, title, value, color) {
|
||
if (!value)
|
||
return;
|
||
pieChart.addSlice(value, color);
|
||
const rowElement = footerElement.createChild('div');
|
||
rowElement.createChild('span', 'timeline-aggregated-legend-value').textContent =
|
||
Number.preciseMillisToString(value, 1);
|
||
rowElement.createChild('span', 'timeline-aggregated-legend-swatch').style.backgroundColor = color;
|
||
rowElement.createChild('span', 'timeline-aggregated-legend-title').textContent = title;
|
||
}
|
||
|
||
// In case of self time, first add self, then children of the same category.
|
||
if (selfCategory) {
|
||
if (selfTime) {
|
||
appendLegendRow(
|
||
selfCategory.name, Common.UIString('%s (self)', selfCategory.title), selfTime, selfCategory.color);
|
||
}
|
||
// Children of the same category.
|
||
const categoryTime = aggregatedStats[selfCategory.name];
|
||
const value = categoryTime - selfTime;
|
||
if (value > 0) {
|
||
appendLegendRow(
|
||
selfCategory.name, Common.UIString('%s (children)', selfCategory.title), value, selfCategory.childColor);
|
||
}
|
||
}
|
||
|
||
// Add other categories.
|
||
for (const categoryName in Timeline.TimelineUIUtils.categories()) {
|
||
const category = Timeline.TimelineUIUtils.categories()[categoryName];
|
||
if (category === selfCategory)
|
||
continue;
|
||
appendLegendRow(category.name, category.title, aggregatedStats[category.name], category.childColor);
|
||
}
|
||
return element;
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.TimelineFrame} frame
|
||
* @param {?SDK.FilmStripModel.Frame} filmStripFrame
|
||
* @return {!Element}
|
||
*/
|
||
static generateDetailsContentForFrame(frame, filmStripFrame) {
|
||
const contentHelper = new Timeline.TimelineDetailsContentHelper(null, null);
|
||
contentHelper.addSection(Common.UIString('Frame'));
|
||
|
||
const duration = Timeline.TimelineUIUtils.frameDuration(frame);
|
||
contentHelper.appendElementRow(Common.UIString('Duration'), duration, frame.hasWarnings());
|
||
const durationInMillis = frame.endTime - frame.startTime;
|
||
contentHelper.appendTextRow(Common.UIString('FPS'), Math.floor(1000 / durationInMillis));
|
||
contentHelper.appendTextRow(Common.UIString('CPU time'), Number.millisToString(frame.cpuTime, true));
|
||
if (filmStripFrame) {
|
||
const filmStripPreview = createElementWithClass('div', 'timeline-filmstrip-preview');
|
||
filmStripFrame.imageDataPromise()
|
||
.then(data => UI.loadImageFromData(data))
|
||
.then(image => image && filmStripPreview.appendChild(image));
|
||
contentHelper.appendElementRow('', filmStripPreview);
|
||
filmStripPreview.addEventListener('click', frameClicked.bind(null, filmStripFrame), false);
|
||
}
|
||
|
||
if (frame.layerTree) {
|
||
contentHelper.appendElementRow(
|
||
Common.UIString('Layer tree'),
|
||
Components.Linkifier.linkifyRevealable(frame.layerTree, Common.UIString('show')));
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.FilmStripModel.Frame} filmStripFrame
|
||
*/
|
||
function frameClicked(filmStripFrame) {
|
||
new PerfUI.FilmStripView.Dialog(filmStripFrame, 0);
|
||
}
|
||
|
||
return contentHelper.fragment;
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.TimelineFrame} frame
|
||
* @return {!Element}
|
||
*/
|
||
static frameDuration(frame) {
|
||
const durationText = Common.UIString(
|
||
'%s (at %s)', Number.millisToString(frame.endTime - frame.startTime, true),
|
||
Number.millisToString(frame.startTimeOffset, true));
|
||
const element = createElement('span');
|
||
element.createTextChild(durationText);
|
||
if (!frame.hasWarnings())
|
||
return element;
|
||
element.createTextChild(Common.UIString('. Long frame times are an indication of '));
|
||
element.appendChild(UI.XLink.create(
|
||
'https://developers.google.com/web/fundamentals/performance/rendering/', Common.UIString('jank')));
|
||
element.createTextChild('.');
|
||
return element;
|
||
}
|
||
|
||
/**
|
||
* @param {!CanvasRenderingContext2D} context
|
||
* @param {number} width
|
||
* @param {number} height
|
||
* @param {string} color0
|
||
* @param {string} color1
|
||
* @param {string} color2
|
||
* @return {!CanvasGradient}
|
||
*/
|
||
static createFillStyle(context, width, height, color0, color1, color2) {
|
||
const gradient = context.createLinearGradient(0, 0, width, height);
|
||
gradient.addColorStop(0, color0);
|
||
gradient.addColorStop(0.25, color1);
|
||
gradient.addColorStop(0.75, color1);
|
||
gradient.addColorStop(1, color2);
|
||
return gradient;
|
||
}
|
||
|
||
/**
|
||
* @param {!Array.<number>} quad
|
||
* @return {number}
|
||
*/
|
||
static quadWidth(quad) {
|
||
return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2)));
|
||
}
|
||
|
||
/**
|
||
* @param {!Array.<number>} quad
|
||
* @return {number}
|
||
*/
|
||
static quadHeight(quad) {
|
||
return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
|
||
}
|
||
|
||
/**
|
||
* @return {!Array.<!Timeline.TimelineUIUtils.EventDispatchTypeDescriptor>}
|
||
*/
|
||
static eventDispatchDesciptors() {
|
||
if (Timeline.TimelineUIUtils._eventDispatchDesciptors)
|
||
return Timeline.TimelineUIUtils._eventDispatchDesciptors;
|
||
const lightOrange = 'hsl(40,100%,80%)';
|
||
const orange = 'hsl(40,100%,50%)';
|
||
const green = 'hsl(90,100%,40%)';
|
||
const purple = 'hsl(256,100%,75%)';
|
||
Timeline.TimelineUIUtils._eventDispatchDesciptors = [
|
||
new Timeline.TimelineUIUtils.EventDispatchTypeDescriptor(
|
||
1, lightOrange, ['mousemove', 'mouseenter', 'mouseleave', 'mouseout', 'mouseover']),
|
||
new Timeline.TimelineUIUtils.EventDispatchTypeDescriptor(
|
||
1, lightOrange, ['pointerover', 'pointerout', 'pointerenter', 'pointerleave', 'pointermove']),
|
||
new Timeline.TimelineUIUtils.EventDispatchTypeDescriptor(2, green, ['wheel']),
|
||
new Timeline.TimelineUIUtils.EventDispatchTypeDescriptor(3, orange, ['click', 'mousedown', 'mouseup']),
|
||
new Timeline.TimelineUIUtils.EventDispatchTypeDescriptor(
|
||
3, orange, ['touchstart', 'touchend', 'touchmove', 'touchcancel']),
|
||
new Timeline.TimelineUIUtils.EventDispatchTypeDescriptor(
|
||
3, orange, ['pointerdown', 'pointerup', 'pointercancel', 'gotpointercapture', 'lostpointercapture']),
|
||
new Timeline.TimelineUIUtils.EventDispatchTypeDescriptor(3, purple, ['keydown', 'keyup', 'keypress'])
|
||
];
|
||
return Timeline.TimelineUIUtils._eventDispatchDesciptors;
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @return {!Timeline.TimelineMarkerStyle}
|
||
*/
|
||
static markerStyleForEvent(event) {
|
||
const tallMarkerDashStyle = [10, 5];
|
||
const title = Timeline.TimelineUIUtils.eventTitle(event);
|
||
|
||
if (event.hasCategory(TimelineModel.TimelineModel.Category.Console) ||
|
||
event.hasCategory(TimelineModel.TimelineModel.Category.UserTiming)) {
|
||
return {
|
||
title: title,
|
||
dashStyle: tallMarkerDashStyle,
|
||
lineWidth: 0.5,
|
||
color: event.hasCategory(TimelineModel.TimelineModel.Category.UserTiming) ? 'purple' : 'orange',
|
||
tall: false,
|
||
lowPriority: false,
|
||
};
|
||
}
|
||
const recordTypes = TimelineModel.TimelineModel.RecordType;
|
||
let tall = false;
|
||
let color = 'grey';
|
||
switch (event.name) {
|
||
case recordTypes.FrameStartedLoading:
|
||
color = 'green';
|
||
tall = true;
|
||
break;
|
||
case recordTypes.MarkDOMContent:
|
||
color = 'blue';
|
||
tall = true;
|
||
break;
|
||
case recordTypes.MarkLoad:
|
||
color = 'red';
|
||
tall = true;
|
||
break;
|
||
case recordTypes.MarkFirstPaint:
|
||
color = 'hsl(180, 45%, 79%)';
|
||
tall = true;
|
||
break;
|
||
case recordTypes.MarkFCP:
|
||
color = 'hsl(160, 43%, 58%)';
|
||
tall = true;
|
||
break;
|
||
case recordTypes.MarkFMPCandidate:
|
||
color = 'hsl(146, 57%, 40%)';
|
||
tall = true;
|
||
break;
|
||
case recordTypes.MarkFMP:
|
||
color = 'hsl(144, 100%, 21%)';
|
||
tall = true;
|
||
break;
|
||
case recordTypes.TimeStamp:
|
||
color = 'orange';
|
||
break;
|
||
}
|
||
return {
|
||
title: title,
|
||
dashStyle: tallMarkerDashStyle,
|
||
lineWidth: 0.5,
|
||
color: color,
|
||
tall: tall,
|
||
lowPriority: false,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @return {!Timeline.TimelineMarkerStyle}
|
||
*/
|
||
static markerStyleForFrame() {
|
||
return {
|
||
title: Common.UIString('Frame'),
|
||
color: 'rgba(100, 100, 100, 0.4)',
|
||
lineWidth: 3,
|
||
dashStyle: [3],
|
||
tall: true,
|
||
lowPriority: true
|
||
};
|
||
}
|
||
|
||
/**
|
||
* @param {string} id
|
||
* @return {string}
|
||
*/
|
||
static colorForId(id) {
|
||
if (!Timeline.TimelineUIUtils.colorForId._colorGenerator) {
|
||
Timeline.TimelineUIUtils.colorForId._colorGenerator =
|
||
new Common.Color.Generator({min: 30, max: 330}, {min: 50, max: 80, count: 3}, 85);
|
||
Timeline.TimelineUIUtils.colorForId._colorGenerator.setColorForID('', '#f2ecdc');
|
||
}
|
||
return Timeline.TimelineUIUtils.colorForId._colorGenerator.colorForID(id);
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {string=} warningType
|
||
* @return {?Element}
|
||
*/
|
||
static eventWarning(event, warningType) {
|
||
const timelineData = TimelineModel.TimelineData.forEvent(event);
|
||
const warning = warningType || timelineData.warning;
|
||
if (!warning)
|
||
return null;
|
||
const warnings = TimelineModel.TimelineModel.WarningType;
|
||
const span = createElement('span');
|
||
const eventData = event.args['data'];
|
||
|
||
switch (warning) {
|
||
case warnings.ForcedStyle:
|
||
case warnings.ForcedLayout:
|
||
span.appendChild(UI.createDocumentationLink(
|
||
'../../fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid-forced-synchronous-layouts',
|
||
Common.UIString('Forced reflow')));
|
||
span.createTextChild(Common.UIString(' is a likely performance bottleneck.'));
|
||
break;
|
||
case warnings.IdleDeadlineExceeded:
|
||
span.textContent = Common.UIString(
|
||
'Idle callback execution extended beyond deadline by ' +
|
||
Number.millisToString(event.duration - eventData['allottedMilliseconds'], true));
|
||
break;
|
||
case warnings.LongHandler:
|
||
span.textContent = Common.UIString('Handler took %s', Number.millisToString(event.duration, true));
|
||
break;
|
||
case warnings.LongRecurringHandler:
|
||
span.textContent = Common.UIString('Recurring handler took %s', Number.millisToString(event.duration, true));
|
||
break;
|
||
case warnings.V8Deopt:
|
||
span.appendChild(UI.XLink.create(
|
||
'https://github.com/GoogleChrome/devtools-docs/issues/53', Common.UIString('Not optimized')));
|
||
span.createTextChild(Common.UIString(': %s', eventData['deoptReason']));
|
||
break;
|
||
default:
|
||
console.assert(false, 'Unhandled TimelineModel.WarningType');
|
||
}
|
||
return span;
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.TimelineModel.PageFrame} frame
|
||
* @param {number=} trimAt
|
||
*/
|
||
static displayNameForFrame(frame, trimAt) {
|
||
const url = frame.url;
|
||
if (!trimAt)
|
||
trimAt = 30;
|
||
return url.startsWith('about:') ? `"${frame.name.trimMiddle(trimAt)}"` : frame.url.trimEnd(trimAt);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @unrestricted
|
||
*/
|
||
Timeline.TimelineRecordStyle = class {
|
||
/**
|
||
* @param {string} title
|
||
* @param {!Timeline.TimelineCategory} category
|
||
* @param {boolean=} hidden
|
||
*/
|
||
constructor(title, category, hidden) {
|
||
this.title = title;
|
||
this.category = category;
|
||
this.hidden = !!hidden;
|
||
}
|
||
};
|
||
|
||
|
||
/**
|
||
* @enum {symbol}
|
||
*/
|
||
Timeline.TimelineUIUtils.NetworkCategory = {
|
||
HTML: Symbol('HTML'),
|
||
Script: Symbol('Script'),
|
||
Style: Symbol('Style'),
|
||
Media: Symbol('Media'),
|
||
Other: Symbol('Other')
|
||
};
|
||
|
||
|
||
Timeline.TimelineUIUtils._aggregatedStatsKey = Symbol('aggregatedStats');
|
||
|
||
|
||
/**
|
||
* @unrestricted
|
||
*/
|
||
Timeline.TimelineUIUtils.InvalidationsGroupElement = class extends UI.TreeElement {
|
||
/**
|
||
* @param {!SDK.Target} target
|
||
* @param {?Map<number, ?SDK.DOMNode>} relatedNodesMap
|
||
* @param {!Timeline.TimelineDetailsContentHelper} contentHelper
|
||
* @param {!Array.<!TimelineModel.InvalidationTrackingEvent>} invalidations
|
||
*/
|
||
constructor(target, relatedNodesMap, contentHelper, invalidations) {
|
||
super('', true);
|
||
|
||
this.listItemElement.classList.add('header');
|
||
this.selectable = false;
|
||
this.toggleOnClick = true;
|
||
|
||
this._relatedNodesMap = relatedNodesMap;
|
||
this._contentHelper = contentHelper;
|
||
this._invalidations = invalidations;
|
||
this.title = this._createTitle(target);
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.Target} target
|
||
* @return {!Element}
|
||
*/
|
||
_createTitle(target) {
|
||
const first = this._invalidations[0];
|
||
const reason = first.cause.reason;
|
||
const topFrame = first.cause.stackTrace && first.cause.stackTrace[0];
|
||
|
||
const title = createElement('span');
|
||
if (reason)
|
||
title.createTextChild(Common.UIString('%s for ', reason));
|
||
else
|
||
title.createTextChild(Common.UIString('Unknown cause for '));
|
||
|
||
this._appendTruncatedNodeList(title, this._invalidations);
|
||
|
||
if (topFrame && this._contentHelper.linkifier()) {
|
||
title.createTextChild(Common.UIString('. '));
|
||
const stack = title.createChild('span', 'monospace');
|
||
stack.createChild('span').textContent = Timeline.TimelineUIUtils.frameDisplayName(topFrame);
|
||
const link = this._contentHelper.linkifier().maybeLinkifyConsoleCallFrame(target, topFrame);
|
||
if (link) {
|
||
stack.createChild('span').textContent = ' @ ';
|
||
stack.createChild('span').appendChild(link);
|
||
}
|
||
}
|
||
|
||
return title;
|
||
}
|
||
|
||
/**
|
||
* @override
|
||
*/
|
||
onpopulate() {
|
||
const content = createElementWithClass('div', 'content');
|
||
|
||
const first = this._invalidations[0];
|
||
if (first.cause.stackTrace) {
|
||
const stack = content.createChild('div');
|
||
stack.createTextChild(Common.UIString('Stack trace:'));
|
||
this._contentHelper.createChildStackTraceElement(
|
||
stack, Timeline.TimelineUIUtils._stackTraceFromCallFrames(first.cause.stackTrace));
|
||
}
|
||
|
||
content.createTextChild(this._invalidations.length > 1 ? Common.UIString('Nodes:') : Common.UIString('Node:'));
|
||
const nodeList = content.createChild('div', 'node-list');
|
||
let firstNode = true;
|
||
for (let i = 0; i < this._invalidations.length; i++) {
|
||
const invalidation = this._invalidations[i];
|
||
const invalidationNode = this._createInvalidationNode(invalidation, true);
|
||
if (invalidationNode) {
|
||
if (!firstNode)
|
||
nodeList.createTextChild(Common.UIString(', '));
|
||
firstNode = false;
|
||
|
||
nodeList.appendChild(invalidationNode);
|
||
|
||
const extraData = invalidation.extraData ? ', ' + invalidation.extraData : '';
|
||
if (invalidation.changedId) {
|
||
nodeList.createTextChild(Common.UIString('(changed id to "%s"%s)', invalidation.changedId, extraData));
|
||
} else if (invalidation.changedClass) {
|
||
nodeList.createTextChild(Common.UIString('(changed class to "%s"%s)', invalidation.changedClass, extraData));
|
||
} else if (invalidation.changedAttribute) {
|
||
nodeList.createTextChild(
|
||
Common.UIString('(changed attribute to "%s"%s)', invalidation.changedAttribute, extraData));
|
||
} else if (invalidation.changedPseudo) {
|
||
nodeList.createTextChild(
|
||
Common.UIString('(changed pesudo to "%s"%s)', invalidation.changedPseudo, extraData));
|
||
} else if (invalidation.selectorPart) {
|
||
nodeList.createTextChild(Common.UIString('(changed "%s"%s)', invalidation.selectorPart, extraData));
|
||
}
|
||
}
|
||
}
|
||
|
||
const contentTreeElement = new UI.TreeElement(content, false);
|
||
contentTreeElement.selectable = false;
|
||
this.appendChild(contentTreeElement);
|
||
}
|
||
|
||
/**
|
||
* @param {!Element} parentElement
|
||
* @param {!Array.<!TimelineModel.InvalidationTrackingEvent>} invalidations
|
||
*/
|
||
_appendTruncatedNodeList(parentElement, invalidations) {
|
||
const invalidationNodes = [];
|
||
const invalidationNodeIdMap = {};
|
||
for (let i = 0; i < invalidations.length; i++) {
|
||
const invalidation = invalidations[i];
|
||
const invalidationNode = this._createInvalidationNode(invalidation, false);
|
||
invalidationNode.addEventListener('click', e => e.consume(), false);
|
||
if (invalidationNode && !invalidationNodeIdMap[invalidation.nodeId]) {
|
||
invalidationNodes.push(invalidationNode);
|
||
invalidationNodeIdMap[invalidation.nodeId] = true;
|
||
}
|
||
}
|
||
|
||
if (invalidationNodes.length === 1) {
|
||
parentElement.appendChild(invalidationNodes[0]);
|
||
} else if (invalidationNodes.length === 2) {
|
||
parentElement.appendChild(invalidationNodes[0]);
|
||
parentElement.createTextChild(Common.UIString(' and '));
|
||
parentElement.appendChild(invalidationNodes[1]);
|
||
} else if (invalidationNodes.length >= 3) {
|
||
parentElement.appendChild(invalidationNodes[0]);
|
||
parentElement.createTextChild(Common.UIString(', '));
|
||
parentElement.appendChild(invalidationNodes[1]);
|
||
parentElement.createTextChild(Common.UIString(', and %s others', invalidationNodes.length - 2));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!TimelineModel.InvalidationTrackingEvent} invalidation
|
||
* @param {boolean} showUnknownNodes
|
||
*/
|
||
_createInvalidationNode(invalidation, showUnknownNodes) {
|
||
const node = (invalidation.nodeId && this._relatedNodesMap) ? this._relatedNodesMap.get(invalidation.nodeId) : null;
|
||
if (node) {
|
||
const nodeSpan = createElement('span');
|
||
Common.Linkifier.linkify(node).then(link => nodeSpan.appendChild(link));
|
||
return nodeSpan;
|
||
}
|
||
if (invalidation.nodeName) {
|
||
const nodeSpan = createElement('span');
|
||
nodeSpan.textContent = Common.UIString('[ %s ]', invalidation.nodeName);
|
||
return nodeSpan;
|
||
}
|
||
if (showUnknownNodes) {
|
||
const nodeSpan = createElement('span');
|
||
return nodeSpan.createTextChild(Common.UIString('[ unknown node ]'));
|
||
}
|
||
}
|
||
};
|
||
|
||
Timeline.TimelineUIUtils._previewElementSymbol = Symbol('previewElement');
|
||
|
||
/**
|
||
* @unrestricted
|
||
*/
|
||
Timeline.TimelineUIUtils.EventDispatchTypeDescriptor = class {
|
||
/**
|
||
* @param {number} priority
|
||
* @param {string} color
|
||
* @param {!Array.<string>} eventTypes
|
||
*/
|
||
constructor(priority, color, eventTypes) {
|
||
this.priority = priority;
|
||
this.color = color;
|
||
this.eventTypes = eventTypes;
|
||
}
|
||
};
|
||
|
||
|
||
/**
|
||
* @unrestricted
|
||
*/
|
||
Timeline.TimelineCategory = class extends Common.Object {
|
||
/**
|
||
* @param {string} name
|
||
* @param {string} title
|
||
* @param {boolean} visible
|
||
* @param {string} childColor
|
||
* @param {string} color
|
||
*/
|
||
constructor(name, title, visible, childColor, color) {
|
||
super();
|
||
this.name = name;
|
||
this.title = title;
|
||
this.visible = visible;
|
||
this.childColor = childColor;
|
||
this.color = color;
|
||
this.hidden = false;
|
||
}
|
||
|
||
/**
|
||
* @return {boolean}
|
||
*/
|
||
get hidden() {
|
||
return this._hidden;
|
||
}
|
||
|
||
/**
|
||
* @param {boolean} hidden
|
||
*/
|
||
set hidden(hidden) {
|
||
this._hidden = hidden;
|
||
this.dispatchEventToListeners(Timeline.TimelineCategory.Events.VisibilityChanged, this);
|
||
}
|
||
};
|
||
|
||
/** @enum {symbol} */
|
||
Timeline.TimelineCategory.Events = {
|
||
VisibilityChanged: Symbol('VisibilityChanged')
|
||
};
|
||
|
||
/**
|
||
* @typedef {!{
|
||
* title: string,
|
||
* color: string,
|
||
* lineWidth: number,
|
||
* dashStyle: !Array.<number>,
|
||
* tall: boolean,
|
||
* lowPriority: boolean
|
||
* }}
|
||
*/
|
||
Timeline.TimelineMarkerStyle;
|
||
|
||
|
||
/**
|
||
* @unrestricted
|
||
*/
|
||
Timeline.TimelinePopupContentHelper = class {
|
||
/**
|
||
* @param {string} title
|
||
*/
|
||
constructor(title) {
|
||
this._contentTable = createElement('table');
|
||
const titleCell = this._createCell(Common.UIString('%s - Details', title), 'timeline-details-title');
|
||
titleCell.colSpan = 2;
|
||
const titleRow = createElement('tr');
|
||
titleRow.appendChild(titleCell);
|
||
this._contentTable.appendChild(titleRow);
|
||
}
|
||
|
||
/**
|
||
* @return {!Element}
|
||
*/
|
||
contentTable() {
|
||
return this._contentTable;
|
||
}
|
||
|
||
/**
|
||
* @param {string|number} content
|
||
* @param {string=} styleName
|
||
*/
|
||
_createCell(content, styleName) {
|
||
const text = createElement('label');
|
||
text.createTextChild(String(content));
|
||
const cell = createElement('td');
|
||
cell.className = 'timeline-details';
|
||
if (styleName)
|
||
cell.className += ' ' + styleName;
|
||
cell.textContent = content;
|
||
return cell;
|
||
}
|
||
|
||
/**
|
||
* @param {string} title
|
||
* @param {string|number} content
|
||
*/
|
||
appendTextRow(title, content) {
|
||
const row = createElement('tr');
|
||
row.appendChild(this._createCell(title, 'timeline-details-row-title'));
|
||
row.appendChild(this._createCell(content, 'timeline-details-row-data'));
|
||
this._contentTable.appendChild(row);
|
||
}
|
||
|
||
/**
|
||
* @param {string} title
|
||
* @param {!Node|string} content
|
||
*/
|
||
appendElementRow(title, content) {
|
||
const row = createElement('tr');
|
||
const titleCell = this._createCell(title, 'timeline-details-row-title');
|
||
row.appendChild(titleCell);
|
||
const cell = createElement('td');
|
||
cell.className = 'details';
|
||
if (content instanceof Node)
|
||
cell.appendChild(content);
|
||
else
|
||
cell.createTextChild(content || '');
|
||
row.appendChild(cell);
|
||
this._contentTable.appendChild(row);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @unrestricted
|
||
*/
|
||
Timeline.TimelineDetailsContentHelper = class {
|
||
/**
|
||
* @param {?SDK.Target} target
|
||
* @param {?Components.Linkifier} linkifier
|
||
*/
|
||
constructor(target, linkifier) {
|
||
this.fragment = createDocumentFragment();
|
||
|
||
this._linkifier = linkifier;
|
||
this._target = target;
|
||
|
||
this.element = createElementWithClass('div', 'timeline-details-view-block');
|
||
this._tableElement = this.element.createChild('div', 'vbox timeline-details-chip-body');
|
||
this.fragment.appendChild(this.element);
|
||
}
|
||
|
||
/**
|
||
* @param {string} title
|
||
* @param {string=} swatchColor
|
||
*/
|
||
addSection(title, swatchColor) {
|
||
if (!this._tableElement.hasChildNodes()) {
|
||
this.element.removeChildren();
|
||
} else {
|
||
this.element = createElementWithClass('div', 'timeline-details-view-block');
|
||
this.fragment.appendChild(this.element);
|
||
}
|
||
|
||
if (title) {
|
||
const titleElement = this.element.createChild('div', 'timeline-details-chip-title');
|
||
if (swatchColor)
|
||
titleElement.createChild('div').style.backgroundColor = swatchColor;
|
||
titleElement.createTextChild(title);
|
||
}
|
||
|
||
this._tableElement = this.element.createChild('div', 'vbox timeline-details-chip-body');
|
||
this.fragment.appendChild(this.element);
|
||
}
|
||
|
||
/**
|
||
* @return {?Components.Linkifier}
|
||
*/
|
||
linkifier() {
|
||
return this._linkifier;
|
||
}
|
||
|
||
/**
|
||
* @param {string} title
|
||
* @param {string|number|boolean} value
|
||
*/
|
||
appendTextRow(title, value) {
|
||
const rowElement = this._tableElement.createChild('div', 'timeline-details-view-row');
|
||
rowElement.createChild('div', 'timeline-details-view-row-title').textContent = title;
|
||
rowElement.createChild('div', 'timeline-details-view-row-value').textContent = value;
|
||
}
|
||
|
||
/**
|
||
* @param {string} title
|
||
* @param {!Node|string} content
|
||
* @param {boolean=} isWarning
|
||
* @param {boolean=} isStacked
|
||
*/
|
||
appendElementRow(title, content, isWarning, isStacked) {
|
||
const rowElement = this._tableElement.createChild('div', 'timeline-details-view-row');
|
||
if (isWarning)
|
||
rowElement.classList.add('timeline-details-warning');
|
||
if (isStacked)
|
||
rowElement.classList.add('timeline-details-stack-values');
|
||
const titleElement = rowElement.createChild('div', 'timeline-details-view-row-title');
|
||
titleElement.textContent = title;
|
||
const valueElement = rowElement.createChild('div', 'timeline-details-view-row-value');
|
||
if (content instanceof Node)
|
||
valueElement.appendChild(content);
|
||
else
|
||
valueElement.createTextChild(content || '');
|
||
}
|
||
|
||
/**
|
||
* @param {string} title
|
||
* @param {string} url
|
||
* @param {number} startLine
|
||
* @param {number=} startColumn
|
||
*/
|
||
appendLocationRow(title, url, startLine, startColumn) {
|
||
if (!this._linkifier || !this._target)
|
||
return;
|
||
const link = this._linkifier.maybeLinkifyScriptLocation(this._target, null, url, startLine, startColumn);
|
||
if (!link)
|
||
return;
|
||
this.appendElementRow(title, link);
|
||
}
|
||
|
||
/**
|
||
* @param {string} title
|
||
* @param {string} url
|
||
* @param {number} startLine
|
||
* @param {number=} endLine
|
||
*/
|
||
appendLocationRange(title, url, startLine, endLine) {
|
||
if (!this._linkifier || !this._target)
|
||
return;
|
||
const locationContent = createElement('span');
|
||
const link = this._linkifier.maybeLinkifyScriptLocation(this._target, null, url, startLine);
|
||
if (!link)
|
||
return;
|
||
locationContent.appendChild(link);
|
||
locationContent.createTextChild(String.sprintf(' [%s\u2026%s]', startLine + 1, endLine + 1 || ''));
|
||
this.appendElementRow(title, locationContent);
|
||
}
|
||
|
||
/**
|
||
* @param {string} title
|
||
* @param {!Protocol.Runtime.StackTrace} stackTrace
|
||
*/
|
||
appendStackTrace(title, stackTrace) {
|
||
if (!this._linkifier || !this._target)
|
||
return;
|
||
|
||
const rowElement = this._tableElement.createChild('div', 'timeline-details-view-row');
|
||
rowElement.createChild('div', 'timeline-details-view-row-title').textContent = title;
|
||
this.createChildStackTraceElement(rowElement, stackTrace);
|
||
}
|
||
|
||
/**
|
||
* @param {!Element} parentElement
|
||
* @param {!Protocol.Runtime.StackTrace} stackTrace
|
||
*/
|
||
createChildStackTraceElement(parentElement, stackTrace) {
|
||
if (!this._linkifier || !this._target)
|
||
return;
|
||
parentElement.classList.add('timeline-details-stack-values');
|
||
const stackTraceElement =
|
||
parentElement.createChild('div', 'timeline-details-view-row-value timeline-details-view-row-stack-trace');
|
||
const callFrameElem =
|
||
Components.JSPresentationUtils.buildStackTracePreviewContents(this._target, this._linkifier, stackTrace);
|
||
stackTraceElement.appendChild(callFrameElem);
|
||
}
|
||
|
||
/**
|
||
* @param {!SDK.TracingModel.Event} event
|
||
* @param {string=} warningType
|
||
*/
|
||
appendWarningRow(event, warningType) {
|
||
const warning = Timeline.TimelineUIUtils.eventWarning(event, warningType);
|
||
if (warning)
|
||
this.appendElementRow(Common.UIString('Warning'), warning, true);
|
||
}
|
||
};
|
||
|
||
Timeline.TimelineUIUtils._categoryBreakdownCacheSymbol = Symbol('categoryBreakdownCache');
|