Battery Status API: add UMA logging for Linux.
[chromium-blink-merge.git] / content / browser / resources / media / client_renderer.js
blob5cdedaabb47890be131bad8070de2fcdb862a097
1 // Copyright 2013 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.
5 var ClientRenderer = (function() {
6   var ClientRenderer = function() {
7     this.playerListElement = document.getElementById('player-list');
8     this.propertiesTable =
9         document.getElementById('property-table').querySelector('tbody');
10     this.logTable = document.getElementById('log').querySelector('tbody');
11     this.graphElement = document.getElementById('graphs');
12     this.propertyName = document.getElementById('property-name');
14     this.selectedPlayer = null;
15     this.selectedAudioComponentType = null;
16     this.selectedAudioComponentId = null;
17     this.selectedAudioCompontentData = null;
19     this.selectedPlayerLogIndex = 0;
21     this.filterFunction = function() { return true; };
22     this.filterText = document.getElementById('filter-text');
23     this.filterText.onkeyup = this.onTextChange_.bind(this);
25     this.bufferCanvas = document.createElement('canvas');
26     this.bufferCanvas.width = media.BAR_WIDTH;
27     this.bufferCanvas.height = media.BAR_HEIGHT;
29     this.clipboardTextarea = document.getElementById('clipboard-textarea');
30     this.clipboardButton = document.getElementById('copy-button');
31     this.clipboardButton.onclick = this.copyToClipboard_.bind(this);
33     this.hiddenKeys = ['component_id', 'component_type', 'owner_id'];
34   };
36   function removeChildren(element) {
37     while (element.hasChildNodes()) {
38       element.removeChild(element.lastChild);
39     }
40   };
42   function createButton(text, select_cb) {
43     var button = document.createElement('button');
45     button.appendChild(document.createTextNode(text));
46     button.onclick = function() {
47       select_cb();
48     };
50     return button;
51   };
53   ClientRenderer.prototype = {
54     /**
55      * Called when an audio component is added to the collection.
56      * @param componentType Integer AudioComponent enum value; must match values
57      * from the AudioLogFactory::AudioComponent enum.
58      * @param components The entire map of components (name -> dict).
59      */
60     audioComponentAdded: function(componentType, components) {
61       this.redrawAudioComponentList_(componentType, components);
63       // Redraw the component if it's currently selected.
64       if (this.selectedAudioComponentType == componentType &&
65           this.selectedAudioComponentId &&
66           this.selectedAudioComponentId in components) {
67         this.selectAudioComponent_(
68             componentType, this.selectedAudioComponentId,
69             components[this.selectedAudioComponentId]);
70       }
71     },
73     /**
74      * Called when an audio component is removed from the collection.
75      * @param componentType Integer AudioComponent enum value; must match values
76      * from the AudioLogFactory::AudioComponent enum.
77      * @param components The entire map of components (name -> dict).
78      */
79     audioComponentRemoved: function(componentType, components) {
80       this.redrawAudioComponentList_(componentType, components);
82       // Clear the component if it was previously currently selected.
83       if (this.selectedAudioComponentType == componentType &&
84           !(this.selectedAudioComponentId in components)) {
85         this.selectAudioComponent_(null, null, {});
86       }
87     },
89     /**
90      * Called when a player is added to the collection.
91      * @param players The entire map of id -> player.
92      * @param player_added The player that is added.
93      */
94     playerAdded: function(players, playerAdded) {
95       this.redrawPlayerList_(players);
96     },
98     /**
99      * Called when a playre is removed from the collection.
100      * @param players The entire map of id -> player.
101      * @param player_added The player that was removed.
102      */
103     playerRemoved: function(players, playerRemoved) {
104       this.redrawPlayerList_(players);
105     },
107     /**
108      * Called when a property on a player is changed.
109      * @param players The entire map of id -> player.
110      * @param player The player that had its property changed.
111      * @param key The name of the property that was changed.
112      * @param value The new value of the property.
113      */
114     playerUpdated: function(players, player, key, value) {
115       if (player === this.selectedPlayer) {
116         this.drawProperties_(player.properties);
117         this.drawLog_();
118         this.drawGraphs_();
119       }
120       if (key === 'name' || key === 'url') {
121         this.redrawPlayerList_(players);
122       }
123     },
125     redrawAudioComponentList_: function(componentType, components) {
126       function redrawList(renderer, baseName, element) {
127         var fragment = document.createDocumentFragment();
128         for (id in components) {
129           var li = document.createElement('li');
130           var friendlyName = baseName + ' ' + id;
131           li.appendChild(createButton(
132               friendlyName, renderer.selectAudioComponent_.bind(
133                   renderer, componentType, id, components[id], friendlyName)));
134           fragment.appendChild(li);
135         }
136         removeChildren(element);
137         element.appendChild(fragment);
138       }
140       switch (componentType) {
141         case 0:
142           redrawList(this, 'Controller', document.getElementById(
143               'audio-input-controller-list'));
144           break;
145         case 1:
146           redrawList(this, 'Controller', document.getElementById(
147               'audio-output-controller-list'));
148           break;
149         case 2:
150           redrawList(this, 'Stream', document.getElementById(
151               'audio-output-stream-list'));
152           break;
153         default:
154           break;
155       }
156     },
158     selectAudioComponent_: function(
159           componentType, componentId, componentData, friendlyName) {
160       this.selectedPlayer = null;
161       this.selectedAudioComponentType = componentType;
162       this.selectedAudioComponentId = componentId;
163       this.selectedAudioCompontentData = componentData;
164       this.drawProperties_(componentData);
165       removeChildren(this.logTable);
166       removeChildren(this.graphElement);
168       removeChildren(this.propertyName);
169       this.propertyName.appendChild(document.createTextNode(friendlyName));
170     },
172     redrawPlayerList_: function(players) {
173       var fragment = document.createDocumentFragment();
174       for (id in players) {
175         var player = players[id];
176         var usableName = player.properties.name ||
177             player.properties.url ||
178             'Player ' + player.id;
180         var li = document.createElement('li');
181         li.appendChild(createButton(
182             usableName, this.selectPlayer_.bind(this, player)));
183         fragment.appendChild(li);
184       }
185       removeChildren(this.playerListElement);
186       this.playerListElement.appendChild(fragment);
187     },
189     selectPlayer_: function(player) {
190       this.selectedPlayer = player;
191       this.selectedPlayerLogIndex = 0;
192       this.selectedAudioComponentType = null;
193       this.selectedAudioComponentId = null;
194       this.selectedAudioCompontentData = null;
195       this.drawProperties_(player.properties);
197       removeChildren(this.logTable);
198       removeChildren(this.graphElement);
199       this.drawLog_();
200       this.drawGraphs_();
201       removeChildren(this.propertyName);
202       this.propertyName.appendChild(document.createTextNode('Player'));
203     },
205     drawProperties_: function(propertyMap) {
206       removeChildren(this.propertiesTable);
207       var sortedKeys = Object.keys(propertyMap).sort();
208       for (var i = 0; i < sortedKeys.length; ++i) {
209         var key = sortedKeys[i];
210         if (this.hiddenKeys.indexOf(key) >= 0)
211           continue;
213         var value = propertyMap[key];
214         var row = this.propertiesTable.insertRow(-1);
215         var keyCell = row.insertCell(-1);
216         var valueCell = row.insertCell(-1);
218         keyCell.appendChild(document.createTextNode(key));
219         valueCell.appendChild(document.createTextNode(value));
220       }
221     },
223     appendEventToLog_: function(event) {
224       if (this.filterFunction(event.key)) {
225         var row = this.logTable.insertRow(-1);
227         var timestampCell = row.insertCell(-1);
228         timestampCell.classList.add('timestamp');
229         timestampCell.appendChild(document.createTextNode(
230             util.millisecondsToString(event.time)));
231         row.insertCell(-1).appendChild(document.createTextNode(event.key));
232         row.insertCell(-1).appendChild(document.createTextNode(event.value));
233       }
234     },
236     drawLog_: function() {
237       var toDraw = this.selectedPlayer.allEvents.slice(
238           this.selectedPlayerLogIndex);
239       toDraw.forEach(this.appendEventToLog_.bind(this));
240       this.selectedPlayerLogIndex = this.selectedPlayer.allEvents.length;
241     },
243     drawGraphs_: function() {
244       function addToGraphs(name, graph, graphElement) {
245         var li = document.createElement('li');
246         li.appendChild(graph);
247         li.appendChild(document.createTextNode(name));
248         graphElement.appendChild(li);
249       }
251       var url = this.selectedPlayer.properties.url;
252       if (!url) {
253         return;
254       }
256       var cache = media.cacheForUrl(url);
258       var player = this.selectedPlayer;
259       var props = player.properties;
261       var cacheExists = false;
262       var bufferExists = false;
264       if (props['buffer_start'] !== undefined &&
265           props['buffer_current'] !== undefined &&
266           props['buffer_end'] !== undefined &&
267           props['total_bytes'] !== undefined) {
268         this.drawBufferGraph_(props['buffer_start'],
269                               props['buffer_current'],
270                               props['buffer_end'],
271                               props['total_bytes']);
272         bufferExists = true;
273       }
275       if (cache) {
276         if (player.properties['total_bytes']) {
277           cache.size = Number(player.properties['total_bytes']);
278         }
279         cache.generateDetails();
280         cacheExists = true;
282       }
284       if (!this.graphElement.hasChildNodes()) {
285         if (bufferExists) {
286           addToGraphs('buffer', this.bufferCanvas, this.graphElement);
287         }
288         if (cacheExists) {
289           addToGraphs('cache read', cache.readCanvas, this.graphElement);
290           addToGraphs('cache write', cache.writeCanvas, this.graphElement);
291         }
292       }
293     },
295     drawBufferGraph_: function(start, current, end, size) {
296       var ctx = this.bufferCanvas.getContext('2d');
297       var width = this.bufferCanvas.width;
298       var height = this.bufferCanvas.height;
299       ctx.fillStyle = '#aaa';
300       ctx.fillRect(0, 0, width, height);
302       var scale_factor = width / size;
303       var left = start * scale_factor;
304       var middle = current * scale_factor;
305       var right = end * scale_factor;
307       ctx.fillStyle = '#a0a';
308       ctx.fillRect(left, 0, middle - left, height);
309       ctx.fillStyle = '#aa0';
310       ctx.fillRect(middle, 0, right - middle, height);
311     },
313     copyToClipboard_: function() {
314       var properties = this.selectedAudioCompontentData ||
315           this.selectedPlayer.properties || false;
316       if (!properties) {
317         return;
318       }
319       var stringBuffer = [];
321       for (var key in properties) {
322         var value = properties[key];
323         stringBuffer.push(key.toString());
324         stringBuffer.push(': ');
325         stringBuffer.push(value.toString());
326         stringBuffer.push('\n');
327       }
329       this.clipboardTextarea.value = stringBuffer.join('');
330       this.clipboardTextarea.classList.remove('hiddenClipboard');
331       this.clipboardTextarea.focus();
332       this.clipboardTextarea.select();
334       // Hide the clipboard element when it loses focus.
335       this.clipboardTextarea.onblur = function(event) {
336         setTimeout(function(element) {
337           event.target.classList.add('hiddenClipboard');
338         }, 0);
339       };
340     },
342     onTextChange_: function(event) {
343       var text = this.filterText.value.toLowerCase();
344       var parts = text.split(',').map(function(part) {
345         return part.trim();
346       }).filter(function(part) {
347         return part.trim().length > 0;
348       });
350       this.filterFunction = function(text) {
351         text = text.toLowerCase();
352         return parts.length === 0 || parts.some(function(part) {
353           return text.indexOf(part) != -1;
354         });
355       };
357       if (this.selectedPlayer) {
358         removeChildren(this.logTable);
359         this.selectedPlayerLogIndex = 0;
360         this.drawLog_();
361       }
362     },
363   };
365   return ClientRenderer;
366 })();