Home | History | Annotate | Download | only in timeline
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  * Copyright (C) 2012 Intel Inc. All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /**
     33  * @constructor
     34  */
     35 WebInspector.TimelineUIUtils = function() { }
     36 
     37 WebInspector.TimelineUIUtils.prototype = {
     38     /**
     39      * @param {!WebInspector.TimelineModel.Record} record
     40      * @return {boolean}
     41      */
     42     isBeginFrame: function(record)
     43     {
     44         throw new Error("Not implemented.");
     45     },
     46     /**
     47      * @param {!WebInspector.TimelineModel.Record} record
     48      * @return {boolean}
     49      */
     50     isProgram: function(record)
     51     {
     52         throw new Error("Not implemented.");
     53     },
     54     /**
     55      * @param {string} recordType
     56      * @return {boolean}
     57      */
     58     isCoalescable: function(recordType)
     59     {
     60         throw new Error("Not implemented.");
     61     },
     62     /**
     63      * @param {!WebInspector.TimelineModel.Record} record
     64      * @return {boolean}
     65      */
     66     isEventDivider: function(record)
     67     {
     68         throw new Error("Not implemented.");
     69     },
     70     /**
     71      * @param {!WebInspector.TimelineModel.Record} record
     72      * @return {?Object}
     73      */
     74     countersForRecord: function(record)
     75     {
     76         throw new Error("Not implemented.");
     77     },
     78     /**
     79      * @param {!WebInspector.TimelineModel.Record} record
     80      * @return {?Object}
     81      */
     82     highlightQuadForRecord: function(record)
     83     {
     84         throw new Error("Not implemented.");
     85     },
     86     /**
     87      * @param {!WebInspector.TimelineModel.Record} record
     88      * @return {string}
     89      */
     90     titleForRecord: function(record)
     91     {
     92         throw new Error("Not implemented.");
     93     },
     94     /**
     95      * @param {!WebInspector.TimelineModel.Record} record
     96      * @return {!WebInspector.TimelineCategory}
     97      */
     98     categoryForRecord: function(record)
     99     {
    100         throw new Error("Not implemented.");
    101     },
    102     /**
    103      * @param {!WebInspector.TimelineModel.Record} record
    104      * @param {!WebInspector.Linkifier} linkifier
    105      * @return {?Node}
    106      */
    107     buildDetailsNode: function(record, linkifier)
    108     {
    109         throw new Error("Not implemented.");
    110     },
    111     /**
    112      * @param {!WebInspector.TimelineModel.Record} record
    113      * @param {!WebInspector.TimelineModel} model
    114      * @param {!WebInspector.Linkifier} linkifier
    115      * @param {function(!DocumentFragment)} callback
    116      */
    117     generateDetailsContent: function(record, model, linkifier, callback)
    118     {
    119         throw new Error("Not implemented.");
    120     },
    121     /**
    122      * @return {!Element}
    123      */
    124     createBeginFrameDivider: function()
    125     {
    126         throw new Error("Not implemented.");
    127     },
    128     /**
    129      * @param {string} recordType
    130      * @param {string=} title
    131      * @return {!Element}
    132      */
    133     createEventDivider: function(recordType, title)
    134     {
    135         throw new Error("Not implemented.");
    136     },
    137     /**
    138      * @param {!WebInspector.TimelineModel.Record} record
    139      * @param {!RegExp} regExp
    140      * @return {boolean}
    141      */
    142     testContentMatching: function(record, regExp)
    143     {
    144         throw new Error("Not implemented.");
    145     },
    146     /**
    147      * @param {!Object} total
    148      * @param {!WebInspector.TimelineModel.Record} record
    149      */
    150     aggregateTimeForRecord: function(total, record)
    151     {
    152         throw new Error("Not implemented.");
    153     },
    154     /**
    155      * @return {!WebInspector.TimelineModel.Filter}
    156      */
    157     hiddenRecordsFilter: function()
    158     {
    159         throw new Error("Not implemented.");
    160     },
    161     /**
    162      * @return {?WebInspector.TimelineModel.Filter}
    163      */
    164     hiddenEmptyRecordsFilter: function()
    165     {
    166         return null;
    167     }
    168 }
    169 
    170 /**
    171  * @return {!Object.<string, !WebInspector.TimelineCategory>}
    172  */
    173 WebInspector.TimelineUIUtils.categories = function()
    174 {
    175     if (WebInspector.TimelineUIUtils._categories)
    176         return WebInspector.TimelineUIUtils._categories;
    177     WebInspector.TimelineUIUtils._categories = {
    178         loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), 0, "hsl(214, 53%, 58%)", "hsl(214, 67%, 90%)", "hsl(214, 67%, 74%)", "hsl(214, 67%, 66%)"),
    179         scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), 1, "hsl(43, 90%, 45%)", "hsl(43, 83%, 90%)", "hsl(43, 83%, 72%)", "hsl(43, 83%, 64%) "),
    180         rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), 2, "hsl(256, 50%, 60%)", "hsl(256, 67%, 90%)", "hsl(256, 67%, 76%)", "hsl(256, 67%, 70%)"),
    181         painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), 2, "hsl(109, 33%, 47%)", "hsl(109, 33%, 90%)", "hsl(109, 33%, 64%)", "hsl(109, 33%, 55%)"),
    182         other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), -1, "hsl(0, 0%, 73%)", "hsl(0, 0%, 90%)", "hsl(0, 0%, 87%)", "hsl(0, 0%, 79%)"),
    183         idle: new WebInspector.TimelineCategory("idle", WebInspector.UIString("Idle"), -1, "hsl(0, 0%, 87%)", "hsl(0, 100%, 100%)", "hsl(0, 100%, 100%)", "hsl(0, 100%, 100%)")
    184     };
    185     return WebInspector.TimelineUIUtils._categories;
    186 };
    187 
    188 /**
    189  * @param {!WebInspector.TimelineModel} model
    190  * @param {!{name: string, tasks: !Array.<!WebInspector.TimelineModel.Record>, firstTaskIndex: number, lastTaskIndex: number}} info
    191  * @return {!Element}
    192  */
    193 WebInspector.TimelineUIUtils.generateMainThreadBarPopupContent = function(model, info)
    194 {
    195     var firstTaskIndex = info.firstTaskIndex;
    196     var lastTaskIndex = info.lastTaskIndex;
    197     var tasks = info.tasks;
    198     var messageCount = lastTaskIndex - firstTaskIndex + 1;
    199     var cpuTime = 0;
    200 
    201     for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) {
    202         var task = tasks[i];
    203         cpuTime += task.endTime() - task.startTime();
    204     }
    205     var startTime = tasks[firstTaskIndex].startTime();
    206     var endTime = tasks[lastTaskIndex].endTime();
    207     var duration = endTime - startTime;
    208 
    209     var contentHelper = new WebInspector.TimelinePopupContentHelper(info.name);
    210     var durationText = WebInspector.UIString("%s (at %s)", Number.millisToString(duration, true),
    211         Number.millisToString(startTime - model.minimumRecordTime(), true));
    212     contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
    213     contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.millisToString(cpuTime, true));
    214     contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount);
    215     return contentHelper.contentTable();
    216 }
    217 
    218 /**
    219  * @param {!Object} aggregatedStats
    220  */
    221 WebInspector.TimelineUIUtils._generateAggregatedInfo = function(aggregatedStats)
    222 {
    223     var cell = document.createElement("span");
    224     cell.className = "timeline-aggregated-info";
    225     for (var index in aggregatedStats) {
    226         var label = document.createElement("div");
    227         label.className = "timeline-aggregated-category timeline-" + index;
    228         cell.appendChild(label);
    229         var text = document.createElement("span");
    230         text.textContent = Number.millisToString(aggregatedStats[index], true);
    231         cell.appendChild(text);
    232     }
    233     return cell;
    234 }
    235 
    236 /**
    237  * @param {!Object} aggregatedStats
    238  * @param {!WebInspector.TimelineCategory=} selfCategory
    239  * @param {number=} selfTime
    240  * @return {!Element}
    241  */
    242 WebInspector.TimelineUIUtils.generatePieChart = function(aggregatedStats, selfCategory, selfTime)
    243 {
    244     var element = document.createElement("div");
    245     element.className = "timeline-aggregated-info";
    246 
    247     var total = 0;
    248     for (var categoryName in aggregatedStats)
    249         total += aggregatedStats[categoryName];
    250 
    251     function formatter(value)
    252     {
    253         return Number.millisToString(value, true);
    254     }
    255     var pieChart = new WebInspector.PieChart(100, formatter);
    256     pieChart.setTotal(total);
    257     element.appendChild(pieChart.element);
    258     var footerElement = element.createChild("div", "timeline-aggregated-info-legend");
    259 
    260     // In case of self time, first add self, then children of the same category.
    261     if (selfCategory && selfTime) {
    262         // Self.
    263         pieChart.addSlice(selfTime, selfCategory.fillColorStop1);
    264         var rowElement = footerElement.createChild("div");
    265         rowElement.createChild("div", "timeline-aggregated-category timeline-" + selfCategory.name);
    266         rowElement.createTextChild(WebInspector.UIString("%s %s (Self)", formatter(selfTime), selfCategory.title));
    267 
    268         // Children of the same category.
    269         var categoryTime = aggregatedStats[selfCategory.name];
    270         var value = categoryTime - selfTime;
    271         if (value > 0) {
    272             pieChart.addSlice(value, selfCategory.fillColorStop0);
    273             rowElement = footerElement.createChild("div");
    274             rowElement.createChild("div", "timeline-aggregated-category timeline-" + selfCategory.name);
    275             rowElement.createTextChild(WebInspector.UIString("%s %s (Children)", formatter(value), selfCategory.title));
    276         }
    277     }
    278 
    279     // Add other categories.
    280     for (var categoryName in WebInspector.TimelineUIUtils.categories()) {
    281         var category = WebInspector.TimelineUIUtils.categories()[categoryName];
    282          if (category === selfCategory)
    283              continue;
    284          var value = aggregatedStats[category.name];
    285          if (!value)
    286              continue;
    287          pieChart.addSlice(value, category.fillColorStop0);
    288          var rowElement = footerElement.createChild("div");
    289          rowElement.createChild("div", "timeline-aggregated-category timeline-" + category.name);
    290          rowElement.createTextChild(WebInspector.UIString("%s %s", formatter(value), category.title));
    291     }
    292     return element;
    293 }
    294 
    295 /**
    296  * @param {!WebInspector.TimelineFrameModel} frameModel
    297  * @param {!WebInspector.TimelineFrame} frame
    298  * @return {!Element}
    299  */
    300 WebInspector.TimelineUIUtils.generateDetailsContentForFrame = function(frameModel, frame)
    301 {
    302     var contentHelper = new WebInspector.TimelineDetailsContentHelper(null, null, true);
    303     var durationInMillis = frame.endTime - frame.startTime;
    304     var durationText = WebInspector.UIString("%s (at %s)", Number.millisToString(frame.endTime - frame.startTime, true),
    305         Number.millisToString(frame.startTimeOffset, true));
    306     contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
    307     contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1000 / durationInMillis));
    308     contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.millisToString(frame.cpuTime, true));
    309     contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
    310         WebInspector.TimelineUIUtils._generateAggregatedInfo(frame.timeByCategory));
    311     if (Runtime.experiments.isEnabled("layersPanel") && frame.layerTree) {
    312         contentHelper.appendElementRow(WebInspector.UIString("Layer tree"),
    313                                        WebInspector.Linkifier.linkifyUsingRevealer(frame.layerTree, WebInspector.UIString("show")));
    314     }
    315     return contentHelper.element;
    316 }
    317 
    318 /**
    319  * @param {!CanvasRenderingContext2D} context
    320  * @param {number} width
    321  * @param {number} height
    322  * @param {string} color0
    323  * @param {string} color1
    324  * @param {string} color2
    325  * @return {!CanvasGradient}
    326  */
    327 WebInspector.TimelineUIUtils.createFillStyle = function(context, width, height, color0, color1, color2)
    328 {
    329     var gradient = context.createLinearGradient(0, 0, width, height);
    330     gradient.addColorStop(0, color0);
    331     gradient.addColorStop(0.25, color1);
    332     gradient.addColorStop(0.75, color1);
    333     gradient.addColorStop(1, color2);
    334     return gradient;
    335 }
    336 
    337 /**
    338  * @param {!CanvasRenderingContext2D} context
    339  * @param {number} width
    340  * @param {number} height
    341  * @param {!WebInspector.TimelineCategory} category
    342  * @return {!CanvasGradient}
    343  */
    344 WebInspector.TimelineUIUtils.createFillStyleForCategory = function(context, width, height, category)
    345 {
    346     return WebInspector.TimelineUIUtils.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor);
    347 }
    348 
    349 /**
    350  * @param {!WebInspector.TimelineCategory} category
    351  * @return {string}
    352  */
    353 WebInspector.TimelineUIUtils.createStyleRuleForCategory = function(category)
    354 {
    355     var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " +
    356         ".panel.timeline .timeline-filters-header .filter-checkbox-filter.filter-checkbox-filter-" + category.name + " .checkbox-filter-checkbox, " +
    357         ".timeline-details-view .timeline-" + category.name + ", " +
    358         ".timeline-category-" + category.name + " .timeline-tree-icon"
    359 
    360     return selector + " { background-image: linear-gradient(" +
    361        category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" +
    362        " border-color: " + category.borderColor +
    363        "}";
    364 }
    365 
    366 /**
    367  * @param {!Array.<number>} quad
    368  * @return {number}
    369  */
    370 WebInspector.TimelineUIUtils.quadWidth = function(quad)
    371 {
    372     return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2)));
    373 }
    374 
    375 /**
    376  * @param {!Array.<number>} quad
    377  * @return {number}
    378  */
    379 WebInspector.TimelineUIUtils.quadHeight = function(quad)
    380 {
    381     return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
    382 }
    383 
    384 /**
    385  * @constructor
    386  * @extends {WebInspector.Object}
    387  * @param {string} name
    388  * @param {string} title
    389  * @param {number} overviewStripGroupIndex
    390  * @param {string} borderColor
    391  * @param {string} backgroundColor
    392  * @param {string} fillColorStop0
    393  * @param {string} fillColorStop1
    394  */
    395 WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, backgroundColor, fillColorStop0, fillColorStop1)
    396 {
    397     this.name = name;
    398     this.title = title;
    399     this.overviewStripGroupIndex = overviewStripGroupIndex;
    400     this.borderColor = borderColor;
    401     this.backgroundColor = backgroundColor;
    402     this.fillColorStop0 = fillColorStop0;
    403     this.fillColorStop1 = fillColorStop1;
    404     this.hidden = false;
    405 }
    406 
    407 WebInspector.TimelineCategory.Events = {
    408     VisibilityChanged: "VisibilityChanged"
    409 };
    410 
    411 WebInspector.TimelineCategory.prototype = {
    412     /**
    413      * @return {boolean}
    414      */
    415     get hidden()
    416     {
    417         return this._hidden;
    418     },
    419 
    420     set hidden(hidden)
    421     {
    422         this._hidden = hidden;
    423         this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this);
    424     },
    425 
    426     __proto__: WebInspector.Object.prototype
    427 }
    428 
    429 /**
    430  * @constructor
    431  * @param {string} title
    432  */
    433 WebInspector.TimelinePopupContentHelper = function(title)
    434 {
    435     this._contentTable = document.createElement("table");
    436     var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
    437     titleCell.colSpan = 2;
    438     var titleRow = document.createElement("tr");
    439     titleRow.appendChild(titleCell);
    440     this._contentTable.appendChild(titleRow);
    441 }
    442 
    443 WebInspector.TimelinePopupContentHelper.prototype = {
    444     /**
    445      * @return {!Element}
    446      */
    447     contentTable: function()
    448     {
    449         return this._contentTable;
    450     },
    451 
    452     /**
    453      * @param {string|number} content
    454      * @param {string=} styleName
    455      */
    456     _createCell: function(content, styleName)
    457     {
    458         var text = document.createElement("label");
    459         text.createTextChild(String(content));
    460         var cell = document.createElement("td");
    461         cell.className = "timeline-details";
    462         if (styleName)
    463             cell.className += " " + styleName;
    464         cell.textContent = content;
    465         return cell;
    466     },
    467 
    468     /**
    469      * @param {string} title
    470      * @param {string|number} content
    471      */
    472     appendTextRow: function(title, content)
    473     {
    474         var row = document.createElement("tr");
    475         row.appendChild(this._createCell(title, "timeline-details-row-title"));
    476         row.appendChild(this._createCell(content, "timeline-details-row-data"));
    477         this._contentTable.appendChild(row);
    478     },
    479 
    480     /**
    481      * @param {string} title
    482      * @param {!Node|string} content
    483      */
    484     appendElementRow: function(title, content)
    485     {
    486         var row = document.createElement("tr");
    487         var titleCell = this._createCell(title, "timeline-details-row-title");
    488         row.appendChild(titleCell);
    489         var cell = document.createElement("td");
    490         cell.className = "details";
    491         if (content instanceof Node)
    492             cell.appendChild(content);
    493         else
    494             cell.createTextChild(content || "");
    495         row.appendChild(cell);
    496         this._contentTable.appendChild(row);
    497     }
    498 }
    499 
    500 /**
    501  * @constructor
    502  * @param {?WebInspector.Target} target
    503  * @param {?WebInspector.Linkifier} linkifier
    504  * @param {boolean} monospaceValues
    505  */
    506 WebInspector.TimelineDetailsContentHelper = function(target, linkifier, monospaceValues)
    507 {
    508     this._linkifier = linkifier;
    509     this._target = target;
    510     this.element = document.createElement("div");
    511     this.element.className = "timeline-details-view-block";
    512     this._monospaceValues = monospaceValues;
    513 }
    514 
    515 WebInspector.TimelineDetailsContentHelper.prototype = {
    516     /**
    517      * @param {string} title
    518      * @param {string|number|boolean} value
    519      */
    520     appendTextRow: function(title, value)
    521     {
    522         var rowElement = this.element.createChild("div", "timeline-details-view-row");
    523         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
    524         rowElement.createChild("span", "timeline-details-view-row-value" + (this._monospaceValues ? " monospace" : "")).textContent = value;
    525     },
    526 
    527     /**
    528      * @param {string} title
    529      * @param {!Node|string} content
    530      */
    531     appendElementRow: function(title, content)
    532     {
    533         var rowElement = this.element.createChild("div", "timeline-details-view-row");
    534         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
    535         var valueElement = rowElement.createChild("span", "timeline-details-view-row-details" + (this._monospaceValues ? " monospace" : ""));
    536         if (content instanceof Node)
    537             valueElement.appendChild(content);
    538         else
    539             valueElement.createTextChild(content || "");
    540     },
    541 
    542     /**
    543      * @param {string} title
    544      * @param {string} url
    545      * @param {number} line
    546      */
    547     appendLocationRow: function(title, url, line)
    548     {
    549         if (!this._linkifier || !this._target)
    550             return;
    551         this.appendElementRow(title, this._linkifier.linkifyScriptLocation(this._target, null, url, line - 1) || "");
    552     },
    553 
    554     /**
    555      * @param {string} title
    556      * @param {!Array.<!ConsoleAgent.CallFrame>} stackTrace
    557      */
    558     appendStackTrace: function(title, stackTrace)
    559     {
    560         if (!this._linkifier || !this._target)
    561             return;
    562 
    563         var rowElement = this.element.createChild("div", "timeline-details-view-row");
    564         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
    565         var stackTraceElement = rowElement.createChild("div", "timeline-details-view-row-stack-trace monospace");
    566 
    567         for (var i = 0; i < stackTrace.length; ++i) {
    568             var stackFrame = stackTrace[i];
    569             var row = stackTraceElement.createChild("div");
    570             row.createTextChild(stackFrame.functionName || WebInspector.UIString("(anonymous function)"));
    571             row.createTextChild(" @ ");
    572             var urlElement = this._linkifier.linkifyScriptLocation(this._target, stackFrame.scriptId, stackFrame.url, stackFrame.lineNumber - 1, stackFrame.columnNumber - 1);
    573             row.appendChild(urlElement);
    574         }
    575     }
    576 }
    577