Home | History | Annotate | Download | only in task_manager
      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));
    697