UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

1,107 lines (1,041 loc) 96 kB
/* * 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 =