Home | History | Annotate | Download | only in js
      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 'use strict';
      6 
      7 /**
      8  * FileManager constructor.
      9  *
     10  * FileManager objects encapsulate the functionality of the file selector
     11  * dialogs, as well as the full screen file manager application (though the
     12  * latter is not yet implemented).
     13  *
     14  * @constructor
     15  */
     16 function FileManager() {
     17   this.initializeQueue_ = new AsyncUtil.Group();
     18 
     19   /**
     20    * Current list type.
     21    * @type {ListType}
     22    * @private
     23    */
     24   this.listType_ = null;
     25 
     26   /**
     27    * True while a user is pressing <Tab>.
     28    * This is used for identifying the trigger causing the filelist to
     29    * be focused.
     30    * @type {boolean}
     31    * @private
     32    */
     33   this.pressingTab_ = false;
     34 
     35   /**
     36    * True while a user is pressing <Ctrl>.
     37    *
     38    * TODO(fukino): This key is used only for controlling gear menu, so it
     39    * shoudl be moved to GearMenu class. crbug.com/366032.
     40    *
     41    * @type {boolean}
     42    * @private
     43    */
     44   this.pressingCtrl_ = false;
     45 
     46   /**
     47    * True if shown gear menu is in secret mode.
     48    *
     49    * TODO(fukino): The state of gear menu should be moved to GearMenu class.
     50    * crbug.com/366032.
     51    *
     52    * @type {boolean}
     53    * @private
     54    */
     55   this.isSecretGearMenuShown_ = false;
     56 
     57   /**
     58    * SelectionHandler.
     59    * @type {SelectionHandler}
     60    * @private
     61    */
     62   this.selectionHandler_ = null;
     63 
     64   /**
     65    * VolumeInfo of the current volume.
     66    * @type {VolumeInfo}
     67    * @private
     68    */
     69   this.currentVolumeInfo_ = null;
     70 }
     71 
     72 FileManager.prototype = {
     73   __proto__: cr.EventTarget.prototype,
     74   get directoryModel() {
     75     return this.directoryModel_;
     76   },
     77   get navigationList() {
     78     return this.navigationList_;
     79   },
     80   get document() {
     81     return this.document_;
     82   },
     83   get fileTransferController() {
     84     return this.fileTransferController_;
     85   },
     86   get fileOperationManager() {
     87     return this.fileOperationManager_;
     88   },
     89   get backgroundPage() {
     90     return this.backgroundPage_;
     91   },
     92   get volumeManager() {
     93     return this.volumeManager_;
     94   },
     95   get ui() {
     96     return this.ui_;
     97   }
     98 };
     99 
    100 /**
    101  * List of dialog types.
    102  *
    103  * Keep this in sync with FileManagerDialog::GetDialogTypeAsString, except
    104  * FULL_PAGE which is specific to this code.
    105  *
    106  * @enum {string}
    107  */
    108 var DialogType = {
    109   SELECT_FOLDER: 'folder',
    110   SELECT_UPLOAD_FOLDER: 'upload-folder',
    111   SELECT_SAVEAS_FILE: 'saveas-file',
    112   SELECT_OPEN_FILE: 'open-file',
    113   SELECT_OPEN_MULTI_FILE: 'open-multi-file',
    114   FULL_PAGE: 'full-page'
    115 };
    116 
    117 /**
    118  * @param {string} type Dialog type.
    119  * @return {boolean} Whether the type is modal.
    120  */
    121 DialogType.isModal = function(type) {
    122   return type == DialogType.SELECT_FOLDER ||
    123       type == DialogType.SELECT_UPLOAD_FOLDER ||
    124       type == DialogType.SELECT_SAVEAS_FILE ||
    125       type == DialogType.SELECT_OPEN_FILE ||
    126       type == DialogType.SELECT_OPEN_MULTI_FILE;
    127 };
    128 
    129 /**
    130  * @param {string} type Dialog type.
    131  * @return {boolean} Whether the type is open dialog.
    132  */
    133 DialogType.isOpenDialog = function(type) {
    134   return type == DialogType.SELECT_OPEN_FILE ||
    135          type == DialogType.SELECT_OPEN_MULTI_FILE ||
    136          type == DialogType.SELECT_FOLDER ||
    137          type == DialogType.SELECT_UPLOAD_FOLDER;
    138 };
    139 
    140 /**
    141  * @param {string} type Dialog type.
    142  * @return {boolean} Whether the type is folder selection dialog.
    143  */
    144 DialogType.isFolderDialog = function(type) {
    145   return type == DialogType.SELECT_FOLDER ||
    146          type == DialogType.SELECT_UPLOAD_FOLDER;
    147 };
    148 
    149 /**
    150  * Bottom margin of the list and tree for transparent preview panel.
    151  * @const
    152  */
    153 var BOTTOM_MARGIN_FOR_PREVIEW_PANEL_PX = 52;
    154 
    155 // Anonymous "namespace".
    156 (function() {
    157 
    158   // Private variables and helper functions.
    159 
    160   /**
    161    * Number of milliseconds in a day.
    162    */
    163   var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
    164 
    165   /**
    166    * Some UI elements react on a single click and standard double click handling
    167    * leads to confusing results. We ignore a second click if it comes soon
    168    * after the first.
    169    */
    170   var DOUBLE_CLICK_TIMEOUT = 200;
    171 
    172   /**
    173    * Updates the element to display the information about remaining space for
    174    * the storage.
    175    *
    176    * @param {!Object<string, number>} sizeStatsResult Map containing remaining
    177    *     space information.
    178    * @param {!Element} spaceInnerBar Block element for a percentage bar
    179    *     representing the remaining space.
    180    * @param {!Element} spaceInfoLabel Inline element to contain the message.
    181    * @param {!Element} spaceOuterBar Block element around the percentage bar.
    182    */
    183   var updateSpaceInfo = function(
    184       sizeStatsResult, spaceInnerBar, spaceInfoLabel, spaceOuterBar) {
    185     spaceInnerBar.removeAttribute('pending');
    186     if (sizeStatsResult) {
    187       var sizeStr = util.bytesToString(sizeStatsResult.remainingSize);
    188       spaceInfoLabel.textContent = strf('SPACE_AVAILABLE', sizeStr);
    189 
    190       var usedSpace =
    191           sizeStatsResult.totalSize - sizeStatsResult.remainingSize;
    192       spaceInnerBar.style.width =
    193           (100 * usedSpace / sizeStatsResult.totalSize) + '%';
    194 
    195       spaceOuterBar.hidden = false;
    196     } else {
    197       spaceOuterBar.hidden = true;
    198       spaceInfoLabel.textContent = str('FAILED_SPACE_INFO');
    199     }
    200   };
    201 
    202   // Public statics.
    203 
    204   FileManager.ListType = {
    205     DETAIL: 'detail',
    206     THUMBNAIL: 'thumb'
    207   };
    208 
    209   FileManager.prototype.initPreferences_ = function(callback) {
    210     var group = new AsyncUtil.Group();
    211 
    212     // DRIVE preferences should be initialized before creating DirectoryModel
    213     // to rebuild the roots list.
    214     group.add(this.getPreferences_.bind(this));
    215 
    216     // Get startup preferences.
    217     this.viewOptions_ = {};
    218     group.add(function(done) {
    219       util.platform.getPreference(this.startupPrefName_, function(value) {
    220         // Load the global default options.
    221         try {
    222           this.viewOptions_ = JSON.parse(value);
    223         } catch (ignore) {}
    224         // Override with window-specific options.
    225         if (window.appState && window.appState.viewOptions) {
    226           for (var key in window.appState.viewOptions) {
    227             if (window.appState.viewOptions.hasOwnProperty(key))
    228               this.viewOptions_[key] = window.appState.viewOptions[key];
    229           }
    230         }
    231         done();
    232       }.bind(this));
    233     }.bind(this));
    234 
    235     group.run(callback);
    236   };
    237 
    238   /**
    239    * One time initialization for the file system and related things.
    240    *
    241    * @param {function()} callback Completion callback.
    242    * @private
    243    */
    244   FileManager.prototype.initFileSystemUI_ = function(callback) {
    245     this.table_.startBatchUpdates();
    246     this.grid_.startBatchUpdates();
    247 
    248     this.initFileList_();
    249     this.setupCurrentDirectory_();
    250 
    251     // PyAuto tests monitor this state by polling this variable
    252     this.__defineGetter__('workerInitialized_', function() {
    253        return this.metadataCache_.isInitialized();
    254     }.bind(this));
    255 
    256     this.initDateTimeFormatters_();
    257 
    258     var self = this;
    259 
    260     // Get the 'allowRedeemOffers' preference before launching
    261     // FileListBannerController.
    262     this.getPreferences_(function(pref) {
    263       /** @type {boolean} */
    264       var showOffers = pref['allowRedeemOffers'];
    265       self.bannersController_ = new FileListBannerController(
    266           self.directoryModel_, self.volumeManager_, self.document_,
    267           showOffers);
    268       self.bannersController_.addEventListener('relayout',
    269                                                self.onResize_.bind(self));
    270     });
    271 
    272     var dm = this.directoryModel_;
    273     dm.addEventListener('directory-changed',
    274                         this.onDirectoryChanged_.bind(this));
    275     dm.addEventListener('begin-update-files', function() {
    276       self.currentList_.startBatchUpdates();
    277     });
    278     dm.addEventListener('end-update-files', function() {
    279       self.restoreItemBeingRenamed_();
    280       self.currentList_.endBatchUpdates();
    281     });
    282     dm.addEventListener('scan-started', this.onScanStarted_.bind(this));
    283     dm.addEventListener('scan-completed', this.onScanCompleted_.bind(this));
    284     dm.addEventListener('scan-failed', this.onScanCancelled_.bind(this));
    285     dm.addEventListener('scan-cancelled', this.onScanCancelled_.bind(this));
    286     dm.addEventListener('scan-updated', this.onScanUpdated_.bind(this));
    287     dm.addEventListener('rescan-completed',
    288                         this.onRescanCompleted_.bind(this));
    289 
    290     this.directoryTree_.addEventListener('change', function() {
    291       this.ensureDirectoryTreeItemNotBehindPreviewPanel_();
    292     }.bind(this));
    293 
    294     var stateChangeHandler =
    295         this.onPreferencesChanged_.bind(this);
    296     chrome.fileBrowserPrivate.onPreferencesChanged.addListener(
    297         stateChangeHandler);
    298     stateChangeHandler();
    299 
    300     var driveConnectionChangedHandler =
    301         this.onDriveConnectionChanged_.bind(this);
    302     this.volumeManager_.addEventListener('drive-connection-changed',
    303         driveConnectionChangedHandler);
    304     driveConnectionChangedHandler();
    305 
    306     // Set the initial focus.
    307     this.refocus();
    308     // Set it as a fallback when there is no focus.
    309     this.document_.addEventListener('focusout', function(e) {
    310       setTimeout(function() {
    311         // When there is no focus, the active element is the <body>.
    312         if (this.document_.activeElement == this.document_.body)
    313           this.refocus();
    314       }.bind(this), 0);
    315     }.bind(this));
    316 
    317     this.initDataTransferOperations_();
    318 
    319     this.initContextMenus_();
    320     this.initCommands_();
    321 
    322     this.updateFileTypeFilter_();
    323 
    324     this.selectionHandler_.onFileSelectionChanged();
    325 
    326     this.table_.endBatchUpdates();
    327     this.grid_.endBatchUpdates();
    328 
    329     callback();
    330   };
    331 
    332   /**
    333    * If |item| in the directory tree is behind the preview panel, scrolls up the
    334    * parent view and make the item visible. This should be called when:
    335    *  - the selected item is changed in the directory tree.
    336    *  - the visibility of the the preview panel is changed.
    337    *
    338    * @private
    339    */
    340   FileManager.prototype.ensureDirectoryTreeItemNotBehindPreviewPanel_ =
    341       function() {
    342     var selectedSubTree = this.directoryTree_.selectedItem;
    343     if (!selectedSubTree)
    344       return;
    345     var item = selectedSubTree.rowElement;
    346     var parentView = this.directoryTree_;
    347 
    348     var itemRect = item.getBoundingClientRect();
    349     if (!itemRect)
    350       return;
    351 
    352     var listRect = parentView.getBoundingClientRect();
    353     if (!listRect)
    354       return;
    355 
    356     var previewPanel = this.dialogDom_.querySelector('.preview-panel');
    357     var previewPanelRect = previewPanel.getBoundingClientRect();
    358     var panelHeight = previewPanelRect ? previewPanelRect.height : 0;
    359 
    360     var itemBottom = itemRect.bottom;
    361     var listBottom = listRect.bottom - panelHeight;
    362 
    363     if (itemBottom > listBottom) {
    364       var scrollOffset = itemBottom - listBottom;
    365       parentView.scrollTop += scrollOffset;
    366     }
    367   };
    368 
    369   /**
    370    * @private
    371    */
    372   FileManager.prototype.initDateTimeFormatters_ = function() {
    373     var use12hourClock = !this.preferences_['use24hourClock'];
    374     this.table_.setDateTimeFormat(use12hourClock);
    375   };
    376 
    377   /**
    378    * @private
    379    */
    380   FileManager.prototype.initDataTransferOperations_ = function() {
    381     this.fileOperationManager_ =
    382         this.backgroundPage_.background.fileOperationManager;
    383 
    384     // CopyManager are required for 'Delete' operation in
    385     // Open and Save dialogs. But drag-n-drop and copy-paste are not needed.
    386     if (this.dialogType != DialogType.FULL_PAGE) return;
    387 
    388     // TODO(hidehiko): Extract FileOperationManager related code from
    389     // FileManager to simplify it.
    390     this.onCopyProgressBound_ = this.onCopyProgress_.bind(this);
    391     this.fileOperationManager_.addEventListener(
    392         'copy-progress', this.onCopyProgressBound_);
    393 
    394     this.onEntryChangedBound_ = this.onEntryChanged_.bind(this);
    395     this.fileOperationManager_.addEventListener(
    396         'entry-changed', this.onEntryChangedBound_);
    397 
    398     var controller = this.fileTransferController_ =
    399         new FileTransferController(this.document_,
    400                                    this.fileOperationManager_,
    401                                    this.metadataCache_,
    402                                    this.directoryModel_,
    403                                    this.volumeManager_,
    404                                    this.ui_.multiProfileShareDialog);
    405     controller.attachDragSource(this.table_.list);
    406     controller.attachFileListDropTarget(this.table_.list);
    407     controller.attachDragSource(this.grid_);
    408     controller.attachFileListDropTarget(this.grid_);
    409     controller.attachTreeDropTarget(this.directoryTree_);
    410     controller.attachNavigationListDropTarget(this.navigationList_, true);
    411     controller.attachCopyPasteHandlers();
    412     controller.addEventListener('selection-copied',
    413         this.blinkSelection.bind(this));
    414     controller.addEventListener('selection-cut',
    415         this.blinkSelection.bind(this));
    416     controller.addEventListener('source-not-found',
    417         this.onSourceNotFound_.bind(this));
    418   };
    419 
    420   /**
    421    * Handles an error that the source entry of file operation is not found.
    422    * @private
    423    */
    424   FileManager.prototype.onSourceNotFound_ = function(event) {
    425     // Ensure this.sourceNotFoundErrorCount_ is integer.
    426     this.sourceNotFoundErrorCount_ = ~~this.sourceNotFoundErrorCount_;
    427     var item = new ProgressCenterItem();
    428     item.id = 'source-not-found-' + this.sourceNotFoundErrorCount_;
    429     if (event.progressType === ProgressItemType.COPY)
    430       item.message = strf('COPY_SOURCE_NOT_FOUND_ERROR', event.fileName);
    431     else if (event.progressType === ProgressItemType.MOVE)
    432       item.message = strf('MOVE_SOURCE_NOT_FOUND_ERROR', event.fileName);
    433     item.state = ProgressItemState.ERROR;
    434     this.backgroundPage_.background.progressCenter.updateItem(item);
    435     this.sourceNotFoundErrorCount_++;
    436   };
    437 
    438   /**
    439    * One-time initialization of context menus.
    440    * @private
    441    */
    442   FileManager.prototype.initContextMenus_ = function() {
    443     this.fileContextMenu_ = this.dialogDom_.querySelector('#file-context-menu');
    444     cr.ui.Menu.decorate(this.fileContextMenu_);
    445 
    446     cr.ui.contextMenuHandler.setContextMenu(this.grid_, this.fileContextMenu_);
    447     cr.ui.contextMenuHandler.setContextMenu(this.table_.querySelector('.list'),
    448         this.fileContextMenu_);
    449     cr.ui.contextMenuHandler.setContextMenu(
    450         this.document_.querySelector('.drive-welcome.page'),
    451         this.fileContextMenu_);
    452 
    453     this.rootsContextMenu_ =
    454         this.dialogDom_.querySelector('#roots-context-menu');
    455     cr.ui.Menu.decorate(this.rootsContextMenu_);
    456     this.navigationList_.setContextMenu(this.rootsContextMenu_);
    457 
    458     this.directoryTreeContextMenu_ =
    459         this.dialogDom_.querySelector('#directory-tree-context-menu');
    460     cr.ui.Menu.decorate(this.directoryTreeContextMenu_);
    461     this.directoryTree_.contextMenuForSubitems = this.directoryTreeContextMenu_;
    462 
    463     this.textContextMenu_ =
    464         this.dialogDom_.querySelector('#text-context-menu');
    465     cr.ui.Menu.decorate(this.textContextMenu_);
    466 
    467     this.gearButton_ = this.dialogDom_.querySelector('#gear-button');
    468     this.gearButton_.addEventListener('menushow',
    469         this.onShowGearMenu_.bind(this));
    470     chrome.fileBrowserPrivate.onDesktopChanged.addListener(function() {
    471       this.updateVisitDesktopMenus_();
    472       this.ui_.updateProfileBadge();
    473     }.bind(this));
    474     chrome.fileBrowserPrivate.onProfileAdded.addListener(
    475         this.updateVisitDesktopMenus_.bind(this));
    476     this.updateVisitDesktopMenus_();
    477 
    478     this.dialogDom_.querySelector('#gear-menu').menuItemSelector =
    479         'menuitem, hr';
    480     cr.ui.decorate(this.gearButton_, cr.ui.MenuButton);
    481 
    482     if (this.dialogType == DialogType.FULL_PAGE) {
    483       // This is to prevent the buttons from stealing focus on mouse down.
    484       var preventFocus = function(event) {
    485         event.preventDefault();
    486       };
    487 
    488       var minimizeButton = this.dialogDom_.querySelector('#minimize-button');
    489       minimizeButton.addEventListener('click', this.onMinimize.bind(this));
    490       minimizeButton.addEventListener('mousedown', preventFocus);
    491 
    492       var maximizeButton = this.dialogDom_.querySelector('#maximize-button');
    493       maximizeButton.addEventListener('click', this.onMaximize.bind(this));
    494       maximizeButton.addEventListener('mousedown', preventFocus);
    495 
    496       var closeButton = this.dialogDom_.querySelector('#close-button');
    497       closeButton.addEventListener('click', this.onClose.bind(this));
    498       closeButton.addEventListener('mousedown', preventFocus);
    499     }
    500 
    501     this.syncButton.checkable = true;
    502     this.hostedButton.checkable = true;
    503     this.detailViewButton_.checkable = true;
    504     this.thumbnailViewButton_.checkable = true;
    505 
    506     if (util.platform.runningInBrowser()) {
    507       // Suppresses the default context menu.
    508       this.dialogDom_.addEventListener('contextmenu', function(e) {
    509         e.preventDefault();
    510         e.stopPropagation();
    511       });
    512     }
    513   };
    514 
    515   FileManager.prototype.onMinimize = function() {
    516     chrome.app.window.current().minimize();
    517   };
    518 
    519   FileManager.prototype.onMaximize = function() {
    520     var appWindow = chrome.app.window.current();
    521     if (appWindow.isMaximized())
    522       appWindow.restore();
    523     else
    524       appWindow.maximize();
    525   };
    526 
    527   FileManager.prototype.onClose = function() {
    528     window.close();
    529   };
    530 
    531   FileManager.prototype.onShowGearMenu_ = function() {
    532     this.refreshRemainingSpace_(false);  /* Without loading caption. */
    533 
    534     // If the menu is opened while CTRL key pressed, secret menu itemscan be
    535     // shown.
    536     this.isSecretGearMenuShown_ = this.pressingCtrl_;
    537 
    538     // Update view of drive-related settings.
    539     this.commandHandler.updateAvailability();
    540     this.document_.getElementById('drive-separator').hidden =
    541         !this.shouldShowDriveSettings();
    542 
    543     // Force to update the gear menu position.
    544     // TODO(hirono): Remove the workaround for the crbug.com/374093 after fixing
    545     // it.
    546     var gearMenu = this.document_.querySelector('#gear-menu');
    547     gearMenu.style.left = '';
    548     gearMenu.style.right = '';
    549     gearMenu.style.top = '';
    550     gearMenu.style.bottom = '';
    551   };
    552 
    553   /**
    554    * One-time initialization of commands.
    555    * @private
    556    */
    557   FileManager.prototype.initCommands_ = function() {
    558     this.commandHandler = new CommandHandler(this);
    559 
    560     // TODO(hirono): Move the following block to the UI part.
    561     var commandButtons = this.dialogDom_.querySelectorAll('button[command]');
    562     for (var j = 0; j < commandButtons.length; j++)
    563       CommandButton.decorate(commandButtons[j]);
    564 
    565     var inputs = this.dialogDom_.querySelectorAll(
    566         'input[type=text], input[type=search], textarea');
    567     for (var i = 0; i < inputs.length; i++) {
    568       cr.ui.contextMenuHandler.setContextMenu(inputs[i], this.textContextMenu_);
    569       this.registerInputCommands_(inputs[i]);
    570     }
    571 
    572     cr.ui.contextMenuHandler.setContextMenu(this.renameInput_,
    573                                             this.textContextMenu_);
    574     this.registerInputCommands_(this.renameInput_);
    575     this.document_.addEventListener('command',
    576                                     this.setNoHover_.bind(this, true));
    577   };
    578 
    579   /**
    580    * Registers cut, copy, paste and delete commands on input element.
    581    *
    582    * @param {Node} node Text input element to register on.
    583    * @private
    584    */
    585   FileManager.prototype.registerInputCommands_ = function(node) {
    586     CommandUtil.forceDefaultHandler(node, 'cut');
    587     CommandUtil.forceDefaultHandler(node, 'copy');
    588     CommandUtil.forceDefaultHandler(node, 'paste');
    589     CommandUtil.forceDefaultHandler(node, 'delete');
    590     node.addEventListener('keydown', function(e) {
    591       var key = util.getKeyModifiers(e) + e.keyCode;
    592       if (key === '190' /* '/' */ || key === '191' /* '.' */) {
    593         // If this key event is propagated, this is handled search command,
    594         // which calls 'preventDefault' method.
    595         e.stopPropagation();
    596       }
    597     });
    598   };
    599 
    600   /**
    601    * Entry point of the initialization.
    602    * This method is called from main.js.
    603    */
    604   FileManager.prototype.initializeCore = function() {
    605     this.initializeQueue_.add(this.initGeneral_.bind(this), [], 'initGeneral');
    606     this.initializeQueue_.add(this.initBackgroundPage_.bind(this),
    607                               [], 'initBackgroundPage');
    608     this.initializeQueue_.add(this.initPreferences_.bind(this),
    609                               ['initGeneral'], 'initPreferences');
    610     this.initializeQueue_.add(this.initVolumeManager_.bind(this),
    611                               ['initGeneral', 'initBackgroundPage'],
    612                               'initVolumeManager');
    613 
    614     this.initializeQueue_.run();
    615     window.addEventListener('pagehide', this.onUnload_.bind(this));
    616   };
    617 
    618   FileManager.prototype.initializeUI = function(dialogDom, callback) {
    619     this.dialogDom_ = dialogDom;
    620     this.document_ = this.dialogDom_.ownerDocument;
    621 
    622     this.initializeQueue_.add(
    623         this.initEssentialUI_.bind(this),
    624         ['initGeneral', 'initBackgroundPage'],
    625         'initEssentialUI');
    626     this.initializeQueue_.add(this.initAdditionalUI_.bind(this),
    627         ['initEssentialUI'], 'initAdditionalUI');
    628     this.initializeQueue_.add(
    629         this.initFileSystemUI_.bind(this),
    630         ['initAdditionalUI', 'initPreferences'], 'initFileSystemUI');
    631 
    632     // Run again just in case if all pending closures have completed and the
    633     // queue has stopped and monitor the completion.
    634     this.initializeQueue_.run(callback);
    635   };
    636 
    637   /**
    638    * Initializes general purpose basic things, which are used by other
    639    * initializing methods.
    640    *
    641    * @param {function()} callback Completion callback.
    642    * @private
    643    */
    644   FileManager.prototype.initGeneral_ = function(callback) {
    645     // Initialize the application state.
    646     // TODO(mtomasz): Unify window.appState with location.search format.
    647     if (window.appState) {
    648       this.params_ = window.appState.params || {};
    649       this.initCurrentDirectoryURL_ = window.appState.currentDirectoryURL;
    650       this.initSelectionURL_ = window.appState.selectionURL;
    651       this.initTargetName_ = window.appState.targetName;
    652     } else {
    653       // Used by the select dialog only.
    654       this.params_ = location.search ?
    655                      JSON.parse(decodeURIComponent(location.search.substr(1))) :
    656                      {};
    657       this.initCurrentDirectoryURL_ = this.params_.currentDirectoryURL;
    658       this.initSelectionURL_ = this.params_.selectionURL;
    659       this.initTargetName_ = this.params_.targetName;
    660     }
    661 
    662     // Initialize the member variables that depend this.params_.
    663     this.dialogType = this.params_.type || DialogType.FULL_PAGE;
    664     this.startupPrefName_ = 'file-manager-' + this.dialogType;
    665     this.fileTypes_ = this.params_.typeList || [];
    666 
    667     callback();
    668   };
    669 
    670   /**
    671    * Initialize the background page.
    672    * @param {function()} callback Completion callback.
    673    * @private
    674    */
    675   FileManager.prototype.initBackgroundPage_ = function(callback) {
    676     chrome.runtime.getBackgroundPage(function(backgroundPage) {
    677       this.backgroundPage_ = backgroundPage;
    678       this.backgroundPage_.background.ready(function() {
    679         loadTimeData.data = this.backgroundPage_.background.stringData;
    680         if (util.platform.runningInBrowser()) {
    681           this.backgroundPage_.registerDialog(window);
    682         }
    683         callback();
    684       }.bind(this));
    685     }.bind(this));
    686   };
    687 
    688   /**
    689    * Initializes the VolumeManager instance.
    690    * @param {function()} callback Completion callback.
    691    * @private
    692    */
    693   FileManager.prototype.initVolumeManager_ = function(callback) {
    694     // Auto resolving to local path does not work for folders (e.g., dialog for
    695     // loading unpacked extensions).
    696     var noLocalPathResolution = DialogType.isFolderDialog(this.params_.type);
    697 
    698     // If this condition is false, VolumeManagerWrapper hides all drive
    699     // related event and data, even if Drive is enabled on preference.
    700     // In other words, even if Drive is disabled on preference but Files.app
    701     // should show Drive when it is re-enabled, then the value should be set to
    702     // true.
    703     // Note that the Drive enabling preference change is listened by
    704     // DriveIntegrationService, so here we don't need to take care about it.
    705     var driveEnabled =
    706         !noLocalPathResolution || !this.params_.shouldReturnLocalPath;
    707     this.volumeManager_ = new VolumeManagerWrapper(
    708         driveEnabled, this.backgroundPage_);
    709     callback();
    710   };
    711 
    712   /**
    713    * One time initialization of the Files.app's essential UI elements. These
    714    * elements will be shown to the user. Only visible elements should be
    715    * initialized here. Any heavy operation should be avoided. Files.app's
    716    * window is shown at the end of this routine.
    717    *
    718    * @param {function()} callback Completion callback.
    719    * @private
    720    */
    721   FileManager.prototype.initEssentialUI_ = function(callback) {
    722     // Record stats of dialog types. New values must NOT be inserted into the
    723     // array enumerating the types. It must be in sync with
    724     // FileDialogType enum in tools/metrics/histograms/histogram.xml.
    725     metrics.recordEnum('Create', this.dialogType,
    726         [DialogType.SELECT_FOLDER,
    727          DialogType.SELECT_UPLOAD_FOLDER,
    728          DialogType.SELECT_SAVEAS_FILE,
    729          DialogType.SELECT_OPEN_FILE,
    730          DialogType.SELECT_OPEN_MULTI_FILE,
    731          DialogType.FULL_PAGE]);
    732 
    733     // Create the metadata cache.
    734     this.metadataCache_ = MetadataCache.createFull(this.volumeManager_);
    735 
    736     // Create the root view of FileManager.
    737     this.ui_ = new FileManagerUI(this.dialogDom_, this.dialogType);
    738     this.fileTypeSelector_ = this.ui_.fileTypeSelector;
    739     this.okButton_ = this.ui_.okButton;
    740     this.cancelButton_ = this.ui_.cancelButton;
    741 
    742     // Show the window as soon as the UI pre-initialization is done.
    743     if (this.dialogType == DialogType.FULL_PAGE &&
    744         !util.platform.runningInBrowser()) {
    745       chrome.app.window.current().show();
    746       setTimeout(callback, 100);  // Wait until the animation is finished.
    747     } else {
    748       callback();
    749     }
    750   };
    751 
    752   /**
    753    * One-time initialization of dialogs.
    754    * @private
    755    */
    756   FileManager.prototype.initDialogs_ = function() {
    757     // Initialize the dialog.
    758     this.ui_.initDialogs();
    759     FileManagerDialogBase.setFileManager(this);
    760 
    761     // Obtains the dialog instances from FileManagerUI.
    762     // TODO(hirono): Remove the properties from the FileManager class.
    763     this.error = this.ui_.errorDialog;
    764     this.alert = this.ui_.alertDialog;
    765     this.confirm = this.ui_.confirmDialog;
    766     this.prompt = this.ui_.promptDialog;
    767     this.shareDialog_ = this.ui_.shareDialog;
    768     this.defaultTaskPicker = this.ui_.defaultTaskPicker;
    769     this.suggestAppsDialog = this.ui_.suggestAppsDialog;
    770   };
    771 
    772   /**
    773    * One-time initialization of various DOM nodes. Loads the additional DOM
    774    * elements visible to the user. Initialize here elements, which are expensive
    775    * or hidden in the beginning.
    776    *
    777    * @param {function()} callback Completion callback.
    778    * @private
    779    */
    780   FileManager.prototype.initAdditionalUI_ = function(callback) {
    781     this.initDialogs_();
    782     this.ui_.initAdditionalUI();
    783 
    784     this.dialogDom_.addEventListener('drop', function(e) {
    785       // Prevent opening an URL by dropping it onto the page.
    786       e.preventDefault();
    787     });
    788 
    789     this.dialogDom_.addEventListener('click',
    790                                      this.onExternalLinkClick_.bind(this));
    791     // Cache nodes we'll be manipulating.
    792     var dom = this.dialogDom_;
    793 
    794     this.filenameInput_ = dom.querySelector('#filename-input-box input');
    795     this.taskItems_ = dom.querySelector('#tasks');
    796 
    797     this.table_ = dom.querySelector('.detail-table');
    798     this.grid_ = dom.querySelector('.thumbnail-grid');
    799     this.spinner_ = dom.querySelector('#list-container > .spinner-layer');
    800     this.showSpinner_(true);
    801 
    802     var fullPage = this.dialogType == DialogType.FULL_PAGE;
    803     FileTable.decorate(this.table_, this.metadataCache_, fullPage);
    804     FileGrid.decorate(this.grid_, this.metadataCache_, this.volumeManager_);
    805 
    806     this.previewPanel_ = new PreviewPanel(
    807         dom.querySelector('.preview-panel'),
    808         DialogType.isOpenDialog(this.dialogType) ?
    809             PreviewPanel.VisibilityType.ALWAYS_VISIBLE :
    810             PreviewPanel.VisibilityType.AUTO,
    811         this.metadataCache_,
    812         this.volumeManager_);
    813     this.previewPanel_.addEventListener(
    814         PreviewPanel.Event.VISIBILITY_CHANGE,
    815         this.onPreviewPanelVisibilityChange_.bind(this));
    816     this.previewPanel_.initialize();
    817 
    818     this.previewPanel_.breadcrumbs.addEventListener(
    819          'pathclick', this.onBreadcrumbClick_.bind(this));
    820 
    821     // Initialize progress center panel.
    822     this.progressCenterPanel_ = new ProgressCenterPanel(
    823         dom.querySelector('#progress-center'));
    824     this.backgroundPage_.background.progressCenter.addPanel(
    825         this.progressCenterPanel_);
    826 
    827     this.document_.addEventListener('keydown', this.onKeyDown_.bind(this));
    828     this.document_.addEventListener('keyup', this.onKeyUp_.bind(this));
    829 
    830     this.renameInput_ = this.document_.createElement('input');
    831     this.renameInput_.className = 'rename entry-name';
    832 
    833     this.renameInput_.addEventListener(
    834         'keydown', this.onRenameInputKeyDown_.bind(this));
    835     this.renameInput_.addEventListener(
    836         'blur', this.onRenameInputBlur_.bind(this));
    837 
    838     // TODO(hirono): Rename the handler after creating the DialogFooter class.
    839     this.filenameInput_.addEventListener(
    840         'input', this.onFilenameInputInput_.bind(this));
    841     this.filenameInput_.addEventListener(
    842         'keydown', this.onFilenameInputKeyDown_.bind(this));
    843     this.filenameInput_.addEventListener(
    844         'focus', this.onFilenameInputFocus_.bind(this));
    845 
    846     this.listContainer_ = this.dialogDom_.querySelector('#list-container');
    847     this.listContainer_.addEventListener(
    848         'keydown', this.onListKeyDown_.bind(this));
    849     this.listContainer_.addEventListener(
    850         'keypress', this.onListKeyPress_.bind(this));
    851     this.listContainer_.addEventListener(
    852         'mousemove', this.onListMouseMove_.bind(this));
    853 
    854     this.okButton_.addEventListener('click', this.onOk_.bind(this));
    855     this.onCancelBound_ = this.onCancel_.bind(this);
    856     this.cancelButton_.addEventListener('click', this.onCancelBound_);
    857 
    858     this.decorateSplitter(
    859         this.dialogDom_.querySelector('#navigation-list-splitter'));
    860     this.decorateSplitter(
    861         this.dialogDom_.querySelector('#middlebar-splitter'));
    862 
    863     this.dialogContainer_ = this.dialogDom_.querySelector('.dialog-container');
    864 
    865     this.syncButton = this.dialogDom_.querySelector(
    866         '#gear-menu-drive-sync-settings');
    867     this.hostedButton = this.dialogDom_.querySelector(
    868         '#gear-menu-drive-hosted-settings');
    869 
    870     this.detailViewButton_ =
    871         this.dialogDom_.querySelector('#detail-view');
    872     this.detailViewButton_.addEventListener('activate',
    873         this.onDetailViewButtonClick_.bind(this));
    874 
    875     this.thumbnailViewButton_ =
    876         this.dialogDom_.querySelector('#thumbnail-view');
    877     this.thumbnailViewButton_.addEventListener('activate',
    878         this.onThumbnailViewButtonClick_.bind(this));
    879 
    880     cr.ui.ComboButton.decorate(this.taskItems_);
    881     this.taskItems_.showMenu = function(shouldSetFocus) {
    882       // Prevent the empty menu from opening.
    883       if (!this.menu.length)
    884         return;
    885       cr.ui.ComboButton.prototype.showMenu.call(this, shouldSetFocus);
    886     };
    887     this.taskItems_.addEventListener('select',
    888         this.onTaskItemClicked_.bind(this));
    889 
    890     this.dialogDom_.ownerDocument.defaultView.addEventListener(
    891         'resize', this.onResize_.bind(this));
    892 
    893     this.filePopup_ = null;
    894 
    895     this.searchBoxWrapper_ = this.ui_.searchBox.element;
    896     this.searchBox_ = this.ui_.searchBox.inputElement;
    897     this.searchBox_.addEventListener(
    898         'input', this.onSearchBoxUpdate_.bind(this));
    899     this.ui_.searchBox.clearButton.addEventListener(
    900         'click', this.onSearchClearButtonClick_.bind(this));
    901 
    902     this.lastSearchQuery_ = '';
    903 
    904     this.autocompleteList_ = this.ui_.searchBox.autocompleteList;
    905     this.autocompleteList_.requestSuggestions =
    906         this.requestAutocompleteSuggestions_.bind(this);
    907 
    908     // Instead, open the suggested item when Enter key is pressed or
    909     // mouse-clicked.
    910     this.autocompleteList_.handleEnterKeydown = function(event) {
    911       this.openAutocompleteSuggestion_();
    912       this.lastAutocompleteQuery_ = '';
    913       this.autocompleteList_.suggestions = [];
    914     }.bind(this);
    915     this.autocompleteList_.addEventListener('mousedown', function(event) {
    916       this.openAutocompleteSuggestion_();
    917       this.lastAutocompleteQuery_ = '';
    918       this.autocompleteList_.suggestions = [];
    919     }.bind(this));
    920 
    921     this.defaultActionMenuItem_ =
    922         this.dialogDom_.querySelector('#default-action');
    923 
    924     this.openWithCommand_ =
    925         this.dialogDom_.querySelector('#open-with');
    926 
    927     this.driveBuyMoreStorageCommand_ =
    928         this.dialogDom_.querySelector('#drive-buy-more-space');
    929 
    930     this.defaultActionMenuItem_.addEventListener('activate',
    931         this.dispatchSelectionAction_.bind(this));
    932 
    933     this.initFileTypeFilter_();
    934 
    935     util.addIsFocusedMethod();
    936 
    937     // Populate the static localized strings.
    938     i18nTemplate.process(this.document_, loadTimeData);
    939 
    940     // Arrange the file list.
    941     this.table_.normalizeColumns();
    942     this.table_.redraw();
    943 
    944     callback();
    945   };
    946 
    947   /**
    948    * @param {Event} event Click event.
    949    * @private
    950    */
    951   FileManager.prototype.onBreadcrumbClick_ = function(event) {
    952     this.directoryModel_.changeDirectoryEntry(event.entry);
    953   };
    954 
    955   /**
    956    * Constructs table and grid (heavy operation).
    957    * @private
    958    **/
    959   FileManager.prototype.initFileList_ = function() {
    960     // Always sharing the data model between the detail/thumb views confuses
    961     // them.  Instead we maintain this bogus data model, and hook it up to the
    962     // view that is not in use.
    963     this.emptyDataModel_ = new cr.ui.ArrayDataModel([]);
    964     this.emptySelectionModel_ = new cr.ui.ListSelectionModel();
    965 
    966     var singleSelection =
    967         this.dialogType == DialogType.SELECT_OPEN_FILE ||
    968         this.dialogType == DialogType.SELECT_FOLDER ||
    969         this.dialogType == DialogType.SELECT_UPLOAD_FOLDER ||
    970         this.dialogType == DialogType.SELECT_SAVEAS_FILE;
    971 
    972     this.fileFilter_ = new FileFilter(
    973         this.metadataCache_,
    974         false  /* Don't show dot files by default. */);
    975 
    976     this.fileWatcher_ = new FileWatcher(this.metadataCache_);
    977     this.fileWatcher_.addEventListener(
    978         'watcher-metadata-changed',
    979         this.onWatcherMetadataChanged_.bind(this));
    980 
    981     this.directoryModel_ = new DirectoryModel(
    982         singleSelection,
    983         this.fileFilter_,
    984         this.fileWatcher_,
    985         this.metadataCache_,
    986         this.volumeManager_);
    987 
    988     this.folderShortcutsModel_ = new FolderShortcutsDataModel(
    989         this.volumeManager_);
    990 
    991     this.selectionHandler_ = new FileSelectionHandler(this);
    992 
    993     var dataModel = this.directoryModel_.getFileList();
    994 
    995     this.table_.setupCompareFunctions(dataModel);
    996 
    997     dataModel.addEventListener('permuted',
    998                                this.updateStartupPrefs_.bind(this));
    999 
   1000     this.directoryModel_.getFileListSelection().addEventListener('change',
   1001         this.selectionHandler_.onFileSelectionChanged.bind(
   1002             this.selectionHandler_));
   1003 
   1004     this.initList_(this.grid_);
   1005     this.initList_(this.table_.list);
   1006 
   1007     var fileListFocusBound = this.onFileListFocus_.bind(this);
   1008     this.table_.list.addEventListener('focus', fileListFocusBound);
   1009     this.grid_.addEventListener('focus', fileListFocusBound);
   1010 
   1011     var draggingBound = this.onDragging_.bind(this);
   1012     var dragEndBound = this.onDragEnd_.bind(this);
   1013 
   1014     // Listen to drag events to hide preview panel while user is dragging files.
   1015     // Files.app prevents default actions in 'dragstart' in some situations,
   1016     // so we listen to 'drag' to know the list is actually being dragged.
   1017     this.table_.list.addEventListener('drag', draggingBound);
   1018     this.grid_.addEventListener('drag', draggingBound);
   1019     this.table_.list.addEventListener('dragend', dragEndBound);
   1020     this.grid_.addEventListener('dragend', dragEndBound);
   1021 
   1022     // Listen to dragselection events to hide preview panel while the user is
   1023     // selecting files by drag operation.
   1024     this.table_.list.addEventListener('dragselectionstart', draggingBound);
   1025     this.grid_.addEventListener('dragselectionstart', draggingBound);
   1026     this.table_.list.addEventListener('dragselectionend', dragEndBound);
   1027     this.grid_.addEventListener('dragselectionend', dragEndBound);
   1028 
   1029     // TODO(mtomasz, yoshiki): Create navigation list earlier, and here just
   1030     // attach the directory model.
   1031     this.initNavigationList_();
   1032 
   1033     this.table_.addEventListener('column-resize-end',
   1034                                  this.updateStartupPrefs_.bind(this));
   1035 
   1036     // Restore preferences.
   1037     this.directoryModel_.getFileList().sort(
   1038         this.viewOptions_.sortField || 'modificationTime',
   1039         this.viewOptions_.sortDirection || 'desc');
   1040     if (this.viewOptions_.columns) {
   1041       var cm = this.table_.columnModel;
   1042       for (var i = 0; i < cm.totalSize; i++) {
   1043         if (this.viewOptions_.columns[i] > 0)
   1044           cm.setWidth(i, this.viewOptions_.columns[i]);
   1045       }
   1046     }
   1047     this.setListType(this.viewOptions_.listType || FileManager.ListType.DETAIL);
   1048 
   1049     this.textSearchState_ = {text: '', date: new Date()};
   1050     this.closeOnUnmount_ = (this.params_.action == 'auto-open');
   1051 
   1052     if (this.closeOnUnmount_) {
   1053       this.volumeManager_.addEventListener('externally-unmounted',
   1054          this.onExternallyUnmounted_.bind(this));
   1055     }
   1056 
   1057     // Update metadata to change 'Today' and 'Yesterday' dates.
   1058     var today = new Date();
   1059     today.setHours(0);
   1060     today.setMinutes(0);
   1061     today.setSeconds(0);
   1062     today.setMilliseconds(0);
   1063     setTimeout(this.dailyUpdateModificationTime_.bind(this),
   1064                today.getTime() + MILLISECONDS_IN_DAY - Date.now() + 1000);
   1065   };
   1066 
   1067   /**
   1068    * @private
   1069    */
   1070   FileManager.prototype.initNavigationList_ = function() {
   1071     this.directoryTree_ = this.dialogDom_.querySelector('#directory-tree');
   1072     DirectoryTree.decorate(this.directoryTree_,
   1073                            this.directoryModel_,
   1074                            this.volumeManager_,
   1075                            this.metadataCache_);
   1076 
   1077     this.navigationList_ = this.dialogDom_.querySelector('#navigation-list');
   1078     NavigationList.decorate(this.navigationList_,
   1079                             this.volumeManager_,
   1080                             this.directoryModel_);
   1081     this.navigationList_.fileManager = this;
   1082     this.navigationList_.dataModel = new NavigationListModel(
   1083         this.volumeManager_, this.folderShortcutsModel_);
   1084   };
   1085 
   1086   /**
   1087    * @private
   1088    */
   1089   FileManager.prototype.updateMiddleBarVisibility_ = function() {
   1090     var entry = this.directoryModel_.getCurrentDirEntry();
   1091     if (!entry)
   1092       return;
   1093 
   1094     var driveVolume = this.volumeManager_.getVolumeInfo(entry);
   1095     var visible = driveVolume && !driveVolume.error &&
   1096         driveVolume.volumeType === VolumeManagerCommon.VolumeType.DRIVE;
   1097     this.dialogDom_.
   1098         querySelector('.dialog-middlebar-contents').hidden = !visible;
   1099     this.dialogDom_.querySelector('#middlebar-splitter').hidden = !visible;
   1100     this.onResize_();
   1101   };
   1102 
   1103   /**
   1104    * @private
   1105    */
   1106   FileManager.prototype.updateStartupPrefs_ = function() {
   1107     var sortStatus = this.directoryModel_.getFileList().sortStatus;
   1108     var prefs = {
   1109       sortField: sortStatus.field,
   1110       sortDirection: sortStatus.direction,
   1111       columns: [],
   1112       listType: this.listType_
   1113     };
   1114     var cm = this.table_.columnModel;
   1115     for (var i = 0; i < cm.totalSize; i++) {
   1116       prefs.columns.push(cm.getWidth(i));
   1117     }
   1118     // Save the global default.
   1119     util.platform.setPreference(this.startupPrefName_, JSON.stringify(prefs));
   1120 
   1121     // Save the window-specific preference.
   1122     if (window.appState) {
   1123       window.appState.viewOptions = prefs;
   1124       util.saveAppState();
   1125     }
   1126   };
   1127 
   1128   FileManager.prototype.refocus = function() {
   1129     var targetElement;
   1130     if (this.dialogType == DialogType.SELECT_SAVEAS_FILE)
   1131       targetElement = this.filenameInput_;
   1132     else
   1133       targetElement = this.currentList_;
   1134 
   1135     // Hack: if the tabIndex is disabled, we can assume a modal dialog is
   1136     // shown. Focus to a button on the dialog instead.
   1137     if (!targetElement.hasAttribute('tabIndex') || targetElement.tabIndex == -1)
   1138       targetElement = document.querySelector('button:not([tabIndex="-1"])');
   1139 
   1140     if (targetElement)
   1141       targetElement.focus();
   1142   };
   1143 
   1144   /**
   1145    * File list focus handler. Used to select the top most element on the list
   1146    * if nothing was selected.
   1147    *
   1148    * @private
   1149    */
   1150   FileManager.prototype.onFileListFocus_ = function() {
   1151     // If the file list is focused by <Tab>, select the first item if no item
   1152     // is selected.
   1153     if (this.pressingTab_) {
   1154       if (this.getSelection() && this.getSelection().totalCount == 0)
   1155         this.directoryModel_.selectIndex(0);
   1156     }
   1157   };
   1158 
   1159   /**
   1160    * Index of selected item in the typeList of the dialog params.
   1161    *
   1162    * @return {number} 1-based index of selected type or 0 if no type selected.
   1163    * @private
   1164    */
   1165   FileManager.prototype.getSelectedFilterIndex_ = function() {
   1166     var index = Number(this.fileTypeSelector_.selectedIndex);
   1167     if (index < 0)  // Nothing selected.
   1168       return 0;
   1169     if (this.params_.includeAllFiles)  // Already 1-based.
   1170       return index;
   1171     return index + 1;  // Convert to 1-based;
   1172   };
   1173 
   1174   FileManager.prototype.setListType = function(type) {
   1175     if (type && type == this.listType_)
   1176       return;
   1177 
   1178     this.table_.list.startBatchUpdates();
   1179     this.grid_.startBatchUpdates();
   1180 
   1181     // TODO(dzvorygin): style.display and dataModel setting order shouldn't
   1182     // cause any UI bugs. Currently, the only right way is first to set display
   1183     // style and only then set dataModel.
   1184 
   1185     if (type == FileManager.ListType.DETAIL) {
   1186       this.table_.dataModel = this.directoryModel_.getFileList();
   1187       this.table_.selectionModel = this.directoryModel_.getFileListSelection();
   1188       this.table_.hidden = false;
   1189       this.grid_.hidden = true;
   1190       this.grid_.selectionModel = this.emptySelectionModel_;
   1191       this.grid_.dataModel = this.emptyDataModel_;
   1192       this.table_.hidden = false;
   1193       /** @type {cr.ui.List} */
   1194       this.currentList_ = this.table_.list;
   1195       this.detailViewButton_.setAttribute('checked', '');
   1196       this.thumbnailViewButton_.removeAttribute('checked');
   1197       this.detailViewButton_.setAttribute('disabled', '');
   1198       this.thumbnailViewButton_.removeAttribute('disabled');
   1199     } else if (type == FileManager.ListType.THUMBNAIL) {
   1200       this.grid_.dataModel = this.directoryModel_.getFileList();
   1201       this.grid_.selectionModel = this.directoryModel_.getFileListSelection();
   1202       this.grid_.hidden = false;
   1203       this.table_.hidden = true;
   1204       this.table_.selectionModel = this.emptySelectionModel_;
   1205       this.table_.dataModel = this.emptyDataModel_;
   1206       this.grid_.hidden = false;
   1207       /** @type {cr.ui.List} */
   1208       this.currentList_ = this.grid_;
   1209       this.thumbnailViewButton_.setAttribute('checked', '');
   1210       this.detailViewButton_.removeAttribute('checked');
   1211       this.thumbnailViewButton_.setAttribute('disabled', '');
   1212       this.detailViewButton_.removeAttribute('disabled');
   1213     } else {
   1214       throw new Error('Unknown list type: ' + type);
   1215     }
   1216 
   1217     this.listType_ = type;
   1218     this.updateStartupPrefs_();
   1219     this.onResize_();
   1220 
   1221     this.table_.list.endBatchUpdates();
   1222     this.grid_.endBatchUpdates();
   1223   };
   1224 
   1225   /**
   1226    * Initialize the file list table or grid.
   1227    *
   1228    * @param {cr.ui.List} list The list.
   1229    * @private
   1230    */
   1231   FileManager.prototype.initList_ = function(list) {
   1232     // Overriding the default role 'list' to 'listbox' for better accessibility
   1233     // on ChromeOS.
   1234     list.setAttribute('role', 'listbox');
   1235     list.addEventListener('click', this.onDetailClick_.bind(this));
   1236     list.id = 'file-list';
   1237   };
   1238 
   1239   /**
   1240    * @private
   1241    */
   1242   FileManager.prototype.onCopyProgress_ = function(event) {
   1243     if (event.reason == 'ERROR' &&
   1244         event.error.code == util.FileOperationErrorType.FILESYSTEM_ERROR &&
   1245         event.error.data.toDrive &&
   1246         event.error.data.name == util.FileError.QUOTA_EXCEEDED_ERR) {
   1247       this.alert.showHtml(
   1248           strf('DRIVE_SERVER_OUT_OF_SPACE_HEADER'),
   1249           strf('DRIVE_SERVER_OUT_OF_SPACE_MESSAGE',
   1250               decodeURIComponent(
   1251                   event.error.data.sourceFileUrl.split('/').pop()),
   1252               str('GOOGLE_DRIVE_BUY_STORAGE_URL')));
   1253     }
   1254   };
   1255 
   1256   /**
   1257    * Handler of file manager operations. Called when an entry has been
   1258    * changed.
   1259    * This updates directory model to reflect operation result immediately (not
   1260    * waiting for directory update event). Also, preloads thumbnails for the
   1261    * images of new entries.
   1262    * See also FileOperationManager.EventRouter.
   1263    *
   1264    * @param {Event} event An event for the entry change.
   1265    * @private
   1266    */
   1267   FileManager.prototype.onEntryChanged_ = function(event) {
   1268     var kind = event.kind;
   1269     var entry = event.entry;
   1270     this.directoryModel_.onEntryChanged(kind, entry);
   1271     this.selectionHandler_.onFileSelectionChanged();
   1272 
   1273     if (kind === util.EntryChangedKind.CREATED && FileType.isImage(entry)) {
   1274       // Preload a thumbnail if the new copied entry an image.
   1275       var locationInfo = this.volumeManager_.getLocationInfo(entry);
   1276       if (!locationInfo)
   1277         return;
   1278       this.metadataCache_.getOne(entry, 'thumbnail|drive', function(metadata) {
   1279         var thumbnailLoader_ = new ThumbnailLoader(
   1280             entry,
   1281             ThumbnailLoader.LoaderType.CANVAS,
   1282             metadata,
   1283             undefined,  // Media type.
   1284             locationInfo.isDriveBased ?
   1285                 ThumbnailLoader.UseEmbedded.USE_EMBEDDED :
   1286                 ThumbnailLoader.UseEmbedded.NO_EMBEDDED,
   1287             10);  // Very low priority.
   1288         thumbnailLoader_.loadDetachedImage(function(success) {});
   1289       });
   1290     }
   1291   };
   1292 
   1293   /**
   1294    * Fills the file type list or hides it.
   1295    * @private
   1296    */
   1297   FileManager.prototype.initFileTypeFilter_ = function() {
   1298     if (this.params_.includeAllFiles) {
   1299       var option = this.document_.createElement('option');
   1300       option.innerText = str('ALL_FILES_FILTER');
   1301       this.fileTypeSelector_.appendChild(option);
   1302       option.value = 0;
   1303     }
   1304 
   1305     for (var i = 0; i !== this.fileTypes_.length; i++) {
   1306       var fileType = this.fileTypes_[i];
   1307       var option = this.document_.createElement('option');
   1308       var description = fileType.description;
   1309       if (!description) {
   1310         // See if all the extensions in the group have the same description.
   1311         for (var j = 0; j !== fileType.extensions.length; j++) {
   1312           var currentDescription = FileType.typeToString(
   1313               FileType.getTypeForName('.' + fileType.extensions[j]));
   1314           if (!description)  // Set the first time.
   1315             description = currentDescription;
   1316           else if (description != currentDescription) {
   1317             // No single description, fall through to the extension list.
   1318             description = null;
   1319             break;
   1320           }
   1321         }
   1322 
   1323         if (!description)
   1324           // Convert ['jpg', 'png'] to '*.jpg, *.png'.
   1325           description = fileType.extensions.map(function(s) {
   1326            return '*.' + s;
   1327           }).join(', ');
   1328        }
   1329        option.innerText = description;
   1330 
   1331        option.value = i + 1;
   1332 
   1333        if (fileType.selected)
   1334          option.selected = true;
   1335 
   1336        this.fileTypeSelector_.appendChild(option);
   1337     }
   1338 
   1339     var options = this.fileTypeSelector_.querySelectorAll('option');
   1340     if (options.length >= 2) {
   1341       // There is in fact no choice, show the selector.
   1342       this.fileTypeSelector_.hidden = false;
   1343 
   1344       this.fileTypeSelector_.addEventListener('change',
   1345           this.updateFileTypeFilter_.bind(this));
   1346     }
   1347   };
   1348 
   1349   /**
   1350    * Filters file according to the selected file type.
   1351    * @private
   1352    */
   1353   FileManager.prototype.updateFileTypeFilter_ = function() {
   1354     this.fileFilter_.removeFilter('fileType');
   1355     var selectedIndex = this.getSelectedFilterIndex_();
   1356     if (selectedIndex > 0) { // Specific filter selected.
   1357       var regexp = new RegExp('\\.(' +
   1358           this.fileTypes_[selectedIndex - 1].extensions.join('|') + ')$', 'i');
   1359       var filter = function(entry) {
   1360         return entry.isDirectory || regexp.test(entry.name);
   1361       };
   1362       this.fileFilter_.addFilter('fileType', filter);
   1363 
   1364       // In save dialog, update the destination name extension.
   1365       if (this.dialogType === DialogType.SELECT_SAVEAS_FILE) {
   1366         var current = this.filenameInput_.value;
   1367         var newExt = this.fileTypes_[selectedIndex - 1].extensions[0];
   1368         if (newExt && !regexp.test(current)) {
   1369           var i = current.lastIndexOf('.');
   1370           if (i >= 0) {
   1371             this.filenameInput_.value = current.substr(0, i) + '.' + newExt;
   1372             this.selectTargetNameInFilenameInput_();
   1373           }
   1374         }
   1375       }
   1376     }
   1377   };
   1378 
   1379   /**
   1380    * Resize details and thumb views to fit the new window size.
   1381    * @private
   1382    */
   1383   FileManager.prototype.onResize_ = function() {
   1384     if (this.listType_ == FileManager.ListType.THUMBNAIL)
   1385       this.grid_.relayout();
   1386     else
   1387       this.table_.relayout();
   1388 
   1389     // May not be available during initialization.
   1390     if (this.directoryTree_)
   1391       this.directoryTree_.relayout();
   1392 
   1393     // TODO(mtomasz, yoshiki): Initialize navigation list earlier, before
   1394     // file system is available.
   1395     if (this.navigationList_)
   1396       this.navigationList_.redraw();
   1397 
   1398     this.previewPanel_.breadcrumbs.truncate();
   1399   };
   1400 
   1401   /**
   1402    * Handles local metadata changes in the currect directory.
   1403    * @param {Event} event Change event.
   1404    * @private
   1405    */
   1406   FileManager.prototype.onWatcherMetadataChanged_ = function(event) {
   1407     this.updateMetadataInUI_(
   1408         event.metadataType, event.entries, event.properties);
   1409   };
   1410 
   1411   /**
   1412    * Resize details and thumb views to fit the new window size.
   1413    * @private
   1414    */
   1415   FileManager.prototype.onPreviewPanelVisibilityChange_ = function() {
   1416     // This method may be called on initialization. Some object may not be
   1417     // initialized.
   1418 
   1419     var panelHeight = this.previewPanel_.visible ?
   1420         this.previewPanel_.height : 0;
   1421     if (this.grid_)
   1422       this.grid_.setBottomMarginForPanel(panelHeight);
   1423     if (this.table_)
   1424       this.table_.setBottomMarginForPanel(panelHeight);
   1425 
   1426     if (this.directoryTree_) {
   1427       this.directoryTree_.setBottomMarginForPanel(panelHeight);
   1428       this.ensureDirectoryTreeItemNotBehindPreviewPanel_();
   1429     }
   1430   };
   1431 
   1432   /**
   1433    * Invoked while the drag is being performed on the list or the grid.
   1434    * Note: this method may be called multiple times before onDragEnd_().
   1435    * @private
   1436    */
   1437   FileManager.prototype.onDragging_ = function() {
   1438     // On open file dialog, the preview panel is always shown.
   1439     if (DialogType.isOpenDialog(this.dialogType))
   1440       return;
   1441     this.previewPanel_.visibilityType =
   1442         PreviewPanel.VisibilityType.ALWAYS_HIDDEN;
   1443   };
   1444 
   1445   /**
   1446    * Invoked when the drag is ended on the list or the grid.
   1447    * @private
   1448    */
   1449   FileManager.prototype.onDragEnd_ = function() {
   1450     // On open file dialog, the preview panel is always shown.
   1451     if (DialogType.isOpenDialog(this.dialogType))
   1452       return;
   1453     this.previewPanel_.visibilityType = PreviewPanel.VisibilityType.AUTO;
   1454   };
   1455 
   1456   /**
   1457    * Sets up the current directory during initialization.
   1458    * @private
   1459    */
   1460   FileManager.prototype.setupCurrentDirectory_ = function() {
   1461     var tracker = this.directoryModel_.createDirectoryChangeTracker();
   1462     var queue = new AsyncUtil.Queue();
   1463 
   1464     // Wait until the volume manager is initialized.
   1465     queue.run(function(callback) {
   1466       tracker.start();
   1467       this.volumeManager_.ensureInitialized(callback);
   1468     }.bind(this));
   1469 
   1470     var nextCurrentDirEntry;
   1471     var selectionEntry;
   1472 
   1473     // Resolve the selectionURL to selectionEntry or to currentDirectoryEntry
   1474     // in case of being a display root.
   1475     queue.run(function(callback) {
   1476       if (!this.initSelectionURL_) {
   1477         callback();
   1478         return;
   1479       }
   1480       webkitResolveLocalFileSystemURL(
   1481           this.initSelectionURL_,
   1482           function(inEntry) {
   1483             var locationInfo = this.volumeManager_.getLocationInfo(inEntry);
   1484             // If location information is not available, then the volume is
   1485             // no longer (or never) available.
   1486             if (!locationInfo) {
   1487               callback();
   1488               return;
   1489             }
   1490             // If the selection is root, then use it as a current directory
   1491             // instead. This is because, selecting a root entry is done as
   1492             // opening it.
   1493             if (locationInfo.isRootEntry)
   1494               nextCurrentDirEntry = inEntry;
   1495             else
   1496               selectionEntry = inEntry;
   1497             callback();
   1498           }.bind(this), callback);
   1499     }.bind(this));
   1500     // Resolve the currentDirectoryURL to currentDirectoryEntry (if not done
   1501     // by the previous step).
   1502     queue.run(function(callback) {
   1503       if (nextCurrentDirEntry || !this.initCurrentDirectoryURL_) {
   1504         callback();
   1505         return;
   1506       }
   1507       webkitResolveLocalFileSystemURL(
   1508           this.initCurrentDirectoryURL_,
   1509           function(inEntry) {
   1510             var locationInfo = this.volumeManager_.getLocationInfo(inEntry);
   1511             if (!locationInfo) {
   1512               callback();
   1513               return;
   1514             }
   1515             nextCurrentDirEntry = inEntry;
   1516             callback();
   1517           }.bind(this), callback);
   1518       // TODO(mtomasz): Implement reopening on special search, when fake
   1519       // entries are converted to directory providers.
   1520     }.bind(this));
   1521 
   1522     // If the directory to be changed to is not available, then first fallback
   1523     // to the parent of the selection entry.
   1524     queue.run(function(callback) {
   1525       if (nextCurrentDirEntry || !selectionEntry) {
   1526         callback();
   1527         return;
   1528       }
   1529       selectionEntry.getParent(function(inEntry) {
   1530         nextCurrentDirEntry = inEntry;
   1531         callback();
   1532       }.bind(this));
   1533     }.bind(this));
   1534 
   1535     // If the directory to be changed to is still not resolved, then fallback
   1536     // to the default display root.
   1537     queue.run(function(callback) {
   1538       if (nextCurrentDirEntry) {
   1539         callback();
   1540         return;
   1541       }
   1542       this.volumeManager_.getDefaultDisplayRoot(function(displayRoot) {
   1543         nextCurrentDirEntry = displayRoot;
   1544         callback();
   1545       }.bind(this));
   1546     }.bind(this));
   1547 
   1548     // If selection failed to be resolved (eg. didn't exist, in case of saving
   1549     // a file, or in case of a fallback of the current directory, then try to
   1550     // resolve again using the target name.
   1551     queue.run(function(callback) {
   1552       if (selectionEntry || !nextCurrentDirEntry || !this.initTargetName_) {
   1553         callback();
   1554         return;
   1555       }
   1556       // Try to resolve as a file first. If it fails, then as a directory.
   1557       nextCurrentDirEntry.getFile(
   1558           this.initTargetName_,
   1559           {},
   1560           function(targetEntry) {
   1561             selectionEntry = targetEntry;
   1562             callback();
   1563           }, function() {
   1564             // Failed to resolve as a file
   1565             nextCurrentDirEntry.getDirectory(
   1566               this.initTargetName_,
   1567               {},
   1568               function(targetEntry) {
   1569                 selectionEntry = targetEntry;
   1570                 callback();
   1571               }, function() {
   1572                 // Failed to resolve as either file or directory.
   1573                 callback();
   1574               });
   1575           }.bind(this));
   1576     }.bind(this));
   1577 
   1578 
   1579     // Finalize.
   1580     queue.run(function(callback) {
   1581       // Check directory change.
   1582       tracker.stop();
   1583       if (tracker.hasChanged) {
   1584         callback();
   1585         return;
   1586       }
   1587       // Finish setup current directory.
   1588       this.finishSetupCurrentDirectory_(
   1589           nextCurrentDirEntry,
   1590           selectionEntry,
   1591           this.initTargetName_);
   1592       callback();
   1593     }.bind(this));
   1594   };
   1595 
   1596   /**
   1597    * @param {DirectoryEntry} directoryEntry Directory to be opened.
   1598    * @param {Entry=} opt_selectionEntry Entry to be selected.
   1599    * @param {string=} opt_suggestedName Suggested name for a non-existing\
   1600    *     selection.
   1601    * @private
   1602    */
   1603   FileManager.prototype.finishSetupCurrentDirectory_ = function(
   1604       directoryEntry, opt_selectionEntry, opt_suggestedName) {
   1605     // Open the directory, and select the selection (if passed).
   1606     if (util.isFakeEntry(directoryEntry)) {
   1607       this.directoryModel_.specialSearch(directoryEntry, '');
   1608     } else {
   1609       this.directoryModel_.changeDirectoryEntry(directoryEntry, function() {
   1610         if (opt_selectionEntry)
   1611           this.directoryModel_.selectEntry(opt_selectionEntry);
   1612       }.bind(this));
   1613     }
   1614 
   1615     if (this.dialogType === DialogType.FULL_PAGE) {
   1616       // In the FULL_PAGE mode if the restored URL points to a file we might
   1617       // have to invoke a task after selecting it.
   1618       if (this.params_.action === 'select')
   1619         return;
   1620 
   1621       var task = null;
   1622       // Handle restoring after crash, or the gallery action.
   1623       // TODO(mtomasz): Use the gallery action instead of just the gallery
   1624       //     field.
   1625       if (this.params_.gallery ||
   1626           this.params_.action === 'gallery' ||
   1627           this.params_.action === 'gallery-video') {
   1628         if (!opt_selectionEntry) {
   1629           // Non-existent file or a directory.
   1630           // Reloading while the Gallery is open with empty or multiple
   1631           // selection. Open the Gallery when the directory is scanned.
   1632           task = function() {
   1633             new FileTasks(this, this.params_).openGallery([]);
   1634           }.bind(this);
   1635         } else {
   1636           // The file or the directory exists.
   1637           task = function() {
   1638             new FileTasks(this, this.params_).openGallery([opt_selectionEntry]);
   1639           }.bind(this);
   1640         }
   1641       } else {
   1642         // TODO(mtomasz): Implement remounting archives after crash.
   1643         //                See: crbug.com/333139
   1644       }
   1645 
   1646       // If there is a task to be run, run it after the scan is completed.
   1647       if (task) {
   1648         var listener = function() {
   1649           if (!util.isSameEntry(this.directoryModel_.getCurrentDirEntry(),
   1650                                directoryEntry)) {
   1651             // Opened on a different URL. Probably fallbacked. Therefore,
   1652             // do not invoke a task.
   1653             return;
   1654           }
   1655           this.directoryModel_.removeEventListener(
   1656               'scan-completed', listener);
   1657           task();
   1658         }.bind(this);
   1659         this.directoryModel_.addEventListener('scan-completed', listener);
   1660       }
   1661     } else if (this.dialogType === DialogType.SELECT_SAVEAS_FILE) {
   1662       this.filenameInput_.value = opt_suggestedName || '';
   1663       this.selectTargetNameInFilenameInput_();
   1664     }
   1665   };
   1666 
   1667   /**
   1668    * @private
   1669    */
   1670   FileManager.prototype.refreshCurrentDirectoryMetadata_ = function() {
   1671     var entries = this.directoryModel_.getFileList().slice();
   1672     var directoryEntry = this.directoryModel_.getCurrentDirEntry();
   1673     if (!directoryEntry)
   1674       return;
   1675     // We don't pass callback here. When new metadata arrives, we have an
   1676     // observer registered to update the UI.
   1677 
   1678     // TODO(dgozman): refresh content metadata only when modificationTime
   1679     // changed.
   1680     var isFakeEntry = util.isFakeEntry(directoryEntry);
   1681     var getEntries = (isFakeEntry ? [] : [directoryEntry]).concat(entries);
   1682     if (!isFakeEntry)
   1683       this.metadataCache_.clearRecursively(directoryEntry, '*');
   1684     this.metadataCache_.get(getEntries, 'filesystem', null);
   1685 
   1686     if (this.isOnDrive())
   1687       this.metadataCache_.get(getEntries, 'drive', null);
   1688 
   1689     var visibleItems = this.currentList_.items;
   1690     var visibleEntries = [];
   1691     for (var i = 0; i < visibleItems.length; i++) {
   1692       var index = this.currentList_.getIndexOfListItem(visibleItems[i]);
   1693       var entry = this.directoryModel_.getFileList().item(index);
   1694       // The following check is a workaround for the bug in list: sometimes item
   1695       // does not have listIndex, and therefore is not found in the list.
   1696       if (entry) visibleEntries.push(entry);
   1697     }
   1698     this.metadataCache_.get(visibleEntries, 'thumbnail', null);
   1699   };
   1700 
   1701   /**
   1702    * @private
   1703    */
   1704   FileManager.prototype.dailyUpdateModificationTime_ = function() {
   1705     var entries = this.directoryModel_.getFileList().slice();
   1706     this.metadataCache_.get(
   1707         entries,
   1708         'filesystem',
   1709         this.updateMetadataInUI_.bind(this, 'filesystem', entries));
   1710 
   1711     setTimeout(this.dailyUpdateModificationTime_.bind(this),
   1712                MILLISECONDS_IN_DAY);
   1713   };
   1714 
   1715   /**
   1716    * @param {string} type Type of metadata changed.
   1717    * @param {Array.<Entry>} entries Array of entries.
   1718    * @param {Object.<string, Object>} props Map from entry URLs to metadata
   1719    *     props.
   1720    * @private
   1721    */
   1722   FileManager.prototype.updateMetadataInUI_ = function(
   1723       type, entries, properties) {
   1724     if (this.listType_ == FileManager.ListType.DETAIL)
   1725       this.table_.updateListItemsMetadata(type, properties);
   1726     else
   1727       this.grid_.updateListItemsMetadata(type, properties);
   1728     // TODO: update bottom panel thumbnails.
   1729   };
   1730 
   1731   /**
   1732    * Restore the item which is being renamed while refreshing the file list. Do
   1733    * nothing if no item is being renamed or such an item disappeared.
   1734    *
   1735    * While refreshing file list it gets repopulated with new file entries.
   1736    * There is not a big difference whether DOM items stay the same or not.
   1737    * Except for the item that the user is renaming.
   1738    *
   1739    * @private
   1740    */
   1741   FileManager.prototype.restoreItemBeingRenamed_ = function() {
   1742     if (!this.isRenamingInProgress())
   1743       return;
   1744 
   1745     var dm = this.directoryModel_;
   1746     var leadIndex = dm.getFileListSelection().leadIndex;
   1747     if (leadIndex < 0)
   1748       return;
   1749 
   1750     var leadEntry = dm.getFileList().item(leadIndex);
   1751     if (!util.isSameEntry(this.renameInput_.currentEntry, leadEntry))
   1752       return;
   1753 
   1754     var leadListItem = this.findListItemForNode_(this.renameInput_);
   1755     if (this.currentList_ == this.table_.list) {
   1756       this.table_.updateFileMetadata(leadListItem, leadEntry);
   1757     }
   1758     this.currentList_.restoreLeadItem(leadListItem);
   1759   };
   1760 
   1761   /**
   1762    * TODO(mtomasz): Move this to a utility function working on the root type.
   1763    * @return {boolean} True if the current directory content is from Google
   1764    *     Drive.
   1765    */
   1766   FileManager.prototype.isOnDrive = function() {
   1767     var rootType = this.directoryModel_.getCurrentRootType();
   1768     return rootType === VolumeManagerCommon.RootType.DRIVE ||
   1769            rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
   1770            rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
   1771            rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE;
   1772   };
   1773 
   1774   /**
   1775    * Check if the drive-related setting items should be shown on currently
   1776    * displayed gear menu.
   1777    * @return {boolean} True if those setting items should be shown.
   1778    */
   1779   FileManager.prototype.shouldShowDriveSettings = function() {
   1780     return this.isOnDrive() && this.isSecretGearMenuShown_;
   1781   };
   1782 
   1783   /**
   1784    * Overrides default handling for clicks on hyperlinks.
   1785    * In a packaged apps links with targer='_blank' open in a new tab by
   1786    * default, other links do not open at all.
   1787    *
   1788    * @param {Event} event Click event.
   1789    * @private
   1790    */
   1791   FileManager.prototype.onExternalLinkClick_ = function(event) {
   1792     if (event.target.tagName != 'A' || !event.target.href)
   1793       return;
   1794 
   1795     if (this.dialogType != DialogType.FULL_PAGE)
   1796       this.onCancel_();
   1797   };
   1798 
   1799   /**
   1800    * Task combobox handler.
   1801    *
   1802    * @param {Object} event Event containing task which was clicked.
   1803    * @private
   1804    */
   1805   FileManager.prototype.onTaskItemClicked_ = function(event) {
   1806     var selection = this.getSelection();
   1807     if (!selection.tasks) return;
   1808 
   1809     if (event.item.task) {
   1810       // Task field doesn't exist on change-default dropdown item.
   1811       selection.tasks.execute(event.item.task.taskId);
   1812     } else {
   1813       var extensions = [];
   1814 
   1815       for (var i = 0; i < selection.entries.length; i++) {
   1816         var match = /\.(\w+)$/g.exec(selection.entries[i].toURL());
   1817         if (match) {
   1818           var ext = match[1].toUpperCase();
   1819           if (extensions.indexOf(ext) == -1) {
   1820             extensions.push(ext);
   1821           }
   1822         }
   1823       }
   1824 
   1825       var format = '';
   1826 
   1827       if (extensions.length == 1) {
   1828         format = extensions[0];
   1829       }
   1830 
   1831       // Change default was clicked. We should open "change default" dialog.
   1832       selection.tasks.showTaskPicker(this.defaultTaskPicker,
   1833           loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM'),
   1834           strf('CHANGE_DEFAULT_CAPTION', format),
   1835           this.onDefaultTaskDone_.bind(this));
   1836     }
   1837   };
   1838 
   1839   /**
   1840    * Sets the given task as default, when this task is applicable.
   1841    *
   1842    * @param {Object} task Task to set as default.
   1843    * @private
   1844    */
   1845   FileManager.prototype.onDefaultTaskDone_ = function(task) {
   1846     // TODO(dgozman): move this method closer to tasks.
   1847     var selection = this.getSelection();
   1848     chrome.fileBrowserPrivate.setDefaultTask(
   1849         task.taskId,
   1850         util.entriesToURLs(selection.entries),
   1851         selection.mimeTypes);
   1852     selection.tasks = new FileTasks(this);
   1853     selection.tasks.init(selection.entries, selection.mimeTypes);
   1854     selection.tasks.display(this.taskItems_);
   1855     this.refreshCurrentDirectoryMetadata_();
   1856     this.selectionHandler_.onFileSelectionChanged();
   1857   };
   1858 
   1859   /**
   1860    * @private
   1861    */
   1862   FileManager.prototype.onPreferencesChanged_ = function() {
   1863     var self = this;
   1864     this.getPreferences_(function(prefs) {
   1865       self.initDateTimeFormatters_();
   1866       self.refreshCurrentDirectoryMetadata_();
   1867 
   1868       if (prefs.cellularDisabled)
   1869         self.syncButton.setAttribute('checked', '');
   1870       else
   1871         self.syncButton.removeAttribute('checked');
   1872 
   1873       if (self.hostedButton.hasAttribute('checked') ===
   1874           prefs.hostedFilesDisabled && self.isOnDrive()) {
   1875         self.directoryModel_.rescan(false);
   1876       }
   1877 
   1878       if (!prefs.hostedFilesDisabled)
   1879         self.hostedButton.setAttribute('checked', '');
   1880       else
   1881         self.hostedButton.removeAttribute('checked');
   1882     },
   1883     true /* refresh */);
   1884   };
   1885 
   1886   FileManager.prototype.onDriveConnectionChanged_ = function() {
   1887     var connection = this.volumeManager_.getDriveConnectionState();
   1888     if (this.commandHandler)
   1889       this.commandHandler.updateAvailability();
   1890     if (this.dialogContainer_)
   1891       this.dialogContainer_.setAttribute('connection', connection.type);
   1892     this.shareDialog_.hideWithResult(ShareDialog.Result.NETWORK_ERROR);
   1893     this.suggestAppsDialog.onDriveConnectionChanged(connection.type);
   1894   };
   1895 
   1896   /**
   1897    * Tells whether the current directory is read only.
   1898    * TODO(mtomasz): Remove and use EntryLocation directly.
   1899    * @return {boolean} True if read only, false otherwise.
   1900    */
   1901   FileManager.prototype.isOnReadonlyDirectory = function() {
   1902     return this.directoryModel_.isReadOnly();
   1903   };
   1904 
   1905   /**
   1906    * @param {Event} Unmount event.
   1907    * @private
   1908    */
   1909   FileManager.prototype.onExternallyUnmounted_ = function(event) {
   1910     if (event.volumeInfo === this.currentVolumeInfo_) {
   1911       if (this.closeOnUnmount_) {
   1912         // If the file manager opened automatically when a usb drive inserted,
   1913         // user have never changed current volume (that implies the current
   1914         // directory is still on the device) then close this window.
   1915         window.close();
   1916       }
   1917     }
   1918   };
   1919 
   1920   /**
   1921    * Shows a modal-like file viewer/editor on top of the File Manager UI.
   1922    *
   1923    * @param {HTMLElement} popup Popup element.
   1924    * @param {function()} closeCallback Function to call after the popup is
   1925    *     closed.
   1926    */
   1927   FileManager.prototype.openFilePopup = function(popup, closeCallback) {
   1928     this.closeFilePopup();
   1929     this.filePopup_ = popup;
   1930     this.filePopupCloseCallback_ = closeCallback;
   1931     this.dialogDom_.insertBefore(
   1932         this.filePopup_, this.dialogDom_.querySelector('#iframe-drag-area'));
   1933     this.filePopup_.focus();
   1934     this.document_.body.setAttribute('overlay-visible', '');
   1935     this.document_.querySelector('#iframe-drag-area').hidden = false;
   1936   };
   1937 
   1938   /**
   1939    * Closes the modal-like file viewer/editor popup.
   1940    */
   1941   FileManager.prototype.closeFilePopup = function() {
   1942     if (this.filePopup_) {
   1943       this.document_.body.removeAttribute('overlay-visible');
   1944       this.document_.querySelector('#iframe-drag-area').hidden = true;
   1945       // The window resize would not be processed properly while the relevant
   1946       // divs had 'display:none', force resize after the layout fired.
   1947       setTimeout(this.onResize_.bind(this), 0);
   1948       if (this.filePopup_.contentWindow &&
   1949           this.filePopup_.contentWindow.unload) {
   1950         this.filePopup_.contentWindow.unload();
   1951       }
   1952 
   1953       if (this.filePopupCloseCallback_) {
   1954         this.filePopupCloseCallback_();
   1955         this.filePopupCloseCallback_ = null;
   1956       }
   1957 
   1958       // These operations have to be in the end, otherwise v8 crashes on an
   1959       // assert. See: crbug.com/224174.
   1960       this.dialogDom_.removeChild(this.filePopup_);
   1961       this.filePopup_ = null;
   1962     }
   1963   };
   1964 
   1965   /**
   1966    * Updates visibility of the draggable app region in the modal-like file
   1967    * viewer/editor.
   1968    *
   1969    * @param {boolean} visible True for visible, false otherwise.
   1970    */
   1971   FileManager.prototype.onFilePopupAppRegionChanged = function(visible) {
   1972     if (!this.filePopup_)
   1973       return;
   1974 
   1975     this.document_.querySelector('#iframe-drag-area').hidden = !visible;
   1976   };
   1977 
   1978   /**
   1979    * @return {Array.<Entry>} List of all entries in the current directory.
   1980    */
   1981   FileManager.prototype.getAllEntriesInCurrentDirectory = function() {
   1982     return this.directoryModel_.getFileList().slice();
   1983   };
   1984 
   1985   FileManager.prototype.isRenamingInProgress = function() {
   1986     return !!this.renameInput_.currentEntry;
   1987   };
   1988 
   1989   /**
   1990    * @private
   1991    */
   1992   FileManager.prototype.focusCurrentList_ = function() {
   1993     if (this.listType_ == FileManager.ListType.DETAIL)
   1994       this.table_.focus();
   1995     else  // this.listType_ == FileManager.ListType.THUMBNAIL)
   1996       this.grid_.focus();
   1997   };
   1998 
   1999   /**
   2000    * Return DirectoryEntry of the current directory or null.
   2001    * @return {DirectoryEntry} DirectoryEntry of the current directory. Returns
   2002    *     null if the directory model is not ready or the current directory is
   2003    *     not set.
   2004    */
   2005   FileManager.prototype.getCurrentDirectoryEntry = function() {
   2006     return this.directoryModel_ && this.directoryModel_.getCurrentDirEntry();
   2007   };
   2008 
   2009   /**
   2010    * Shows the share dialog for the selected file or directory.
   2011    */
   2012   FileManager.prototype.shareSelection = function() {
   2013     var entries = this.getSelection().entries;
   2014     if (entries.length != 1) {
   2015       console.warn('Unable to share multiple items at once.');
   2016       return;
   2017     }
   2018     // Add the overlapped class to prevent the applicaiton window from
   2019     // captureing mouse events.
   2020     this.shareDialog_.show(entries[0], function(result) {
   2021       if (result == ShareDialog.Result.NETWORK_ERROR)
   2022         this.error.show(str('SHARE_ERROR'));
   2023     }.bind(this));
   2024   };
   2025 
   2026   /**
   2027    * Creates a folder shortcut.
   2028    * @param {Entry} entry A shortcut which refers to |entry| to be created.
   2029    */
   2030   FileManager.prototype.createFolderShortcut = function(entry) {
   2031     // Duplicate entry.
   2032     if (this.folderShortcutExists(entry))
   2033       return;
   2034 
   2035     this.folderShortcutsModel_.add(entry);
   2036   };
   2037 
   2038   /**
   2039    * Checkes if the shortcut which refers to the given folder exists or not.
   2040    * @param {Entry} entry Entry of the folder to be checked.
   2041    */
   2042   FileManager.prototype.folderShortcutExists = function(entry) {
   2043     return this.folderShortcutsModel_.exists(entry);
   2044   };
   2045 
   2046   /**
   2047    * Removes the folder shortcut.
   2048    * @param {Entry} entry The shortcut which refers to |entry| is to be removed.
   2049    */
   2050   FileManager.prototype.removeFolderShortcut = function(entry) {
   2051     this.folderShortcutsModel_.remove(entry);
   2052   };
   2053 
   2054   /**
   2055    * Blinks the selection. Used to give feedback when copying or cutting the
   2056    * selection.
   2057    */
   2058   FileManager.prototype.blinkSelection = function() {
   2059     var selection = this.getSelection();
   2060     if (!selection || selection.totalCount == 0)
   2061       return;
   2062 
   2063     for (var i = 0; i < selection.entries.length; i++) {
   2064       var selectedIndex = selection.indexes[i];
   2065       var listItem = this.currentList_.getListItemByIndex(selectedIndex);
   2066       if (listItem)
   2067         this.blinkListItem_(listItem);
   2068     }
   2069   };
   2070 
   2071   /**
   2072    * @param {Element} listItem List item element.
   2073    * @private
   2074    */
   2075   FileManager.prototype.blinkListItem_ = function(listItem) {
   2076     listItem.classList.add('blink');
   2077     setTimeout(function() {
   2078       listItem.classList.remove('blink');
   2079     }, 100);
   2080   };
   2081 
   2082   /**
   2083    * @private
   2084    */
   2085   FileManager.prototype.selectTargetNameInFilenameInput_ = function() {
   2086     var input = this.filenameInput_;
   2087     input.focus();
   2088     var selectionEnd = input.value.lastIndexOf('.');
   2089     if (selectionEnd == -1) {
   2090       input.select();
   2091     } else {
   2092       input.selectionStart = 0;
   2093       input.selectionEnd = selectionEnd;
   2094     }
   2095   };
   2096 
   2097   /**
   2098    * Handles mouse click or tap.
   2099    *
   2100    * @param {Event} event The click event.
   2101    * @private
   2102    */
   2103   FileManager.prototype.onDetailClick_ = function(event) {
   2104     if (this.isRenamingInProgress()) {
   2105       // Don't pay attention to clicks during a rename.
   2106       return;
   2107     }
   2108 
   2109     var listItem = this.findListItemForEvent_(event);
   2110     var selection = this.getSelection();
   2111     if (!listItem || !listItem.selected || selection.totalCount != 1) {
   2112       return;
   2113     }
   2114 
   2115     // React on double click, but only if both clicks hit the same item.
   2116     // TODO(mtomasz): Simplify it, and use a double click handler if possible.
   2117     var clickNumber = (this.lastClickedItem_ == listItem) ? 2 : undefined;
   2118     this.lastClickedItem_ = listItem;
   2119 
   2120     if (event.detail != clickNumber)
   2121       return;
   2122 
   2123     var entry = selection.entries[0];
   2124     if (entry.isDirectory) {
   2125       this.onDirectoryAction_(entry);
   2126     } else {
   2127       this.dispatchSelectionAction_();
   2128     }
   2129   };
   2130 
   2131   /**
   2132    * @private
   2133    */
   2134   FileManager.prototype.dispatchSelectionAction_ = function() {
   2135     if (this.dialogType == DialogType.FULL_PAGE) {
   2136       var selection = this.getSelection();
   2137       var tasks = selection.tasks;
   2138       var urls = selection.urls;
   2139       var mimeTypes = selection.mimeTypes;
   2140       if (tasks)
   2141         tasks.executeDefault();
   2142       return true;
   2143     }
   2144     if (!this.okButton_.disabled) {
   2145       this.onOk_();
   2146       return true;
   2147     }
   2148     return false;
   2149   };
   2150 
   2151   /**
   2152    * Opens the suggest file dialog.
   2153    *
   2154    * @param {Entry} entry Entry of the file.
   2155    * @param {function()} onSuccess Success callback.
   2156    * @param {function()} onCancelled User-cancelled callback.
   2157    * @param {function()} onFailure Failure callback.
   2158    * @private
   2159    */
   2160   FileManager.prototype.openSuggestAppsDialog =
   2161       function(entry, onSuccess, onCancelled, onFailure) {
   2162     if (!url) {
   2163       onFailure();
   2164       return;
   2165     }
   2166 
   2167     this.metadataCache_.getOne(entry, 'drive', function(prop) {
   2168       if (!prop || !prop.contentMimeType) {
   2169         onFailure();
   2170         return;
   2171       }
   2172 
   2173       var basename = entry.name;
   2174       var splitted = util.splitExtension(basename);
   2175       var filename = splitted[0];
   2176       var extension = splitted[1];
   2177       var mime = prop.contentMimeType;
   2178 
   2179       // Returns with failure if the file has neither extension nor mime.
   2180       if (!extension || !mime) {
   2181         onFailure();
   2182         return;
   2183       }
   2184 
   2185       var onDialogClosed = function(result) {
   2186         switch (result) {
   2187           case SuggestAppsDialog.Result.INSTALL_SUCCESSFUL:
   2188             onSuccess();
   2189             break;
   2190           case SuggestAppsDialog.Result.FAILED:
   2191             onFailure();
   2192             break;
   2193           default:
   2194             onCancelled();
   2195         }
   2196       };
   2197 
   2198       if (FileTasks.EXECUTABLE_EXTENSIONS.indexOf(extension) !== -1) {
   2199         this.suggestAppsDialog.showByFilename(filename, onDialogClosed);
   2200       } else {
   2201         this.suggestAppsDialog.showByExtensionAndMime(
   2202             extension, mime, onDialogClosed);
   2203       }
   2204     }.bind(this));
   2205   };
   2206 
   2207   /**
   2208    * Called when a dialog is shown or hidden.
   2209    * @param {boolean} flag True if a dialog is shown, false if hidden.
   2210    */
   2211   FileManager.prototype.onDialogShownOrHidden = function(show) {
   2212     if (show) {
   2213       // If a dialog is shown, activate the window.
   2214       var appWindow = chrome.app.window.current();
   2215       if (appWindow)
   2216         appWindow.focus();
   2217     }
   2218 
   2219     // Set/unset a flag to disable dragging on the title area.
   2220     this.dialogContainer_.classList.toggle('disable-header-drag', show);
   2221   };
   2222 
   2223   /**
   2224    * Executes directory action (i.e. changes directory).
   2225    *
   2226    * @param {DirectoryEntry} entry Directory entry to which directory should be
   2227    *                               changed.
   2228    * @private
   2229    */
   2230   FileManager.prototype.onDirectoryAction_ = function(entry) {
   2231     return this.directoryModel_.changeDirectoryEntry(entry);
   2232   };
   2233 
   2234   /**
   2235    * Update the window title.
   2236    * @private
   2237    */
   2238   FileManager.prototype.updateTitle_ = function() {
   2239     if (this.dialogType != DialogType.FULL_PAGE)
   2240       return;
   2241 
   2242     if (!this.currentVolumeInfo_)
   2243       return;
   2244 
   2245     this.document_.title = this.currentVolumeInfo_.label;
   2246   };
   2247 
   2248   /**
   2249    * Update the gear menu.
   2250    * @private
   2251    */
   2252   FileManager.prototype.updateGearMenu_ = function() {
   2253     this.refreshRemainingSpace_(true);  // Show loading caption.
   2254   };
   2255 
   2256   /**
   2257    * Update menus that move the window to the other profile's desktop.
   2258    * TODO(hirono): Add the GearMenu class and make it a member of the class.
   2259    * TODO(hirono): Handle the case where a profile is added while the menu is
   2260    *     opened.
   2261    * @private
   2262    */
   2263   FileManager.prototype.updateVisitDesktopMenus_ = function() {
   2264     var gearMenu = this.document_.querySelector('#gear-menu');
   2265     var separator =
   2266         this.document_.querySelector('#multi-profile-separator');
   2267 
   2268     // Remove existing menu items.
   2269     var oldItems =
   2270         this.document_.querySelectorAll('#gear-menu .visit-desktop');
   2271     for (var i = 0; i < oldItems.length; i++) {
   2272       gearMenu.removeChild(oldItems[i]);
   2273     }
   2274     separator.hidden = true;
   2275 
   2276     if (this.dialogType !== DialogType.FULL_PAGE)
   2277       return;
   2278 
   2279     // Obtain the profile information.
   2280     chrome.fileBrowserPrivate.getProfiles(function(profiles,
   2281                                                    currentId,
   2282                                                    displayedId) {
   2283       // Check if the menus are needed or not.
   2284       var insertingPosition = separator.nextSibling;
   2285       if (profiles.length === 1 && profiles[0].profileId === displayedId)
   2286         return;
   2287 
   2288       separator.hidden = false;
   2289       for (var i = 0; i < profiles.length; i++) {
   2290         var profile = profiles[i];
   2291         if (profile.profileId === displayedId)
   2292           continue;
   2293         var item = this.document_.createElement('menuitem');
   2294         cr.ui.MenuItem.decorate(item);
   2295         gearMenu.insertBefore(item, insertingPosition);
   2296         item.className = 'visit-desktop';
   2297         item.label = strf('VISIT_DESKTOP_OF_USER',
   2298                           profile.displayName,
   2299                           profile.profileId);
   2300         item.addEventListener('activate', function(inProfile, event) {
   2301           // Stop propagate and hide the menu manually, in order to prevent the
   2302           // focus from being back to the button. (cf. http://crbug.com/248479)
   2303           event.stopPropagation();
   2304           this.gearButton_.hideMenu();
   2305           this.gearButton_.blur();
   2306           chrome.fileBrowserPrivate.visitDesktop(inProfile.profileId);
   2307         }.bind(this, profile));
   2308       }
   2309     }.bind(this));
   2310   };
   2311 
   2312   /**
   2313    * Refreshes space info of the current volume.
   2314    * @param {boolean} showLoadingCaption Whether show loading caption or not.
   2315    * @private
   2316    */
   2317   FileManager.prototype.refreshRemainingSpace_ = function(showLoadingCaption) {
   2318     if (!this.currentVolumeInfo_)
   2319       return;
   2320 
   2321     var volumeSpaceInfo =
   2322         this.dialogDom_.querySelector('#volume-space-info');
   2323     var volumeSpaceInfoSeparator =
   2324         this.dialogDom_.querySelector('#volume-space-info-separator');
   2325     var volumeSpaceInfoLabel =
   2326         this.dialogDom_.querySelector('#volume-space-info-label');
   2327     var volumeSpaceInnerBar =
   2328         this.dialogDom_.querySelector('#volume-space-info-bar');
   2329     var volumeSpaceOuterBar =
   2330         this.dialogDom_.querySelector('#volume-space-info-bar').parentNode;
   2331 
   2332     var currentVolumeInfo = this.currentVolumeInfo_;
   2333 
   2334     // TODO(mtomasz): Add support for remaining space indication for provided
   2335     // file systems.
   2336     if (currentVolumeInfo.volumeType ==
   2337         VolumeManagerCommon.VolumeType.PROVIDED) {
   2338       volumeSpaceInfo.hidden = true;
   2339       volumeSpaceInfoSeparator.hidden = true;
   2340       return;
   2341     }
   2342 
   2343     volumeSpaceInfo.hidden = false;
   2344     volumeSpaceInfoSeparator.hidden = false;
   2345     volumeSpaceInnerBar.setAttribute('pending', '');
   2346 
   2347     if (showLoadingCaption) {
   2348       volumeSpaceInfoLabel.innerText = str('WAITING_FOR_SPACE_INFO');
   2349       volumeSpaceInnerBar.style.width = '100%';
   2350     }
   2351 
   2352     chrome.fileBrowserPrivate.getSizeStats(
   2353         currentVolumeInfo.volumeId, function(result) {
   2354           var volumeInfo = this.volumeManager_.getVolumeInfo(
   2355               this.directoryModel_.getCurrentDirEntry());
   2356           if (currentVolumeInfo !== this.currentVolumeInfo_)
   2357             return;
   2358           updateSpaceInfo(result,
   2359                           volumeSpaceInnerBar,
   2360                           volumeSpaceInfoLabel,
   2361                           volumeSpaceOuterBar);
   2362         }.bind(this));
   2363   };
   2364 
   2365   /**
   2366    * Update the UI when the current directory changes.
   2367    *
   2368    * @param {Event} event The directory-changed event.
   2369    * @private
   2370    */
   2371   FileManager.prototype.onDirectoryChanged_ = function(event) {
   2372     var oldCurrentVolumeInfo = this.currentVolumeInfo_;
   2373 
   2374     // Remember the current volume info.
   2375     this.currentVolumeInfo_ = this.volumeManager_.getVolumeInfo(
   2376         event.newDirEntry);
   2377 
   2378     // If volume has changed, then update the gear menu.
   2379     if (oldCurrentVolumeInfo !== this.currentVolumeInfo_) {
   2380       this.updateGearMenu_();
   2381       // If the volume has changed, and it was previously set, then do not
   2382       // close on unmount anymore.
   2383       if (oldCurrentVolumeInfo)
   2384         this.closeOnUnmount_ = false;
   2385     }
   2386 
   2387     this.selectionHandler_.onFileSelectionChanged();
   2388     this.ui_.searchBox.clear();
   2389     // TODO(mtomasz): Consider remembering the selection.
   2390     util.updateAppState(
   2391         this.getCurrentDirectoryEntry() ?
   2392         this.getCurrentDirectoryEntry().toURL() : '',
   2393         '' /* selectionURL */,
   2394         '' /* opt_param */);
   2395 
   2396     if (this.commandHandler)
   2397       this.commandHandler.updateAvailability();
   2398 
   2399     this.updateUnformattedVolumeStatus_();
   2400     this.updateTitle_();
   2401 
   2402     var currentEntry = this.getCurrentDirectoryEntry();
   2403     this.previewPanel_.currentEntry = util.isFakeEntry(currentEntry) ?
   2404         null : currentEntry;
   2405   };
   2406 
   2407   FileManager.prototype.updateUnformattedVolumeStatus_ = function() {
   2408     var volumeInfo = this.volumeManager_.getVolumeInfo(
   2409         this.directoryModel_.getCurrentDirEntry());
   2410 
   2411     if (volumeInfo && volumeInfo.error) {
   2412       this.dialogDom_.setAttribute('unformatted', '');
   2413 
   2414       var errorNode = this.dialogDom_.querySelector('#format-panel > .error');
   2415       if (volumeInfo.error ===
   2416           VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
   2417         errorNode.textContent = str('UNSUPPORTED_FILESYSTEM_WARNING');
   2418       } else {
   2419         errorNode.textContent = str('UNKNOWN_FILESYSTEM_WARNING');
   2420       }
   2421 
   2422       // Update 'canExecute' for format command so the format button's disabled
   2423       // property is properly set.
   2424       if (this.commandHandler)
   2425         this.commandHandler.updateAvailability();
   2426     } else {
   2427       this.dialogDom_.removeAttribute('unformatted');
   2428     }
   2429   };
   2430 
   2431   FileManager.prototype.findListItemForEvent_ = function(event) {
   2432     return this.findListItemForNode_(event.touchedElement || event.srcElement);
   2433   };
   2434 
   2435   FileManager.prototype.findListItemForNode_ = function(node) {
   2436     var item = this.currentList_.getListItemAncestor(node);
   2437     // TODO(serya): list should check that.
   2438     return item && this.currentList_.isItem(item) ? item : null;
   2439   };
   2440 
   2441   /**
   2442    * Unload handler for the page.
   2443    * @private
   2444    */
   2445   FileManager.prototype.onUnload_ = function() {
   2446     if (this.directoryModel_)
   2447       this.directoryModel_.dispose();
   2448     if (this.volumeManager_)
   2449       this.volumeManager_.dispose();
   2450     if (this.filePopup_ &&
   2451         this.filePopup_.contentWindow &&
   2452         this.filePopup_.contentWindow.unload)
   2453       this.filePopup_.contentWindow.unload(true /* exiting */);
   2454     if (this.progressCenterPanel_)
   2455       this.backgroundPage_.background.progressCenter.removePanel(
   2456           this.progressCenterPanel_);
   2457     if (this.fileOperationManager_) {
   2458       if (this.onCopyProgressBound_) {
   2459         this.fileOperationManager_.removeEventListener(
   2460             'copy-progress', this.onCopyProgressBound_);
   2461       }
   2462       if (this.onEntryChangedBound_) {
   2463         this.fileOperationManager_.removeEventListener(
   2464             'entry-changed', this.onEntryChangedBound_);
   2465       }
   2466     }
   2467     window.closing = true;
   2468     if (this.backgroundPage_)
   2469       this.backgroundPage_.background.tryClose();
   2470   };
   2471 
   2472   FileManager.prototype.initiateRename = function() {
   2473     var item = this.currentList_.ensureLeadItemExists();
   2474     if (!item)
   2475       return;
   2476     var label = item.querySelector('.filename-label');
   2477     var input = this.renameInput_;
   2478     var currentEntry = this.currentList_.dataModel.item(item.listIndex);
   2479 
   2480     input.value = label.textContent;
   2481     item.setAttribute('renaming', '');
   2482     label.parentNode.appendChild(input);
   2483     input.focus();
   2484 
   2485     var selectionEnd = input.value.lastIndexOf('.');
   2486     if (currentEntry.isFile && selectionEnd !== -1) {
   2487       input.selectionStart = 0;
   2488       input.selectionEnd = selectionEnd;
   2489     } else {
   2490       input.select();
   2491     }
   2492 
   2493     // This has to be set late in the process so we don't handle spurious
   2494     // blur events.
   2495     input.currentEntry = currentEntry;
   2496     this.table_.startBatchUpdates();
   2497     this.grid_.startBatchUpdates();
   2498   };
   2499 
   2500   /**
   2501    * @type {Event} Key event.
   2502    * @private
   2503    */
   2504   FileManager.prototype.onRenameInputKeyDown_ = function(event) {
   2505     if (!this.isRenamingInProgress())
   2506       return;
   2507 
   2508     // Do not move selection or lead item in list during rename.
   2509     if (event.keyIdentifier == 'Up' || event.keyIdentifier == 'Down') {
   2510       event.stopPropagation();
   2511     }
   2512 
   2513     switch (util.getKeyModifiers(event) + event.keyIdentifier) {
   2514       case 'U+001B':  // Escape
   2515         this.cancelRename_();
   2516         event.preventDefault();
   2517         break;
   2518 
   2519       case 'Enter':
   2520         this.commitRename_();
   2521         event.preventDefault();
   2522         break;
   2523     }
   2524   };
   2525 
   2526   /**
   2527    * @type {Event} Blur event.
   2528    * @private
   2529    */
   2530   FileManager.prototype.onRenameInputBlur_ = function(event) {
   2531     if (this.isRenamingInProgress() && !this.renameInput_.validation_)
   2532       this.commitRename_();
   2533   };
   2534 
   2535   /**
   2536    * @private
   2537    */
   2538   FileManager.prototype.commitRename_ = function() {
   2539     var input = this.renameInput_;
   2540     var entry = input.currentEntry;
   2541     var newName = input.value;
   2542 
   2543     if (newName == entry.name) {
   2544       this.cancelRename_();
   2545       return;
   2546     }
   2547 
   2548     var renamedItemElement = this.findListItemForNode_(this.renameInput_);
   2549     var nameNode = renamedItemElement.querySelector('.filename-label');
   2550 
   2551     input.validation_ = true;
   2552     var validationDone = function(valid) {
   2553       input.validation_ = false;
   2554 
   2555       if (!valid) {
   2556         // Cancel rename if it fails to restore focus from alert dialog.
   2557         // Otherwise, just cancel the commitment and continue to rename.
   2558         if (this.document_.activeElement != input)
   2559           this.cancelRename_();
   2560         return;
   2561       }
   2562 
   2563       // Validation succeeded. Do renaming.
   2564       this.renameInput_.currentEntry = null;
   2565       if (this.renameInput_.parentNode)
   2566         this.renameInput_.parentNode.removeChild(this.renameInput_);
   2567       renamedItemElement.setAttribute('renaming', 'provisional');
   2568 
   2569       // Optimistically apply new name immediately to avoid flickering in
   2570       // case of success.
   2571       nameNode.textContent = newName;
   2572 
   2573       util.rename(
   2574           entry, newName,
   2575           function(newEntry) {
   2576             this.directoryModel_.onRenameEntry(entry, newEntry);
   2577             renamedItemElement.removeAttribute('renaming');
   2578             this.table_.endBatchUpdates();
   2579             this.grid_.endBatchUpdates();
   2580             // Focus may go out of the list. Back it to the list.
   2581             this.currentList_.focus();
   2582           }.bind(this),
   2583           function(error) {
   2584             // Write back to the old name.
   2585             nameNode.textContent = entry.name;
   2586             renamedItemElement.removeAttribute('renaming');
   2587             this.table_.endBatchUpdates();
   2588             this.grid_.endBatchUpdates();
   2589 
   2590             // Show error dialog.
   2591             var message;
   2592             if (error.name == util.FileError.PATH_EXISTS_ERR ||
   2593                 error.name == util.FileError.TYPE_MISMATCH_ERR) {
   2594               // Check the existing entry is file or not.
   2595               // 1) If the entry is a file:
   2596               //   a) If we get PATH_EXISTS_ERR, a file exists.
   2597               //   b) If we get TYPE_MISMATCH_ERR, a directory exists.
   2598               // 2) If the entry is a directory:
   2599               //   a) If we get PATH_EXISTS_ERR, a directory exists.
   2600               //   b) If we get TYPE_MISMATCH_ERR, a file exists.
   2601               message = strf(
   2602                   (entry.isFile && error.name ==
   2603                       util.FileError.PATH_EXISTS_ERR) ||
   2604                   (!entry.isFile && error.name ==
   2605                       util.FileError.TYPE_MISMATCH_ERR) ?
   2606                       'FILE_ALREADY_EXISTS' :
   2607                       'DIRECTORY_ALREADY_EXISTS',
   2608                   newName);
   2609             } else {
   2610               message = strf('ERROR_RENAMING', entry.name,
   2611                              util.getFileErrorString(error.name));
   2612             }
   2613 
   2614             this.alert.show(message);
   2615           }.bind(this));
   2616     };
   2617 
   2618     // TODO(haruki): this.getCurrentDirectoryEntry() might not return the actual
   2619     // parent if the directory content is a search result. Fix it to do proper
   2620     // validation.
   2621     this.validateFileName_(this.getCurrentDirectoryEntry(),
   2622                            newName,
   2623                            validationDone.bind(this));
   2624   };
   2625 
   2626   /**
   2627    * @private
   2628    */
   2629   FileManager.prototype.cancelRename_ = function() {
   2630     this.renameInput_.currentEntry = null;
   2631 
   2632     var item = this.findListItemForNode_(this.renameInput_);
   2633     if (item)
   2634       item.removeAttribute('renaming');
   2635 
   2636     var parent = this.renameInput_.parentNode;
   2637     if (parent)
   2638       parent.removeChild(this.renameInput_);
   2639 
   2640     this.table_.endBatchUpdates();
   2641     this.grid_.endBatchUpdates();
   2642   };
   2643 
   2644   /**
   2645    * @param {Event} Key event.
   2646    * @private
   2647    */
   2648   FileManager.prototype.onFilenameInputInput_ = function() {
   2649     this.selectionHandler_.updateOkButton();
   2650   };
   2651 
   2652   /**
   2653    * @param {Event} Key event.
   2654    * @private
   2655    */
   2656   FileManager.prototype.onFilenameInputKeyDown_ = function(event) {
   2657     if ((util.getKeyModifiers(event) + event.keyCode) === '13' /* Enter */)
   2658       this.okButton_.click();
   2659   };
   2660 
   2661   /**
   2662    * @param {Event} Focus event.
   2663    * @private
   2664    */
   2665   FileManager.prototype.onFilenameInputFocus_ = function(event) {
   2666     var input = this.filenameInput_;
   2667 
   2668     // On focus we want to select everything but the extension, but
   2669     // Chrome will select-all after the focus event completes.  We
   2670     // schedule a timeout to alter the focus after that happens.
   2671     setTimeout(function() {
   2672         var selectionEnd = input.value.lastIndexOf('.');
   2673         if (selectionEnd == -1) {
   2674           input.select();
   2675         } else {
   2676           input.selectionStart = 0;
   2677           input.selectionEnd = selectionEnd;
   2678         }
   2679     }, 0);
   2680   };
   2681 
   2682   /**
   2683    * @private
   2684    */
   2685   FileManager.prototype.onScanStarted_ = function() {
   2686     if (this.scanInProgress_) {
   2687       this.table_.list.endBatchUpdates();
   2688       this.grid_.endBatchUpdates();
   2689     }
   2690 
   2691     if (this.commandHandler)
   2692       this.commandHandler.updateAvailability();
   2693     this.table_.list.startBatchUpdates();
   2694     this.grid_.startBatchUpdates();
   2695     this.scanInProgress_ = true;
   2696 
   2697     this.scanUpdatedAtLeastOnceOrCompleted_ = false;
   2698     if (this.scanCompletedTimer_) {
   2699       clearTimeout(this.scanCompletedTimer_);
   2700       this.scanCompletedTimer_ = null;
   2701     }
   2702 
   2703     if (this.scanUpdatedTimer_) {
   2704       clearTimeout(this.scanUpdatedTimer_);
   2705       this.scanUpdatedTimer_ = null;
   2706     }
   2707 
   2708     if (this.spinner_.hidden) {
   2709       this.cancelSpinnerTimeout_();
   2710       this.showSpinnerTimeout_ =
   2711           setTimeout(this.showSpinner_.bind(this, true), 500);
   2712     }
   2713   };
   2714 
   2715   /**
   2716    * @private
   2717    */
   2718   FileManager.prototype.onScanCompleted_ = function() {
   2719     if (!this.scanInProgress_) {
   2720       console.error('Scan-completed event recieved. But scan is not started.');
   2721       return;
   2722     }
   2723 
   2724     if (this.commandHandler)
   2725       this.commandHandler.updateAvailability();
   2726     this.hideSpinnerLater_();
   2727 
   2728     if (this.scanUpdatedTimer_) {
   2729       clearTimeout(this.scanUpdatedTimer_);
   2730       this.scanUpdatedTimer_ = null;
   2731     }
   2732 
   2733     // To avoid flickering postpone updating the ui by a small amount of time.
   2734     // There is a high chance, that metadata will be received within 50 ms.
   2735     this.scanCompletedTimer_ = setTimeout(function() {
   2736       // Check if batch updates are already finished by onScanUpdated_().
   2737       if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
   2738         this.scanUpdatedAtLeastOnceOrCompleted_ = true;
   2739         this.updateMiddleBarVisibility_();
   2740       }
   2741 
   2742       this.scanInProgress_ = false;
   2743       this.table_.list.endBatchUpdates();
   2744       this.grid_.endBatchUpdates();
   2745       this.scanCompletedTimer_ = null;
   2746     }.bind(this), 50);
   2747   };
   2748 
   2749   /**
   2750    * @private
   2751    */
   2752   FileManager.prototype.onScanUpdated_ = function() {
   2753     if (!this.scanInProgress_) {
   2754       console.error('Scan-updated event recieved. But scan is not started.');
   2755       return;
   2756     }
   2757 
   2758     if (this.scanUpdatedTimer_ || this.scanCompletedTimer_)
   2759       return;
   2760 
   2761     // Show contents incrementally by finishing batch updated, but only after
   2762     // 200ms elapsed, to avoid flickering when it is not necessary.
   2763     this.scanUpdatedTimer_ = setTimeout(function() {
   2764       // We need to hide the spinner only once.
   2765       if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
   2766         this.scanUpdatedAtLeastOnceOrCompleted_ = true;
   2767         this.hideSpinnerLater_();
   2768         this.updateMiddleBarVisibility_();
   2769       }
   2770 
   2771       // Update the UI.
   2772       if (this.scanInProgress_) {
   2773         this.table_.list.endBatchUpdates();
   2774         this.grid_.endBatchUpdates();
   2775         this.table_.list.startBatchUpdates();
   2776         this.grid_.startBatchUpdates();
   2777       }
   2778       this.scanUpdatedTimer_ = null;
   2779     }.bind(this), 200);
   2780   };
   2781 
   2782   /**
   2783    * @private
   2784    */
   2785   FileManager.prototype.onScanCancelled_ = function() {
   2786     if (!this.scanInProgress_) {
   2787       console.error('Scan-cancelled event recieved. But scan is not started.');
   2788       return;
   2789     }
   2790 
   2791     if (this.commandHandler)
   2792       this.commandHandler.updateAvailability();
   2793     this.hideSpinnerLater_();
   2794     if (this.scanCompletedTimer_) {
   2795       clearTimeout(this.scanCompletedTimer_);
   2796       this.scanCompletedTimer_ = null;
   2797     }
   2798     if (this.scanUpdatedTimer_) {
   2799       clearTimeout(this.scanUpdatedTimer_);
   2800       this.scanUpdatedTimer_ = null;
   2801     }
   2802     // Finish unfinished batch updates.
   2803     if (!this.scanUpdatedAtLeastOnceOrCompleted_) {
   2804       this.scanUpdatedAtLeastOnceOrCompleted_ = true;
   2805       this.updateMiddleBarVisibility_();
   2806     }
   2807 
   2808     this.scanInProgress_ = false;
   2809     this.table_.list.endBatchUpdates();
   2810     this.grid_.endBatchUpdates();
   2811   };
   2812 
   2813   /**
   2814    * Handle the 'rescan-completed' from the DirectoryModel.
   2815    * @private
   2816    */
   2817   FileManager.prototype.onRescanCompleted_ = function() {
   2818     this.selectionHandler_.onFileSelectionChanged();
   2819   };
   2820 
   2821   /**
   2822    * @private
   2823    */
   2824   FileManager.prototype.cancelSpinnerTimeout_ = function() {
   2825     if (this.showSpinnerTimeout_) {
   2826       clearTimeout(this.showSpinnerTimeout_);
   2827       this.showSpinnerTimeout_ = null;
   2828     }
   2829   };
   2830 
   2831   /**
   2832    * @private
   2833    */
   2834   FileManager.prototype.hideSpinnerLater_ = function() {
   2835     this.cancelSpinnerTimeout_();
   2836     this.showSpinner_(false);
   2837   };
   2838 
   2839   /**
   2840    * @param {boolean} on True to show, false to hide.
   2841    * @private
   2842    */
   2843   FileManager.prototype.showSpinner_ = function(on) {
   2844     if (on && this.directoryModel_ && this.directoryModel_.isScanning())
   2845       this.spinner_.hidden = false;
   2846 
   2847     if (!on && (!this.directoryModel_ ||
   2848                 !this.directoryModel_.isScanning() ||
   2849                 this.directoryModel_.getFileList().length != 0)) {
   2850       this.spinner_.hidden = true;
   2851     }
   2852   };
   2853 
   2854   FileManager.prototype.createNewFolder = function() {
   2855     var defaultName = str('DEFAULT_NEW_FOLDER_NAME');
   2856 
   2857     // Find a name that doesn't exist in the data model.
   2858     var files = this.directoryModel_.getFileList();
   2859     var hash = {};
   2860     for (var i = 0; i < files.length; i++) {
   2861       var name = files.item(i).name;
   2862       // Filtering names prevents from conflicts with prototype's names
   2863       // and '__proto__'.
   2864       if (name.substring(0, defaultName.length) == defaultName)
   2865         hash[name] = 1;
   2866     }
   2867 
   2868     var baseName = defaultName;
   2869     var separator = '';
   2870     var suffix = '';
   2871     var index = '';
   2872 
   2873     var advance = function() {
   2874       separator = ' (';
   2875       suffix = ')';
   2876       index++;
   2877     };
   2878 
   2879     var current = function() {
   2880       return baseName + separator + index + suffix;
   2881     };
   2882 
   2883     // Accessing hasOwnProperty is safe since hash properties filtered.
   2884     while (hash.hasOwnProperty(current())) {
   2885       advance();
   2886     }
   2887 
   2888     var self = this;
   2889     var list = self.currentList_;
   2890     var tryCreate = function() {
   2891     };
   2892 
   2893     var onSuccess = function(entry) {
   2894       metrics.recordUserAction('CreateNewFolder');
   2895       list.selectedItem = entry;
   2896 
   2897       self.table_.list.endBatchUpdates();
   2898       self.grid_.endBatchUpdates();
   2899 
   2900       self.initiateRename();
   2901     };
   2902 
   2903     var onError = function(error) {
   2904       self.table_.list.endBatchUpdates();
   2905       self.grid_.endBatchUpdates();
   2906 
   2907       self.alert.show(strf('ERROR_CREATING_FOLDER', current(),
   2908                            util.getFileErrorString(error.name)));
   2909     };
   2910 
   2911     var onAbort = function() {
   2912       self.table_.list.endBatchUpdates();
   2913       self.grid_.endBatchUpdates();
   2914     };
   2915 
   2916     this.table_.list.startBatchUpdates();
   2917     this.grid_.startBatchUpdates();
   2918     this.directoryModel_.createDirectory(current(),
   2919                                          onSuccess,
   2920                                          onError,
   2921                                          onAbort);
   2922   };
   2923 
   2924   /**
   2925    * @param {Event} event Click event.
   2926    * @private
   2927    */
   2928   FileManager.prototype.onDetailViewButtonClick_ = function(event) {
   2929     // Stop propagate and hide the menu manually, in order to prevent the focus
   2930     // from being back to the button. (cf. http://crbug.com/248479)
   2931     event.stopPropagation();
   2932     this.gearButton_.hideMenu();
   2933     this.gearButton_.blur();
   2934     this.setListType(FileManager.ListType.DETAIL);
   2935   };
   2936 
   2937   /**
   2938    * @param {Event} event Click event.
   2939    * @private
   2940    */
   2941   FileManager.prototype.onThumbnailViewButtonClick_ = function(event) {
   2942     // Stop propagate and hide the menu manually, in order to prevent the focus
   2943     // from being back to the button. (cf. http://crbug.com/248479)
   2944     event.stopPropagation();
   2945     this.gearButton_.hideMenu();
   2946     this.gearButton_.blur();
   2947     this.setListType(FileManager.ListType.THUMBNAIL);
   2948   };
   2949 
   2950   /**
   2951    * KeyDown event handler for the document.
   2952    * @param {Event} event Key event.
   2953    * @private
   2954    */
   2955   FileManager.prototype.onKeyDown_ = function(event) {
   2956     if (event.keyCode === 9)  // Tab
   2957       this.pressingTab_ = true;
   2958     if (event.keyCode === 17)  // Ctrl
   2959       this.pressingCtrl_ = true;
   2960 
   2961     if (event.srcElement === this.renameInput_) {
   2962       // Ignore keydown handler in the rename input box.
   2963       return;
   2964     }
   2965 
   2966     switch (util.getKeyModifiers(event) + event.keyIdentifier) {
   2967       case 'Ctrl-U+00BE':  // Ctrl-. => Toggle filter files.
   2968         this.fileFilter_.setFilterHidden(
   2969             !this.fileFilter_.isFilterHiddenOn());
   2970         event.preventDefault();
   2971         return;
   2972 
   2973       case 'U+001B':  // Escape => Cancel dialog.
   2974         if (this.dialogType != DialogType.FULL_PAGE) {
   2975           // If there is nothing else for ESC to do, then cancel the dialog.
   2976           event.preventDefault();
   2977           this.cancelButton_.click();
   2978         }
   2979         break;
   2980     }
   2981   };
   2982 
   2983   /**
   2984    * KeyUp event handler for the document.
   2985    * @param {Event} event Key event.
   2986    * @private
   2987    */
   2988   FileManager.prototype.onKeyUp_ = function(event) {
   2989     if (event.keyCode === 9)  // Tab
   2990       this.pressingTab_ = false;
   2991     if (event.keyCode == 17)  // Ctrl
   2992       this.pressingCtrl_ = false;
   2993   };
   2994 
   2995   /**
   2996    * KeyDown event handler for the div#list-container element.
   2997    * @param {Event} event Key event.
   2998    * @private
   2999    */
   3000   FileManager.prototype.onListKeyDown_ = function(event) {
   3001     if (event.srcElement.tagName == 'INPUT') {
   3002       // Ignore keydown handler in the rename input box.
   3003       return;
   3004     }
   3005 
   3006     switch (util.getKeyModifiers(event) + event.keyCode) {
   3007       case '8':  // Backspace => Up one directory.
   3008         event.preventDefault();
   3009         // TODO(mtomasz): Use Entry.getParent() instead.
   3010         if (!this.getCurrentDirectoryEntry())
   3011           break;
   3012         var currentEntry = this.getCurrentDirectoryEntry();
   3013         var locationInfo = this.volumeManager_.getLocationInfo(currentEntry);
   3014         // TODO(mtomasz): There may be a tiny race in here.
   3015         if (locationInfo && !locationInfo.isRootEntry &&
   3016             !locationInfo.isSpecialSearchRoot) {
   3017           currentEntry.getParent(function(parentEntry) {
   3018             this.directoryModel_.changeDirectoryEntry(parentEntry);
   3019           }.bind(this), function() { /* Ignore errors. */});
   3020         }
   3021         break;
   3022 
   3023       case '13':  // Enter => Change directory or perform default action.
   3024         // TODO(dgozman): move directory action to dispatchSelectionAction.
   3025         var selection = this.getSelection();
   3026         if (selection.totalCount == 1 &&
   3027             selection.entries[0].isDirectory &&
   3028             !DialogType.isFolderDialog(this.dialogType)) {
   3029           event.preventDefault();
   3030           this.onDirectoryAction_(selection.entries[0]);
   3031         } else if (this.dispatchSelectionAction_()) {
   3032           event.preventDefault();
   3033         }
   3034         break;
   3035     }
   3036 
   3037     switch (event.keyIdentifier) {
   3038       case 'Home':
   3039       case 'End':
   3040       case 'Up':
   3041       case 'Down':
   3042       case 'Left':
   3043       case 'Right':
   3044         // When navigating with keyboard we hide the distracting mouse hover
   3045         // highlighting until the user moves the mouse again.
   3046         this.setNoHover_(true);
   3047         break;
   3048     }
   3049   };
   3050 
   3051   /**
   3052    * Suppress/restore hover highlighting in the list container.
   3053    * @param {boolean} on True to temporarity hide hover state.
   3054    * @private
   3055    */
   3056   FileManager.prototype.setNoHover_ = function(on) {
   3057     if (on) {
   3058       this.listContainer_.classList.add('nohover');
   3059     } else {
   3060       this.listContainer_.classList.remove('nohover');
   3061     }
   3062   };
   3063 
   3064   /**
   3065    * KeyPress event handler for the div#list-container element.
   3066    * @param {Event} event Key event.
   3067    * @private
   3068    */
   3069   FileManager.prototype.onListKeyPress_ = function(event) {
   3070     if (event.srcElement.tagName == 'INPUT') {
   3071       // Ignore keypress handler in the rename input box.
   3072       return;
   3073     }
   3074 
   3075     if (event.ctrlKey || event.metaKey || event.altKey)
   3076       return;
   3077 
   3078     var now = new Date();
   3079     var char = String.fromCharCode(event.charCode).toLowerCase();
   3080     var text = now - this.textSearchState_.date > 1000 ? '' :
   3081         this.textSearchState_.text;
   3082     this.textSearchState_ = {text: text + char, date: now};
   3083 
   3084     this.doTextSearch_();
   3085   };
   3086 
   3087   /**
   3088    * Mousemove event handler for the div#list-container element.
   3089    * @param {Event} event Mouse event.
   3090    * @private
   3091    */
   3092   FileManager.prototype.onListMouseMove_ = function(event) {
   3093     // The user grabbed the mouse, restore the hover highlighting.
   3094     this.setNoHover_(false);
   3095   };
   3096 
   3097   /**
   3098    * Performs a 'text search' - selects a first list entry with name
   3099    * starting with entered text (case-insensitive).
   3100    * @private
   3101    */
   3102   FileManager.prototype.doTextSearch_ = function() {
   3103     var text = this.textSearchState_.text;
   3104     if (!text)
   3105       return;
   3106 
   3107     var dm = this.directoryModel_.getFileList();
   3108     for (var index = 0; index < dm.length; ++index) {
   3109       var name = dm.item(index).name;
   3110       if (name.substring(0, text.length).toLowerCase() == text) {
   3111         this.currentList_.selectionModel.selectedIndexes = [index];
   3112         return;
   3113       }
   3114     }
   3115 
   3116     this.textSearchState_.text = '';
   3117   };
   3118 
   3119   /**
   3120    * Handle a click of the cancel button.  Closes the window.
   3121    * TODO(jamescook): Make unload handler work automatically, crbug.com/104811
   3122    *
   3123    * @param {Event} event The click event.
   3124    * @private
   3125    */
   3126   FileManager.prototype.onCancel_ = function(event) {
   3127     chrome.fileBrowserPrivate.cancelDialog();
   3128     window.close();
   3129   };
   3130 
   3131   /**
   3132    * Resolves selected file urls returned from an Open dialog.
   3133    *
   3134    * For drive files this involves some special treatment.
   3135    * Starts getting drive files if needed.
   3136    *
   3137    * @param {Array.<string>} fileUrls Drive URLs.
   3138    * @param {function(Array.<string>)} callback To be called with fixed URLs.
   3139    * @private
   3140    */
   3141   FileManager.prototype.resolveSelectResults_ = function(fileUrls, callback) {
   3142     if (this.isOnDrive()) {
   3143       chrome.fileBrowserPrivate.getDriveFiles(
   3144         fileUrls,
   3145         function(localPaths) {
   3146           callback(fileUrls);
   3147         });
   3148     } else {
   3149       callback(fileUrls);
   3150     }
   3151   };
   3152 
   3153   /**
   3154    * Closes this modal dialog with some files selected.
   3155    * TODO(jamescook): Make unload handler work automatically, crbug.com/104811
   3156    * @param {Object} selection Contains urls, filterIndex and multiple fields.
   3157    * @private
   3158    */
   3159   FileManager.prototype.callSelectFilesApiAndClose_ = function(selection) {
   3160     var self = this;
   3161     function callback() {
   3162       window.close();
   3163     }
   3164     if (selection.multiple) {
   3165       chrome.fileBrowserPrivate.selectFiles(
   3166           selection.urls, this.params_.shouldReturnLocalPath, callback);
   3167     } else {
   3168       var forOpening = (this.dialogType != DialogType.SELECT_SAVEAS_FILE);
   3169       chrome.fileBrowserPrivate.selectFile(
   3170           selection.urls[0], selection.filterIndex, forOpening,
   3171           this.params_.shouldReturnLocalPath, callback);
   3172     }
   3173   };
   3174 
   3175   /**
   3176    * Tries to close this modal dialog with some files selected.
   3177    * Performs preprocessing if needed (e.g. for Drive).
   3178    * @param {Object} selection Contains urls, filterIndex and multiple fields.
   3179    * @private
   3180    */
   3181   FileManager.prototype.selectFilesAndClose_ = function(selection) {
   3182     if (!this.isOnDrive() ||
   3183         this.dialogType == DialogType.SELECT_SAVEAS_FILE) {
   3184       setTimeout(this.callSelectFilesApiAndClose_.bind(this, selection), 0);
   3185       return;
   3186     }
   3187 
   3188     var shade = this.document_.createElement('div');
   3189     shade.className = 'shade';
   3190     var footer = this.dialogDom_.querySelector('.button-panel');
   3191     var progress = footer.querySelector('.progress-track');
   3192     progress.style.width = '0%';
   3193     var cancelled = false;
   3194 
   3195     var progressMap = {};
   3196     var filesStarted = 0;
   3197     var filesTotal = selection.urls.length;
   3198     for (var index = 0; index < selection.urls.length; index++) {
   3199       progressMap[selection.urls[index]] = -1;
   3200     }
   3201     var lastPercent = 0;
   3202     var bytesTotal = 0;
   3203     var bytesDone = 0;
   3204 
   3205     var onFileTransfersUpdated = function(statusList) {
   3206       for (var index = 0; index < statusList.length; index++) {
   3207         var status = statusList[index];
   3208         var escaped = encodeURI(status.fileUrl);
   3209         if (!(escaped in progressMap)) continue;
   3210         if (status.total == -1) continue;
   3211 
   3212         var old = progressMap[escaped];
   3213         if (old == -1) {
   3214           // -1 means we don't know file size yet.
   3215           bytesTotal += status.total;
   3216           filesStarted++;
   3217           old = 0;
   3218         }
   3219         bytesDone += status.processed - old;
   3220         progressMap[escaped] = status.processed;
   3221       }
   3222 
   3223       var percent = bytesTotal == 0 ? 0 : bytesDone / bytesTotal;
   3224       // For files we don't have information about, assume the progress is zero.
   3225       percent = percent * filesStarted / filesTotal * 100;
   3226       // Do not decrease the progress. This may happen, if first downloaded
   3227       // file is small, and the second one is large.
   3228       lastPercent = Math.max(lastPercent, percent);
   3229       progress.style.width = lastPercent + '%';
   3230     }.bind(this);
   3231 
   3232     var setup = function() {
   3233       this.document_.querySelector('.dialog-container').appendChild(shade);
   3234       setTimeout(function() { shade.setAttribute('fadein', 'fadein') }, 100);
   3235       footer.setAttribute('progress', 'progress');
   3236       this.cancelButton_.removeEventListener('click', this.onCancelBound_);
   3237       this.cancelButton_.addEventListener('click', onCancel);
   3238       chrome.fileBrowserPrivate.onFileTransfersUpdated.addListener(
   3239           onFileTransfersUpdated);
   3240     }.bind(this);
   3241 
   3242     var cleanup = function() {
   3243       shade.parentNode.removeChild(shade);
   3244       footer.removeAttribute('progress');
   3245       this.cancelButton_.removeEventListener('click', onCancel);
   3246       this.cancelButton_.addEventListener('click', this.onCancelBound_);
   3247       chrome.fileBrowserPrivate.onFileTransfersUpdated.removeListener(
   3248           onFileTransfersUpdated);
   3249     }.bind(this);
   3250 
   3251     var onCancel = function() {
   3252       cancelled = true;
   3253       // According to API cancel may fail, but there is no proper UI to reflect
   3254       // this. So, we just silently assume that everything is cancelled.
   3255       chrome.fileBrowserPrivate.cancelFileTransfers(
   3256           selection.urls, function(response) {});
   3257       cleanup();
   3258     }.bind(this);
   3259 
   3260     var onResolved = function(resolvedUrls) {
   3261       if (cancelled) return;
   3262       cleanup();
   3263       selection.urls = resolvedUrls;
   3264       // Call next method on a timeout, as it's unsafe to
   3265       // close a window from a callback.
   3266       setTimeout(this.callSelectFilesApiAndClose_.bind(this, selection), 0);
   3267     }.bind(this);
   3268 
   3269     var onProperties = function(properties) {
   3270       for (var i = 0; i < properties.length; i++) {
   3271         if (!properties[i] || properties[i].present) {
   3272           // For files already in GCache, we don't get any transfer updates.
   3273           filesTotal--;
   3274         }
   3275       }
   3276       this.resolveSelectResults_(selection.urls, onResolved);
   3277     }.bind(this);
   3278 
   3279     setup();
   3280 
   3281     // TODO(mtomasz): Use Entry instead of URLs, if possible.
   3282     util.URLsToEntries(selection.urls, function(entries) {
   3283       this.metadataCache_.get(entries, 'drive', onProperties);
   3284     }.bind(this));
   3285   };
   3286 
   3287   /**
   3288    * Handle a click of the ok button.
   3289    *
   3290    * The ok button has different UI labels depending on the type of dialog, but
   3291    * in code it's always referred to as 'ok'.
   3292    *
   3293    * @param {Event} event The click event.
   3294    * @private
   3295    */
   3296   FileManager.prototype.onOk_ = function(event) {
   3297     if (this.dialogType == DialogType.SELECT_SAVEAS_FILE) {
   3298       // Save-as doesn't require a valid selection from the list, since
   3299       // we're going to take the filename from the text input.
   3300       var filename = this.filenameInput_.value;
   3301       if (!filename)
   3302         throw new Error('Missing filename!');
   3303 
   3304       var directory = this.getCurrentDirectoryEntry();
   3305       this.validateFileName_(directory, filename, function(isValid) {
   3306         if (!isValid)
   3307           return;
   3308 
   3309         if (util.isFakeEntry(directory)) {
   3310           // Can't save a file into a fake directory.
   3311           return;
   3312         }
   3313 
   3314         var selectFileAndClose = function() {
   3315           // TODO(mtomasz): Clean this up by avoiding constructing a URL
   3316           //                via string concatenation.
   3317           var currentDirUrl = directory.toURL();
   3318           if (currentDirUrl.charAt(currentDirUrl.length - 1) != '/')
   3319           currentDirUrl += '/';
   3320           this.selectFilesAndClose_({
   3321             urls: [currentDirUrl + encodeURIComponent(filename)],
   3322             multiple: false,
   3323             filterIndex: this.getSelectedFilterIndex_(filename)
   3324           });
   3325         }.bind(this);
   3326 
   3327         directory.getFile(
   3328             filename, {create: false},
   3329             function(entry) {
   3330               // An existing file is found. Show confirmation dialog to
   3331               // overwrite it. If the user select "OK" on the dialog, save it.
   3332               this.confirm.show(strf('CONFIRM_OVERWRITE_FILE', filename),
   3333                                 selectFileAndClose);
   3334             }.bind(this),
   3335             function(error) {
   3336               if (error.name == util.FileError.NOT_FOUND_ERR) {
   3337                 // The file does not exist, so it should be ok to create a
   3338                 // new file.
   3339                 selectFileAndClose();
   3340                 return;
   3341               }
   3342               if (error.name == util.FileError.TYPE_MISMATCH_ERR) {
   3343                 // An directory is found.
   3344                 // Do not allow to overwrite directory.
   3345                 this.alert.show(strf('DIRECTORY_ALREADY_EXISTS', filename));
   3346                 return;
   3347               }
   3348 
   3349               // Unexpected error.
   3350               console.error('File save failed: ' + error.code);
   3351             }.bind(this));
   3352       }.bind(this));
   3353       return;
   3354     }
   3355 
   3356     var files = [];
   3357     var selectedIndexes = this.currentList_.selectionModel.selectedIndexes;
   3358 
   3359     if (DialogType.isFolderDialog(this.dialogType) &&
   3360         selectedIndexes.length == 0) {
   3361       var url = this.getCurrentDirectoryEntry().toURL();
   3362       var singleSelection = {
   3363         urls: [url],
   3364         multiple: false,
   3365         filterIndex: this.getSelectedFilterIndex_()
   3366       };
   3367       this.selectFilesAndClose_(singleSelection);
   3368       return;
   3369     }
   3370 
   3371     // All other dialog types require at least one selected list item.
   3372     // The logic to control whether or not the ok button is enabled should
   3373     // prevent us from ever getting here, but we sanity check to be sure.
   3374     if (!selectedIndexes.length)
   3375       throw new Error('Nothing selected!');
   3376 
   3377     var dm = this.directoryModel_.getFileList();
   3378     for (var i = 0; i < selectedIndexes.length; i++) {
   3379       var entry = dm.item(selectedIndexes[i]);
   3380       if (!entry) {
   3381         console.error('Error locating selected file at index: ' + i);
   3382         continue;
   3383       }
   3384 
   3385       files.push(entry.toURL());
   3386     }
   3387 
   3388     // Multi-file selection has no other restrictions.
   3389     if (this.dialogType == DialogType.SELECT_OPEN_MULTI_FILE) {
   3390       var multipleSelection = {
   3391         urls: files,
   3392         multiple: true
   3393       };
   3394       this.selectFilesAndClose_(multipleSelection);
   3395       return;
   3396     }
   3397 
   3398     // Everything else must have exactly one.
   3399     if (files.length > 1)
   3400       throw new Error('Too many files selected!');
   3401 
   3402     var selectedEntry = dm.item(selectedIndexes[0]);
   3403 
   3404     if (DialogType.isFolderDialog(this.dialogType)) {
   3405       if (!selectedEntry.isDirectory)
   3406         throw new Error('Selected entry is not a folder!');
   3407     } else if (this.dialogType == DialogType.SELECT_OPEN_FILE) {
   3408       if (!selectedEntry.isFile)
   3409         throw new Error('Selected entry is not a file!');
   3410     }
   3411 
   3412     var singleSelection = {
   3413       urls: [files[0]],
   3414       multiple: false,
   3415       filterIndex: this.getSelectedFilterIndex_()
   3416     };
   3417     this.selectFilesAndClose_(singleSelection);
   3418   };
   3419 
   3420   /**
   3421    * Verifies the user entered name for file or folder to be created or
   3422    * renamed to. Name restrictions must correspond to File API restrictions
   3423    * (see DOMFilePath::isValidPath). Curernt WebKit implementation is
   3424    * out of date (spec is
   3425    * http://dev.w3.org/2009/dap/file-system/file-dir-sys.html, 8.3) and going to
   3426    * be fixed. Shows message box if the name is invalid.
   3427    *
   3428    * It also verifies if the name length is in the limit of the filesystem.
   3429    *
   3430    * @param {DirectoryEntry} parentEntry The URL of the parent directory entry.
   3431    * @param {string} name New file or folder name.
   3432    * @param {function} onDone Function to invoke when user closes the
   3433    *    warning box or immediatelly if file name is correct. If the name was
   3434    *    valid it is passed true, and false otherwise.
   3435    * @private
   3436    */
   3437   FileManager.prototype.validateFileName_ = function(
   3438       parentEntry, name, onDone) {
   3439     var msg;
   3440     var testResult = /[\/\\\<\>\:\?\*\"\|]/.exec(name);
   3441     if (testResult) {
   3442       msg = strf('ERROR_INVALID_CHARACTER', testResult[0]);
   3443     } else if (/^\s*$/i.test(name)) {
   3444       msg = str('ERROR_WHITESPACE_NAME');
   3445     } else if (/^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(name)) {
   3446       msg = str('ERROR_RESERVED_NAME');
   3447     } else if (this.fileFilter_.isFilterHiddenOn() && name[0] == '.') {
   3448       msg = str('ERROR_HIDDEN_NAME');
   3449     }
   3450 
   3451     if (msg) {
   3452       this.alert.show(msg, function() {
   3453         onDone(false);
   3454       });
   3455       return;
   3456     }
   3457 
   3458     var self = this;
   3459     chrome.fileBrowserPrivate.validatePathNameLength(
   3460         parentEntry.toURL(), name, function(valid) {
   3461           if (!valid) {
   3462             self.alert.show(str('ERROR_LONG_NAME'),
   3463                             function() { onDone(false); });
   3464           } else {
   3465             onDone(true);
   3466           }
   3467         });
   3468   };
   3469 
   3470   /**
   3471    * Toggle whether mobile data is used for sync.
   3472    */
   3473   FileManager.prototype.toggleDriveSyncSettings = function() {
   3474     // If checked, the sync is disabled.
   3475     var nowCellularDisabled = this.syncButton.hasAttribute('checked');
   3476     var changeInfo = {cellularDisabled: !nowCellularDisabled};
   3477     chrome.fileBrowserPrivate.setPreferences(changeInfo);
   3478   };
   3479 
   3480   /**
   3481    * Toggle whether Google Docs files are shown.
   3482    */
   3483   FileManager.prototype.toggleDriveHostedSettings = function() {
   3484     // If checked, showing drive hosted files is enabled.
   3485     var nowHostedFilesEnabled = this.hostedButton.hasAttribute('checked');
   3486     var nowHostedFilesDisabled = !nowHostedFilesEnabled;
   3487     /*
   3488     var changeInfo = {hostedFilesDisabled: !nowHostedFilesDisabled};
   3489     */
   3490     var changeInfo = {};
   3491     changeInfo['hostedFilesDisabled'] = !nowHostedFilesDisabled;
   3492     chrome.fileBrowserPrivate.setPreferences(changeInfo);
   3493   };
   3494 
   3495   /**
   3496    * Invoked when the search box is changed.
   3497    *
   3498    * @param {Event} event The changed event.
   3499    * @private
   3500    */
   3501   FileManager.prototype.onSearchBoxUpdate_ = function(event) {
   3502     var searchString = this.searchBox_.value;
   3503 
   3504     if (this.isOnDrive()) {
   3505       // When the search text is changed, finishes the search and showes back
   3506       // the last directory by passing an empty string to
   3507       // {@code DirectoryModel.search()}.
   3508       if (this.directoryModel_.isSearching() &&
   3509           this.lastSearchQuery_ != searchString) {
   3510         this.doSearch('');
   3511       }
   3512 
   3513       // On drive, incremental search is not invoked since we have an auto-
   3514       // complete suggestion instead.
   3515       return;
   3516     }
   3517 
   3518     this.search_(searchString);
   3519   };
   3520 
   3521   /**
   3522    * Handle the search clear button click.
   3523    * @private
   3524    */
   3525   FileManager.prototype.onSearchClearButtonClick_ = function() {
   3526     this.ui_.searchBox.clear();
   3527     this.onSearchBoxUpdate_();
   3528   };
   3529 
   3530   /**
   3531    * Search files and update the list with the search result.
   3532    *
   3533    * @param {string} searchString String to be searched with.
   3534    * @private
   3535    */
   3536   FileManager.prototype.search_ = function(searchString) {
   3537     var noResultsDiv = this.document_.getElementById('no-search-results');
   3538 
   3539     var reportEmptySearchResults = function() {
   3540       if (this.directoryModel_.getFileList().length === 0) {
   3541         // The string 'SEARCH_NO_MATCHING_FILES_HTML' may contain HTML tags,
   3542         // hence we escapes |searchString| here.
   3543         var html = strf('SEARCH_NO_MATCHING_FILES_HTML',
   3544                         util.htmlEscape(searchString));
   3545         noResultsDiv.innerHTML = html;
   3546         noResultsDiv.setAttribute('show', 'true');
   3547       } else {
   3548         noResultsDiv.removeAttribute('show');
   3549       }
   3550     };
   3551 
   3552     var hideNoResultsDiv = function() {
   3553       noResultsDiv.removeAttribute('show');
   3554     };
   3555 
   3556     this.doSearch(searchString,
   3557                   reportEmptySearchResults.bind(this),
   3558                   hideNoResultsDiv.bind(this));
   3559   };
   3560 
   3561   /**
   3562    * Performs search and displays results.
   3563    *
   3564    * @param {string} query Query that will be searched for.
   3565    * @param {function()=} opt_onSearchRescan Function that will be called when
   3566    *     the search directory is rescanned (i.e. search results are displayed).
   3567    * @param {function()=} opt_onClearSearch Function to be called when search
   3568    *     state gets cleared.
   3569    */
   3570   FileManager.prototype.doSearch = function(
   3571       searchString, opt_onSearchRescan, opt_onClearSearch) {
   3572     var onSearchRescan = opt_onSearchRescan || function() {};
   3573     var onClearSearch = opt_onClearSearch || function() {};
   3574 
   3575     this.lastSearchQuery_ = searchString;
   3576     this.directoryModel_.search(searchString, onSearchRescan, onClearSearch);
   3577   };
   3578 
   3579   /**
   3580    * Requests autocomplete suggestions for files on Drive.
   3581    * Once the suggestions are returned, the autocomplete popup will show up.
   3582    *
   3583    * @param {string} query The text to autocomplete from.
   3584    * @private
   3585    */
   3586   FileManager.prototype.requestAutocompleteSuggestions_ = function(query) {
   3587     query = query.trimLeft();
   3588 
   3589     // Only Drive supports auto-compelete
   3590     if (!this.isOnDrive())
   3591       return;
   3592 
   3593     // Remember the most recent query. If there is an other request in progress,
   3594     // then it's result will be discarded and it will call a new request for
   3595     // this query.
   3596     this.lastAutocompleteQuery_ = query;
   3597     if (this.autocompleteSuggestionsBusy_)
   3598       return;
   3599 
   3600     // The autocomplete list should be resized and repositioned here as the
   3601     // search box is resized when it's focused.
   3602     this.autocompleteList_.syncWidthAndPositionToInput();
   3603 
   3604     if (!query) {
   3605       this.autocompleteList_.suggestions = [];
   3606       return;
   3607     }
   3608 
   3609     var headerItem = {isHeaderItem: true, searchQuery: query};
   3610     if (!this.autocompleteList_.dataModel ||
   3611         this.autocompleteList_.dataModel.length == 0)
   3612       this.autocompleteList_.suggestions = [headerItem];
   3613     else
   3614       // Updates only the head item to prevent a flickering on typing.
   3615       this.autocompleteList_.dataModel.splice(0, 1, headerItem);
   3616 
   3617     this.autocompleteSuggestionsBusy_ = true;
   3618 
   3619     var searchParams = {
   3620       'query': query,
   3621       'types': 'ALL',
   3622       'maxResults': 4
   3623     };
   3624     chrome.fileBrowserPrivate.searchDriveMetadata(
   3625       searchParams,
   3626       function(suggestions) {
   3627         this.autocompleteSuggestionsBusy_ = false;
   3628 
   3629         // Discard results for previous requests and fire a new search
   3630         // for the most recent query.
   3631         if (query != this.lastAutocompleteQuery_) {
   3632           this.requestAutocompleteSuggestions_(this.lastAutocompleteQuery_);
   3633           return;
   3634         }
   3635 
   3636         // Keeps the items in the suggestion list.
   3637         this.autocompleteList_.suggestions = [headerItem].concat(suggestions);
   3638       }.bind(this));
   3639   };
   3640 
   3641   /**
   3642    * Opens the currently selected suggestion item.
   3643    * @private
   3644    */
   3645   FileManager.prototype.openAutocompleteSuggestion_ = function() {
   3646     var selectedItem = this.autocompleteList_.selectedItem;
   3647 
   3648     // If the entry is the search item or no entry is selected, just change to
   3649     // the search result.
   3650     if (!selectedItem || selectedItem.isHeaderItem) {
   3651       var query = selectedItem ?
   3652           selectedItem.searchQuery : this.searchBox_.value;
   3653       this.search_(query);
   3654       return;
   3655     }
   3656 
   3657     var entry = selectedItem.entry;
   3658     // If the entry is a directory, just change the directory.
   3659     if (entry.isDirectory) {
   3660       this.onDirectoryAction_(entry);
   3661       return;
   3662     }
   3663 
   3664     var entries = [entry];
   3665     var self = this;
   3666 
   3667     // To open a file, first get the mime type.
   3668     this.metadataCache_.get(entries, 'drive', function(props) {
   3669       var mimeType = props[0].contentMimeType || '';
   3670       var mimeTypes = [mimeType];
   3671       var openIt = function() {
   3672         if (self.dialogType == DialogType.FULL_PAGE) {
   3673           var tasks = new FileTasks(self);
   3674           tasks.init(entries, mimeTypes);
   3675           tasks.executeDefault();
   3676         } else {
   3677           self.onOk_();
   3678         }
   3679       };
   3680 
   3681       // Change the current directory to the directory that contains the
   3682       // selected file. Note that this is necessary for an image or a video,
   3683       // which should be opened in the gallery mode, as the gallery mode
   3684       // requires the entry to be in the current directory model. For
   3685       // consistency, the current directory is always changed regardless of
   3686       // the file type.
   3687       entry.getParent(function(parentEntry) {
   3688         var onDirectoryChanged = function(event) {
   3689           self.directoryModel_.removeEventListener('scan-completed',
   3690                                                    onDirectoryChanged);
   3691           self.directoryModel_.selectEntry(entry);
   3692           openIt();
   3693         };
   3694         // changeDirectoryEntry() returns immediately. We should wait until the
   3695         // directory scan is complete.
   3696         self.directoryModel_.addEventListener('scan-completed',
   3697                                               onDirectoryChanged);
   3698         self.directoryModel_.changeDirectoryEntry(
   3699           parentEntry,
   3700           function() {
   3701             // Remove the listner if the change directory failed.
   3702             self.directoryModel_.removeEventListener('scan-completed',
   3703                                                      onDirectoryChanged);
   3704           });
   3705       });
   3706     });
   3707   };
   3708 
   3709   FileManager.prototype.decorateSplitter = function(splitterElement) {
   3710     var self = this;
   3711 
   3712     var Splitter = cr.ui.Splitter;
   3713 
   3714     var customSplitter = cr.ui.define('div');
   3715 
   3716     customSplitter.prototype = {
   3717       __proto__: Splitter.prototype,
   3718 
   3719       handleSplitterDragStart: function(e) {
   3720         Splitter.prototype.handleSplitterDragStart.apply(this, arguments);
   3721         this.ownerDocument.documentElement.classList.add('col-resize');
   3722       },
   3723 
   3724       handleSplitterDragMove: function(deltaX) {
   3725         Splitter.prototype.handleSplitterDragMove.apply(this, arguments);
   3726         self.onResize_();
   3727       },
   3728 
   3729       handleSplitterDragEnd: function(e) {
   3730         Splitter.prototype.handleSplitterDragEnd.apply(this, arguments);
   3731         this.ownerDocument.documentElement.classList.remove('col-resize');
   3732       }
   3733     };
   3734 
   3735     customSplitter.decorate(splitterElement);
   3736   };
   3737 
   3738   /**
   3739    * Updates default action menu item to match passed taskItem (icon,
   3740    * label and action).
   3741    *
   3742    * @param {Object} defaultItem - taskItem to match.
   3743    * @param {boolean} isMultiple - if multiple tasks available.
   3744    */
   3745   FileManager.prototype.updateContextMenuActionItems = function(defaultItem,
   3746                                                                 isMultiple) {
   3747     if (defaultItem) {
   3748       if (defaultItem.iconType) {
   3749         this.defaultActionMenuItem_.style.backgroundImage = '';
   3750         this.defaultActionMenuItem_.setAttribute('file-type-icon',
   3751                                                  defaultItem.iconType);
   3752       } else if (defaultItem.iconUrl) {
   3753         this.defaultActionMenuItem_.style.backgroundImage =
   3754             'url(' + defaultItem.iconUrl + ')';
   3755       } else {
   3756         this.defaultActionMenuItem_.style.backgroundImage = '';
   3757       }
   3758 
   3759       this.defaultActionMenuItem_.label = defaultItem.title;
   3760       this.defaultActionMenuItem_.disabled = !!defaultItem.disabled;
   3761       this.defaultActionMenuItem_.taskId = defaultItem.taskId;
   3762     }
   3763 
   3764     var defaultActionSeparator =
   3765         this.dialogDom_.querySelector('#default-action-separator');
   3766 
   3767     this.openWithCommand_.canExecuteChange();
   3768     this.openWithCommand_.setHidden(!(defaultItem && isMultiple));
   3769     this.openWithCommand_.disabled = defaultItem && !!defaultItem.disabled;
   3770 
   3771     this.defaultActionMenuItem_.hidden = !defaultItem;
   3772     defaultActionSeparator.hidden = !defaultItem;
   3773   };
   3774 
   3775   /**
   3776    * @return {FileSelection} Selection object.
   3777    */
   3778   FileManager.prototype.getSelection = function() {
   3779     return this.selectionHandler_.selection;
   3780   };
   3781 
   3782   /**
   3783    * @return {ArrayDataModel} File list.
   3784    */
   3785   FileManager.prototype.getFileList = function() {
   3786     return this.directoryModel_.getFileList();
   3787   };
   3788 
   3789   /**
   3790    * @return {cr.ui.List} Current list object.
   3791    */
   3792   FileManager.prototype.getCurrentList = function() {
   3793     return this.currentList_;
   3794   };
   3795 
   3796   /**
   3797    * Retrieve the preferences of the files.app. This method caches the result
   3798    * and returns it unless opt_update is true.
   3799    * @param {function(Object.<string, *>)} callback Callback to get the
   3800    *     preference.
   3801    * @param {boolean=} opt_update If is's true, don't use the cache and
   3802    *     retrieve latest preference. Default is false.
   3803    * @private
   3804    */
   3805   FileManager.prototype.getPreferences_ = function(callback, opt_update) {
   3806     if (!opt_update && this.preferences_ !== undefined) {
   3807       callback(this.preferences_);
   3808       return;
   3809     }
   3810 
   3811     chrome.fileBrowserPrivate.getPreferences(function(prefs) {
   3812       this.preferences_ = prefs;
   3813       callback(prefs);
   3814     }.bind(this));
   3815   };
   3816 })();
   3817