| /* |
| * 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; |
| |
| var recordTypes = TimelineModel.TimelineModel.RecordType; |
| var categories = Timeline.TimelineUIUtils.categories(); |
| |
| var 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.ParseScriptOnBackground] = |
| new Timeline.TimelineRecordStyle(Common.UIString('Parse Script'), categories['scripting']); |
| 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.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']); |
| |
| Timeline.TimelineUIUtils._eventStylesMap = eventStyles; |
| return eventStyles; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineIRModel.InputEvents} inputEventType |
| * @return {?string} |
| */ |
| static inputEventDisplayName(inputEventType) { |
| if (!Timeline.TimelineUIUtils._inputEventToDisplayName) { |
| var 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) { |
| var title = Timeline.TimelineUIUtils.eventStyle(traceEvent).title; |
| var tokens = [title]; |
| var url = TimelineModel.TimelineData.forEvent(traceEvent).url; |
| if (url) |
| tokens.push(url); |
| for (var argName in traceEvent.args) { |
| var argValue = traceEvent.args[argName]; |
| for (var key in argValue) |
| tokens.push(argValue[key]); |
| } |
| return regExp.test(tokens.join('|')); |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.Record} record |
| * @return {!Timeline.TimelineCategory} |
| */ |
| static categoryForRecord(record) { |
| return Timeline.TimelineUIUtils.eventStyle(record.traceEvent()).category; |
| } |
| |
| /** |
| * @param {!SDK.TracingModel.Event} event |
| * @return {!{title: string, category: !Timeline.TimelineCategory}} |
| */ |
| static eventStyle(event) { |
| var 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 */ |
| var prefix = 'InputLatency::'; |
| var inputEventType = event.name.startsWith(prefix) ? event.name.substr(prefix.length) : event.name; |
| var displayName = Timeline.TimelineUIUtils.inputEventDisplayName( |
| /** @type {!TimelineModel.TimelineIRModel.InputEvents} */ (inputEventType)); |
| return {title: displayName || inputEventType, category: Timeline.TimelineUIUtils.categories()['scripting']}; |
| } |
| var 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) { |
| var frame = event.args['data']; |
| if (Timeline.TimelineUIUtils.isUserFrame(frame)) |
| return Timeline.TimelineUIUtils.colorForURL(frame.url); |
| } |
| return Timeline.TimelineUIUtils.eventStyle(event).category.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() { |
| var 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) { |
| var 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) { |
| var 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) { |
| var recordType = TimelineModel.TimelineModel.RecordType; |
| var detailsText; |
| var eventData = event.args['data']; |
| switch (event.name) { |
| case recordType.GCEvent: |
| case recordType.MajorGC: |
| case recordType.MinorGC: |
| var delta = event.args['usedHeapSizeBefore'] - event.args['usedHeapSizeAfter']; |
| detailsText = Common.UIString('%s collected', Number.bytesToString(delta)); |
| break; |
| case recordType.FunctionCall: |
| // Omit internally generated script names. |
| if (eventData) |
| detailsText = linkifyLocationAsText(eventData['scriptId'], eventData['lineNumber'], 0); |
| break; |
| case recordType.JSFrame: |
| detailsText = Timeline.TimelineUIUtils.frameDisplayName(eventData); |
| break; |
| case recordType.EventDispatch: |
| detailsText = eventData ? eventData['type'] : null; |
| break; |
| case recordType.Paint: |
| var width = Timeline.TimelineUIUtils.quadWidth(eventData.clip); |
| var height = Timeline.TimelineUIUtils.quadHeight(eventData.clip); |
| if (width && height) |
| detailsText = Common.UIString('%d\u2009\u00d7\u2009%d', width, height); |
| break; |
| case recordType.ParseHTML: |
| var endLine = event.args['endData'] && event.args['endData']['endLine']; |
| var 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.CompileScript: |
| case recordType.EvaluateScript: |
| var url = eventData && eventData['url']; |
| if (url) |
| detailsText = Bindings.displayNameForURL(url) + ':' + (eventData['lineNumber'] + 1); |
| break; |
| case recordType.ParseScriptOnBackground: |
| case recordType.XHRReadyStateChange: |
| case recordType.XHRLoad: |
| var 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: |
| var 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; |
| |
| 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) { |
| var debuggerModel = SDK.DebuggerModel.fromTarget(target); |
| if (!target || target.isDisposed() || !scriptId || !debuggerModel) |
| return null; |
| var rawLocation = debuggerModel.createRawLocationByScriptId(scriptId, lineNumber, columnNumber); |
| if (!rawLocation) |
| return null; |
| var uiLocation = Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(rawLocation); |
| return uiLocation.linkText(); |
| } |
| |
| /** |
| * @return {?string} |
| */ |
| function linkifyTopCallFrameAsText() { |
| var frame = TimelineModel.TimelineData.forEvent(event).topFrame(); |
| if (!frame) |
| return null; |
| var 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) { |
| var recordType = TimelineModel.TimelineModel.RecordType; |
| var details = null; |
| var detailsText; |
| var 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: |
| var url = TimelineModel.TimelineData.forEvent(event).url; |
| if (url) |
| details = Components.linkifyResourceAsNode(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.CompileScript: |
| case recordType.EvaluateScript: |
| var url = eventData['url']; |
| if (url) |
| details = linkifyLocation('', url, eventData['lineNumber'], 0); |
| break; |
| case recordType.ParseScriptOnBackground: |
| var 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() { |
| var 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 {boolean} detailed |
| * @param {function(!DocumentFragment)} callback |
| */ |
| static buildTraceEventDetails(event, model, linkifier, detailed, callback) { |
| var target = model.targetByEvent(event); |
| if (!target) { |
| callbackWrapper(); |
| return; |
| } |
| var relatedNodes = null; |
| var barrier = new CallbackBarrier(); |
| if (!event[Timeline.TimelineUIUtils._previewElementSymbol]) { |
| var url = TimelineModel.TimelineData.forEvent(event).url; |
| if (url) { |
| Components.DOMPresentationUtils.buildImagePreviewContents( |
| target, url, false, barrier.createCallback(saveImage)); |
| } else if (TimelineModel.TimelineData.forEvent(event).picture) { |
| Timeline.TimelineUIUtils.buildPicturePreviewContent(event, target, barrier.createCallback(saveImage)); |
| } |
| } |
| var nodeIdsToResolve = new Set(); |
| var timelineData = TimelineModel.TimelineData.forEvent(event); |
| if (timelineData.backendNodeId) |
| nodeIdsToResolve.add(timelineData.backendNodeId); |
| var invalidationTrackingEvents = TimelineModel.InvalidationTracker.invalidationEventsFor(event); |
| if (invalidationTrackingEvents) |
| Timeline.TimelineUIUtils._collectInvalidationNodeIds(nodeIdsToResolve, invalidationTrackingEvents); |
| if (nodeIdsToResolve.size) { |
| var domModel = SDK.DOMModel.fromTarget(target); |
| if (domModel) |
| domModel.pushNodesByBackendIdsToFrontend(nodeIdsToResolve, barrier.createCallback(setRelatedNodeMap)); |
| } |
| barrier.callWhenDone(callbackWrapper); |
| |
| /** |
| * @param {!Element=} element |
| */ |
| function saveImage(element) { |
| event[Timeline.TimelineUIUtils._previewElementSymbol] = element || null; |
| } |
| |
| /** |
| * @param {?Map<number, ?SDK.DOMNode>} nodeMap |
| */ |
| function setRelatedNodeMap(nodeMap) { |
| relatedNodes = nodeMap; |
| } |
| |
| function callbackWrapper() { |
| callback(Timeline.TimelineUIUtils._buildTraceEventDetailsSynchronously( |
| event, model, linkifier, detailed, relatedNodes)); |
| } |
| } |
| |
| /** |
| * @param {!SDK.TracingModel.Event} event |
| * @param {!TimelineModel.TimelineModel} model |
| * @param {!Components.Linkifier} linkifier |
| * @param {boolean} detailed |
| * @param {?Map<number, ?SDK.DOMNode>} relatedNodesMap |
| * @return {!DocumentFragment} |
| */ |
| static _buildTraceEventDetailsSynchronously(event, model, linkifier, detailed, relatedNodesMap) { |
| var recordTypes = TimelineModel.TimelineModel.RecordType; |
| // This message may vary per event.name; |
| var relatedNodeLabel; |
| |
| var contentHelper = new Timeline.TimelineDetailsContentHelper(model.targetByEvent(event), linkifier); |
| contentHelper.addSection( |
| Timeline.TimelineUIUtils.eventTitle(event), Timeline.TimelineUIUtils.eventStyle(event).category); |
| |
| var eventData = event.args['data']; |
| var timelineData = TimelineModel.TimelineData.forEvent(event); |
| var initiator = timelineData.initiator(); |
| |
| 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('Self Time'), Number.millisToString(event.selfTime, true)); |
| contentHelper.appendTextRow( |
| Common.UIString('Total Time'), Number.millisToString(event.duration || 0, true)); |
| } |
| |
| switch (event.name) { |
| case recordTypes.GCEvent: |
| case recordTypes.MajorGC: |
| case recordTypes.MinorGC: |
| var delta = event.args['usedHeapSizeBefore'] - event.args['usedHeapSizeAfter']; |
| contentHelper.appendTextRow(Common.UIString('Collected'), Number.bytesToString(delta)); |
| break; |
| case recordTypes.JSFrame: |
| case recordTypes.FunctionCall: |
| var 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: |
| var url = timelineData.url; |
| if (url) |
| contentHelper.appendElementRow(Common.UIString('Resource'), Components.linkifyResourceAsNode(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) { |
| var priority = Components.uiLabelForPriority(eventData['priority']); |
| contentHelper.appendTextRow(Common.UIString('Priority'), priority); |
| } |
| if (eventData['encodedDataLength']) |
| contentHelper.appendTextRow( |
| Common.UIString('Encoded Data Length'), |
| Common.UIString('%d Bytes', eventData['encodedDataLength'])); |
| break; |
| case recordTypes.CompileScript: |
| case recordTypes.EvaluateScript: |
| var url = eventData && eventData['url']; |
| if (url) |
| contentHelper.appendLocationRow( |
| Common.UIString('Script'), url, eventData['lineNumber'], eventData['columnNumber']); |
| break; |
| case recordTypes.Paint: |
| var clip = eventData['clip']; |
| contentHelper.appendTextRow( |
| Common.UIString('Location'), Common.UIString('(%d, %d)', clip[0], clip[1])); |
| var clipWidth = Timeline.TimelineUIUtils.quadWidth(clip); |
| var 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'); |
| if (timelineData.url) |
| contentHelper.appendElementRow( |
| Common.UIString('Image URL'), Components.linkifyResourceAsNode(timelineData.url)); |
| break; |
| case recordTypes.ParseAuthorStyleSheet: |
| var url = eventData['styleSheetUrl']; |
| if (url) |
| contentHelper.appendElementRow( |
| Common.UIString('Stylesheet URL'), Components.linkifyResourceAsNode(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: |
| var 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: |
| var 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: |
| var beginData = event.args['beginData']; |
| var url = beginData['url']; |
| var startLine = beginData['startLine'] - 1; |
| var endLine = event.args['endData'] ? event.args['endData']['endLine'] - 1 : undefined; |
| 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: |
| var detailsNode = |
| Timeline.TimelineUIUtils.buildDetailsNodeForTraceEvent(event, model.targetByEvent(event), linkifier); |
| if (detailsNode) |
| contentHelper.appendElementRow(Common.UIString('Details'), detailsNode); |
| break; |
| } |
| |
| if (timelineData.timeWaitingForMainThread) |
| contentHelper.appendTextRow( |
| Common.UIString('Time Waiting for Main Thread'), |
| Number.millisToString(timelineData.timeWaitingForMainThread, true)); |
| |
| var relatedNode = relatedNodesMap && relatedNodesMap.get(timelineData.backendNodeId); |
| if (relatedNode) |
| contentHelper.appendElementRow( |
| relatedNodeLabel || Common.UIString('Related Node'), |
| Components.DOMPresentationUtils.linkifyNodeReference(relatedNode)); |
| |
| if (event[Timeline.TimelineUIUtils._previewElementSymbol]) { |
| contentHelper.addSection(Common.UIString('Preview')); |
| contentHelper.appendElementRow('', event[Timeline.TimelineUIUtils._previewElementSymbol]); |
| } |
| |
| if (timelineData.stackTraceForSelfOrInitiator() || TimelineModel.InvalidationTracker.invalidationEventsFor(event)) |
| Timeline.TimelineUIUtils._generateCauses(event, model.targetByEvent(event), relatedNodesMap, contentHelper); |
| |
| var stats = {}; |
| var showPieChart = detailed && Timeline.TimelineUIUtils._aggregatedStatsForTraceEvent(stats, model, event); |
| if (showPieChart) { |
| contentHelper.addSection(Common.UIString('Aggregated Time')); |
| var pieChart = Timeline.TimelineUIUtils.generatePieChart( |
| stats, Timeline.TimelineUIUtils.eventStyle(event).category, event.selfTime); |
| contentHelper.appendElementRow('', pieChart); |
| } |
| |
| return contentHelper.fragment; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel} model |
| * @param {number} startTime |
| * @param {number} endTime |
| * @return {!DocumentFragment} |
| */ |
| static buildRangeStats(model, startTime, endTime) { |
| var aggregatedStats = {}; |
| |
| /** |
| * @param {number} value |
| * @param {!TimelineModel.TimelineModel.Record} task |
| * @return {number} |
| */ |
| function compareEndTime(value, task) { |
| return value < task.endTime() ? -1 : 1; |
| } |
| var mainThreadTasks = model.mainThreadTasks(); |
| var taskIndex = mainThreadTasks.lowerBound(startTime, compareEndTime); |
| for (; taskIndex < mainThreadTasks.length; ++taskIndex) { |
| var task = mainThreadTasks[taskIndex]; |
| if (task.startTime() > endTime) |
| break; |
| if (task.startTime() > startTime && task.endTime() < endTime) { |
| // cache stats for top-level entries that fit the range entirely. |
| var taskStats = task[Timeline.TimelineUIUtils._aggregatedStatsKey]; |
| if (!taskStats) { |
| taskStats = {}; |
| Timeline.TimelineUIUtils._collectAggregatedStatsForRecord(task, startTime, endTime, taskStats); |
| task[Timeline.TimelineUIUtils._aggregatedStatsKey] = taskStats; |
| } |
| for (var key in taskStats) |
| aggregatedStats[key] = (aggregatedStats[key] || 0) + taskStats[key]; |
| continue; |
| } |
| Timeline.TimelineUIUtils._collectAggregatedStatsForRecord(task, startTime, endTime, aggregatedStats); |
| } |
| |
| var aggregatedTotal = 0; |
| for (var categoryName in aggregatedStats) |
| aggregatedTotal += aggregatedStats[categoryName]; |
| aggregatedStats['idle'] = Math.max(0, endTime - startTime - aggregatedTotal); |
| |
| var startOffset = startTime - model.minimumRecordTime(); |
| var endOffset = endTime - model.minimumRecordTime(); |
| |
| var contentHelper = new Timeline.TimelineDetailsContentHelper(null, null); |
| contentHelper.addSection(Common.UIString( |
| 'Range: %s \u2013 %s', Number.millisToString(startOffset), Number.millisToString(endOffset))); |
| var pieChart = Timeline.TimelineUIUtils.generatePieChart(aggregatedStats); |
| contentHelper.appendElementRow('', pieChart); |
| return contentHelper.fragment; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.Record} record |
| * @param {number} startTime |
| * @param {number} endTime |
| * @param {!Object} aggregatedStats |
| */ |
| static _collectAggregatedStatsForRecord(record, startTime, endTime, aggregatedStats) { |
| var records = []; |
| |
| if (!record.endTime() || record.endTime() < startTime || record.startTime() > endTime) |
| return; |
| |
| var childrenTime = 0; |
| var children = record.children() || []; |
| for (var i = 0; i < children.length; ++i) { |
| var child = children[i]; |
| if (!child.endTime() || child.endTime() < startTime || child.startTime() > endTime) |
| continue; |
| childrenTime += Math.min(endTime, child.endTime()) - Math.max(startTime, child.startTime()); |
| Timeline.TimelineUIUtils._collectAggregatedStatsForRecord(child, startTime, endTime, aggregatedStats); |
| } |
| var categoryName = Timeline.TimelineUIUtils.categoryForRecord(record).name; |
| var ownTime = Math.min(endTime, record.endTime()) - Math.max(startTime, record.startTime()) - childrenTime; |
| aggregatedStats[categoryName] = (aggregatedStats[categoryName] || 0) + ownTime; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.NetworkRequest} request |
| * @param {!TimelineModel.TimelineModel} model |
| * @param {!Components.Linkifier} linkifier |
| * @return {!Promise<!DocumentFragment>} |
| */ |
| static buildNetworkRequestDetails(request, model, linkifier) { |
| var target = model.targetByEvent(request.children[0]); |
| var contentHelper = new Timeline.TimelineDetailsContentHelper(target, linkifier); |
| |
| var duration = request.endTime - (request.startTime || -Infinity); |
| var items = []; |
| if (request.url) |
| contentHelper.appendElementRow(Common.UIString('URL'), UI.linkifyURLAsNode(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') { |
| var priority = Components.uiLabelForPriority(/** @type {!Protocol.Network.ResourcePriority} */ (request.priority)); |
| contentHelper.appendTextRow(Common.UIString('Priority'), priority); |
| } |
| if (request.mimeType) |
| contentHelper.appendTextRow(Common.UIString('Mime Type'), request.mimeType); |
| |
| var title = Common.UIString('Initiator'); |
| var sendRequest = request.children[0]; |
| var topFrame = TimelineModel.TimelineData.forEvent(sendRequest).topFrame(); |
| if (topFrame) { |
| var link = linkifier.maybeLinkifyConsoleCallFrame(target, topFrame); |
| if (link) |
| contentHelper.appendElementRow(title, link); |
| } else { |
| var initiator = TimelineModel.TimelineData.forEvent(sendRequest).initiator(); |
| if (initiator) { |
| var initiatorURL = TimelineModel.TimelineData.forEvent(initiator).url; |
| if (initiatorURL) { |
| var link = linkifier.maybeLinkifyScriptLocation(target, null, initiatorURL, 0); |
| if (link) |
| contentHelper.appendElementRow(title, link); |
| } |
| } |
| } |
| |
| /** |
| * @param {function(?Element)} fulfill |
| */ |
| function action(fulfill) { |
| Components.DOMPresentationUtils.buildImagePreviewContents( |
| /** @type {!SDK.Target} */ (target), request.url, false, saveImage); |
| /** |
| * @param {!Element=} element |
| */ |
| function saveImage(element) { |
| request.previewElement = element || null; |
| fulfill(request.previewElement); |
| } |
| } |
| var previewPromise; |
| if (request.previewElement) |
| previewPromise = Promise.resolve(request.previewElement); |
| else |
| previewPromise = request.url && target ? new Promise(action) : Promise.resolve(null); |
| /** |
| * @param {?Element} element |
| * @return {!DocumentFragment} |
| */ |
| function appendPreview(element) { |
| if (element) |
| contentHelper.appendElementRow(Common.UIString('Preview'), request.previewElement); |
| return contentHelper.fragment; |
| } |
| return previewPromise.then(appendPreview); |
| } |
| |
| /** |
| * @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) { |
| var recordTypes = TimelineModel.TimelineModel.RecordType; |
| |
| var callSiteStackLabel; |
| var 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; |
| } |
| |
| var 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)); |
| } |
| |
| var 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. |
| var 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) { |
| var invalidationTrackingEvents = TimelineModel.InvalidationTracker.invalidationEventsFor(event); |
| var 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) { |
| var 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; |
| } |
| |
| var invalidationsTreeOutline = new TreeOutlineInShadow(); |
| invalidationsTreeOutline.registerRequiredCSS('timeline/invalidationsTree.css'); |
| invalidationsTreeOutline.element.classList.add('invalidations-tree'); |
| |
| var invalidationGroups = groupInvalidationsByCause(invalidations); |
| invalidationGroups.forEach(function(group) { |
| var 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>>} */ |
| var causeToInvalidationMap = new Map(); |
| for (var index = 0; index < invalidations.length; index++) { |
| var invalidation = invalidations[index]; |
| var 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) { |
| var events = model.inspectedTargetEvents(); |
| /** |
| * @param {number} startTime |
| * @param {!SDK.TracingModel.Event} e |
| * @return {number} |
| */ |
| function eventComparator(startTime, e) { |
| return startTime - e.startTime; |
| } |
| var index = events.binaryIndexOf(event.startTime, eventComparator); |
| // Not a main thread event? |
| if (index < 0) |
| return false; |
| var hasChildren = false; |
| var endTime = event.endTime; |
| if (endTime) { |
| for (var i = index; i < events.length; i++) { |
| var nextEvent = events[i]; |
| if (nextEvent.startTime >= endTime) |
| break; |
| if (!nextEvent.selfTime) |
| continue; |
| if (nextEvent.thread !== event.thread) |
| continue; |
| if (i > index) |
| hasChildren = true; |
| var categoryName = Timeline.TimelineUIUtils.eventStyle(nextEvent).category.name; |
| total[categoryName] = (total[categoryName] || 0) + nextEvent.selfTime; |
| } |
| } |
| if (SDK.TracingModel.isAsyncPhase(event.phase)) { |
| if (event.endTime) { |
| var aggregatedTotal = 0; |
| for (var 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 |
| * @param {function(!Element=)} callback |
| */ |
| static buildPicturePreviewContent(event, target, callback) { |
| new TimelineModel.LayerPaintEvent(event, target).snapshotPromise().then(onSnapshotLoaded); |
| /** |
| * @param {?SDK.SnapshotWithRect} snapshotWithRect |
| */ |
| function onSnapshotLoaded(snapshotWithRect) { |
| if (!snapshotWithRect) { |
| callback(); |
| return; |
| } |
| snapshotWithRect.snapshot.replay(null, null, 1).then(imageURL => onGotImage(imageURL)); |
| snapshotWithRect.snapshot.release(); |
| } |
| |
| /** |
| * @param {?string} imageURL |
| */ |
| function onGotImage(imageURL) { |
| if (!imageURL) { |
| callback(); |
| return; |
| } |
| var container = createElement('div'); |
| container.classList.add('image-preview-container', 'vbox', 'link'); |
| var img = container.createChild('img'); |
| img.src = imageURL; |
| var paintProfilerButton = container.createChild('a'); |
| paintProfilerButton.textContent = Common.UIString('Paint Profiler'); |
| container.addEventListener('click', showPaintProfiler, false); |
| callback(container); |
| } |
| |
| function showPaintProfiler() { |
| Timeline.TimelinePanel.instance().select( |
| Timeline.TimelineSelection.fromTraceEvent(event), Timeline.TimelinePanel.DetailsTab.PaintProfiler); |
| } |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.RecordType} recordType |
| * @param {?string} title |
| * @param {number} position |
| * @return {!Element} |
| */ |
| static createEventDivider(recordType, title, position) { |
| var eventDivider = createElement('div'); |
| eventDivider.className = 'resources-event-divider'; |
| var recordTypes = TimelineModel.TimelineModel.RecordType; |
| |
| if (recordType === recordTypes.MarkDOMContent) |
| eventDivider.className += ' resources-blue-divider'; |
| else if (recordType === recordTypes.MarkLoad) |
| eventDivider.className += ' resources-red-divider'; |
| else if (recordType === recordTypes.MarkFirstPaint) |
| eventDivider.className += ' resources-green-divider'; |
| else if ( |
| recordType === recordTypes.TimeStamp || recordType === recordTypes.ConsoleTime || |
| recordType === recordTypes.UserTiming) |
| eventDivider.className += ' resources-orange-divider'; |
| else if (recordType === recordTypes.BeginFrame) |
| eventDivider.className += ' timeline-frame-divider'; |
| |
| if (title) |
| eventDivider.title = title; |
| eventDivider.style.left = position + 'px'; |
| return eventDivider; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.Record} record |
| * @param {number} zeroTime |
| * @param {number} position |
| * @return {!Element} |
| */ |
| static createDividerForRecord(record, zeroTime, position) { |
| var startTime = Number.millisToString(record.startTime() - zeroTime); |
| var title = |
| Common.UIString('%s at %s', Timeline.TimelineUIUtils.eventTitle(record.traceEvent()), startTime); |
| return Timeline.TimelineUIUtils.createEventDivider(record.type(), title, position); |
| } |
| |
| /** |
| * @return {!Array.<string>} |
| */ |
| static _visibleTypes() { |
| var eventStyles = Timeline.TimelineUIUtils._initEventStyles(); |
| var result = []; |
| for (var name in eventStyles) { |
| if (!eventStyles[name].hidden) |
| result.push(name); |
| } |
| return result; |
| } |
| |
| /** |
| * @return {!TimelineModel.TimelineModel.Filter} |
| */ |
| static visibleEventsFilter() { |
| return new TimelineModel.TimelineVisibleEventsFilter(Timeline.TimelineUIUtils._visibleTypes()); |
| } |
| |
| /** |
| * @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%)'), |
| 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, 100%, 100%)', 'hsl(0, 100%, 100%)') |
| }; |
| return Timeline.TimelineUIUtils._categories; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.AsyncEventGroup} group |
| * @return {string} |
| */ |
| static titleForAsyncEventGroup(group) { |
| if (!Timeline.TimelineUIUtils._titleForAsyncEventGroupMap) { |
| var groups = TimelineModel.TimelineModel.AsyncEventGroup; |
| Timeline.TimelineUIUtils._titleForAsyncEventGroupMap = new Map([ |
| [groups.animation, Common.UIString('Animation')], [groups.console, Common.UIString('Console')], |
| [groups.userTiming, Common.UIString('User Timing')], [groups.input, Common.UIString('Input')] |
| ]); |
| } |
| return Timeline.TimelineUIUtils._titleForAsyncEventGroupMap.get(group) || ''; |
| } |
| |
| /** |
| * @param {!Object} aggregatedStats |
| * @param {!Timeline.TimelineCategory=} selfCategory |
| * @param {number=} selfTime |
| * @return {!Element} |
| */ |
| static generatePieChart(aggregatedStats, selfCategory, selfTime) { |
| var total = 0; |
| for (var categoryName in aggregatedStats) |
| total += aggregatedStats[categoryName]; |
| |
| var element = createElementWithClass('div', 'timeline-details-view-pie-chart-wrapper hbox'); |
| var pieChart = new UI.PieChart(100); |
| pieChart.element.classList.add('timeline-details-view-pie-chart'); |
| pieChart.setTotal(total); |
| var pieChartContainer = element.createChild('div', 'vbox'); |
| pieChartContainer.appendChild(pieChart.element); |
| pieChartContainer.createChild('div', 'timeline-details-view-pie-chart-total').textContent = |
| Common.UIString('Total: %s', Number.millisToString(total, true)); |
| var 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); |
| var 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. |
| var categoryTime = aggregatedStats[selfCategory.name]; |
| var value = categoryTime - selfTime; |
| if (value > 0) |
| appendLegendRow( |
| selfCategory.name, Common.UIString('%s (children)', selfCategory.title), value, |
| selfCategory.childColor); |
| } |
| |
| // Add other categories. |
| for (var categoryName in Timeline.TimelineUIUtils.categories()) { |
| var category = Timeline.TimelineUIUtils.categories()[categoryName]; |
| if (category === selfCategory) |
| continue; |
| appendLegendRow(category.name, category.title, aggregatedStats[category.name], category.childColor); |
| } |
| return element; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineFrameModel} frameModel |
| * @param {!TimelineModel.TimelineFrame} frame |
| * @param {?Components.FilmStripModel.Frame} filmStripFrame |
| * @return {!Element} |
| */ |
| static generateDetailsContentForFrame(frameModel, frame, filmStripFrame) { |
| var pieChart = Timeline.TimelineUIUtils.generatePieChart(frame.timeByCategory); |
| var contentHelper = new Timeline.TimelineDetailsContentHelper(null, null); |
| contentHelper.addSection(Common.UIString('Frame')); |
| |
| var duration = Timeline.TimelineUIUtils.frameDuration(frame); |
| contentHelper.appendElementRow(Common.UIString('Duration'), duration, frame.hasWarnings()); |
| if (filmStripFrame) { |
| var filmStripPreview = createElementWithClass('img', 'timeline-filmstrip-preview'); |
| filmStripFrame.imageDataPromise().then(onGotImageData.bind(null, filmStripPreview)); |
| contentHelper.appendElementRow('', filmStripPreview); |
| filmStripPreview.addEventListener('click', frameClicked.bind(null, filmStripFrame), false); |
| } |
| var 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 (Runtime.experiments.isEnabled('layersPanel') && frame.layerTree) { |
| contentHelper.appendElementRow( |
| Common.UIString('Layer tree'), |
| Components.Linkifier.linkifyUsingRevealer(frame.layerTree, Common.UIString('show'))); |
| } |
| |
| /** |
| * @param {!Element} image |
| * @param {?string} data |
| */ |
| function onGotImageData(image, data) { |
| if (data) |
| image.src = 'data:image/jpg;base64,' + data; |
| } |
| |
| /** |
| * @param {!Components.FilmStripModel.Frame} filmStripFrame |
| */ |
| function frameClicked(filmStripFrame) { |
| new Components.FilmStripView.Dialog(filmStripFrame, 0); |
| } |
| |
| return contentHelper.fragment; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineFrame} frame |
| * @return {!Element} |
| */ |
| static frameDuration(frame) { |
| var durationText = Common.UIString( |
| '%s (at %s)', Number.millisToString(frame.endTime - frame.startTime, true), |
| Number.millisToString(frame.startTimeOffset, true)); |
| var 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.linkifyURLAsNode( |
| 'https://developers.google.com/web/fundamentals/performance/rendering/', Common.UIString('jank'), |
| undefined, true)); |
| 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) { |
| var 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; |
| var lightOrange = 'hsl(40,100%,80%)'; |
| var orange = 'hsl(40,100%,50%)'; |
| var green = 'hsl(90,100%,40%)'; |
| var 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) { |
| var red = 'rgb(255, 0, 0)'; |
| var blue = 'rgb(0, 0, 255)'; |
| var orange = 'rgb(255, 178, 23)'; |
| var green = 'rgb(0, 130, 0)'; |
| var tallMarkerDashStyle = [10, 5]; |
| |
| var 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: orange, |
| tall: false, |
| lowPriority: false, |
| }; |
| } |
| var recordTypes = TimelineModel.TimelineModel.RecordType; |
| var tall = false; |
| var color = green; |
| switch (event.name) { |
| case recordTypes.MarkDOMContent: |
| color = blue; |
| tall = true; |
| break; |
| case recordTypes.MarkLoad: |
| color = red; |
| tall = true; |
| break; |
| case recordTypes.MarkFirstPaint: |
| color = green; |
| 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} url |
| * @return {string} |
| */ |
| static colorForURL(url) { |
| if (!Timeline.TimelineUIUtils.colorForURL._colorGenerator) { |
| Timeline.TimelineUIUtils.colorForURL._colorGenerator = |
| new UI.FlameChart.ColorGenerator({min: 30, max: 330}, {min: 50, max: 80, count: 3}, 85); |
| } |
| return Timeline.TimelineUIUtils.colorForURL._colorGenerator.colorForID(url); |
| } |
| |
| /** |
| * @param {!SDK.TracingModel.Event} event |
| * @param {string=} warningType |
| * @return {?Element} |
| */ |
| static eventWarning(event, warningType) { |
| var timelineData = TimelineModel.TimelineData.forEvent(event); |
| var warning = warningType || timelineData.warning; |
| if (!warning) |
| return null; |
| var warnings = TimelineModel.TimelineModel.WarningType; |
| var span = createElement('span'); |
| var eventData = event.args['data']; |
| |
| switch (warning) { |
| case warnings.ForcedStyle: |
| case warnings.ForcedLayout: |
| span.appendChild(UI.linkifyDocumentationURLAsNode( |
| '../../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.V8Deopt: |
| span.appendChild(UI.linkifyURLAsNode( |
| 'https://github.com/GoogleChrome/devtools-docs/issues/53', Common.UIString('Not optimized'), |
| undefined, true)); |
| 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) { |
| var 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 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) { |
| var first = this._invalidations[0]; |
| var reason = first.cause.reason; |
| var topFrame = first.cause.stackTrace && first.cause.stackTrace[0]; |
| |
| var 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('. ')); |
| var stack = title.createChild('span', 'monospace'); |
| stack.createChild('span').textContent = Timeline.TimelineUIUtils.frameDisplayName(topFrame); |
| var link = this._contentHelper.linkifier().maybeLinkifyConsoleCallFrame(target, topFrame); |
| if (link) { |
| stack.createChild('span').textContent = ' @ '; |
| stack.createChild('span').appendChild(link); |
| } |
| } |
| |
| return title; |
| } |
| |
| /** |
| * @override |
| */ |
| onpopulate() { |
| var content = createElementWithClass('div', 'content'); |
| |
| var first = this._invalidations[0]; |
| if (first.cause.stackTrace) { |
| var 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:')); |
| var nodeList = content.createChild('div', 'node-list'); |
| var firstNode = true; |
| for (var i = 0; i < this._invalidations.length; i++) { |
| var invalidation = this._invalidations[i]; |
| var invalidationNode = this._createInvalidationNode(invalidation, true); |
| if (invalidationNode) { |
| if (!firstNode) |
| nodeList.createTextChild(Common.UIString(', ')); |
| firstNode = false; |
| |
| nodeList.appendChild(invalidationNode); |
| |
| var 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)); |
| } |
| } |
| |
| var contentTreeElement = new TreeElement(content, false); |
| contentTreeElement.selectable = false; |
| this.appendChild(contentTreeElement); |
| } |
| |
| /** |
| * @param {!Element} parentElement |
| * @param {!Array.<!TimelineModel.InvalidationTrackingEvent>} invalidations |
| */ |
| _appendTruncatedNodeList(parentElement, invalidations) { |
| var invalidationNodes = []; |
| var invalidationNodeIdMap = {}; |
| for (var i = 0; i < invalidations.length; i++) { |
| var invalidation = invalidations[i]; |
| var 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) { |
| var node = (invalidation.nodeId && this._relatedNodesMap) ? this._relatedNodesMap.get(invalidation.nodeId) : null; |
| if (node) |
| return Components.DOMPresentationUtils.linkifyNodeReference(node); |
| if (invalidation.nodeName) { |
| var nodeSpan = createElement('span'); |
| nodeSpan.textContent = Common.UIString('[ %s ]', invalidation.nodeName); |
| return nodeSpan; |
| } |
| if (showUnknownNodes) { |
| var 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'); |
| var titleCell = this._createCell(Common.UIString('%s - Details', title), 'timeline-details-title'); |
| titleCell.colSpan = 2; |
| var 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) { |
| var text = createElement('label'); |
| text.createTextChild(String(content)); |
| var 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) { |
| var 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) { |
| var row = createElement('tr'); |
| var titleCell = this._createCell(title, 'timeline-details-row-title'); |
| row.appendChild(titleCell); |
| var 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 {!Timeline.TimelineCategory=} category |
| */ |
| addSection(title, category) { |
| if (!this._tableElement.hasChildNodes()) { |
| this.element.removeChildren(); |
| } else { |
| this.element = createElementWithClass('div', 'timeline-details-view-block'); |
| this.fragment.appendChild(this.element); |
| } |
| |
| if (title) { |
| var titleElement = this.element.createChild('div', 'timeline-details-chip-title'); |
| if (category) |
| titleElement.createChild('div').style.backgroundColor = category.color; |
| 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) { |
| var 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) { |
| var 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'); |
| var titleElement = rowElement.createChild('div', 'timeline-details-view-row-title'); |
| titleElement.textContent = title; |
| var 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; |
| var 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; |
| var locationContent = createElement('span'); |
| var 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; |
| |
| var 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'); |
| var stackTraceElement = |
| parentElement.createChild('div', 'timeline-details-view-row-value timeline-details-view-row-stack-trace'); |
| var callFrameElem = |
| Components.DOMPresentationUtils.buildStackTracePreviewContents(this._target, this._linkifier, stackTrace); |
| stackTraceElement.appendChild(callFrameElem); |
| } |
| |
| /** |
| * @param {!SDK.TracingModel.Event} event |
| * @param {string=} warningType |
| */ |
| appendWarningRow(event, warningType) { |
| var warning = Timeline.TimelineUIUtils.eventWarning(event, warningType); |
| if (warning) |
| this.appendElementRow(Common.UIString('Warning'), warning, true); |
| } |
| }; |