Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(163)

Side by Side Diff: chrome/browser/resources/task_manager/main.js

Issue 101013004: Delete the WebUI implementation of the task manager. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix build Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 /** @constructor */
6 function TaskManager() { }
7
8 cr.addSingletonGetter(TaskManager);
9
10 TaskManager.prototype = {
11 /**
12 * Handle window close.
13 * @this
14 */
15 onClose: function() {
16 if (!this.disabled_) {
17 this.disabled_ = true;
18 commands.disableTaskManager();
19 }
20 },
21
22 /**
23 * Handles selection changes.
24 * This is also called when data of tasks are refreshed, even if selection
25 * has not been changed.
26 * @this
27 */
28 onSelectionChange: function() {
29 var sm = this.selectionModel_;
30 var dm = this.dataModel_;
31 var selectedIndexes = sm.selectedIndexes;
32 var isEndProcessEnabled = true;
33 if (selectedIndexes.length == 0)
34 isEndProcessEnabled = false;
35 for (var i = 0; i < selectedIndexes.length; i++) {
36 var index = selectedIndexes[i];
37 var task = dm.item(index);
38 if (task['type'] == 'BROWSER')
39 isEndProcessEnabled = false;
40 }
41 if (this.isEndProcessEnabled_ != isEndProcessEnabled) {
42 if (isEndProcessEnabled)
43 $('kill-process').removeAttribute('disabled');
44 else
45 $('kill-process').setAttribute('disabled', 'true');
46
47 this.isEndProcessEnabled_ = isEndProcessEnabled;
48 }
49 },
50
51 /**
52 * Closes taskmanager dialog.
53 * After this function is called, onClose() will be called.
54 * @this
55 */
56 close: function() {
57 window.close();
58 },
59
60 /**
61 * Sends commands to kill selected processes.
62 * @this
63 */
64 killSelectedProcesses: function() {
65 var selectedIndexes = this.selectionModel_.selectedIndexes;
66 var dm = this.dataModel_;
67 var uniqueIds = [];
68 for (var i = 0; i < selectedIndexes.length; i++) {
69 var index = selectedIndexes[i];
70 var task = dm.item(index);
71 uniqueIds.push(task['uniqueId'][0]);
72 }
73
74 commands.killSelectedProcesses(uniqueIds);
75 },
76
77 /**
78 * Initializes taskmanager.
79 * @this
80 */
81 initialize: function(dialogDom, opt) {
82 if (!dialogDom) {
83 console.log('ERROR: dialogDom is not defined.');
84 return;
85 }
86
87 measureTime.startInterval('Load.DOM');
88
89 this.opt_ = opt;
90
91 this.initialized_ = true;
92
93 this.elementsCache_ = {};
94 this.dialogDom_ = dialogDom;
95 this.document_ = dialogDom.ownerDocument;
96
97 this.localized_column_ = [];
98 for (var i = 0; i < DEFAULT_COLUMNS.length; i++) {
99 var columnLabelId = DEFAULT_COLUMNS[i][1];
100 this.localized_column_[i] = loadTimeData.getString(columnLabelId);
101 }
102
103 this.initElements_();
104 this.initColumnModel_();
105 this.selectionModel_ = new cr.ui.ListSelectionModel();
106 this.dataModel_ = new cr.ui.ArrayDataModel([]);
107
108 this.selectionModel_.addEventListener('change',
109 this.onSelectionChange.bind(this));
110
111 // Initializes compare functions for column sort.
112 var dm = this.dataModel_;
113 // List of columns to sort by its numerical value as opposed to the
114 // formatted value, e.g., 20480 vs. 20KB.
115 var COLUMNS_SORTED_BY_VALUE = [
116 'cpuUsage', 'physicalMemory', 'sharedMemory', 'privateMemory',
117 'networkUsage', 'webCoreImageCacheSize', 'webCoreScriptsCacheSize',
118 'webCoreCSSCacheSize', 'fps', 'videoMemory', 'sqliteMemoryUsed',
119 'goatsTeleported', 'v8MemoryAllocatedSize'];
120
121 for (var i = 0; i < DEFAULT_COLUMNS.length; i++) {
122 var columnId = DEFAULT_COLUMNS[i][0];
123 var compareFunc = (function() {
124 var columnIdToSort = columnId;
125 if (COLUMNS_SORTED_BY_VALUE.indexOf(columnId) != -1)
126 columnIdToSort += 'Value';
127
128 return function(a, b) {
129 var aValues = a[columnIdToSort];
130 var bValues = b[columnIdToSort];
131 var aValue = aValues && aValues[0] || 0;
132 var bvalue = bValues && bValues[0] || 0;
133 return dm.defaultValuesCompareFunction(aValue, bvalue);
134 };
135 })();
136 dm.setCompareFunction(columnId, compareFunc);
137 }
138
139 if (isColumnEnabled(DEFAULT_SORT_COLUMN))
140 dm.sort(DEFAULT_SORT_COLUMN, DEFAULT_SORT_DIRECTION);
141
142 this.initTable_();
143
144 commands.enableTaskManager();
145
146 // Populate the static localized strings.
147 i18nTemplate.process(this.document_, loadTimeData);
148
149 measureTime.recordInterval('Load.DOM');
150 measureTime.recordInterval('Load.Total');
151
152 loadDelayedIncludes(this);
153 },
154
155 /**
156 * Initializes the visibilities and handlers of the elements.
157 * This method is called by initialize().
158 * @private
159 * @this
160 */
161 initElements_: function() {
162 // <if expr="pp_ifdef('chromeos')">
163 // The 'close-window' element exists only on ChromeOS.
164 // This <if ... /if> section is removed while flattening HTML if chrome is
165 // built as Desktop Chrome.
166 if (!this.opt_['isShowCloseButton'])
167 $('close-window').style.display = 'none';
168 $('close-window').addEventListener('click', this.close.bind(this));
169 // </if>
170
171 $('kill-process').addEventListener('click',
172 this.killSelectedProcesses.bind(this));
173 $('about-memory-link').addEventListener('click', commands.openAboutMemory);
174 },
175
176 /**
177 * Additional initialization of taskmanager. This function is called when
178 * the loading of delayed scripts finished.
179 * @this
180 */
181 delayedInitialize: function() {
182 this.initColumnMenu_();
183 this.initTableMenu_();
184
185 var dm = this.dataModel_;
186 for (var i = 0; i < dm.length; i++) {
187 var processId = dm.item(i)['processId'][0];
188 for (var j = 0; j < DEFAULT_COLUMNS.length; j++) {
189 var columnId = DEFAULT_COLUMNS[j][0];
190
191 var row = dm.item(i)[columnId];
192 if (!row)
193 continue;
194
195 for (var k = 0; k < row.length; k++) {
196 var labelId = 'detail-' + columnId + '-pid' + processId + '-' + k;
197 var label = $(labelId);
198
199 // Initialize a context-menu, if the label exists and its context-
200 // menu is not initialized yet.
201 if (label && !label.contextMenu)
202 cr.ui.contextMenuHandler.setContextMenu(label,
203 this.tableContextMenu_);
204 }
205 }
206 }
207
208 this.isFinishedInitDelayed_ = true;
209 var t = this.table_;
210 t.redraw();
211 addEventListener('resize', t.redraw.bind(t));
212 },
213
214 initColumnModel_: function() {
215 var tableColumns = new Array();
216 for (var i = 0; i < DEFAULT_COLUMNS.length; i++) {
217 var column = DEFAULT_COLUMNS[i];
218 var columnId = column[0];
219 if (!isColumnEnabled(columnId))
220 continue;
221
222 tableColumns.push(new cr.ui.table.TableColumn(columnId,
223 this.localized_column_[i],
224 column[2]));
225 }
226
227 for (var i = 0; i < tableColumns.length; i++) {
228 tableColumns[i].renderFunction = this.renderColumn_.bind(this);
229 }
230
231 this.columnModel_ = new cr.ui.table.TableColumnModel(tableColumns);
232 },
233
234 initColumnMenu_: function() {
235 this.column_menu_commands_ = [];
236
237 this.commandsElement_ = this.document_.createElement('commands');
238 this.document_.body.appendChild(this.commandsElement_);
239
240 this.columnSelectContextMenu_ = this.document_.createElement('menu');
241 for (var i = 0; i < DEFAULT_COLUMNS.length; i++) {
242 var column = DEFAULT_COLUMNS[i];
243
244 // Creates command element to receive event.
245 var command = this.document_.createElement('command');
246 command.id = COMMAND_CONTEXTMENU_COLUMN_PREFIX + '-' + column[0];
247 cr.ui.Command.decorate(command);
248 this.column_menu_commands_[command.id] = command;
249 this.commandsElement_.appendChild(command);
250
251 // Creates menuitem element.
252 var item = this.document_.createElement('menuitem');
253 item.command = command;
254 command.menuitem = item;
255 item.textContent = this.localized_column_[i];
256 if (isColumnEnabled(column[0]))
257 item.setAttributeNode(this.document_.createAttribute('checked'));
258 this.columnSelectContextMenu_.appendChild(item);
259 }
260
261 this.document_.body.appendChild(this.columnSelectContextMenu_);
262 cr.ui.Menu.decorate(this.columnSelectContextMenu_);
263
264 cr.ui.contextMenuHandler.setContextMenu(this.table_.header,
265 this.columnSelectContextMenu_);
266 cr.ui.contextMenuHandler.setContextMenu(this.table_.list,
267 this.columnSelectContextMenu_);
268
269 this.document_.addEventListener('command', this.onCommand_.bind(this));
270 this.document_.addEventListener('canExecute',
271 this.onCommandCanExecute_.bind(this));
272 },
273
274 initTableMenu_: function() {
275 this.table_menu_commands_ = [];
276 this.tableContextMenu_ = this.document_.createElement('menu');
277
278 var addMenuItem = function(tm, commandId, string_id) {
279 // Creates command element to receive event.
280 var command = tm.document_.createElement('command');
281 command.id = COMMAND_CONTEXTMENU_TABLE_PREFIX + '-' + commandId;
282 cr.ui.Command.decorate(command);
283 tm.table_menu_commands_[command.id] = command;
284 tm.commandsElement_.appendChild(command);
285
286 // Creates menuitem element.
287 var item = tm.document_.createElement('menuitem');
288 item.command = command;
289 command.menuitem = item;
290 item.textContent = loadTimeData.getString(string_id);
291 tm.tableContextMenu_.appendChild(item);
292 };
293
294 addMenuItem(this, 'inspect', 'inspect');
295 addMenuItem(this, 'activate', 'activate');
296
297 this.document_.body.appendChild(this.tableContextMenu_);
298 cr.ui.Menu.decorate(this.tableContextMenu_);
299 },
300
301 initTable_: function() {
302 if (!this.dataModel_ || !this.selectionModel_ || !this.columnModel_) {
303 console.log('ERROR: some models are not defined.');
304 return;
305 }
306
307 this.table_ = this.dialogDom_.querySelector('.detail-table');
308 cr.ui.Table.decorate(this.table_);
309
310 this.table_.dataModel = this.dataModel_;
311 this.table_.selectionModel = this.selectionModel_;
312 this.table_.columnModel = this.columnModel_;
313
314 // Expands height of row when a process has some tasks.
315 this.table_.fixedHeight = false;
316
317 this.table_.list.addEventListener('contextmenu',
318 this.onTableContextMenuOpened_.bind(this),
319 true);
320
321 // Sets custom row render function.
322 this.table_.setRenderFunction(this.getRow_.bind(this));
323 },
324
325 /**
326 * Returns a list item element of the list. This method trys to reuse the
327 * cached element, or creates a new element.
328 * @return {cr.ui.ListItem} list item element which contains the given data.
329 * @private
330 * @this
331 */
332 getRow_: function(data, table) {
333 // Trys to reuse the cached row;
334 var listItemElement = this.renderRowFromCache_(data, table);
335 if (listItemElement)
336 return listItemElement;
337
338 // Initializes the cache.
339 var pid = data['processId'][0];
340 this.elementsCache_[pid] = {
341 listItem: null,
342 cell: [],
343 icon: [],
344 columns: {}
345 };
346
347 // Create new row.
348 return this.renderRow_(data, table);
349 },
350
351 /**
352 * Returns a list item element with re-using the previous cached element, or
353 * returns null if failed.
354 * @return {cr.ui.ListItem} cached un-used element to be reused.
355 * @private
356 * @this
357 */
358 renderRowFromCache_: function(data, table) {
359 var pid = data['processId'][0];
360
361 // Checks whether the cache exists or not.
362 var cache = this.elementsCache_[pid];
363 if (!cache)
364 return null;
365
366 var listItemElement = cache.listItem;
367 var cm = table.columnModel;
368 // Checks whether the number of columns has been changed or not.
369 if (cache.cachedColumnSize != cm.size)
370 return null;
371 // Checks whether the number of childlen tasks has been changed or not.
372 if (cache.cachedChildSize != data['uniqueId'].length)
373 return null;
374
375 // Updates informations of the task if necessary.
376 for (var i = 0; i < cm.size; i++) {
377 var columnId = cm.getId(i);
378 var columnData = data[columnId];
379 var oldColumnData = listItemElement.data[columnId];
380 var columnElements = cache.columns[columnId];
381
382 if (!columnData || !oldColumnData || !columnElements)
383 return null;
384
385 // Sets new width of the cell.
386 var cellElement = cache.cell[i];
387 cellElement.style.width = cm.getWidth(i) + '%';
388
389 for (var j = 0; j < columnData.length; j++) {
390 // Sets the new text, if the text has been changed.
391 if (oldColumnData[j] != columnData[j]) {
392 var textElement = columnElements[j];
393 textElement.textContent = columnData[j];
394 }
395 }
396 }
397
398 // Updates icon of the task if necessary.
399 var oldIcons = listItemElement.data['icon'];
400 var newIcons = data['icon'];
401 if (oldIcons && newIcons) {
402 for (var j = 0; j < columnData.length; j++) {
403 var oldIcon = oldIcons[j];
404 var newIcon = newIcons[j];
405 if (oldIcon != newIcon) {
406 var iconElement = cache.icon[j];
407 iconElement.src = newIcon;
408 }
409 }
410 }
411 listItemElement.data = data;
412
413 // Removes 'selected' and 'lead' attributes.
414 listItemElement.removeAttribute('selected');
415 listItemElement.removeAttribute('lead');
416
417 return listItemElement;
418 },
419
420 /**
421 * Create a new list item element.
422 * @return {cr.ui.ListItem} created new list item element.
423 * @private
424 * @this
425 */
426 renderRow_: function(data, table) {
427 var pid = data['processId'][0];
428 var cm = table.columnModel;
429 var listItem = new cr.ui.ListItem({label: ''});
430
431 listItem.className = 'table-row';
432
433 for (var i = 0; i < cm.size; i++) {
434 var cell = document.createElement('div');
435 cell.style.width = cm.getWidth(i) + '%';
436 cell.className = 'table-row-cell';
437 cell.id = 'column-' + pid + '-' + cm.getId(i);
438 cell.appendChild(
439 cm.getRenderFunction(i).call(null, data, cm.getId(i), table));
440
441 listItem.appendChild(cell);
442
443 // Stores the cell element to the dictionary.
444 this.elementsCache_[pid].cell[i] = cell;
445 }
446
447 // Specifies the height of the row. The height of each row is
448 // 'num_of_tasks * HEIGHT_OF_TASK' px.
449 listItem.style.height = (data['uniqueId'].length * HEIGHT_OF_TASK) + 'px';
450
451 listItem.data = data;
452
453 // Stores the list item element, the number of columns and the number of
454 // childlen.
455 this.elementsCache_[pid].listItem = listItem;
456 this.elementsCache_[pid].cachedColumnSize = cm.size;
457 this.elementsCache_[pid].cachedChildSize = data['uniqueId'].length;
458
459 return listItem;
460 },
461
462 /**
463 * Create a new element of the cell.
464 * @return {HTMLDIVElement} created cell
465 * @private
466 * @this
467 */
468 renderColumn_: function(entry, columnId, table) {
469 var container = this.document_.createElement('div');
470 container.className = 'detail-container-' + columnId;
471 var pid = entry['processId'][0];
472
473 var cache = [];
474 var cacheIcon = [];
475
476 if (entry && entry[columnId]) {
477 container.id = 'detail-container-' + columnId + '-pid' + entry.processId;
478
479 for (var i = 0; i < entry[columnId].length; i++) {
480 var label = document.createElement('div');
481 if (columnId == 'title') {
482 // Creates a page title element with icon.
483 var image = this.document_.createElement('img');
484 image.className = 'detail-title-image';
485 image.src = entry['icon'][i];
486 image.id = 'detail-title-icon-pid' + pid + '-' + i;
487 label.appendChild(image);
488 var text = this.document_.createElement('div');
489 text.className = 'detail-title-text';
490 text.id = 'detail-title-text-pid' + pid + '-' + i;
491 text.textContent = entry['title'][i];
492 label.appendChild(text);
493
494 // Chech if the delayed scripts (included in includes.js) have been
495 // loaded or not. If the delayed scripts ware not loaded yet, a
496 // context menu could not be initialized. In such case, it will be
497 // initialized at delayedInitialize() just after loading of delayed
498 // scripts instead of here.
499 if (this.isFinishedInitDelayed_)
500 cr.ui.contextMenuHandler.setContextMenu(label,
501 this.tableContextMenu_);
502
503 label.addEventListener('dblclick', (function(uniqueId) {
504 commands.activatePage(uniqueId);
505 }).bind(this, entry['uniqueId'][i]));
506
507 label.data = entry;
508 label.index_in_group = i;
509
510 cache[i] = text;
511 cacheIcon[i] = image;
512 } else {
513 label.textContent = entry[columnId][i];
514 cache[i] = label;
515 }
516 label.id = 'detail-' + columnId + '-pid' + pid + '-' + i;
517 label.className = 'detail-' + columnId + ' pid' + pid;
518 container.appendChild(label);
519 }
520
521 this.elementsCache_[pid].columns[columnId] = cache;
522 if (columnId == 'title')
523 this.elementsCache_[pid].icon = cacheIcon;
524 }
525 return container;
526 },
527
528 /**
529 * Updates the task list with the supplied task.
530 * @private
531 * @this
532 */
533 processTaskChange: function(task) {
534 var dm = this.dataModel_;
535 var sm = this.selectionModel_;
536 if (!dm || !sm) return;
537
538 this.table_.list.startBatchUpdates();
539 sm.beginChange();
540
541 var type = task.type;
542 var start = task.start;
543 var length = task.length;
544 var tasks = task.tasks;
545
546 // We have to store the selected pids and restore them after
547 // splice(), because it might replace some items but the replaced
548 // items would lose the selection.
549 var oldSelectedIndexes = sm.selectedIndexes;
550
551 // Create map of selected PIDs.
552 var selectedPids = {};
553 for (var i = 0; i < oldSelectedIndexes.length; i++) {
554 var item = dm.item(oldSelectedIndexes[i]);
555 if (item) selectedPids[item['processId'][0]] = true;
556 }
557
558 var args = tasks.slice();
559 args.unshift(start, dm.length);
560 dm.splice.apply(dm, args);
561
562 // Create new array of selected indexes from map of old PIDs.
563 var newSelectedIndexes = [];
564 for (var i = 0; i < dm.length; i++) {
565 if (selectedPids[dm.item(i)['processId'][0]])
566 newSelectedIndexes.push(i);
567 }
568
569 sm.selectedIndexes = newSelectedIndexes;
570
571 var pids = [];
572 for (var i = 0; i < dm.length; i++) {
573 pids.push(dm.item(i)['processId'][0]);
574 }
575
576 // Sweeps unused caches, which elements no longer exist on the list.
577 for (var pid in this.elementsCache_) {
578 if (pids.indexOf(pid) == -1)
579 delete this.elementsCache_[pid];
580 }
581
582 sm.endChange();
583 this.table_.list.endBatchUpdates();
584 },
585
586 /**
587 * Respond to a command being executed.
588 * @this
589 */
590 onCommand_: function(event) {
591 var command = event.command;
592 var commandId = command.id.split('-', 2);
593
594 var mainCommand = commandId[0];
595 var subCommand = commandId[1];
596
597 if (mainCommand == COMMAND_CONTEXTMENU_COLUMN_PREFIX) {
598 this.onColumnContextMenu_(subCommand, command);
599 } else if (mainCommand == COMMAND_CONTEXTMENU_TABLE_PREFIX) {
600 var targetUniqueId = this.currentContextMenuTarget_;
601
602 if (!targetUniqueId)
603 return;
604
605 if (subCommand == 'inspect')
606 commands.inspect(targetUniqueId);
607 else if (subCommand == 'activate')
608 commands.activatePage(targetUniqueId);
609
610 this.currentContextMenuTarget_ = undefined;
611 }
612 },
613
614 onCommandCanExecute_: function(event) {
615 event.canExecute = true;
616 },
617
618 /**
619 * Store resourceIndex of target resource of context menu, because resource
620 * will be replaced when it is refreshed.
621 * @this
622 */
623 onTableContextMenuOpened_: function(e) {
624 if (!this.isFinishedInitDelayed_)
625 return;
626
627 var mc = this.table_menu_commands_;
628 var inspectMenuitem =
629 mc[COMMAND_CONTEXTMENU_TABLE_PREFIX + '-inspect'].menuitem;
630 var activateMenuitem =
631 mc[COMMAND_CONTEXTMENU_TABLE_PREFIX + '-activate'].menuitem;
632
633 // Disabled by default.
634 inspectMenuitem.disabled = true;
635 activateMenuitem.disabled = true;
636
637 var target = e.target;
638 for (;; target = target.parentNode) {
639 if (!target) return;
640 var classes = target.classList;
641 if (classes &&
642 Array.prototype.indexOf.call(classes, 'detail-title') != -1) break;
643 }
644
645 var indexInGroup = target.index_in_group;
646
647 // Sets the uniqueId for current target page under the mouse corsor.
648 this.currentContextMenuTarget_ = target.data['uniqueId'][indexInGroup];
649
650 // Enables if the page can be inspected.
651 if (target.data['canInspect'][indexInGroup])
652 inspectMenuitem.disabled = false;
653
654 // Enables if the page can be activated.
655 if (target.data['canActivate'][indexInGroup])
656 activateMenuitem.disabled = false;
657 },
658
659 onColumnContextMenu_: function(columnId, command) {
660 var menuitem = command.menuitem;
661 var checkedItemCount = 0;
662 var checked = isColumnEnabled(columnId);
663
664 // Leaves a item visible when user tries making invisible but it is the
665 // last one.
666 var enabledColumns = getEnabledColumns();
667 for (var id in enabledColumns) {
668 if (enabledColumns[id])
669 checkedItemCount++;
670 }
671 if (checkedItemCount == 1 && checked)
672 return;
673
674 // Toggles the visibility of the column.
675 var newChecked = !checked;
676 menuitem.checked = newChecked;
677 setColumnEnabled(columnId, newChecked);
678
679 this.initColumnModel_();
680 this.table_.columnModel = this.columnModel_;
681 this.table_.redraw();
682 },
683 };
684
685 // |taskmanager| has been declared in preload.js.
686 taskmanager = TaskManager.getInstance();
687
688 function init() {
689 var params = parseQueryParams(window.location);
690 var opt = {};
691 opt['isShowCloseButton'] = params.showclose;
692 taskmanager.initialize(document.body, opt);
693 }
694
695 document.addEventListener('DOMContentLoaded', init);
696 document.addEventListener('Close', taskmanager.onClose.bind(taskmanager));
OLDNEW
« no previous file with comments | « chrome/browser/resources/task_manager/main.html ('k') | chrome/browser/resources/task_manager/measure_time.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698