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