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<include src="../../../../third_party/polymer_legacy/platform/platform.js"> 6<include src="../../../../third_party/polymer_legacy/polymer/polymer.js"> 7 8/** 9 * Formats size to a human readable form. 10 * @param {number} size Size in bytes. 11 * @return {string} Output string in a human-readable format. 12 */ 13function formatSizeCommon(size) { 14 if (size < 1024) 15 return size + ' B'; 16 if (size < 1024 * 1024) 17 return Math.round(size / 1024) + ' KB'; 18 return Math.round(size / 1024 / 1024) + ' MB'; 19} 20 21// Defines the file-systems element. 22Polymer('file-systems', { 23 /** 24 * Called when the element is created. 25 */ 26 ready: function() { 27 }, 28 29 /** 30 * Selects an active file system from the list. 31 * @param {Event} event Event. 32 * @param {number} detail Detail. 33 * @param {HTMLElement} sender Sender. 34 */ 35 rowClicked: function(event, detail, sender) { 36 var requestEventsNode = document.querySelector('#request-events'); 37 requestEventsNode.hidden = false; 38 requestEventsNode.model = []; 39 40 var requestTimelineNode = document.querySelector('#request-timeline'); 41 requestTimelineNode.hidden = false; 42 requestTimelineNode.model = []; 43 44 chrome.send('selectFileSystem', [sender.dataset.extensionId, 45 sender.dataset.id]); 46 }, 47 48 /** 49 * List of provided file system information maps. 50 * @type {Array.<Object>} 51 */ 52 model: [] 53}); 54 55// Defines the request-log element. 56Polymer('request-events', { 57 /** 58 * Called when the element is created. 59 */ 60 ready: function() { 61 }, 62 63 /** 64 * Formats time to a hh:mm:ss.xxxx format. 65 * @param {Date} time Input time. 66 * @return {string} Output string in a human-readable format. 67 */ 68 formatTime: function(time) { 69 return ('0' + time.getHours()).slice(-2) + ':' + 70 ('0' + time.getMinutes()).slice(-2) + ':' + 71 ('0' + time.getSeconds()).slice(-2) + '.' + 72 ('000' + time.getMilliseconds()).slice(-3); 73 }, 74 75 /** 76 * Formats size to a human readable form. 77 * @param {number} size Size in bytes. 78 * @return {string} Output string in a human-readable format. 79 */ 80 formatSize: function(size) { 81 return formatSizeCommon(size); 82 }, 83 84 /** 85 * Formats a boolean value to human-readable form. 86 * @param {boolean=} opt_hasMore Input value. 87 * @return {string} Output string in a human-readable format. 88 */ 89 formatHasMore: function(opt_hasMore) { 90 if (opt_hasMore == undefined) 91 return ''; 92 93 return opt_hasMore ? 'HAS_MORE' : 'LAST'; 94 }, 95 96 /** 97 * Formats execution time to human-readable form. 98 * @param {boolean=} opt_executionTime Input value. 99 * @return {string} Output string in a human-readable format. 100 */ 101 formatExecutionTime: function(opt_executionTime) { 102 if (opt_executionTime == undefined) 103 return ''; 104 105 return opt_executionTime + ' ms'; 106 }, 107 108 /** 109 * List of events. 110 * @type {Array.<Object>} 111 */ 112 model: [] 113}); 114 115// Defines the request-timeline element. 116Polymer('request-timeline', { 117 /** 118 * Step for zoomin in and out. 119 * @type {number} 120 * @const 121 */ 122 SCALE_STEP: 1.5, 123 124 /** 125 * Height of each row in the chart in pixels. 126 * @type {number} 127 * @const 128 */ 129 ROW_HEIGHT: 14, 130 131 /** 132 * Observes changes in the model. 133 * @type {Object.<string, string>} 134 */ 135 observe: { 136 'model.length': 'chartUpdate' 137 }, 138 139 /** 140 * Called when the element is created. 141 */ 142 ready: function() { 143 // Update active requests in the background for nice animation. 144 var activeUpdateAnimation = function() { 145 this.activeUpdate(); 146 requestAnimationFrame(activeUpdateAnimation); 147 }.bind(this); 148 activeUpdateAnimation(); 149 }, 150 151 /** 152 * Formats size to a human readable form. 153 * @param {number} size Size in bytes. 154 * @return {string} Output string in a human-readable format. 155 */ 156 formatSize: function(size) { 157 return formatSizeCommon(size); 158 }, 159 160 /** 161 * Zooms in the timeline. 162 * @param {Event} event Event. 163 * @param {number} detail Detail. 164 * @param {HTMLElement} sender Sender. 165 */ 166 zoomInClicked: function(event, detail, sender) { 167 this.scale *= this.SCALE_STEP; 168 }, 169 170 /** 171 * Zooms out the timeline. 172 * @param {Event} event Event. 173 * @param {number} detail Detail. 174 * @param {HTMLElement} sender Sender. 175 */ 176 zoomOutClicked: function(event, detail, sender) { 177 this.scale /= this.SCALE_STEP; 178 }, 179 180 /** 181 * Selects or deselects an element on the timeline. 182 * @param {Event} event Event. 183 * @param {number} detail Detail. 184 * @param {HTMLElement} sender Sender. 185 */ 186 elementClicked: function(event, detail, sender) { 187 if (sender.dataset.id in this.selected) { 188 delete this.selected[sender.dataset.id]; 189 sender.classList.remove('selected'); 190 } else { 191 this.selected[sender.dataset.id] = true; 192 sender.classList.add('selected'); 193 } 194 195 var requestEventsNode = document.querySelector('#request-events'); 196 requestEventsNode.hidden = false; 197 198 requestEventsNode.model = []; 199 for (var i = 0; i < this.model.length; i++) { 200 if (this.model[i].id in this.selected) 201 requestEventsNode.model.push(this.model[i]); 202 } 203 }, 204 205 /** 206 * Updates chart elements of active requests, so they grow with time. 207 */ 208 activeUpdate: function() { 209 if (Object.keys(this.active).length == 0) 210 return; 211 212 for (var id in this.active) { 213 var index = this.active[id]; 214 this.chart[index].length = Date.now() - this.chart[index].time; 215 } 216 }, 217 218 /** 219 * Generates <code>chart</code> from the new <code>model</code> value. 220 */ 221 chartUpdate: function(oldLength, newLength) { 222 // If the new value is empty, then clear the model. 223 if (!newLength) { 224 this.active = {}; 225 this.rows = []; 226 this.chart = []; 227 this.timeStart = null; 228 this.idleStart = null; 229 this.idleTotal = 0; 230 this.selected = []; 231 return; 232 } 233 234 // Only adding new entries to the model is supported (or clearing). 235 console.assert(newLength >= oldLength); 236 237 for (var i = oldLength; i < newLength; i++) { 238 var event = this.model[i]; 239 switch (event.eventType) { 240 case 'created': 241 // If this is the first creation event in the chart, then store its 242 // time as beginning time of the chart. 243 if (!this.timeStart) 244 this.timeStart = event.time; 245 246 // If this event terminates idling, then add the idling time to total 247 // idling time. This is used to avoid gaps in the chart while idling. 248 if (Object.keys(this.active).length == 0 && this.idleStart) 249 this.idleTotal += event.time.getTime() - this.idleStart.getTime(); 250 251 // Find the appropriate row for this chart element. 252 var rowIndex = 0; 253 while (true) { 254 // Add to this row only if there is enough space, and if the row 255 // is of the same type. 256 var addToRow = (rowIndex >= this.rows.length) || 257 (this.rows[rowIndex].time.getTime() <= event.time.getTime() && 258 !this.rows[rowIndex].active && 259 (this.rows[rowIndex].requestType == event.requestType)); 260 261 if (addToRow) { 262 this.chart.push({ 263 index: this.chart.length, 264 id: event.id, 265 time: event.time, 266 executionTime: 0, 267 length: 0, 268 requestType: event.requestType, 269 left: event.time - this.timeStart - this.idleTotal, 270 row: rowIndex, 271 modelIndexes: [i] 272 }); 273 274 this.rows[rowIndex] = { 275 requestType: event.requestType, 276 time: event.time, 277 active: true 278 }; 279 280 this.active[event.id] = this.chart.length - 1; 281 break; 282 } 283 284 rowIndex++; 285 } 286 break; 287 288 case 'fulfilled': 289 case 'rejected': 290 if (!(event.id in this.active)) 291 return; 292 var chartIndex = this.active[event.id]; 293 this.chart[chartIndex].state = event.eventType; 294 this.chart[chartIndex].executionTime = event.executionTime; 295 this.chart[chartIndex].valueSize = event.valueSize; 296 this.chart[chartIndex].modelIndexes.push(i); 297 break; 298 299 case 'destroyed': 300 if (!(event.id in this.active)) 301 return; 302 303 var chartIndex = this.active[event.id]; 304 this.chart[chartIndex].length = 305 event.time - this.chart[chartIndex].time; 306 this.chart[chartIndex].modelIndexes.push(i); 307 this.rows[this.chart[chartIndex].row].time = event.time; 308 this.rows[this.chart[chartIndex].row].active = false; 309 delete this.active[event.id]; 310 311 // If this was the last active request, then idling starts. 312 if (Object.keys(this.active).length == 0) 313 this.idleStart = event.time; 314 break; 315 } 316 } 317 }, 318 319 /** 320 * Map of selected requests. 321 * @type {Object.<number, boolean>} 322 */ 323 selected: {}, 324 325 /** 326 * Map of requests which has started, but are not completed yet, from 327 * a request id to the chart element index. 328 * @type {Object.<number, number>}} 329 */ 330 active: {}, 331 332 /** 333 * List of chart elements, calculated from the model. 334 * @type {Array.<Object>} 335 */ 336 chart: [], 337 338 /** 339 * List of rows in the chart, with the last endTime value on it. 340 * @type {Array.<Object>} 341 */ 342 rows: [], 343 344 /** 345 * Scale of the chart. 346 * @type {number} 347 */ 348 scale: 1, 349 350 /** 351 * Time of the first created request. 352 * @type {Date} 353 */ 354 timeStart: null, 355 356 /** 357 * Time of the last idling started. 358 * @type {Date} 359 */ 360 idleStart: null, 361 362 /** 363 * Total idling time since chart generation started. Used to avoid 364 * generating gaps in the chart when there is no activity. In milliseconds. 365 * @type {number} 366 */ 367 idleTotal: 0, 368 369 /** 370 * List of requests information maps. 371 * @type {Array.<Object>} 372 */ 373 model: [] 374}); 375 376/* 377 * Updates the mounted file system list. 378 * @param {Array.<Object>} fileSystems Array containing provided file system 379 * information. 380 */ 381function updateFileSystems(fileSystems) { 382 var fileSystemsNode = document.querySelector('#file-systems'); 383 fileSystemsNode.model = fileSystems; 384} 385 386/** 387 * Called when a request is created. 388 * @param {Object} event Event. 389 */ 390function onRequestEvent(event) { 391 event.time = new Date(event.time); // Convert to a real Date object. 392 var requestTimelineNode = document.querySelector('#request-timeline'); 393 requestTimelineNode.model.push(event); 394} 395 396document.addEventListener('DOMContentLoaded', function() { 397 var context = document.getCSSCanvasContext('2d', 'dashedPattern', 4, 4); 398 context.beginPath(); 399 context.strokeStyle = '#ffffff'; 400 context.moveTo(0, 0); 401 context.lineTo(4, 4); 402 context.stroke(); 403 404 chrome.send('updateFileSystems'); 405 406 // Refresh periodically. 407 setInterval(function() { 408 chrome.send('updateFileSystems'); 409 }, 1000); 410}); 411