@dcloudio/uni-debugger
Version:
uni-app debugger
1,107 lines (1,041 loc) • 96 kB
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 =