1/* 2 * Copyright (C) 2014 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.TimelineDataGridNode = function(graphOnly, graphDataSource, hasChildren) 27{ 28 WebInspector.DataGridNode.call(this, {}, hasChildren); 29 30 this._graphOnly = graphOnly || false; 31 this._graphDataSource = graphDataSource || null; 32 33 if (graphDataSource) { 34 this._graphContainerElement = document.createElement("div"); 35 this._timelineRecordBars = []; 36 } 37}; 38 39WebInspector.Object.addConstructorFunctions(WebInspector.TimelineDataGridNode); 40 41WebInspector.TimelineDataGridNode.prototype = { 42 constructor: WebInspector.TimelineDataGridNode, 43 __proto__: WebInspector.DataGridNode.prototype, 44 45 // Public 46 47 get records() 48 { 49 // Implemented by subclasses. 50 return []; 51 }, 52 53 get graphDataSource() 54 { 55 return this._graphDataSource; 56 }, 57 58 get data() 59 { 60 if (!this._graphDataSource) 61 return {}; 62 63 var records = this.records || []; 64 return {graph: records.length ? records[0].startTime : 0}; 65 }, 66 67 collapse: function() 68 { 69 WebInspector.DataGridNode.prototype.collapse.call(this); 70 71 if (!this._graphDataSource || !this.revealed) 72 return; 73 74 // Refresh to show child bars in our graph now that we collapsed. 75 this.refreshGraph(); 76 }, 77 78 expand: function() 79 { 80 WebInspector.DataGridNode.prototype.expand.call(this); 81 82 if (!this._graphDataSource || !this.revealed) 83 return; 84 85 // Refresh to remove child bars from our graph now that we expanded. 86 this.refreshGraph(); 87 88 // Refresh child graphs since they haven't been updating while we were collapsed. 89 var childNode = this.children[0]; 90 while (childNode) { 91 if (childNode instanceof WebInspector.TimelineDataGridNode) 92 childNode.refreshGraph(); 93 childNode = childNode.traverseNextNode(true, this); 94 } 95 }, 96 97 createCellContent: function(columnIdentifier, cell) 98 { 99 if (columnIdentifier === "graph" && this._graphDataSource) { 100 this.needsGraphRefresh(); 101 return this._graphContainerElement; 102 } 103 104 var value = this.data[columnIdentifier]; 105 if (!value) 106 return "\u2014"; 107 108 if (value instanceof WebInspector.SourceCodeLocation) { 109 if (value.sourceCode instanceof WebInspector.Resource) { 110 cell.classList.add(WebInspector.ResourceTreeElement.ResourceIconStyleClassName); 111 cell.classList.add(value.sourceCode.type); 112 } else if (value.sourceCode instanceof WebInspector.Script) { 113 if (value.sourceCode.url) { 114 cell.classList.add(WebInspector.ResourceTreeElement.ResourceIconStyleClassName); 115 cell.classList.add(WebInspector.Resource.Type.Script); 116 } else 117 cell.classList.add(WebInspector.ScriptTreeElement.AnonymousScriptIconStyleClassName); 118 } else 119 console.error("Unknown SourceCode subclass."); 120 121 // Give the whole cell a tooltip and keep it up to date. 122 value.populateLiveDisplayLocationTooltip(cell); 123 124 var fragment = document.createDocumentFragment(); 125 126 var goToArrowButtonLink = WebInspector.createSourceCodeLocationLink(value, false, true); 127 fragment.appendChild(goToArrowButtonLink); 128 129 var icon = document.createElement("div"); 130 icon.className = WebInspector.ScriptTimelineDataGridNode.IconStyleClassName; 131 fragment.appendChild(icon); 132 133 var titleElement = document.createElement("span"); 134 value.populateLiveDisplayLocationString(titleElement, "textContent"); 135 fragment.appendChild(titleElement); 136 137 return fragment; 138 } 139 140 if (value instanceof WebInspector.CallFrame) { 141 var callFrame = value; 142 143 var isAnonymousFunction = false; 144 var functionName = callFrame.functionName; 145 if (!functionName) { 146 functionName = WebInspector.UIString("(anonymous function)"); 147 isAnonymousFunction = true; 148 } 149 150 cell.classList.add(WebInspector.CallFrameTreeElement.FunctionIconStyleClassName); 151 152 var fragment = document.createDocumentFragment(); 153 154 if (callFrame.sourceCodeLocation && callFrame.sourceCodeLocation.sourceCode) { 155 // Give the whole cell a tooltip and keep it up to date. 156 callFrame.sourceCodeLocation.populateLiveDisplayLocationTooltip(cell); 157 158 var goToArrowButtonLink = WebInspector.createSourceCodeLocationLink(callFrame.sourceCodeLocation, false, true); 159 fragment.appendChild(goToArrowButtonLink); 160 161 var icon = document.createElement("div"); 162 icon.className = WebInspector.LayoutTimelineDataGridNode.IconStyleClassName; 163 fragment.appendChild(icon); 164 165 if (isAnonymousFunction) { 166 // For anonymous functions we show the resource or script icon and name. 167 if (callFrame.sourceCodeLocation.sourceCode instanceof WebInspector.Resource) { 168 cell.classList.add(WebInspector.ResourceTreeElement.ResourceIconStyleClassName); 169 cell.classList.add(callFrame.sourceCodeLocation.sourceCode.type); 170 } else if (callFrame.sourceCodeLocation.sourceCode instanceof WebInspector.Script) { 171 if (callFrame.sourceCodeLocation.sourceCode.url) { 172 cell.classList.add(WebInspector.ResourceTreeElement.ResourceIconStyleClassName); 173 cell.classList.add(WebInspector.Resource.Type.Script); 174 } else 175 cell.classList.add(WebInspector.ScriptTreeElement.AnonymousScriptIconStyleClassName); 176 } else 177 console.error("Unknown SourceCode subclass."); 178 179 var titleElement = document.createElement("span"); 180 callFrame.sourceCodeLocation.populateLiveDisplayLocationString(titleElement, "textContent"); 181 182 fragment.appendChild(titleElement); 183 } else { 184 // Show the function name and icon. 185 cell.classList.add(WebInspector.CallFrameTreeElement.FunctionIconStyleClassName); 186 187 fragment.appendChild(document.createTextNode(functionName)); 188 189 var subtitleElement = document.createElement("span"); 190 subtitleElement.className = WebInspector.LayoutTimelineDataGridNode.SubtitleStyleClassName; 191 callFrame.sourceCodeLocation.populateLiveDisplayLocationString(subtitleElement, "textContent"); 192 193 fragment.appendChild(subtitleElement); 194 } 195 196 return fragment; 197 } 198 199 var icon = document.createElement("div"); 200 icon.className = WebInspector.LayoutTimelineDataGridNode.IconStyleClassName; 201 fragment.appendChild(icon); 202 203 fragment.appendChild(document.createTextNode(functionName)); 204 205 return fragment; 206 } 207 208 return WebInspector.DataGridNode.prototype.createCellContent.call(this, columnIdentifier, cell); 209 }, 210 211 refresh: function() 212 { 213 if (this._graphDataSource && this._graphOnly) { 214 this.needsGraphRefresh(); 215 return; 216 } 217 218 WebInspector.DataGridNode.prototype.refresh.call(this); 219 }, 220 221 refreshGraph: function() 222 { 223 if (!this._graphDataSource) 224 return; 225 226 if (this._scheduledGraphRefreshIdentifier) { 227 cancelAnimationFrame(this._scheduledGraphRefreshIdentifier); 228 delete this._scheduledGraphRefreshIdentifier; 229 } 230 231 // We are not visible, but an ancestor will draw our graph. 232 // They need notified by using our needsGraphRefresh. 233 console.assert(this.revealed); 234 if (!this.revealed) 235 return; 236 237 var secondsPerPixel = this._graphDataSource.secondsPerPixel; 238 console.assert(isFinite(secondsPerPixel) && secondsPerPixel > 0); 239 240 var recordBarIndex = 0; 241 242 function createBar(records, renderMode) 243 { 244 var timelineRecordBar = this._timelineRecordBars[recordBarIndex]; 245 if (!timelineRecordBar) 246 timelineRecordBar = this._timelineRecordBars[recordBarIndex] = new WebInspector.TimelineRecordBar(records, renderMode); 247 else { 248 timelineRecordBar.renderMode = renderMode; 249 timelineRecordBar.records = records; 250 } 251 timelineRecordBar.refresh(this._graphDataSource); 252 if (!timelineRecordBar.element.parentNode) 253 this._graphContainerElement.appendChild(timelineRecordBar.element); 254 ++recordBarIndex; 255 } 256 257 var boundCreateBar = createBar.bind(this); 258 259 if (this.expanded) { 260 // When expanded just use the records for this node. 261 WebInspector.TimelineRecordBar.createCombinedBars(this.records, secondsPerPixel, this._graphDataSource, boundCreateBar); 262 } else { 263 // When collapsed use the records for this node and its descendants. 264 // To share bars better, group records by type. 265 266 var recordTypeMap = new Map; 267 268 function collectRecordsByType(records) 269 { 270 for (var record of records) { 271 var typedRecords = recordTypeMap.get(record.type); 272 if (!typedRecords) { 273 typedRecords = []; 274 recordTypeMap.set(record.type, typedRecords); 275 } 276 277 typedRecords.push(record); 278 } 279 } 280 281 collectRecordsByType(this.records); 282 283 var childNode = this.children[0]; 284 while (childNode) { 285 if (childNode instanceof WebInspector.TimelineDataGridNode) 286 collectRecordsByType(childNode.records); 287 childNode = childNode.traverseNextNode(false, this); 288 } 289 290 for (var records of recordTypeMap.values()) 291 WebInspector.TimelineRecordBar.createCombinedBars(records, secondsPerPixel, this._graphDataSource, boundCreateBar); 292 } 293 294 // Remove the remaining unused TimelineRecordBars. 295 for (; recordBarIndex < this._timelineRecordBars.length; ++recordBarIndex) { 296 this._timelineRecordBars[recordBarIndex].records = null; 297 this._timelineRecordBars[recordBarIndex].element.remove(); 298 } 299 }, 300 301 needsGraphRefresh: function() 302 { 303 if (!this.revealed) { 304 // We are not visible, but an ancestor will be drawing our graph. 305 // Notify the next visible ancestor that their graph needs to refresh. 306 var ancestor = this; 307 while (ancestor && !ancestor.root) { 308 if (ancestor.revealed && ancestor instanceof WebInspector.TimelineDataGridNode) { 309 ancestor.needsGraphRefresh(); 310 return; 311 } 312 313 ancestor = ancestor.parent; 314 } 315 316 return; 317 } 318 319 if (!this._graphDataSource || this._scheduledGraphRefreshIdentifier) 320 return; 321 322 this._scheduledGraphRefreshIdentifier = requestAnimationFrame(this.refreshGraph.bind(this)); 323 }, 324 325 // Protected 326 327 isRecordVisible: function(record) 328 { 329 if (!this._graphDataSource) 330 return false; 331 332 if (isNaN(record.startTime)) 333 return false; 334 335 // If this bar is completely before the bounds of the graph, not visible. 336 if (record.endTime < this.graphDataSource.startTime) 337 return false; 338 339 // If this record is completely after the current time or end time, not visible. 340 if (record.startTime > this.graphDataSource.currentTime || record.startTime > this.graphDataSource.endTime) 341 return false; 342 343 return true; 344 } 345}; 346