Home | History | Annotate | Download | only in chromeos
      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