• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @constructor
7 * @extends {WebInspector.TimelineUIUtils}
8 */
9WebInspector.TimelineUIUtilsImpl = function()
10{
11    WebInspector.TimelineUIUtils.call(this);
12}
13
14WebInspector.TimelineUIUtilsImpl.prototype = {
15    /**
16     * @param {!WebInspector.TimelineModel.Record} record
17     * @return {boolean}
18     */
19    isBeginFrame: function(record)
20    {
21        return record.type() === WebInspector.TimelineModel.RecordType.BeginFrame;
22    },
23    /**
24     * @param {!WebInspector.TimelineModel.Record} record
25     * @return {boolean}
26     */
27    isProgram: function(record)
28    {
29        return record.type() === WebInspector.TimelineModel.RecordType.Program;
30    },
31    /**
32     * @param {string} recordType
33     * @return {boolean}
34     */
35    isCoalescable: function(recordType)
36    {
37        return !!WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[recordType];
38    },
39
40    /**
41     * @param {!WebInspector.TimelineModel.Record} record
42     * @return {boolean}
43     */
44    isEventDivider: function(record)
45    {
46        return WebInspector.TimelineUIUtilsImpl.isEventDivider(record);
47    },
48
49    /**
50     * @param {!WebInspector.TimelineModel.Record} record
51     * @return {?Object}
52     */
53    countersForRecord: function(record)
54    {
55        return record.type() === WebInspector.TimelineModel.RecordType.UpdateCounters ? record.data() : null;
56    },
57
58    /**
59     * @param {!WebInspector.TimelineModel.Record} record
60     * @return {?Object}
61     */
62    highlightQuadForRecord: function(record)
63    {
64        var recordTypes = WebInspector.TimelineModel.RecordType;
65        switch(record.type()) {
66        case recordTypes.Layout:
67            return record.data().root;
68        case recordTypes.Paint:
69            return record.data().clip;
70        default:
71            return null;
72        }
73    },
74
75    /**
76     * @param {!WebInspector.TimelineModel.Record} record
77     * @return {string}
78     */
79    titleForRecord: function(record)
80    {
81        return WebInspector.TimelineUIUtilsImpl.recordTitle(record);
82    },
83
84    /**
85     * @param {!WebInspector.TimelineModel.Record} record
86     * @param {!WebInspector.Linkifier} linkifier
87     * @param {boolean} loadedFromFile
88     * @return {?Node}
89     */
90    buildDetailsNode: function(record, linkifier, loadedFromFile)
91    {
92        return WebInspector.TimelineUIUtilsImpl.buildDetailsNode(record, linkifier, loadedFromFile);
93    },
94
95    /**
96     * @param {!WebInspector.TimelineModel.Record} record
97     * @param {!WebInspector.TimelineModel} model
98     * @param {!WebInspector.Linkifier} linkifier
99     * @param {function(!DocumentFragment)} callback
100     * @param {boolean} loadedFromFile
101     */
102    generateDetailsContent: function(record, model, linkifier, callback, loadedFromFile)
103    {
104        WebInspector.TimelineUIUtilsImpl.generateDetailsContent(record, model, linkifier, callback, loadedFromFile);
105    },
106
107    /**
108     * @return {!Element}
109     */
110    createBeginFrameDivider: function()
111    {
112        return this.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame);
113    },
114
115    /**
116     * @param {string} recordType
117     * @param {string=} title
118     * @return {!Element}
119     */
120    createEventDivider: function(recordType, title)
121    {
122        return WebInspector.TimelineUIUtilsImpl._createEventDivider(recordType, title);
123    },
124
125    /**
126     * @param {!WebInspector.TimelineModel.Record} record
127     * @param {!RegExp} regExp
128     * @return {boolean}
129     */
130    testContentMatching: function(record, regExp)
131    {
132        var tokens = [WebInspector.TimelineUIUtilsImpl.recordTitle(record)];
133        var data = record.data();
134        for (var key in data)
135            tokens.push(data[key])
136        return regExp.test(tokens.join("|"));
137    },
138
139    __proto__: WebInspector.TimelineUIUtils.prototype
140}
141
142
143WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes = {};
144WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.Layout] = 1;
145WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.Paint] = 1;
146WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.Rasterize] = 1;
147WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.DecodeImage] = 1;
148WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.ResizeImage] = 1;
149
150
151/**
152 * @param {!WebInspector.TimelineModel.Record} record
153 * @return {string}
154 */
155WebInspector.TimelineUIUtilsImpl.recordTitle = function(record)
156{
157    var recordData = record.data();
158    if (record.type() === WebInspector.TimelineModel.RecordType.TimeStamp)
159        return recordData["message"];
160    if (record.type() === WebInspector.TimelineModel.RecordType.JSFrame)
161        return recordData["functionName"];
162    if (WebInspector.TimelineUIUtilsImpl.isEventDivider(record)) {
163        var startTime = Number.millisToString(record.startTime() - record._model.minimumRecordTime());
164        return WebInspector.UIString("%s at %s", WebInspector.TimelineUIUtils.recordStyle(record).title, startTime, true);
165    }
166    return WebInspector.TimelineUIUtils.recordStyle(record).title;
167}
168
169/**
170 * @param {!WebInspector.TimelineModel.Record} record
171 * @return {boolean}
172 */
173WebInspector.TimelineUIUtilsImpl.isEventDivider = function(record)
174{
175    var recordTypes = WebInspector.TimelineModel.RecordType;
176    if (record.type() === recordTypes.TimeStamp)
177        return true;
178    if (record.type() === recordTypes.MarkFirstPaint)
179        return true;
180    if (record.type() === recordTypes.MarkDOMContent || record.type() === recordTypes.MarkLoad)
181        return record.data()["isMainFrame"];
182    return false;
183}
184
185/**
186 * @param {!WebInspector.TimelineModel.Record} record
187 * @param {!WebInspector.Linkifier} linkifier
188 * @param {boolean} loadedFromFile
189 * @return {?Node}
190 */
191WebInspector.TimelineUIUtilsImpl.buildDetailsNode = function(record, linkifier, loadedFromFile)
192{
193    var details;
194    var detailsText;
195    var recordData = record.data();
196    switch (record.type()) {
197    case WebInspector.TimelineModel.RecordType.GCEvent:
198        detailsText = WebInspector.UIString("%s collected", Number.bytesToString(recordData["usedHeapSizeDelta"]));
199        break;
200    case WebInspector.TimelineModel.RecordType.TimerFire:
201        detailsText = recordData["timerId"];
202        break;
203    case WebInspector.TimelineModel.RecordType.FunctionCall:
204        details = linkifyLocation(recordData["scriptId"], recordData["scriptName"], recordData["scriptLine"], 0);
205        break;
206    case WebInspector.TimelineModel.RecordType.FireAnimationFrame:
207        detailsText = recordData["id"];
208        break;
209    case WebInspector.TimelineModel.RecordType.EventDispatch:
210        detailsText = recordData ? recordData["type"] : null;
211        break;
212    case WebInspector.TimelineModel.RecordType.Paint:
213        var width = WebInspector.TimelineUIUtils._quadWidth(recordData.clip);
214        var height = WebInspector.TimelineUIUtils._quadHeight(recordData.clip);
215        if (width && height)
216            detailsText = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height);
217        break;
218    case WebInspector.TimelineModel.RecordType.TimerInstall:
219    case WebInspector.TimelineModel.RecordType.TimerRemove:
220        details = linkifyTopCallFrame();
221        detailsText = recordData["timerId"];
222        break;
223    case WebInspector.TimelineModel.RecordType.RequestAnimationFrame:
224    case WebInspector.TimelineModel.RecordType.CancelAnimationFrame:
225        details = linkifyTopCallFrame();
226        detailsText = recordData["id"];
227        break;
228    case WebInspector.TimelineModel.RecordType.ParseHTML:
229    case WebInspector.TimelineModel.RecordType.RecalculateStyles:
230        details = linkifyTopCallFrame();
231        break;
232    case WebInspector.TimelineModel.RecordType.EvaluateScript:
233        var url = recordData["url"];
234        if (url)
235            details = linkifyLocation("", url, recordData["lineNumber"], 0);
236        break;
237    case WebInspector.TimelineModel.RecordType.XHRReadyStateChange:
238    case WebInspector.TimelineModel.RecordType.XHRLoad:
239    case WebInspector.TimelineModel.RecordType.ResourceSendRequest:
240    case WebInspector.TimelineModel.RecordType.DecodeImage:
241    case WebInspector.TimelineModel.RecordType.ResizeImage:
242        var url = recordData["url"];
243        if (url)
244            detailsText = WebInspector.displayNameForURL(url);
245        break;
246    case WebInspector.TimelineModel.RecordType.ResourceReceivedData:
247    case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse:
248    case WebInspector.TimelineModel.RecordType.ResourceFinish:
249        var initiator = record.initiator();
250        if (initiator) {
251            var url = initiator.data()["url"];
252            if (url)
253                detailsText = WebInspector.displayNameForURL(url);
254        }
255        break;
256    case WebInspector.TimelineModel.RecordType.ConsoleTime:
257        detailsText = recordData["message"];
258        break;
259    case WebInspector.TimelineModel.RecordType.EmbedderCallback:
260        detailsText = recordData["callbackName"];
261        break;
262    default:
263        details = linkifyTopCallFrame();
264        break;
265    }
266
267    if (!details && detailsText)
268        details = document.createTextNode(detailsText);
269    return details;
270
271    /**
272     * @param {string} scriptId
273     * @param {string} url
274     * @param {number} lineNumber
275     * @param {number=} columnNumber
276     */
277    function linkifyLocation(scriptId, url, lineNumber, columnNumber)
278    {
279        if (!loadedFromFile && scriptId !== "0") {
280            var location = new WebInspector.DebuggerModel.Location(
281                record.target(),
282                scriptId,
283                lineNumber - 1,
284                (columnNumber || 1) - 1);
285            return linkifier.linkifyRawLocation(location, "timeline-details");
286        }
287
288        if (!url)
289            return null;
290
291        // FIXME(62725): stack trace line/column numbers are one-based.
292        columnNumber = columnNumber ? columnNumber - 1 : 0;
293        return linkifier.linkifyLocation(record.target(), url, lineNumber - 1, columnNumber, "timeline-details");
294    }
295
296    /**
297     * @param {!ConsoleAgent.CallFrame} callFrame
298     */
299    function linkifyCallFrame(callFrame)
300    {
301        return linkifyLocation(callFrame.scriptId, callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
302    }
303
304    /**
305     * @return {?Element}
306     */
307    function linkifyTopCallFrame()
308    {
309        if (record.stackTrace())
310            return linkifyCallFrame(record.stackTrace()[0]);
311        if (record.callSiteStackTrace())
312            return linkifyCallFrame(record.callSiteStackTrace()[0]);
313        return null;
314    }
315}
316
317/**
318 * @param {string=} recordType
319 * @return {boolean}
320 */
321WebInspector.TimelineUIUtilsImpl._needsPreviewElement = function(recordType)
322{
323    if (!recordType)
324        return false;
325    const recordTypes = WebInspector.TimelineModel.RecordType;
326    switch (recordType) {
327    case recordTypes.ResourceSendRequest:
328    case recordTypes.ResourceReceiveResponse:
329    case recordTypes.ResourceReceivedData:
330    case recordTypes.ResourceFinish:
331        return true;
332    default:
333        return false;
334    }
335}
336
337/**
338 * @param {!WebInspector.TimelineModel.Record} record
339 * @param {!WebInspector.TimelineModel} model
340 * @param {!WebInspector.Linkifier} linkifier
341 * @param {function(!DocumentFragment)} callback
342 * @param {boolean} loadedFromFile
343 */
344WebInspector.TimelineUIUtilsImpl.generateDetailsContent = function(record, model, linkifier, callback, loadedFromFile)
345{
346    var imageElement = /** @type {?Element} */ (record.getUserObject("TimelineUIUtils::preview-element") || null);
347    var relatedNode = null;
348    var recordData = record.data();
349    var barrier = new CallbackBarrier();
350    if (!imageElement && WebInspector.TimelineUIUtilsImpl._needsPreviewElement(record.type()))
351        WebInspector.DOMPresentationUtils.buildImagePreviewContents(record.target(), recordData["url"], false, barrier.createCallback(saveImage));
352    if (recordData["backendNodeId"])
353        record.target().domModel.pushNodesByBackendIdsToFrontend([recordData["backendNodeId"]], barrier.createCallback(setRelatedNode));
354    barrier.callWhenDone(callbackWrapper);
355
356    /**
357     * @param {!Element=} element
358     */
359    function saveImage(element)
360    {
361        imageElement = element || null;
362        record.setUserObject("TimelineUIUtils::preview-element", element);
363    }
364
365    /**
366     * @param {?Array.<!DOMAgent.NodeId>} nodeIds
367     */
368    function setRelatedNode(nodeIds)
369    {
370        if (nodeIds)
371            relatedNode = record.target().domModel.nodeForId(nodeIds[0]);
372    }
373
374    function callbackWrapper()
375    {
376        callback(WebInspector.TimelineUIUtilsImpl._generateDetailsContentSynchronously(record, model, linkifier, imageElement, relatedNode, loadedFromFile));
377    }
378}
379
380/**
381 * @param {!WebInspector.TimelineModel.Record} record
382 * @param {!WebInspector.TimelineModel} model
383 * @param {!WebInspector.Linkifier} linkifier
384 * @param {?Element} imagePreviewElement
385 * @param {?WebInspector.DOMNode} relatedNode
386 * @param {boolean} loadedFromFile
387 * @return {!DocumentFragment}
388 */
389WebInspector.TimelineUIUtilsImpl._generateDetailsContentSynchronously = function(record, model, linkifier, imagePreviewElement, relatedNode, loadedFromFile)
390{
391    var fragment = document.createDocumentFragment();
392    if (record.children().length)
393        fragment.appendChild(WebInspector.TimelineUIUtils.generatePieChart(record.aggregatedStats(), record.category(), record.selfTime()));
394    else
395        fragment.appendChild(WebInspector.TimelineUIUtils.generatePieChart(record.aggregatedStats()));
396
397    const recordTypes = WebInspector.TimelineModel.RecordType;
398
399    // The messages may vary per record.type();
400    var callSiteStackTraceLabel;
401    var callStackLabel;
402    var relatedNodeLabel;
403
404    var contentHelper = new WebInspector.TimelineDetailsContentHelper(record.target(), linkifier, true);
405    contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.millisToString(record.selfTime(), true));
406    contentHelper.appendTextRow(WebInspector.UIString("Start Time"), Number.millisToString(record.startTime() - model.minimumRecordTime()));
407    var recordData = record.data();
408
409    switch (record.type()) {
410        case recordTypes.GCEvent:
411            contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(recordData["usedHeapSizeDelta"]));
412            break;
413        case recordTypes.TimerFire:
414            callSiteStackTraceLabel = WebInspector.UIString("Timer installed");
415            // Fall-through intended.
416
417        case recordTypes.TimerInstall:
418        case recordTypes.TimerRemove:
419            contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), recordData["timerId"]);
420            if (record.type() === recordTypes.TimerInstall) {
421                contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.millisToString(recordData["timeout"]));
422                contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !recordData["singleShot"]);
423            }
424            break;
425        case recordTypes.FireAnimationFrame:
426            callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested");
427            contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), recordData["id"]);
428            break;
429        case recordTypes.FunctionCall:
430            if (recordData["scriptName"])
431                contentHelper.appendLocationRow(WebInspector.UIString("Location"), recordData["scriptName"], recordData["scriptLine"]);
432            break;
433        case recordTypes.ResourceSendRequest:
434        case recordTypes.ResourceReceiveResponse:
435        case recordTypes.ResourceReceivedData:
436        case recordTypes.ResourceFinish:
437            var url;
438            if (record.type() === recordTypes.ResourceSendRequest)
439                url = recordData["url"];
440            else if (record.initiator())
441                url = record.initiator().data()["url"];
442            if (url)
443                contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(url));
444            if (imagePreviewElement)
445                contentHelper.appendElementRow(WebInspector.UIString("Preview"), imagePreviewElement);
446            if (recordData["requestMethod"])
447                contentHelper.appendTextRow(WebInspector.UIString("Request Method"), recordData["requestMethod"]);
448            if (typeof recordData["statusCode"] === "number")
449                contentHelper.appendTextRow(WebInspector.UIString("Status Code"), recordData["statusCode"]);
450            if (recordData["mimeType"])
451                contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), recordData["mimeType"]);
452            if (recordData["encodedDataLength"])
453                contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", recordData["encodedDataLength"]));
454            break;
455        case recordTypes.EvaluateScript:
456            var url = recordData["url"];
457            if (url)
458                contentHelper.appendLocationRow(WebInspector.UIString("Script"), url, recordData["lineNumber"]);
459            break;
460        case recordTypes.Paint:
461            var clip = recordData["clip"];
462            contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1]));
463            var clipWidth = WebInspector.TimelineUIUtils._quadWidth(clip);
464            var clipHeight = WebInspector.TimelineUIUtils._quadHeight(clip);
465            contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", clipWidth, clipHeight));
466            // Fall-through intended.
467
468        case recordTypes.PaintSetup:
469        case recordTypes.Rasterize:
470        case recordTypes.ScrollLayer:
471            relatedNodeLabel = WebInspector.UIString("Layer root");
472            break;
473        case recordTypes.DecodeImage:
474        case recordTypes.ResizeImage:
475            relatedNodeLabel = WebInspector.UIString("Image element");
476            var url = recordData["url"];
477            if (url)
478                contentHelper.appendElementRow(WebInspector.UIString("Image URL"), WebInspector.linkifyResourceAsNode(url));
479            break;
480        case recordTypes.RecalculateStyles: // We don't want to see default details.
481            if (recordData["elementCount"])
482                contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), recordData["elementCount"]);
483            callStackLabel = WebInspector.UIString("Styles recalculation forced");
484            break;
485        case recordTypes.Layout:
486            if (recordData["dirtyObjects"])
487                contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), recordData["dirtyObjects"]);
488            if (recordData["totalObjects"])
489                contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), recordData["totalObjects"]);
490            if (typeof recordData["partialLayout"] === "boolean") {
491                contentHelper.appendTextRow(WebInspector.UIString("Layout scope"),
492                   recordData["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document"));
493            }
494            callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated");
495            callStackLabel = WebInspector.UIString("Layout forced");
496            relatedNodeLabel = WebInspector.UIString("Layout root");
497            break;
498        case recordTypes.ConsoleTime:
499            contentHelper.appendTextRow(WebInspector.UIString("Message"), recordData["message"]);
500            break;
501        case recordTypes.WebSocketCreate:
502        case recordTypes.WebSocketSendHandshakeRequest:
503        case recordTypes.WebSocketReceiveHandshakeResponse:
504        case recordTypes.WebSocketDestroy:
505            var initiatorData = record.initiator() ? record.initiator().data() : recordData;
506            if (typeof initiatorData["webSocketURL"] !== "undefined")
507                contentHelper.appendTextRow(WebInspector.UIString("URL"), initiatorData["webSocketURL"]);
508            if (typeof initiatorData["webSocketProtocol"] !== "undefined")
509                contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), initiatorData["webSocketProtocol"]);
510            if (typeof recordData["message"] !== "undefined")
511                contentHelper.appendTextRow(WebInspector.UIString("Message"), recordData["message"]);
512            break;
513        case recordTypes.EmbedderCallback:
514            contentHelper.appendTextRow(WebInspector.UIString("Callback Function"), recordData["callbackName"]);
515            break;
516        default:
517            var detailsNode = WebInspector.TimelineUIUtilsImpl.buildDetailsNode(record, linkifier, loadedFromFile);
518            if (detailsNode)
519                contentHelper.appendElementRow(WebInspector.UIString("Details"), detailsNode);
520            break;
521    }
522
523    if (relatedNode)
524        contentHelper.appendElementRow(relatedNodeLabel || WebInspector.UIString("Related node"), WebInspector.DOMPresentationUtils.linkifyNodeReference(relatedNode));
525
526    if (recordData["scriptName"] && record.type() !== recordTypes.FunctionCall)
527        contentHelper.appendLocationRow(WebInspector.UIString("Function Call"), recordData["scriptName"], recordData["scriptLine"]);
528    var callSiteStackTrace = record.callSiteStackTrace();
529    if (callSiteStackTrace)
530        contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), callSiteStackTrace);
531    var recordStackTrace = record.stackTrace();
532    if (recordStackTrace)
533        contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), recordStackTrace);
534
535    if (record.warnings()) {
536        var ul = document.createElement("ul");
537        for (var i = 0; i < record.warnings().length; ++i)
538            ul.createChild("li").textContent = record.warnings()[i];
539        contentHelper.appendElementRow(WebInspector.UIString("Warning"), ul);
540    }
541    fragment.appendChild(contentHelper.element);
542    return fragment;
543}
544
545/**
546 * @param {string} recordType
547 * @param {string=} title
548 * @return {!Element}
549 */
550WebInspector.TimelineUIUtilsImpl._createEventDivider = function(recordType, title)
551{
552    var eventDivider = document.createElement("div");
553    eventDivider.className = "resources-event-divider";
554    var recordTypes = WebInspector.TimelineModel.RecordType;
555
556    if (recordType === recordTypes.MarkDOMContent)
557        eventDivider.className += " resources-blue-divider";
558    else if (recordType === recordTypes.MarkLoad)
559        eventDivider.className += " resources-red-divider";
560    else if (recordType === recordTypes.MarkFirstPaint)
561        eventDivider.className += " resources-green-divider";
562    else if (recordType === recordTypes.TimeStamp)
563        eventDivider.className += " resources-orange-divider";
564    else if (recordType === recordTypes.BeginFrame)
565        eventDivider.className += " timeline-frame-divider";
566
567    if (title)
568        eventDivider.title = title;
569
570    return eventDivider;
571}
572