Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / foreground / js / file_manager.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 /**
6  * FileManager constructor.
7  *
8  * FileManager objects encapsulate the functionality of the file selector
9  * dialogs, as well as the full screen file manager application (though the
10  * latter is not yet implemented).
11  *
12  * @constructor
13  * @struct
14  */
15 function FileManager() {
16   // --------------------------------------------------------------------------
17   // Services FileManager depends on.
18
19   /**
20    * Volume manager.
21    * @type {VolumeManagerWrapper}
22    * @private
23    */
24   this.volumeManager_ = null;
25
26   /**
27    * Metadata cache.
28    * @type {MetadataCache}
29    * @private
30    */
31   this.metadataCache_ = null;
32
33   /**
34    * File operation manager.
35    * @type {FileOperationManager}
36    * @private
37    */
38   this.fileOperationManager_ = null;
39
40   /**
41    * File transfer controller.
42    * @type {FileTransferController}
43    * @private
44    */
45   this.fileTransferController_ = null;
46
47   /**
48    * File filter.
49    * @type {FileFilter}
50    * @private
51    */
52   this.fileFilter_ = null;
53
54   /**
55    * File watcher.
56    * @type {FileWatcher}
57    * @private
58    */
59   this.fileWatcher_ = null;
60
61   /**
62    * Model of current directory.
63    * @type {DirectoryModel}
64    * @private
65    */
66   this.directoryModel_ = null;
67
68   /**
69    * Model of folder shortcuts.
70    * @type {FolderShortcutsDataModel}
71    * @private
72    */
73   this.folderShortcutsModel_ = null;
74
75   /**
76    * VolumeInfo of the current volume.
77    * @type {VolumeInfo}
78    * @private
79    */
80   this.currentVolumeInfo_ = null;
81
82   /**
83    * Handler for command events.
84    * @type {CommandHandler}
85    */
86   this.commandHandler = null;
87
88   /**
89    * Handler for the change of file selection.
90    * @type {FileSelectionHandler}
91    * @private
92    */
93   this.selectionHandler_ = null;
94
95   /**
96    * Dialog action controller.
97    * @type {DialogActionController}
98    * @private
99    */
100   this.dialogActionController_ = null;
101
102   // --------------------------------------------------------------------------
103   // Parameters determining the type of file manager.
104
105   /**
106    * Dialog type of this window.
107    * @type {DialogType}
108    */
109   this.dialogType = DialogType.FULL_PAGE;
110
111   /**
112    * List of acceptable file types for open dialog.
113    * @type {!Array.<Object>}
114    * @private
115    */
116   this.fileTypes_ = [];
117
118   /**
119    * Startup parameters for this application.
120    * @type {?{includeAllFiles:boolean,
121    *          action:string,
122    *          shouldReturnLocalPath:boolean}}
123    * @private
124    */
125   this.params_ = null;
126
127   /**
128    * Startup preference about the view.
129    * @type {Object}
130    * @private
131    */
132   this.viewOptions_ = {};
133
134   /**
135    * The user preference.
136    * @type {Object}
137    * @private
138    */
139   this.preferences_ = null;
140
141   // --------------------------------------------------------------------------
142   // UI components.
143
144   /**
145    * UI management class of file manager.
146    * @type {FileManagerUI}
147    * @private
148    */
149   this.ui_ = null;
150
151   /**
152    * Progress center panel.
153    * @type {ProgressCenterPanel}
154    * @private
155    */
156   this.progressCenterPanel_ = null;
157
158   /**
159    * Directory tree.
160    * @type {DirectoryTree}
161    * @private
162    */
163   this.directoryTree_ = null;
164
165   /**
166    * Naming controller.
167    * @type {NamingController}
168    * @private
169    */
170   this.namingController_ = null;
171
172   /**
173    * Controller for search UI.
174    * @type {SearchController}
175    * @private
176    */
177   this.searchController_ = null;
178
179   /**
180    * Controller for directory scan.
181    * @type {ScanController}
182    * @private
183    */
184   this.scanController_ = null;
185
186   /**
187    * Controller for spinner.
188    * @type {SpinnerController}
189    * @private
190    */
191   this.spinnerController_ = null;
192
193   /**
194    * Banners in the file list.
195    * @type {FileListBannerController}
196    * @private
197    */
198   this.bannersController_ = null;
199
200   // --------------------------------------------------------------------------
201   // Dialogs.
202
203   /**
204    * Error dialog.
205    * @type {ErrorDialog}
206    */
207   this.error = null;
208
209   /**
210    * Alert dialog.
211    * @type {cr.ui.dialogs.AlertDialog}
212    */
213   this.alert = null;
214
215   /**
216    * Confirm dialog.
217    * @type {cr.ui.dialogs.ConfirmDialog}
218    */
219   this.confirm = null;
220
221   /**
222    * Prompt dialog.
223    * @type {cr.ui.dialogs.PromptDialog}
224    */
225   this.prompt = null;
226
227   /**
228    * Share dialog.
229    * @type {ShareDialog}
230    * @private
231    */
232   this.shareDialog_ = null;
233
234   /**
235    * Default task picker.
236    * @type {cr.filebrowser.DefaultActionDialog}
237    */
238   this.defaultTaskPicker = null;
239
240   /**
241    * Suggest apps dialog.
242    * @type {SuggestAppsDialog}
243    */
244   this.suggestAppsDialog = null;
245
246   // --------------------------------------------------------------------------
247   // Menus.
248
249   /**
250    * Context menu for texts.
251    * @type {cr.ui.Menu}
252    * @private
253    */
254   this.textContextMenu_ = null;
255
256   // --------------------------------------------------------------------------
257   // DOM elements.
258
259   /**
260    * Background page.
261    * @type {BackgroundWindow}
262    * @private
263    */
264   this.backgroundPage_ = null;
265
266   /**
267    * The root DOM element of this app.
268    * @type {HTMLBodyElement}
269    * @private
270    */
271   this.dialogDom_ = null;
272
273   /**
274    * The document object of this app.
275    * @type {HTMLDocument}
276    * @private
277    */
278   this.document_ = null;
279
280   /**
281    * The menu item to toggle "Do not use mobile data for sync".
282    * @type {HTMLMenuItemElement}
283    */
284   this.syncButton = null;
285
286   /**
287    * The menu item to toggle "Show Google Docs files".
288    * @type {HTMLMenuItemElement}
289    */
290   this.hostedButton = null;
291
292   /**
293    * The menu item for doing an action.
294    * @type {HTMLMenuItemElement}
295    * @private
296    */
297   this.actionMenuItem_ = null;
298
299   /**
300    * The button to open gear menu.
301    * @type {cr.ui.MenuButton}
302    * @private
303    */
304   this.gearButton_ = null;
305
306   /**
307    * The combo button to specify the task.
308    * @type {HTMLButtonElement}
309    * @private
310    */
311   this.taskItems_ = null;
312
313   /**
314    * The container element of the dialog.
315    * @type {HTMLDivElement}
316    * @private
317    */
318   this.dialogContainer_ = null;
319
320   /**
321    * Open-with command in the context menu.
322    * @type {cr.ui.Command}
323    * @private
324    */
325   this.openWithCommand_ = null;
326
327   // --------------------------------------------------------------------------
328   // Bound functions.
329
330   /**
331    * Bound function for onCopyProgress_.
332    * @type {?function(this:FileManager, Event)}
333    * @private
334    */
335   this.onCopyProgressBound_ = null;
336
337   /**
338    * Bound function for onEntriesChanged_.
339    * @type {?function(this:FileManager, Event)}
340    * @private
341    */
342   this.onEntriesChangedBound_ = null;
343
344   // --------------------------------------------------------------------------
345   // Miscellaneous FileManager's states.
346
347   /**
348    * Queue for ordering FileManager's initialization process.
349    * @type {AsyncUtil.Group}
350    * @private
351    */
352   this.initializeQueue_ = new AsyncUtil.Group();
353
354   /**
355    * True while a user is pressing <Tab>.
356    * This is used for identifying the trigger causing the filelist to
357    * be focused.
358    * @type {boolean}
359    * @private
360    */
361   this.pressingTab_ = false;
362
363   /**
364    * True while a user is pressing <Ctrl>.
365    *
366    * TODO(fukino): This key is used only for controlling gear menu, so it
367    * should be moved to GearMenu class. crbug.com/366032.
368    *
369    * @type {boolean}
370    * @private
371    */
372   this.pressingCtrl_ = false;
373
374   /**
375    * True if shown gear menu is in secret mode.
376    *
377    * TODO(fukino): The state of gear menu should be moved to GearMenu class.
378    * crbug.com/366032.
379    *
380    * @type {boolean}
381    * @private
382    */
383   this.isSecretGearMenuShown_ = false;
384
385   /**
386    * The last clicked item in the file list.
387    * @type {HTMLLIElement}
388    * @private
389    */
390   this.lastClickedItem_ = null;
391
392   /**
393    * Count of the SourceNotFound error.
394    * @type {number}
395    * @private
396    */
397   this.sourceNotFoundErrorCount_ = 0;
398
399   /**
400    * Whether the app should be closed on unmount.
401    * @type {boolean}
402    * @private
403    */
404   this.closeOnUnmount_ = false;
405
406   /**
407    * The key for storing startup preference.
408    * @type {string}
409    * @private
410    */
411   this.startupPrefName_ = '';
412
413   /**
414    * URL of directory which should be initial current directory.
415    * @type {string}
416    * @private
417    */
418   this.initCurrentDirectoryURL_ = '';
419
420   /**
421    * URL of entry which should be initially selected.
422    * @type {string}
423    * @private
424    */
425   this.initSelectionURL_ = '';
426
427   /**
428    * The name of target entry (not URL).
429    * @type {string}
430    * @private
431    */
432   this.initTargetName_ = '';
433
434
435   // Object.seal() has big performance/memory overhead for now, so we use
436   // Object.preventExtensions() here. crbug.com/412239.
437   Object.preventExtensions(this);
438 }
439
440 FileManager.prototype = /** @struct */ {
441   __proto__: cr.EventTarget.prototype,
442   /**
443    * @return {DirectoryModel}
444    */
445   get directoryModel() {
446     return this.directoryModel_;
447   },
448   /**
449    * @return {DirectoryTree}
450    */
451   get directoryTree() {
452     return this.directoryTree_;
453   },
454   /**
455    * @return {HTMLDocument}
456    */
457   get document() {
458     return this.document_;
459   },
460   /**
461    * @return {FileTransferController}
462    */
463   get fileTransferController() {
464     return this.fileTransferController_;
465   },
466   /**
467    * @return {NamingController}
468    */
469   get namingController() {
470     return this.namingController_;
471   },
472   /**
473    * @return {FileOperationManager}
474    */
475   get fileOperationManager() {
476     return this.fileOperationManager_;
477   },
478   /**
479    * @return {BackgroundWindow}
480    */
481   get backgroundPage() {
482     return this.backgroundPage_;
483   },
484   /**
485    * @return {VolumeManagerWrapper}
486    */
487   get volumeManager() {
488     return this.volumeManager_;
489   },
490   /**
491    * @return {FileManagerUI}
492    */
493   get ui() {
494     return this.ui_;
495   }
496 };
497
498 /**
499  * List of dialog types.
500  *
501  * Keep this in sync with FileManagerDialog::GetDialogTypeAsString, except
502  * FULL_PAGE which is specific to this code.
503  *
504  * @enum {string}
505  * @const
506  */
507 var DialogType = {
508   SELECT_FOLDER: 'folder',
509   SELECT_UPLOAD_FOLDER: 'upload-folder',
510   SELECT_SAVEAS_FILE: 'saveas-file',
511   SELECT_OPEN_FILE: 'open-file',
512   SELECT_OPEN_MULTI_FILE: 'open-multi-file',
513   FULL_PAGE: 'full-page'
514 };
515
516 /**
517  * @param {DialogType} type Dialog type.
518  * @return {boolean} Whether the type is modal.
519  */
520 DialogType.isModal = function(type) {
521   return type == DialogType.SELECT_FOLDER ||
522       type == DialogType.SELECT_UPLOAD_FOLDER ||
523       type == DialogType.SELECT_SAVEAS_FILE ||
524       type == DialogType.SELECT_OPEN_FILE ||
525       type == DialogType.SELECT_OPEN_MULTI_FILE;
526 };
527
528 /**
529  * @param {DialogType} type Dialog type.
530  * @return {boolean} Whether the type is open dialog.
531  */
532 DialogType.isOpenDialog = function(type) {
533   return type == DialogType.SELECT_OPEN_FILE ||
534          type == DialogType.SELECT_OPEN_MULTI_FILE ||
535          type == DialogType.SELECT_FOLDER ||
536          type == DialogType.SELECT_UPLOAD_FOLDER;
537 };
538
539 /**
540  * @param {DialogType} type Dialog type.
541  * @return {boolean} Whether the type is open dialog for file(s).
542  */
543 DialogType.isOpenFileDialog = function(type) {
544   return type == DialogType.SELECT_OPEN_FILE ||
545          type == DialogType.SELECT_OPEN_MULTI_FILE;
546 };
547
548 /**
549  * @param {DialogType} type Dialog type.
550  * @return {boolean} Whether the type is folder selection dialog.
551  */
552 DialogType.isFolderDialog = function(type) {
553   return type == DialogType.SELECT_FOLDER ||
554          type == DialogType.SELECT_UPLOAD_FOLDER;
555 };
556
557 Object.freeze(DialogType);
558
559 /**
560  * Bottom margin of the list and tree for transparent preview panel.
561  * @const
562  */
563 var BOTTOM_MARGIN_FOR_PREVIEW_PANEL_PX = 52;
564
565 // Anonymous "namespace".
566 (function() {
567   // Private variables and helper functions.
568
569   /**
570    * Number of milliseconds in a day.
571    */
572   var MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
573
574   /**
575    * Some UI elements react on a single click and standard double click handling
576    * leads to confusing results. We ignore a second click if it comes soon
577    * after the first.
578    */
579   var DOUBLE_CLICK_TIMEOUT = 200;
580
581   /**
582    * Updates the element to display the information about remaining space for
583    * the storage.
584    *
585    * @param {!Object<string, number>} sizeStatsResult Map containing remaining
586    *     space information.
587    * @param {!Element} spaceInnerBar Block element for a percentage bar
588    *     representing the remaining space.
589    * @param {!Element} spaceInfoLabel Inline element to contain the message.
590    * @param {!Element} spaceOuterBar Block element around the percentage bar.
591    */
592   var updateSpaceInfo = function(
593       sizeStatsResult, spaceInnerBar, spaceInfoLabel, spaceOuterBar) {
594     spaceInnerBar.removeAttribute('pending');
595     if (sizeStatsResult) {
596       var sizeStr = util.bytesToString(sizeStatsResult.remainingSize);
597       spaceInfoLabel.textContent = strf('SPACE_AVAILABLE', sizeStr);
598
599       var usedSpace =
600           sizeStatsResult.totalSize - sizeStatsResult.remainingSize;
601       spaceInnerBar.style.width =
602           (100 * usedSpace / sizeStatsResult.totalSize) + '%';
603
604       spaceOuterBar.hidden = false;
605     } else {
606       spaceOuterBar.hidden = true;
607       spaceInfoLabel.textContent = str('FAILED_SPACE_INFO');
608     }
609   };
610
611   FileManager.prototype.initPreferences_ = function(callback) {
612     var group = new AsyncUtil.Group();
613
614     // DRIVE preferences should be initialized before creating DirectoryModel
615     // to rebuild the roots list.
616     group.add(this.getPreferences_.bind(this));
617
618     // Get startup preferences.
619     group.add(function(done) {
620       chrome.storage.local.get(this.startupPrefName_, function(values) {
621         var value = values[this.startupPrefName_];
622         if (!value) {
623           done();
624           return;
625         }
626         // Load the global default options.
627         try {
628           this.viewOptions_ = JSON.parse(value);
629         } catch (ignore) {}
630         // Override with window-specific options.
631         if (window.appState && window.appState.viewOptions) {
632           for (var key in window.appState.viewOptions) {
633             if (window.appState.viewOptions.hasOwnProperty(key))
634               this.viewOptions_[key] = window.appState.viewOptions[key];
635           }
636         }
637         done();
638       }.bind(this));
639     }.bind(this));
640
641     group.run(callback);
642   };
643
644   /**
645    * One time initialization for the file system and related things.
646    *
647    * @param {function()} callback Completion callback.
648    * @private
649    */
650   FileManager.prototype.initFileSystemUI_ = function(callback) {
651     this.ui_.listContainer.startBatchUpdates();
652
653     this.initFileList_();
654     this.setupCurrentDirectory_();
655
656     // PyAuto tests monitor this state by polling this variable
657     this.__defineGetter__('workerInitialized_', function() {
658       return this.metadataCache_.isInitialized();
659     }.bind(this));
660
661     this.initDateTimeFormatters_();
662
663     var self = this;
664
665     // Get the 'allowRedeemOffers' preference before launching
666     // FileListBannerController.
667     this.getPreferences_(function(pref) {
668       /** @type {boolean} */
669       var showOffers = !!pref['allowRedeemOffers'];
670       self.bannersController_ = new FileListBannerController(
671           self.directoryModel_, self.volumeManager_, self.document_,
672           showOffers);
673       self.bannersController_.addEventListener('relayout',
674                                                self.onResize_.bind(self));
675     });
676
677     var dm = this.directoryModel_;
678     dm.addEventListener('directory-changed',
679                         this.onDirectoryChanged_.bind(this));
680
681     var listBeingUpdated = null;
682     dm.addEventListener('begin-update-files', function() {
683       self.ui_.listContainer.currentList.startBatchUpdates();
684       // Remember the list which was used when updating files started, so
685       // endBatchUpdates() is called on the same list.
686       listBeingUpdated = self.ui_.listContainer.currentList;
687     });
688     dm.addEventListener('end-update-files', function() {
689       self.namingController_.restoreItemBeingRenamed();
690       listBeingUpdated.endBatchUpdates();
691       listBeingUpdated = null;
692     });
693
694     this.initContextMenus_();
695     this.initCommands_();
696     assert(this.directoryModel_);
697     assert(this.spinnerController_);
698     assert(this.commandHandler);
699     assert(this.selectionHandler_);
700     this.scanController_ = new ScanController(
701         this.directoryModel_,
702         this.ui_.listContainer,
703         this.spinnerController_,
704         this.commandHandler,
705         this.selectionHandler_);
706
707     this.directoryTree_.addEventListener('change', function() {
708       this.ensureDirectoryTreeItemNotBehindPreviewPanel_();
709     }.bind(this));
710
711     var stateChangeHandler =
712         this.onPreferencesChanged_.bind(this);
713     chrome.fileManagerPrivate.onPreferencesChanged.addListener(
714         stateChangeHandler);
715     stateChangeHandler();
716
717     var driveConnectionChangedHandler =
718         this.onDriveConnectionChanged_.bind(this);
719     this.volumeManager_.addEventListener('drive-connection-changed',
720         driveConnectionChangedHandler);
721     driveConnectionChangedHandler();
722
723     // Set the initial focus.
724     this.refocus();
725     // Set it as a fallback when there is no focus.
726     this.document_.addEventListener('focusout', function(e) {
727       setTimeout(function() {
728         // When there is no focus, the active element is the <body>.
729         if (this.document_.activeElement == this.document_.body)
730           this.refocus();
731       }.bind(this), 0);
732     }.bind(this));
733
734     this.initDataTransferOperations_();
735
736     this.updateFileTypeFilter_();
737     this.selectionHandler_.onFileSelectionChanged();
738     this.ui_.listContainer.endBatchUpdates();
739
740     callback();
741   };
742
743   /**
744    * If |item| in the directory tree is behind the preview panel, scrolls up the
745    * parent view and make the item visible. This should be called when:
746    *  - the selected item is changed in the directory tree.
747    *  - the visibility of the the preview panel is changed.
748    *
749    * @private
750    */
751   FileManager.prototype.ensureDirectoryTreeItemNotBehindPreviewPanel_ =
752       function() {
753     var selectedSubTree = this.directoryTree_.selectedItem;
754     if (!selectedSubTree)
755       return;
756     var item = selectedSubTree.rowElement;
757     var parentView = this.directoryTree_;
758
759     var itemRect = item.getBoundingClientRect();
760     if (!itemRect)
761       return;
762
763     var listRect = parentView.getBoundingClientRect();
764     if (!listRect)
765       return;
766
767     var previewPanel = this.dialogDom_.querySelector('.preview-panel');
768     var previewPanelRect = previewPanel.getBoundingClientRect();
769     var panelHeight = previewPanelRect ? previewPanelRect.height : 0;
770
771     var itemBottom = itemRect.bottom;
772     var listBottom = listRect.bottom - panelHeight;
773
774     if (itemBottom > listBottom) {
775       var scrollOffset = itemBottom - listBottom;
776       parentView.scrollTop += scrollOffset;
777     }
778   };
779
780   /**
781    * @private
782    */
783   FileManager.prototype.initDateTimeFormatters_ = function() {
784     var use12hourClock = !this.preferences_['use24hourClock'];
785     this.ui_.listContainer.table.setDateTimeFormat(use12hourClock);
786   };
787
788   /**
789    * @private
790    */
791   FileManager.prototype.initDataTransferOperations_ = function() {
792     this.fileOperationManager_ =
793         this.backgroundPage_.background.fileOperationManager;
794
795     // CopyManager are required for 'Delete' operation in
796     // Open and Save dialogs. But drag-n-drop and copy-paste are not needed.
797     if (this.dialogType != DialogType.FULL_PAGE) return;
798
799     // TODO(hidehiko): Extract FileOperationManager related code from
800     // FileManager to simplify it.
801     this.onCopyProgressBound_ = this.onCopyProgress_.bind(this);
802     this.fileOperationManager_.addEventListener(
803         'copy-progress', this.onCopyProgressBound_);
804
805     this.onEntriesChangedBound_ = this.onEntriesChanged_.bind(this);
806     this.fileOperationManager_.addEventListener(
807         'entries-changed', this.onEntriesChangedBound_);
808
809     var controller = this.fileTransferController_ =
810         new FileTransferController(
811                 this.document_,
812                 this.fileOperationManager_,
813                 this.metadataCache_,
814                 this.directoryModel_,
815                 this.volumeManager_,
816                 this.ui_.multiProfileShareDialog,
817                 this.backgroundPage_.background.progressCenter);
818     controller.attachDragSource(this.ui_.listContainer.table.list);
819     controller.attachFileListDropTarget(this.ui_.listContainer.table.list);
820     controller.attachDragSource(this.ui_.listContainer.grid);
821     controller.attachFileListDropTarget(this.ui_.listContainer.grid);
822     controller.attachTreeDropTarget(this.directoryTree_);
823     controller.attachCopyPasteHandlers();
824     controller.addEventListener('selection-copied',
825         this.blinkSelection.bind(this));
826     controller.addEventListener('selection-cut',
827         this.blinkSelection.bind(this));
828     controller.addEventListener('source-not-found',
829         this.onSourceNotFound_.bind(this));
830   };
831
832   /**
833    * Handles an error that the source entry of file operation is not found.
834    * @private
835    */
836   FileManager.prototype.onSourceNotFound_ = function(event) {
837     var item = new ProgressCenterItem();
838     item.id = 'source-not-found-' + this.sourceNotFoundErrorCount_;
839     if (event.progressType === ProgressItemType.COPY)
840       item.message = strf('COPY_SOURCE_NOT_FOUND_ERROR', event.fileName);
841     else if (event.progressType === ProgressItemType.MOVE)
842       item.message = strf('MOVE_SOURCE_NOT_FOUND_ERROR', event.fileName);
843     item.state = ProgressItemState.ERROR;
844     this.backgroundPage_.background.progressCenter.updateItem(item);
845     this.sourceNotFoundErrorCount_++;
846   };
847
848   /**
849    * One-time initialization of context menus.
850    * @private
851    */
852   FileManager.prototype.initContextMenus_ = function() {
853     assert(this.ui_.listContainer.grid);
854     assert(this.ui_.listContainer.table);
855     assert(this.document_);
856     assert(this.dialogDom_);
857
858     // Set up the context menu for the file list.
859     var fileContextMenu = queryRequiredElement(
860         this.dialogDom_, '#file-context-menu');
861     cr.ui.Menu.decorate(fileContextMenu);
862     fileContextMenu = /** @type {!cr.ui.Menu} */ (fileContextMenu);
863
864     cr.ui.contextMenuHandler.setContextMenu(
865         this.ui_.listContainer.grid, fileContextMenu);
866     cr.ui.contextMenuHandler.setContextMenu(
867         this.ui_.listContainer.table.list, fileContextMenu);
868     cr.ui.contextMenuHandler.setContextMenu(
869         queryRequiredElement(this.document_, '.drive-welcome.page'),
870         fileContextMenu);
871
872     // Set up the context menu for the volume/shortcut items in directory tree.
873     var rootsContextMenu = queryRequiredElement(
874         this.dialogDom_, '#roots-context-menu');
875     cr.ui.Menu.decorate(rootsContextMenu);
876     rootsContextMenu = /** @type {!cr.ui.Menu} */ (rootsContextMenu);
877
878     this.directoryTree_.contextMenuForRootItems = rootsContextMenu;
879
880     // Set up the context menu for the folder items in directory tree.
881     var directoryTreeContextMenu = queryRequiredElement(
882         this.dialogDom_, '#directory-tree-context-menu');
883     cr.ui.Menu.decorate(directoryTreeContextMenu);
884     directoryTreeContextMenu =
885         /** @type {!cr.ui.Menu} */ (directoryTreeContextMenu);
886
887     this.directoryTree_.contextMenuForSubitems = directoryTreeContextMenu;
888
889     // Set up the context menu for the text editing.
890     var textContextMenu = queryRequiredElement(
891         this.dialogDom_, '#text-context-menu');
892     cr.ui.Menu.decorate(textContextMenu);
893     this.textContextMenu_ = /** @type {!cr.ui.Menu} */ (textContextMenu);
894
895     var gearButton = queryRequiredElement(this.dialogDom_, '#gear-button');
896     gearButton.addEventListener('menushow', this.onShowGearMenu_.bind(this));
897     this.dialogDom_.querySelector('#gear-menu').menuItemSelector =
898         'menuitem, hr';
899     cr.ui.decorate(gearButton, cr.ui.MenuButton);
900     this.gearButton_ = /** @type {!cr.ui.MenuButton} */ (gearButton);
901
902     this.syncButton.checkable = true;
903     this.hostedButton.checkable = true;
904
905     if (util.runningInBrowser()) {
906       // Suppresses the default context menu.
907       this.dialogDom_.addEventListener('contextmenu', function(e) {
908         e.preventDefault();
909         e.stopPropagation();
910       });
911     }
912   };
913
914   FileManager.prototype.onShowGearMenu_ = function() {
915     this.refreshRemainingSpace_(false);  /* Without loading caption. */
916
917     // If the menu is opened while CTRL key pressed, secret menu itemscan be
918     // shown.
919     this.isSecretGearMenuShown_ = this.pressingCtrl_;
920
921     // Update view of drive-related settings.
922     this.commandHandler.updateAvailability();
923     this.document_.getElementById('drive-separator').hidden =
924         !this.shouldShowDriveSettings();
925
926     // Force to update the gear menu position.
927     // TODO(hirono): Remove the workaround for the crbug.com/374093 after fixing
928     // it.
929     var gearMenu = this.document_.querySelector('#gear-menu');
930     gearMenu.style.left = '';
931     gearMenu.style.right = '';
932     gearMenu.style.top = '';
933     gearMenu.style.bottom = '';
934   };
935
936   /**
937    * One-time initialization of commands.
938    * @private
939    */
940   FileManager.prototype.initCommands_ = function() {
941     assert(this.textContextMenu_);
942
943     this.commandHandler = new CommandHandler(this);
944
945     // TODO(hirono): Move the following block to the UI part.
946     var commandButtons = this.dialogDom_.querySelectorAll('button[command]');
947     for (var j = 0; j < commandButtons.length; j++)
948       CommandButton.decorate(commandButtons[j]);
949
950     var inputs = this.dialogDom_.querySelectorAll(
951         'input[type=text], input[type=search], textarea');
952     for (var i = 0; i < inputs.length; i++) {
953       cr.ui.contextMenuHandler.setContextMenu(inputs[i], this.textContextMenu_);
954       this.registerInputCommands_(inputs[i]);
955     }
956
957     cr.ui.contextMenuHandler.setContextMenu(this.ui_.listContainer.renameInput,
958                                             this.textContextMenu_);
959     this.registerInputCommands_(this.ui_.listContainer.renameInput);
960     this.document_.addEventListener(
961         'command',
962         this.ui_.listContainer.clearHover.bind(this.ui_.listContainer));
963   };
964
965   /**
966    * Registers cut, copy, paste and delete commands on input element.
967    *
968    * @param {Node} node Text input element to register on.
969    * @private
970    */
971   FileManager.prototype.registerInputCommands_ = function(node) {
972     CommandUtil.forceDefaultHandler(node, 'cut');
973     CommandUtil.forceDefaultHandler(node, 'copy');
974     CommandUtil.forceDefaultHandler(node, 'paste');
975     CommandUtil.forceDefaultHandler(node, 'delete');
976     node.addEventListener('keydown', function(e) {
977       var key = util.getKeyModifiers(e) + e.keyCode;
978       if (key === '190' /* '/' */ || key === '191' /* '.' */) {
979         // If this key event is propagated, this is handled search command,
980         // which calls 'preventDefault' method.
981         e.stopPropagation();
982       }
983     });
984   };
985
986   /**
987    * Entry point of the initialization.
988    * This method is called from main.js.
989    */
990   FileManager.prototype.initializeCore = function() {
991     this.initializeQueue_.add(this.initGeneral_.bind(this), [], 'initGeneral');
992     this.initializeQueue_.add(this.initBackgroundPage_.bind(this),
993                               [], 'initBackgroundPage');
994     this.initializeQueue_.add(this.initPreferences_.bind(this),
995                               ['initGeneral'], 'initPreferences');
996     this.initializeQueue_.add(this.initVolumeManager_.bind(this),
997                               ['initGeneral', 'initBackgroundPage'],
998                               'initVolumeManager');
999
1000     this.initializeQueue_.run();
1001     window.addEventListener('pagehide', this.onUnload_.bind(this));
1002   };
1003
1004   FileManager.prototype.initializeUI = function(dialogDom, callback) {
1005     this.dialogDom_ = dialogDom;
1006     this.document_ = this.dialogDom_.ownerDocument;
1007
1008     this.initializeQueue_.add(
1009         this.initEssentialUI_.bind(this),
1010         ['initGeneral', 'initBackgroundPage'],
1011         'initEssentialUI');
1012     this.initializeQueue_.add(this.initAdditionalUI_.bind(this),
1013         ['initEssentialUI'], 'initAdditionalUI');
1014     this.initializeQueue_.add(
1015         this.initFileSystemUI_.bind(this),
1016         ['initAdditionalUI', 'initPreferences'], 'initFileSystemUI');
1017
1018     // Run again just in case if all pending closures have completed and the
1019     // queue has stopped and monitor the completion.
1020     this.initializeQueue_.run(callback);
1021   };
1022
1023   /**
1024    * Initializes general purpose basic things, which are used by other
1025    * initializing methods.
1026    *
1027    * @param {function()} callback Completion callback.
1028    * @private
1029    */
1030   FileManager.prototype.initGeneral_ = function(callback) {
1031     // Initialize the application state.
1032     // TODO(mtomasz): Unify window.appState with location.search format.
1033     if (window.appState) {
1034       this.params_ = window.appState.params || {};
1035       this.initCurrentDirectoryURL_ = window.appState.currentDirectoryURL;
1036       this.initSelectionURL_ = window.appState.selectionURL;
1037       this.initTargetName_ = window.appState.targetName;
1038     } else {
1039       // Used by the select dialog only.
1040       this.params_ = location.search ?
1041                      JSON.parse(decodeURIComponent(location.search.substr(1))) :
1042                      {};
1043       this.initCurrentDirectoryURL_ = this.params_.currentDirectoryURL;
1044       this.initSelectionURL_ = this.params_.selectionURL;
1045       this.initTargetName_ = this.params_.targetName;
1046     }
1047
1048     // Initialize the member variables that depend this.params_.
1049     this.dialogType = this.params_.type || DialogType.FULL_PAGE;
1050     this.startupPrefName_ = 'file-manager-' + this.dialogType;
1051     this.fileTypes_ = this.params_.typeList || [];
1052
1053     callback();
1054   };
1055
1056   /**
1057    * Initialize the background page.
1058    * @param {function()} callback Completion callback.
1059    * @private
1060    */
1061   FileManager.prototype.initBackgroundPage_ = function(callback) {
1062     chrome.runtime.getBackgroundPage(function(backgroundPage) {
1063       this.backgroundPage_ = backgroundPage;
1064       this.backgroundPage_.background.ready(function() {
1065         loadTimeData.data = this.backgroundPage_.background.stringData;
1066         if (util.runningInBrowser())
1067           this.backgroundPage_.registerDialog(window);
1068         callback();
1069       }.bind(this));
1070     }.bind(this));
1071   };
1072
1073   /**
1074    * Initializes the VolumeManager instance.
1075    * @param {function()} callback Completion callback.
1076    * @private
1077    */
1078   FileManager.prototype.initVolumeManager_ = function(callback) {
1079     // Auto resolving to local path does not work for folders (e.g., dialog for
1080     // loading unpacked extensions).
1081     var noLocalPathResolution = DialogType.isFolderDialog(this.params_.type);
1082
1083     // If this condition is false, VolumeManagerWrapper hides all drive
1084     // related event and data, even if Drive is enabled on preference.
1085     // In other words, even if Drive is disabled on preference but Files.app
1086     // should show Drive when it is re-enabled, then the value should be set to
1087     // true.
1088     // Note that the Drive enabling preference change is listened by
1089     // DriveIntegrationService, so here we don't need to take care about it.
1090     var driveEnabled =
1091         !noLocalPathResolution || !this.params_.shouldReturnLocalPath;
1092     this.volumeManager_ = new VolumeManagerWrapper(
1093         /** @type {VolumeManagerWrapper.DriveEnabledStatus} */ (driveEnabled),
1094         this.backgroundPage_);
1095     callback();
1096   };
1097
1098   /**
1099    * One time initialization of the Files.app's essential UI elements. These
1100    * elements will be shown to the user. Only visible elements should be
1101    * initialized here. Any heavy operation should be avoided. Files.app's
1102    * window is shown at the end of this routine.
1103    *
1104    * @param {function()} callback Completion callback.
1105    * @private
1106    */
1107   FileManager.prototype.initEssentialUI_ = function(callback) {
1108     // Record stats of dialog types. New values must NOT be inserted into the
1109     // array enumerating the types. It must be in sync with
1110     // FileDialogType enum in tools/metrics/histograms/histogram.xml.
1111     metrics.recordEnum('Create', this.dialogType,
1112         [DialogType.SELECT_FOLDER,
1113          DialogType.SELECT_UPLOAD_FOLDER,
1114          DialogType.SELECT_SAVEAS_FILE,
1115          DialogType.SELECT_OPEN_FILE,
1116          DialogType.SELECT_OPEN_MULTI_FILE,
1117          DialogType.FULL_PAGE]);
1118
1119     // Create the metadata cache.
1120     this.metadataCache_ = MetadataCache.createFull(this.volumeManager_);
1121
1122     // Create the root view of FileManager.
1123     assert(this.dialogDom_);
1124     this.ui_ = new FileManagerUI(this.dialogDom_, this.dialogType);
1125
1126     // Show the window as soon as the UI pre-initialization is done.
1127     if (this.dialogType == DialogType.FULL_PAGE && !util.runningInBrowser()) {
1128       chrome.app.window.current().show();
1129       setTimeout(callback, 100);  // Wait until the animation is finished.
1130     } else {
1131       callback();
1132     }
1133   };
1134
1135   /**
1136    * One-time initialization of dialogs.
1137    * @private
1138    */
1139   FileManager.prototype.initDialogs_ = function() {
1140     // Initialize the dialog.
1141     this.ui_.initDialogs();
1142     FileManagerDialogBase.setFileManager(this);
1143
1144     // Obtains the dialog instances from FileManagerUI.
1145     // TODO(hirono): Remove the properties from the FileManager class.
1146     this.error = this.ui_.errorDialog;
1147     this.alert = this.ui_.alertDialog;
1148     this.confirm = this.ui_.confirmDialog;
1149     this.prompt = this.ui_.promptDialog;
1150     this.shareDialog_ = this.ui_.shareDialog;
1151     this.defaultTaskPicker = this.ui_.defaultTaskPicker;
1152     this.suggestAppsDialog = this.ui_.suggestAppsDialog;
1153   };
1154
1155   /**
1156    * One-time initialization of various DOM nodes. Loads the additional DOM
1157    * elements visible to the user. Initialize here elements, which are expensive
1158    * or hidden in the beginning.
1159    *
1160    * @param {function()} callback Completion callback.
1161    * @private
1162    */
1163   FileManager.prototype.initAdditionalUI_ = function(callback) {
1164     // Cache nodes we'll be manipulating.
1165     var dom = this.dialogDom_;
1166     assert(dom);
1167
1168     this.initDialogs_();
1169
1170     var table = queryRequiredElement(dom, '.detail-table');
1171     FileTable.decorate(
1172         table,
1173         this.metadataCache_,
1174         this.volumeManager_,
1175         this.dialogType == DialogType.FULL_PAGE);
1176     var grid = queryRequiredElement(dom, '.thumbnail-grid');
1177     FileGrid.decorate(grid, this.metadataCache_, this.volumeManager_);
1178
1179     this.ui_.initAdditionalUI(
1180         assertInstanceof(table, FileTable),
1181         assertInstanceof(grid, FileGrid),
1182         new PreviewPanel(
1183             queryRequiredElement(dom, '.preview-panel'),
1184             DialogType.isOpenDialog(this.dialogType) ?
1185                 PreviewPanel.VisibilityType.ALWAYS_VISIBLE :
1186                 PreviewPanel.VisibilityType.AUTO,
1187             this.metadataCache_,
1188             this.volumeManager_));
1189
1190     this.dialogDom_.addEventListener('click',
1191                                      this.onExternalLinkClick_.bind(this));
1192
1193
1194     var taskItems = queryRequiredElement(dom, '#tasks');
1195     this.taskItems_ = /** @type {HTMLButtonElement} */ (taskItems);
1196
1197     this.ui_.locationLine = new LocationLine(
1198         queryRequiredElement(dom, '#location-breadcrumbs'),
1199         queryRequiredElement(dom, '#location-volume-icon'),
1200         this.metadataCache_,
1201         this.volumeManager_);
1202     this.ui_.locationLine.addEventListener(
1203         'pathclick', this.onBreadcrumbClick_.bind(this));
1204
1205     // Initialize progress center panel.
1206     this.progressCenterPanel_ = new ProgressCenterPanel(
1207         queryRequiredElement(dom, '#progress-center'));
1208     this.backgroundPage_.background.progressCenter.addPanel(
1209         this.progressCenterPanel_);
1210
1211     this.document_.addEventListener('keydown', this.onKeyDown_.bind(this));
1212     this.document_.addEventListener('keyup', this.onKeyUp_.bind(this));
1213
1214     this.ui_.listContainer.element.addEventListener(
1215         'keydown', this.onListKeyDown_.bind(this));
1216     this.ui_.listContainer.element.addEventListener(
1217         ListContainer.EventType.TEXT_SEARCH, this.onTextSearch_.bind(this));
1218
1219     // TODO(hirono): Rename the handler after creating the DialogFooter class.
1220     this.ui_.dialogFooter.filenameInput.addEventListener(
1221         'input', this.onFilenameInputInput_.bind(this));
1222     this.ui_.dialogFooter.filenameInput.addEventListener(
1223         'keydown', this.onFilenameInputKeyDown_.bind(this));
1224     this.ui_.dialogFooter.filenameInput.addEventListener(
1225         'focus', this.onFilenameInputFocus_.bind(this));
1226
1227     this.decorateSplitter(
1228         this.dialogDom_.querySelector('#navigation-list-splitter'));
1229
1230     this.dialogContainer_ = /** @type {!HTMLDivElement} */
1231         (this.dialogDom_.querySelector('.dialog-container'));
1232
1233     this.syncButton = /** @type {!HTMLMenuItemElement} */
1234         (queryRequiredElement(this.dialogDom_,
1235                               '#gear-menu-drive-sync-settings'));
1236     this.hostedButton = /** @type {!HTMLMenuItemElement} */
1237         (queryRequiredElement(this.dialogDom_,
1238                              '#gear-menu-drive-hosted-settings'));
1239
1240     this.ui_.toggleViewButton.addEventListener('click',
1241         this.onToggleViewButtonClick_.bind(this));
1242
1243     cr.ui.ComboButton.decorate(this.taskItems_);
1244     this.taskItems_.showMenu = function(shouldSetFocus) {
1245       // Prevent the empty menu from opening.
1246       if (!this.menu.length)
1247         return;
1248       cr.ui.ComboButton.prototype.showMenu.call(this, shouldSetFocus);
1249     };
1250     this.taskItems_.addEventListener('select',
1251         this.onTaskItemClicked_.bind(this));
1252
1253     this.dialogDom_.ownerDocument.defaultView.addEventListener(
1254         'resize', this.onResize_.bind(this));
1255
1256     this.actionMenuItem_ = /** @type {!HTMLMenuItemElement} */
1257         (queryRequiredElement(this.dialogDom_, '#default-action'));
1258
1259     this.openWithCommand_ = /** @type {cr.ui.Command} */
1260         (this.dialogDom_.querySelector('#open-with'));
1261
1262     this.actionMenuItem_.addEventListener('activate',
1263         this.onActionMenuItemActivated_.bind(this));
1264
1265     this.ui_.dialogFooter.initFileTypeFilter(
1266         this.fileTypes_, this.params_.includeAllFiles);
1267     this.ui_.dialogFooter.fileTypeSelector.addEventListener(
1268         'change', this.updateFileTypeFilter_.bind(this));
1269
1270     util.addIsFocusedMethod();
1271
1272     // Populate the static localized strings.
1273     i18nTemplate.process(this.document_, loadTimeData);
1274
1275     // Arrange the file list.
1276     this.ui_.listContainer.table.normalizeColumns();
1277     this.ui_.listContainer.table.redraw();
1278
1279     callback();
1280   };
1281
1282   /**
1283    * @param {Event} event Click event.
1284    * @private
1285    */
1286   FileManager.prototype.onBreadcrumbClick_ = function(event) {
1287     this.directoryModel_.changeDirectoryEntry(event.entry);
1288   };
1289
1290   /**
1291    * Constructs table and grid (heavy operation).
1292    * @private
1293    **/
1294   FileManager.prototype.initFileList_ = function() {
1295     var singleSelection =
1296         this.dialogType == DialogType.SELECT_OPEN_FILE ||
1297         this.dialogType == DialogType.SELECT_FOLDER ||
1298         this.dialogType == DialogType.SELECT_UPLOAD_FOLDER ||
1299         this.dialogType == DialogType.SELECT_SAVEAS_FILE;
1300
1301     assert(this.metadataCache_);
1302     this.fileFilter_ = new FileFilter(
1303         this.metadataCache_,
1304         false  /* Don't show dot files and *.crdownload by default. */);
1305
1306     this.fileWatcher_ = new FileWatcher(this.metadataCache_);
1307     this.fileWatcher_.addEventListener(
1308         'watcher-metadata-changed',
1309         this.onWatcherMetadataChanged_.bind(this));
1310
1311     this.directoryModel_ = new DirectoryModel(
1312         singleSelection,
1313         this.fileFilter_,
1314         this.fileWatcher_,
1315         this.metadataCache_,
1316         this.volumeManager_);
1317
1318     this.folderShortcutsModel_ = new FolderShortcutsDataModel(
1319         this.volumeManager_);
1320
1321     this.selectionHandler_ = new FileSelectionHandler(this);
1322
1323     var dataModel = this.directoryModel_.getFileList();
1324     dataModel.addEventListener('permuted',
1325                                this.updateStartupPrefs_.bind(this));
1326
1327     this.directoryModel_.getFileListSelection().addEventListener('change',
1328         this.selectionHandler_.onFileSelectionChanged.bind(
1329             this.selectionHandler_));
1330
1331     var onDetailClickBound = this.onDetailClick_.bind(this);
1332     this.ui_.listContainer.table.list.addEventListener(
1333         'click', onDetailClickBound);
1334     this.ui_.listContainer.grid.addEventListener(
1335         'click', onDetailClickBound);
1336
1337     var fileListFocusBound = this.onFileListFocus_.bind(this);
1338     this.ui_.listContainer.table.list.addEventListener(
1339         'focus', fileListFocusBound);
1340     this.ui_.listContainer.grid.addEventListener('focus', fileListFocusBound);
1341
1342     // TODO(mtomasz, yoshiki): Create navigation list earlier, and here just
1343     // attach the directory model.
1344     this.initDirectoryTree_();
1345
1346     this.ui_.listContainer.table.addEventListener('column-resize-end',
1347                                  this.updateStartupPrefs_.bind(this));
1348
1349     // Restore preferences.
1350     this.directoryModel_.getFileList().sort(
1351         this.viewOptions_.sortField || 'modificationTime',
1352         this.viewOptions_.sortDirection || 'desc');
1353     if (this.viewOptions_.columns) {
1354       var cm = this.ui_.listContainer.table.columnModel;
1355       for (var i = 0; i < cm.totalSize; i++) {
1356         if (this.viewOptions_.columns[i] > 0)
1357           cm.setWidth(i, this.viewOptions_.columns[i]);
1358       }
1359     }
1360
1361     this.ui_.listContainer.dataModel = this.directoryModel_.getFileList();
1362     this.ui_.listContainer.selectionModel =
1363         this.directoryModel_.getFileListSelection();
1364     this.setListType(
1365         this.viewOptions_.listType || ListContainer.ListType.DETAIL);
1366
1367     this.closeOnUnmount_ = (this.params_.action == 'auto-open');
1368
1369     if (this.closeOnUnmount_) {
1370       this.volumeManager_.addEventListener('externally-unmounted',
1371           this.onExternallyUnmounted_.bind(this));
1372     }
1373
1374     // Create search controller.
1375     this.searchController_ = new SearchController(
1376         this.ui_.searchBox,
1377         this.ui_.locationLine,
1378         this.directoryModel_,
1379         this.volumeManager_,
1380         {
1381           // TODO (hirono): Make the real task controller and pass it here.
1382           doAction: this.doEntryAction_.bind(this)
1383         });
1384
1385     // Create naming controller.
1386     assert(this.ui_.alertDialog);
1387     assert(this.ui_.confirmDialog);
1388     this.namingController_ = new NamingController(
1389         this.ui_.listContainer,
1390         this.ui_.alertDialog,
1391         this.ui_.confirmDialog,
1392         this.directoryModel_,
1393         this.fileFilter_,
1394         this.selectionHandler_);
1395
1396     // Create spinner controller.
1397     this.spinnerController_ = new SpinnerController(
1398         this.ui_.listContainer.spinner, this.directoryModel_);
1399     this.spinnerController_.show();
1400
1401     // Create dialog action controller.
1402     this.dialogActionController_ = new DialogActionController(
1403         this.dialogType,
1404         this.ui_.dialogFooter,
1405         this.directoryModel_,
1406         this.metadataCache_,
1407         this.namingController_,
1408         this.params_.shouldReturnLocalPath);
1409
1410     // Update metadata to change 'Today' and 'Yesterday' dates.
1411     var today = new Date();
1412     today.setHours(0);
1413     today.setMinutes(0);
1414     today.setSeconds(0);
1415     today.setMilliseconds(0);
1416     setTimeout(this.dailyUpdateModificationTime_.bind(this),
1417                today.getTime() + MILLISECONDS_IN_DAY - Date.now() + 1000);
1418   };
1419
1420   /**
1421    * @private
1422    */
1423   FileManager.prototype.initDirectoryTree_ = function() {
1424     var fakeEntriesVisible =
1425         this.dialogType !== DialogType.SELECT_SAVEAS_FILE;
1426     this.directoryTree_ = /** @type {DirectoryTree} */
1427         (this.dialogDom_.querySelector('#directory-tree'));
1428     DirectoryTree.decorate(this.directoryTree_,
1429                            this.directoryModel_,
1430                            this.volumeManager_,
1431                            this.metadataCache_,
1432                            fakeEntriesVisible);
1433     this.directoryTree_.dataModel = new NavigationListModel(
1434         this.volumeManager_, this.folderShortcutsModel_);
1435
1436     // Visible height of the directory tree depends on the size of progress
1437     // center panel. When the size of progress center panel changes, directory
1438     // tree has to be notified to adjust its components (e.g. progress bar).
1439     var observer = new MutationObserver(
1440         this.directoryTree_.relayout.bind(this.directoryTree_));
1441     observer.observe(this.progressCenterPanel_.element,
1442                      /** @type {MutationObserverInit} */
1443                      ({subtree: true, attributes: true, childList: true}));
1444   };
1445
1446   /**
1447    * @private
1448    */
1449   FileManager.prototype.updateStartupPrefs_ = function() {
1450     var sortStatus = this.directoryModel_.getFileList().sortStatus;
1451     var prefs = {
1452       sortField: sortStatus.field,
1453       sortDirection: sortStatus.direction,
1454       columns: [],
1455       listType: this.ui_.listContainer.currentListType
1456     };
1457     var cm = this.ui_.listContainer.table.columnModel;
1458     for (var i = 0; i < cm.totalSize; i++) {
1459       prefs.columns.push(cm.getWidth(i));
1460     }
1461     // Save the global default.
1462     var items = {};
1463     items[this.startupPrefName_] = JSON.stringify(prefs);
1464     chrome.storage.local.set(items);
1465
1466     // Save the window-specific preference.
1467     if (window.appState) {
1468       window.appState.viewOptions = prefs;
1469       util.saveAppState();
1470     }
1471   };
1472
1473   FileManager.prototype.refocus = function() {
1474     var targetElement;
1475     if (this.dialogType == DialogType.SELECT_SAVEAS_FILE)
1476       targetElement = this.ui_.dialogFooter.filenameInput;
1477     else
1478       targetElement = this.ui.listContainer.currentList;
1479
1480     // Hack: if the tabIndex is disabled, we can assume a modal dialog is
1481     // shown. Focus to a button on the dialog instead.
1482     if (!targetElement.hasAttribute('tabIndex') || targetElement.tabIndex == -1)
1483       targetElement = document.querySelector('button:not([tabIndex="-1"])');
1484
1485     if (targetElement)
1486       targetElement.focus();
1487   };
1488
1489   /**
1490    * File list focus handler. Used to select the top most element on the list
1491    * if nothing was selected.
1492    *
1493    * @private
1494    */
1495   FileManager.prototype.onFileListFocus_ = function() {
1496     // If the file list is focused by <Tab>, select the first item if no item
1497     // is selected.
1498     if (this.pressingTab_) {
1499       if (this.getSelection() && this.getSelection().totalCount == 0)
1500         this.directoryModel_.selectIndex(0);
1501     }
1502   };
1503
1504   /**
1505    * Sets the current list type.
1506    * @param {ListContainer.ListType} type New list type.
1507    */
1508   FileManager.prototype.setListType = function(type) {
1509     if ((type && type == this.ui_.listContainer.currentListType) ||
1510         !this.directoryModel_) {
1511       return;
1512     }
1513
1514     this.ui_.setCurrentListType(type);
1515     this.updateStartupPrefs_();
1516     this.onResize_();
1517   };
1518
1519   /**
1520    * @private
1521    */
1522   FileManager.prototype.onCopyProgress_ = function(event) {
1523     if (event.reason == 'ERROR' &&
1524         event.error.code == util.FileOperationErrorType.FILESYSTEM_ERROR &&
1525         event.error.data.toDrive &&
1526         event.error.data.name == util.FileError.QUOTA_EXCEEDED_ERR) {
1527       this.alert.showHtml(
1528           strf('DRIVE_SERVER_OUT_OF_SPACE_HEADER'),
1529           strf('DRIVE_SERVER_OUT_OF_SPACE_MESSAGE',
1530               decodeURIComponent(
1531                   event.error.data.sourceFileUrl.split('/').pop()),
1532               str('GOOGLE_DRIVE_BUY_STORAGE_URL')));
1533     }
1534   };
1535
1536   /**
1537    * Handler of file manager operations. Called when an entry has been
1538    * changed.
1539    * This updates directory model to reflect operation result immediately (not
1540    * waiting for directory update event). Also, preloads thumbnails for the
1541    * images of new entries.
1542    * See also FileOperationManager.EventRouter.
1543    *
1544    * @param {Event} event An event for the entry change.
1545    * @private
1546    */
1547   FileManager.prototype.onEntriesChanged_ = function(event) {
1548     var kind = event.kind;
1549     var entries = event.entries;
1550     this.directoryModel_.onEntriesChanged(kind, entries);
1551     this.selectionHandler_.onFileSelectionChanged();
1552
1553     if (kind !== util.EntryChangedKind.CREATED)
1554       return;
1555
1556     var preloadThumbnail = function(entry) {
1557       var locationInfo = this.volumeManager_.getLocationInfo(entry);
1558       if (!locationInfo)
1559         return;
1560       this.metadataCache_.getOne(entry, 'thumbnail|external',
1561           function(metadata) {
1562             var thumbnailLoader_ = new ThumbnailLoader(
1563                 entry,
1564                 ThumbnailLoader.LoaderType.CANVAS,
1565                 metadata,
1566                 undefined,  // Media type.
1567                 locationInfo.isDriveBased ?
1568                     ThumbnailLoader.UseEmbedded.USE_EMBEDDED :
1569                     ThumbnailLoader.UseEmbedded.NO_EMBEDDED,
1570                 10);  // Very low priority.
1571             thumbnailLoader_.loadDetachedImage(function(success) {});
1572           });
1573     }.bind(this);
1574
1575     for (var i = 0; i < entries.length; i++) {
1576       // Preload a thumbnail if the new copied entry an image.
1577       if (FileType.isImage(entries[i]))
1578         preloadThumbnail(entries[i]);
1579     }
1580   };
1581
1582   /**
1583    * Filters file according to the selected file type.
1584    * @private
1585    */
1586   FileManager.prototype.updateFileTypeFilter_ = function() {
1587     this.fileFilter_.removeFilter('fileType');
1588     var selectedIndex = this.ui_.dialogFooter.selectedFilterIndex;
1589     if (selectedIndex > 0) { // Specific filter selected.
1590       var regexp = new RegExp('\\.(' +
1591           this.fileTypes_[selectedIndex - 1].extensions.join('|') + ')$', 'i');
1592       var filter = function(entry) {
1593         return entry.isDirectory || regexp.test(entry.name);
1594       };
1595       this.fileFilter_.addFilter('fileType', filter);
1596
1597       // In save dialog, update the destination name extension.
1598       if (this.dialogType === DialogType.SELECT_SAVEAS_FILE) {
1599         var current = this.ui_.dialogFooter.filenameInput.value;
1600         var newExt = this.fileTypes_[selectedIndex - 1].extensions[0];
1601         if (newExt && !regexp.test(current)) {
1602           var i = current.lastIndexOf('.');
1603           if (i >= 0) {
1604             this.ui_.dialogFooter.filenameInput.value =
1605                 current.substr(0, i) + '.' + newExt;
1606             this.selectTargetNameInFilenameInput_();
1607           }
1608         }
1609       }
1610     }
1611   };
1612
1613   /**
1614    * Resize details and thumb views to fit the new window size.
1615    * @private
1616    */
1617   FileManager.prototype.onResize_ = function() {
1618     // May not be available during initialization.
1619     if (this.directoryTree_)
1620       this.directoryTree_.relayout();
1621
1622     this.ui_.relayout();
1623   };
1624
1625   /**
1626    * Handles local metadata changes in the currect directory.
1627    * @param {Event} event Change event.
1628    * @this {FileManager}
1629    * @private
1630    */
1631   FileManager.prototype.onWatcherMetadataChanged_ = function(event) {
1632     this.ui_.listContainer.currentView.updateListItemsMetadata(
1633         event.metadataType, event.entries);
1634   };
1635
1636   /**
1637    * Sets up the current directory during initialization.
1638    * @private
1639    */
1640   FileManager.prototype.setupCurrentDirectory_ = function() {
1641     var tracker = this.directoryModel_.createDirectoryChangeTracker();
1642     var queue = new AsyncUtil.Queue();
1643
1644     // Wait until the volume manager is initialized.
1645     queue.run(function(callback) {
1646       tracker.start();
1647       this.volumeManager_.ensureInitialized(callback);
1648     }.bind(this));
1649
1650     var nextCurrentDirEntry;
1651     var selectionEntry;
1652
1653     // Resolve the selectionURL to selectionEntry or to currentDirectoryEntry
1654     // in case of being a display root or a default directory to open files.
1655     queue.run(function(callback) {
1656       if (!this.initSelectionURL_) {
1657         callback();
1658         return;
1659       }
1660       webkitResolveLocalFileSystemURL(
1661           this.initSelectionURL_,
1662           function(inEntry) {
1663             var locationInfo = this.volumeManager_.getLocationInfo(inEntry);
1664             // If location information is not available, then the volume is
1665             // no longer (or never) available.
1666             if (!locationInfo) {
1667               callback();
1668               return;
1669             }
1670             // If the selection is root, then use it as a current directory
1671             // instead. This is because, selecting a root entry is done as
1672             // opening it.
1673             if (locationInfo.isRootEntry)
1674               nextCurrentDirEntry = inEntry;
1675
1676             // If this dialog attempts to open file(s) and the selection is a
1677             // directory, the selection should be the current directory.
1678             if (DialogType.isOpenFileDialog(this.dialogType) &&
1679                 inEntry.isDirectory) {
1680               nextCurrentDirEntry = inEntry;
1681             }
1682
1683             // By default, the selection should be selected entry and the
1684             // parent directory of it should be the current directory.
1685             if (!nextCurrentDirEntry)
1686               selectionEntry = inEntry;
1687
1688             callback();
1689           }.bind(this), callback);
1690     }.bind(this));
1691     // Resolve the currentDirectoryURL to currentDirectoryEntry (if not done
1692     // by the previous step).
1693     queue.run(function(callback) {
1694       if (nextCurrentDirEntry || !this.initCurrentDirectoryURL_) {
1695         callback();
1696         return;
1697       }
1698       webkitResolveLocalFileSystemURL(
1699           this.initCurrentDirectoryURL_,
1700           function(inEntry) {
1701             var locationInfo = this.volumeManager_.getLocationInfo(inEntry);
1702             if (!locationInfo) {
1703               callback();
1704               return;
1705             }
1706             nextCurrentDirEntry = inEntry;
1707             callback();
1708           }.bind(this), callback);
1709       // TODO(mtomasz): Implement reopening on special search, when fake
1710       // entries are converted to directory providers.
1711     }.bind(this));
1712
1713     // If the directory to be changed to is not available, then first fallback
1714     // to the parent of the selection entry.
1715     queue.run(function(callback) {
1716       if (nextCurrentDirEntry || !selectionEntry) {
1717         callback();
1718         return;
1719       }
1720       selectionEntry.getParent(function(inEntry) {
1721         nextCurrentDirEntry = inEntry;
1722         callback();
1723       }.bind(this));
1724     }.bind(this));
1725
1726     // Check if the next current directory is not a virtual directory which is
1727     // not available in UI. This may happen to shared on Drive.
1728     queue.run(function(callback) {
1729       if (!nextCurrentDirEntry) {
1730         callback();
1731         return;
1732       }
1733       var locationInfo = this.volumeManager_.getLocationInfo(
1734           nextCurrentDirEntry);
1735       // If we can't check, assume that the directory is illegal.
1736       if (!locationInfo) {
1737         nextCurrentDirEntry = null;
1738         callback();
1739         return;
1740       }
1741       // Having root directory of DRIVE_OTHER here should be only for shared
1742       // with me files. Fallback to Drive root in such case.
1743       if (locationInfo.isRootEntry && locationInfo.rootType ===
1744               VolumeManagerCommon.RootType.DRIVE_OTHER) {
1745         var volumeInfo = this.volumeManager_.getVolumeInfo(nextCurrentDirEntry);
1746         if (!volumeInfo) {
1747           nextCurrentDirEntry = null;
1748           callback();
1749           return;
1750         }
1751         volumeInfo.resolveDisplayRoot().then(
1752             function(entry) {
1753               nextCurrentDirEntry = entry;
1754               callback();
1755             }).catch(function(error) {
1756               console.error(error.stack || error);
1757               nextCurrentDirEntry = null;
1758               callback();
1759             });
1760       } else {
1761         callback();
1762       }
1763     }.bind(this));
1764
1765     // If the directory to be changed to is still not resolved, then fallback
1766     // to the default display root.
1767     queue.run(function(callback) {
1768       if (nextCurrentDirEntry) {
1769         callback();
1770         return;
1771       }
1772       this.volumeManager_.getDefaultDisplayRoot(function(displayRoot) {
1773         nextCurrentDirEntry = displayRoot;
1774         callback();
1775       }.bind(this));
1776     }.bind(this));
1777
1778     // If selection failed to be resolved (eg. didn't exist, in case of saving
1779     // a file, or in case of a fallback of the current directory, then try to
1780     // resolve again using the target name.
1781     queue.run(function(callback) {
1782       if (selectionEntry || !nextCurrentDirEntry || !this.initTargetName_) {
1783         callback();
1784         return;
1785       }
1786       // Try to resolve as a file first. If it fails, then as a directory.
1787       nextCurrentDirEntry.getFile(
1788           this.initTargetName_,
1789           {},
1790           function(targetEntry) {
1791             selectionEntry = targetEntry;
1792             callback();
1793           }, function() {
1794             // Failed to resolve as a file
1795             nextCurrentDirEntry.getDirectory(
1796                 this.initTargetName_,
1797                 {},
1798                 function(targetEntry) {
1799                   selectionEntry = targetEntry;
1800                   callback();
1801                 }, function() {
1802                   // Failed to resolve as either file or directory.
1803                   callback();
1804                 });
1805           }.bind(this));
1806     }.bind(this));
1807
1808     // Finalize.
1809     queue.run(function(callback) {
1810       // Check directory change.
1811       tracker.stop();
1812       if (tracker.hasChanged) {
1813         callback();
1814         return;
1815       }
1816       // Finish setup current directory.
1817       this.finishSetupCurrentDirectory_(
1818           nextCurrentDirEntry,
1819           selectionEntry,
1820           this.initTargetName_);
1821       callback();
1822     }.bind(this));
1823   };
1824
1825   /**
1826    * @param {DirectoryEntry} directoryEntry Directory to be opened.
1827    * @param {Entry=} opt_selectionEntry Entry to be selected.
1828    * @param {string=} opt_suggestedName Suggested name for a non-existing\
1829    *     selection.
1830    * @private
1831    */
1832   FileManager.prototype.finishSetupCurrentDirectory_ = function(
1833       directoryEntry, opt_selectionEntry, opt_suggestedName) {
1834     // Open the directory, and select the selection (if passed).
1835     if (util.isFakeEntry(directoryEntry)) {
1836       this.directoryModel_.specialSearch(directoryEntry, '');
1837     } else {
1838       this.directoryModel_.changeDirectoryEntry(directoryEntry, function() {
1839         if (opt_selectionEntry)
1840           this.directoryModel_.selectEntry(opt_selectionEntry);
1841       }.bind(this));
1842     }
1843
1844     if (this.dialogType === DialogType.FULL_PAGE) {
1845       // In the FULL_PAGE mode if the restored URL points to a file we might
1846       // have to invoke a task after selecting it.
1847       if (this.params_.action === 'select')
1848         return;
1849
1850       var task = null;
1851
1852       // TODO(mtomasz): Implement remounting archives after crash.
1853       //                See: crbug.com/333139
1854
1855       // If there is a task to be run, run it after the scan is completed.
1856       if (task) {
1857         var listener = function() {
1858           if (!util.isSameEntry(this.directoryModel_.getCurrentDirEntry(),
1859                                 directoryEntry)) {
1860             // Opened on a different URL. Probably fallbacked. Therefore,
1861             // do not invoke a task.
1862             return;
1863           }
1864           this.directoryModel_.removeEventListener(
1865               'scan-completed', listener);
1866           task();
1867         }.bind(this);
1868         this.directoryModel_.addEventListener('scan-completed', listener);
1869       }
1870     } else if (this.dialogType === DialogType.SELECT_SAVEAS_FILE) {
1871       this.ui_.dialogFooter.filenameInput.value = opt_suggestedName || '';
1872       this.selectTargetNameInFilenameInput_();
1873     }
1874   };
1875
1876   /**
1877    * @private
1878    */
1879   FileManager.prototype.refreshCurrentDirectoryMetadata_ = function() {
1880     var entries = this.directoryModel_.getFileList().slice();
1881     var directoryEntry = this.directoryModel_.getCurrentDirEntry();
1882     if (!directoryEntry)
1883       return;
1884     // We don't pass callback here. When new metadata arrives, we have an
1885     // observer registered to update the UI.
1886
1887     // TODO(dgozman): refresh content metadata only when modificationTime
1888     // changed.
1889     var isFakeEntry = util.isFakeEntry(directoryEntry);
1890     var getEntries = (isFakeEntry ? [] : [directoryEntry]).concat(entries);
1891     if (!isFakeEntry)
1892       this.metadataCache_.clearRecursively(directoryEntry, '*');
1893     this.metadataCache_.get(getEntries, 'filesystem|external', null);
1894
1895     var visibleItems = this.ui.listContainer.currentList.items;
1896     var visibleEntries = [];
1897     for (var i = 0; i < visibleItems.length; i++) {
1898       var index = this.ui.listContainer.currentList.getIndexOfListItem(
1899           visibleItems[i]);
1900       var entry = this.directoryModel_.getFileList().item(index);
1901       // The following check is a workaround for the bug in list: sometimes item
1902       // does not have listIndex, and therefore is not found in the list.
1903       if (entry) visibleEntries.push(entry);
1904     }
1905     // Refreshes the metadata.
1906     this.metadataCache_.getLatest(visibleEntries, 'thumbnail', null);
1907   };
1908
1909   /**
1910    * @private
1911    */
1912   FileManager.prototype.dailyUpdateModificationTime_ = function() {
1913     var entries = this.directoryModel_.getFileList().slice();
1914     this.metadataCache_.get(
1915         entries,
1916         'filesystem',
1917         function() {
1918           this.ui_.listContainer.currentView.updateListItemsMetadata(
1919               'filesystem', entries);
1920         }.bind(this));
1921
1922     setTimeout(this.dailyUpdateModificationTime_.bind(this),
1923                MILLISECONDS_IN_DAY);
1924   };
1925
1926   /**
1927    * TODO(mtomasz): Move this to a utility function working on the root type.
1928    * @return {boolean} True if the current directory content is from Google
1929    *     Drive.
1930    */
1931   FileManager.prototype.isOnDrive = function() {
1932     var rootType = this.directoryModel_.getCurrentRootType();
1933     return rootType != null &&
1934         VolumeManagerCommon.getVolumeTypeFromRootType(rootType) ==
1935             VolumeManagerCommon.VolumeType.DRIVE;
1936   };
1937
1938   /**
1939    * Check if the drive-related setting items should be shown on currently
1940    * displayed gear menu.
1941    * @return {boolean} True if those setting items should be shown.
1942    */
1943   FileManager.prototype.shouldShowDriveSettings = function() {
1944     return this.isOnDrive();
1945   };
1946
1947   /**
1948    * Overrides default handling for clicks on hyperlinks.
1949    * In a packaged apps links with targer='_blank' open in a new tab by
1950    * default, other links do not open at all.
1951    *
1952    * @param {Event} event Click event.
1953    * @private
1954    */
1955   FileManager.prototype.onExternalLinkClick_ = function(event) {
1956     if (event.target.tagName != 'A' || !event.target.href)
1957       return;
1958
1959     if (this.dialogType != DialogType.FULL_PAGE)
1960       this.ui_.dialogFooter.cancelButton.click();
1961   };
1962
1963   /**
1964    * Task combobox handler.
1965    *
1966    * @param {Object} event Event containing task which was clicked.
1967    * @private
1968    */
1969   FileManager.prototype.onTaskItemClicked_ = function(event) {
1970     var selection = this.getSelection();
1971     if (!selection.tasks) return;
1972
1973     if (event.item.task) {
1974       // Task field doesn't exist on change-default dropdown item.
1975       selection.tasks.execute(event.item.task.taskId);
1976     } else {
1977       var extensions = [];
1978
1979       for (var i = 0; i < selection.entries.length; i++) {
1980         var match = /\.(\w+)$/g.exec(selection.entries[i].toURL());
1981         if (match) {
1982           var ext = match[1].toUpperCase();
1983           if (extensions.indexOf(ext) == -1) {
1984             extensions.push(ext);
1985           }
1986         }
1987       }
1988
1989       var format = '';
1990
1991       if (extensions.length == 1) {
1992         format = extensions[0];
1993       }
1994
1995       // Change default was clicked. We should open "change default" dialog.
1996       selection.tasks.showTaskPicker(this.defaultTaskPicker,
1997           loadTimeData.getString('CHANGE_DEFAULT_MENU_ITEM'),
1998           strf('CHANGE_DEFAULT_CAPTION', format),
1999           this.onDefaultTaskDone_.bind(this),
2000           true);
2001     }
2002   };
2003
2004   /**
2005    * Sets the given task as default, when this task is applicable.
2006    *
2007    * @param {Object} task Task to set as default.
2008    * @private
2009    */
2010   FileManager.prototype.onDefaultTaskDone_ = function(task) {
2011     // TODO(dgozman): move this method closer to tasks.
2012     var selection = this.getSelection();
2013     // TODO(mtomasz): Move conversion from entry to url to custom bindings.
2014     // crbug.com/345527.
2015     chrome.fileManagerPrivate.setDefaultTask(
2016         task.taskId,
2017         util.entriesToURLs(selection.entries),
2018         selection.mimeTypes);
2019     selection.tasks = new FileTasks(this);
2020     selection.tasks.init(selection.entries, selection.mimeTypes);
2021     selection.tasks.display(this.taskItems_);
2022     this.refreshCurrentDirectoryMetadata_();
2023     this.selectionHandler_.onFileSelectionChanged();
2024   };
2025
2026   /**
2027    * @private
2028    */
2029   FileManager.prototype.onPreferencesChanged_ = function() {
2030     var self = this;
2031     this.getPreferences_(function(prefs) {
2032       self.initDateTimeFormatters_();
2033       self.refreshCurrentDirectoryMetadata_();
2034
2035       if (prefs.cellularDisabled)
2036         self.syncButton.setAttribute('checked', '');
2037       else
2038         self.syncButton.removeAttribute('checked');
2039
2040       if (self.hostedButton.hasAttribute('checked') ===
2041           prefs.hostedFilesDisabled && self.isOnDrive()) {
2042         self.directoryModel_.rescan(false);
2043       }
2044
2045       if (!prefs.hostedFilesDisabled)
2046         self.hostedButton.setAttribute('checked', '');
2047       else
2048         self.hostedButton.removeAttribute('checked');
2049     },
2050     true /* refresh */);
2051   };
2052
2053   FileManager.prototype.onDriveConnectionChanged_ = function() {
2054     var connection = this.volumeManager_.getDriveConnectionState();
2055     if (this.commandHandler)
2056       this.commandHandler.updateAvailability();
2057     if (this.dialogContainer_)
2058       this.dialogContainer_.setAttribute('connection', connection.type);
2059     this.shareDialog_.hideWithResult(ShareDialog.Result.NETWORK_ERROR);
2060     this.suggestAppsDialog.onDriveConnectionChanged(connection.type);
2061   };
2062
2063   /**
2064    * Tells whether the current directory is read only.
2065    * TODO(mtomasz): Remove and use EntryLocation directly.
2066    * @return {boolean} True if read only, false otherwise.
2067    */
2068   FileManager.prototype.isOnReadonlyDirectory = function() {
2069     return this.directoryModel_.isReadOnly();
2070   };
2071
2072   /**
2073    * @param {Event} event Unmount event.
2074    * @private
2075    */
2076   FileManager.prototype.onExternallyUnmounted_ = function(event) {
2077     if (event.volumeInfo === this.currentVolumeInfo_) {
2078       if (this.closeOnUnmount_) {
2079         // If the file manager opened automatically when a usb drive inserted,
2080         // user have never changed current volume (that implies the current
2081         // directory is still on the device) then close this window.
2082         window.close();
2083       }
2084     }
2085   };
2086
2087   /**
2088    * @return {Array.<Entry>} List of all entries in the current directory.
2089    */
2090   FileManager.prototype.getAllEntriesInCurrentDirectory = function() {
2091     return this.directoryModel_.getFileList().slice();
2092   };
2093
2094   /**
2095    * Return DirectoryEntry of the current directory or null.
2096    * @return {DirectoryEntry} DirectoryEntry of the current directory. Returns
2097    *     null if the directory model is not ready or the current directory is
2098    *     not set.
2099    */
2100   FileManager.prototype.getCurrentDirectoryEntry = function() {
2101     return this.directoryModel_ && this.directoryModel_.getCurrentDirEntry();
2102   };
2103
2104   /**
2105    * Shows the share dialog for the selected file or directory.
2106    */
2107   FileManager.prototype.shareSelection = function() {
2108     var entries = this.getSelection().entries;
2109     if (entries.length != 1) {
2110       console.warn('Unable to share multiple items at once.');
2111       return;
2112     }
2113     // Add the overlapped class to prevent the applicaiton window from
2114     // captureing mouse events.
2115     this.shareDialog_.show(entries[0], function(result) {
2116       if (result == ShareDialog.Result.NETWORK_ERROR)
2117         this.error.show(str('SHARE_ERROR'));
2118     }.bind(this));
2119   };
2120
2121   /**
2122    * Creates a folder shortcut.
2123    * @param {Entry} entry A shortcut which refers to |entry| to be created.
2124    */
2125   FileManager.prototype.createFolderShortcut = function(entry) {
2126     // Duplicate entry.
2127     if (this.folderShortcutExists(entry))
2128       return;
2129
2130     this.folderShortcutsModel_.add(entry);
2131   };
2132
2133   /**
2134    * Checkes if the shortcut which refers to the given folder exists or not.
2135    * @param {Entry} entry Entry of the folder to be checked.
2136    */
2137   FileManager.prototype.folderShortcutExists = function(entry) {
2138     return this.folderShortcutsModel_.exists(entry);
2139   };
2140
2141   /**
2142    * Removes the folder shortcut.
2143    * @param {Entry} entry The shortcut which refers to |entry| is to be removed.
2144    */
2145   FileManager.prototype.removeFolderShortcut = function(entry) {
2146     this.folderShortcutsModel_.remove(entry);
2147   };
2148
2149   /**
2150    * Blinks the selection. Used to give feedback when copying or cutting the
2151    * selection.
2152    */
2153   FileManager.prototype.blinkSelection = function() {
2154     var selection = this.getSelection();
2155     if (!selection || selection.totalCount == 0)
2156       return;
2157
2158     for (var i = 0; i < selection.entries.length; i++) {
2159       var selectedIndex = selection.indexes[i];
2160       var listItem =
2161           this.ui.listContainer.currentList.getListItemByIndex(selectedIndex);
2162       if (listItem)
2163         this.blinkListItem_(listItem);
2164     }
2165   };
2166
2167   /**
2168    * @param {Element} listItem List item element.
2169    * @private
2170    */
2171   FileManager.prototype.blinkListItem_ = function(listItem) {
2172     listItem.classList.add('blink');
2173     setTimeout(function() {
2174       listItem.classList.remove('blink');
2175     }, 100);
2176   };
2177
2178   /**
2179    * @private
2180    */
2181   FileManager.prototype.selectTargetNameInFilenameInput_ = function() {
2182     var input = this.ui_.dialogFooter.filenameInput;
2183     input.focus();
2184     var selectionEnd = input.value.lastIndexOf('.');
2185     if (selectionEnd == -1) {
2186       input.select();
2187     } else {
2188       input.selectionStart = 0;
2189       input.selectionEnd = selectionEnd;
2190     }
2191   };
2192
2193   /**
2194    * Handles mouse click or tap.
2195    *
2196    * @param {Event} event The click event.
2197    * @private
2198    */
2199   FileManager.prototype.onDetailClick_ = function(event) {
2200     if (this.namingController_.isRenamingInProgress()) {
2201       // Don't pay attention to clicks during a rename.
2202       return;
2203     }
2204
2205     var listItem = this.ui_.listContainer.findListItemForNode(
2206         event.touchedElement || event.srcElement);
2207     var selection = this.getSelection();
2208     if (!listItem || !listItem.selected || selection.totalCount != 1) {
2209       return;
2210     }
2211
2212     // React on double click, but only if both clicks hit the same item.
2213     // TODO(mtomasz): Simplify it, and use a double click handler if possible.
2214     var clickNumber = (this.lastClickedItem_ == listItem) ? 2 : undefined;
2215     this.lastClickedItem_ = listItem;
2216
2217     if (event.detail != clickNumber)
2218       return;
2219
2220     var entry = selection.entries[0];
2221     if (entry.isDirectory) {
2222       this.onDirectoryAction_(entry);
2223     } else {
2224       this.dispatchSelectionAction_();
2225     }
2226   };
2227
2228   /**
2229    * @private
2230    */
2231   FileManager.prototype.dispatchSelectionAction_ = function() {
2232     if (this.dialogType == DialogType.FULL_PAGE) {
2233       var selection = this.getSelection();
2234       var tasks = selection.tasks;
2235       var urls = selection.urls;
2236       var mimeTypes = selection.mimeTypes;
2237       if (tasks)
2238         tasks.executeDefault();
2239       return true;
2240     }
2241     if (!this.ui_.dialogFooter.okButton.disabled) {
2242       this.ui_.dialogFooter.okButton.click();
2243       return true;
2244     }
2245     return false;
2246   };
2247
2248   /**
2249    * Handles activate event of action menu item.
2250    *
2251    * @private
2252    */
2253   FileManager.prototype.onActionMenuItemActivated_ = function() {
2254     var tasks = this.getSelection().tasks;
2255     if (tasks)
2256       tasks.execute(this.actionMenuItem_.taskId);
2257   };
2258
2259   /**
2260    * Opens the suggest file dialog.
2261    *
2262    * @param {Entry} entry Entry of the file.
2263    * @param {function()} onSuccess Success callback.
2264    * @param {function()} onCancelled User-cancelled callback.
2265    * @param {function()} onFailure Failure callback.
2266    * @private
2267    */
2268   FileManager.prototype.openSuggestAppsDialog =
2269       function(entry, onSuccess, onCancelled, onFailure) {
2270     if (!url) {
2271       onFailure();
2272       return;
2273     }
2274
2275     this.metadataCache_.getOne(entry, 'external', function(prop) {
2276       if (!prop || !prop.contentMimeType) {
2277         onFailure();
2278         return;
2279       }
2280
2281       var basename = entry.name;
2282       var splitted = util.splitExtension(basename);
2283       var filename = splitted[0];
2284       var extension = splitted[1];
2285       var mime = prop.contentMimeType;
2286
2287       // Returns with failure if the file has neither extension nor mime.
2288       if (!extension || !mime) {
2289         onFailure();
2290         return;
2291       }
2292
2293       var onDialogClosed = function(result) {
2294         switch (result) {
2295           case SuggestAppsDialog.Result.INSTALL_SUCCESSFUL:
2296             onSuccess();
2297             break;
2298           case SuggestAppsDialog.Result.FAILED:
2299             onFailure();
2300             break;
2301           default:
2302             onCancelled();
2303         }
2304       };
2305
2306       if (FileTasks.EXECUTABLE_EXTENSIONS.indexOf(extension) !== -1) {
2307         this.suggestAppsDialog.showByFilename(filename, onDialogClosed);
2308       } else {
2309         this.suggestAppsDialog.showByExtensionAndMime(
2310             extension, mime, onDialogClosed);
2311       }
2312     }.bind(this));
2313   };
2314
2315   /**
2316    * Called when a dialog is shown or hidden.
2317    * @param {boolean} show True if a dialog is shown, false if hidden.
2318    */
2319   FileManager.prototype.onDialogShownOrHidden = function(show) {
2320     if (show) {
2321       // If a dialog is shown, activate the window.
2322       var appWindow = chrome.app.window.current();
2323       if (appWindow)
2324         appWindow.focus();
2325     }
2326
2327     // Set/unset a flag to disable dragging on the title area.
2328     this.dialogContainer_.classList.toggle('disable-header-drag', show);
2329   };
2330
2331   /**
2332    * Executes directory action (i.e. changes directory).
2333    *
2334    * @param {DirectoryEntry} entry Directory entry to which directory should be
2335    *                               changed.
2336    * @private
2337    */
2338   FileManager.prototype.onDirectoryAction_ = function(entry) {
2339     return this.directoryModel_.changeDirectoryEntry(entry);
2340   };
2341
2342   /**
2343    * Update the window title.
2344    * @private
2345    */
2346   FileManager.prototype.updateTitle_ = function() {
2347     if (this.dialogType != DialogType.FULL_PAGE)
2348       return;
2349
2350     if (!this.currentVolumeInfo_)
2351       return;
2352
2353     this.document_.title = this.currentVolumeInfo_.label;
2354   };
2355
2356   /**
2357    * Update the gear menu.
2358    * @private
2359    */
2360   FileManager.prototype.updateGearMenu_ = function() {
2361     this.refreshRemainingSpace_(true);  // Show loading caption.
2362   };
2363
2364   /**
2365    * Refreshes space info of the current volume.
2366    * @param {boolean} showLoadingCaption Whether show loading caption or not.
2367    * @private
2368    */
2369   FileManager.prototype.refreshRemainingSpace_ = function(showLoadingCaption) {
2370     if (!this.currentVolumeInfo_)
2371       return;
2372
2373     var volumeSpaceInfo = /** @type {!HTMLElement} */
2374         (this.dialogDom_.querySelector('#volume-space-info'));
2375     var volumeSpaceInfoSeparator = /** @type {!HTMLElement} */
2376         (this.dialogDom_.querySelector('#volume-space-info-separator'));
2377     var volumeSpaceInfoLabel = /** @type {!HTMLElement} */
2378         (this.dialogDom_.querySelector('#volume-space-info-label'));
2379     var volumeSpaceInnerBar = /** @type {!HTMLElement} */
2380         (this.dialogDom_.querySelector('#volume-space-info-bar'));
2381     var volumeSpaceOuterBar = /** @type {!HTMLElement} */
2382         (this.dialogDom_.querySelector('#volume-space-info-bar').parentNode);
2383
2384     var currentVolumeInfo = this.currentVolumeInfo_;
2385
2386     // TODO(mtomasz): Add support for remaining space indication for provided
2387     // file systems.
2388     if (currentVolumeInfo.volumeType ==
2389         VolumeManagerCommon.VolumeType.PROVIDED) {
2390       volumeSpaceInfo.hidden = true;
2391       volumeSpaceInfoSeparator.hidden = true;
2392       return;
2393     }
2394
2395     volumeSpaceInfo.hidden = false;
2396     volumeSpaceInfoSeparator.hidden = false;
2397     volumeSpaceInnerBar.setAttribute('pending', '');
2398
2399     if (showLoadingCaption) {
2400       volumeSpaceInfoLabel.innerText = str('WAITING_FOR_SPACE_INFO');
2401       volumeSpaceInnerBar.style.width = '100%';
2402     }
2403
2404     chrome.fileManagerPrivate.getSizeStats(
2405         currentVolumeInfo.volumeId, function(result) {
2406           var volumeInfo = this.volumeManager_.getVolumeInfo(
2407               this.directoryModel_.getCurrentDirEntry());
2408           if (currentVolumeInfo !== this.currentVolumeInfo_)
2409             return;
2410           updateSpaceInfo(result,
2411                           volumeSpaceInnerBar,
2412                           volumeSpaceInfoLabel,
2413                           volumeSpaceOuterBar);
2414         }.bind(this));
2415   };
2416
2417   /**
2418    * Update the UI when the current directory changes.
2419    *
2420    * @param {Event} event The directory-changed event.
2421    * @private
2422    */
2423   FileManager.prototype.onDirectoryChanged_ = function(event) {
2424     var oldCurrentVolumeInfo = this.currentVolumeInfo_;
2425
2426     // Remember the current volume info.
2427     this.currentVolumeInfo_ = this.volumeManager_.getVolumeInfo(
2428         event.newDirEntry);
2429
2430     // If volume has changed, then update the gear menu.
2431     if (oldCurrentVolumeInfo !== this.currentVolumeInfo_) {
2432       this.updateGearMenu_();
2433       // If the volume has changed, and it was previously set, then do not
2434       // close on unmount anymore.
2435       if (oldCurrentVolumeInfo)
2436         this.closeOnUnmount_ = false;
2437     }
2438
2439     this.selectionHandler_.onFileSelectionChanged();
2440     this.searchController_.clear();
2441     // TODO(mtomasz): Consider remembering the selection.
2442     util.updateAppState(
2443         this.getCurrentDirectoryEntry() ?
2444         this.getCurrentDirectoryEntry().toURL() : '',
2445         '' /* selectionURL */,
2446         '' /* opt_param */);
2447
2448     if (this.commandHandler)
2449       this.commandHandler.updateAvailability();
2450
2451     this.updateUnformattedVolumeStatus_();
2452     this.updateTitle_();
2453
2454     var currentEntry = this.getCurrentDirectoryEntry();
2455     this.ui_.locationLine.show(currentEntry);
2456     this.ui_.previewPanel.currentEntry = util.isFakeEntry(currentEntry) ?
2457         null : currentEntry;
2458   };
2459
2460   FileManager.prototype.updateUnformattedVolumeStatus_ = function() {
2461     var volumeInfo = this.volumeManager_.getVolumeInfo(
2462         this.directoryModel_.getCurrentDirEntry());
2463
2464     if (volumeInfo && volumeInfo.error) {
2465       this.dialogDom_.setAttribute('unformatted', '');
2466
2467       var errorNode = this.dialogDom_.querySelector('#format-panel > .error');
2468       if (volumeInfo.error ===
2469           VolumeManagerCommon.VolumeError.UNSUPPORTED_FILESYSTEM) {
2470         errorNode.textContent = str('UNSUPPORTED_FILESYSTEM_WARNING');
2471       } else {
2472         errorNode.textContent = str('UNKNOWN_FILESYSTEM_WARNING');
2473       }
2474
2475       // Update 'canExecute' for format command so the format button's disabled
2476       // property is properly set.
2477       if (this.commandHandler)
2478         this.commandHandler.updateAvailability();
2479     } else {
2480       this.dialogDom_.removeAttribute('unformatted');
2481     }
2482   };
2483
2484   /**
2485    * Unload handler for the page.
2486    * @private
2487    */
2488   FileManager.prototype.onUnload_ = function() {
2489     if (this.directoryModel_)
2490       this.directoryModel_.dispose();
2491     if (this.volumeManager_)
2492       this.volumeManager_.dispose();
2493     if (this.fileTransferController_) {
2494       for (var i = 0;
2495            i < this.fileTransferController_.pendingTaskIds.length;
2496            i++) {
2497         var taskId = this.fileTransferController_.pendingTaskIds[i];
2498         var item =
2499             this.backgroundPage_.background.progressCenter.getItemById(taskId);
2500         item.message = '';
2501         item.state = ProgressItemState.CANCELED;
2502         this.backgroundPage_.background.progressCenter.updateItem(item);
2503       }
2504     }
2505     if (this.progressCenterPanel_) {
2506       this.backgroundPage_.background.progressCenter.removePanel(
2507           this.progressCenterPanel_);
2508     }
2509     if (this.fileOperationManager_) {
2510       if (this.onCopyProgressBound_) {
2511         this.fileOperationManager_.removeEventListener(
2512             'copy-progress', this.onCopyProgressBound_);
2513       }
2514       if (this.onEntriesChangedBound_) {
2515         this.fileOperationManager_.removeEventListener(
2516             'entries-changed', this.onEntriesChangedBound_);
2517       }
2518     }
2519     window.closing = true;
2520     if (this.backgroundPage_)
2521       this.backgroundPage_.background.tryClose();
2522   };
2523
2524   /**
2525    * @private
2526    */
2527   FileManager.prototype.onFilenameInputInput_ = function() {
2528     this.selectionHandler_.updateOkButton();
2529   };
2530
2531   /**
2532    * @param {Event} event Key event.
2533    * @private
2534    */
2535   FileManager.prototype.onFilenameInputKeyDown_ = function(event) {
2536     if ((util.getKeyModifiers(event) + event.keyCode) === '13' /* Enter */)
2537       this.ui_.dialogFooter.okButton.click();
2538   };
2539
2540   /**
2541    * @param {Event} event Focus event.
2542    * @private
2543    */
2544   FileManager.prototype.onFilenameInputFocus_ = function(event) {
2545     var input = this.ui_.dialogFooter.filenameInput;
2546
2547     // On focus we want to select everything but the extension, but
2548     // Chrome will select-all after the focus event completes.  We
2549     // schedule a timeout to alter the focus after that happens.
2550     setTimeout(function() {
2551       var selectionEnd = input.value.lastIndexOf('.');
2552       if (selectionEnd == -1) {
2553         input.select();
2554       } else {
2555         input.selectionStart = 0;
2556         input.selectionEnd = selectionEnd;
2557       }
2558     }, 0);
2559   };
2560
2561   FileManager.prototype.createNewFolder = function() {
2562     var defaultName = str('DEFAULT_NEW_FOLDER_NAME');
2563
2564     // Find a name that doesn't exist in the data model.
2565     var files = this.directoryModel_.getFileList();
2566     var hash = {};
2567     for (var i = 0; i < files.length; i++) {
2568       var name = files.item(i).name;
2569       // Filtering names prevents from conflicts with prototype's names
2570       // and '__proto__'.
2571       if (name.substring(0, defaultName.length) == defaultName)
2572         hash[name] = 1;
2573     }
2574
2575     var baseName = defaultName;
2576     var separator = '';
2577     var suffix = '';
2578     var index = '';
2579
2580     var advance = function() {
2581       separator = ' (';
2582       suffix = ')';
2583       index++;
2584     };
2585
2586     var current = function() {
2587       return baseName + separator + index + suffix;
2588     };
2589
2590     // Accessing hasOwnProperty is safe since hash properties filtered.
2591     while (hash.hasOwnProperty(current())) {
2592       advance();
2593     }
2594
2595     var self = this;
2596     var list = self.ui_.listContainer.currentList;
2597
2598     var onSuccess = function(entry) {
2599       metrics.recordUserAction('CreateNewFolder');
2600       list.selectedItem = entry;
2601
2602       self.ui_.listContainer.endBatchUpdates();
2603
2604       self.namingController_.initiateRename();
2605     };
2606
2607     var onError = function(error) {
2608       self.ui_.listContainer.endBatchUpdates();
2609
2610       self.alert.show(strf('ERROR_CREATING_FOLDER', current(),
2611                            util.getFileErrorString(error.name)));
2612     };
2613
2614     var onAbort = function() {
2615       self.ui_.listContainer.endBatchUpdates();
2616     };
2617
2618     this.ui_.listContainer.startBatchUpdates();
2619     this.directoryModel_.createDirectory(current(),
2620                                          onSuccess,
2621                                          onError,
2622                                          onAbort);
2623   };
2624
2625   /**
2626    * Handles click event on the toggle-view button.
2627    * @param {Event} event Click event.
2628    * @private
2629    */
2630   FileManager.prototype.onToggleViewButtonClick_ = function(event) {
2631     if (this.ui_.listContainer.currentListType ===
2632         ListContainer.ListType.DETAIL) {
2633       this.setListType(ListContainer.ListType.THUMBNAIL);
2634     } else {
2635       this.setListType(ListContainer.ListType.DETAIL);
2636     }
2637
2638     event.target.blur();
2639   };
2640
2641   /**
2642    * KeyDown event handler for the document.
2643    * @param {Event} event Key event.
2644    * @private
2645    */
2646   FileManager.prototype.onKeyDown_ = function(event) {
2647     if (event.keyCode === 9)  // Tab
2648       this.pressingTab_ = true;
2649     if (event.keyCode === 17)  // Ctrl
2650       this.pressingCtrl_ = true;
2651
2652     if (event.srcElement === this.ui_.listContainer.renameInput) {
2653       // Ignore keydown handler in the rename input box.
2654       return;
2655     }
2656
2657     switch (util.getKeyModifiers(event) + event.keyIdentifier) {
2658       case 'Ctrl-U+00BE':  // Ctrl-. => Toggle filter files.
2659         this.fileFilter_.setFilterHidden(
2660             !this.fileFilter_.isFilterHiddenOn());
2661         event.preventDefault();
2662         return;
2663
2664       case 'U+001B':  // Escape => Cancel dialog.
2665         if (this.dialogType != DialogType.FULL_PAGE) {
2666           // If there is nothing else for ESC to do, then cancel the dialog.
2667           event.preventDefault();
2668           this.ui_.dialogFooter.cancelButton.click();
2669         }
2670         break;
2671     }
2672   };
2673
2674   /**
2675    * KeyUp event handler for the document.
2676    * @param {Event} event Key event.
2677    * @private
2678    */
2679   FileManager.prototype.onKeyUp_ = function(event) {
2680     if (event.keyCode === 9)  // Tab
2681       this.pressingTab_ = false;
2682     if (event.keyCode == 17)  // Ctrl
2683       this.pressingCtrl_ = false;
2684   };
2685
2686   /**
2687    * KeyDown event handler for the div#list-container element.
2688    * @param {Event} event Key event.
2689    * @private
2690    */
2691   FileManager.prototype.onListKeyDown_ = function(event) {
2692     switch (util.getKeyModifiers(event) + event.keyIdentifier) {
2693       case 'U+0008':  // Backspace => Up one directory.
2694         event.preventDefault();
2695         // TODO(mtomasz): Use Entry.getParent() instead.
2696         if (!this.getCurrentDirectoryEntry())
2697           break;
2698         var currentEntry = this.getCurrentDirectoryEntry();
2699         var locationInfo = this.volumeManager_.getLocationInfo(currentEntry);
2700         // TODO(mtomasz): There may be a tiny race in here.
2701         if (locationInfo && !locationInfo.isRootEntry &&
2702             !locationInfo.isSpecialSearchRoot) {
2703           currentEntry.getParent(function(parentEntry) {
2704             this.directoryModel_.changeDirectoryEntry(parentEntry);
2705           }.bind(this), function() { /* Ignore errors. */});
2706         }
2707         break;
2708
2709       case 'Enter':  // Enter => Change directory or perform default action.
2710         // TODO(dgozman): move directory action to dispatchSelectionAction.
2711         var selection = this.getSelection();
2712         if (selection.totalCount === 1 &&
2713             selection.entries[0].isDirectory &&
2714             !DialogType.isFolderDialog(this.dialogType)) {
2715           var item = this.ui.listContainer.currentList.getListItemByIndex(
2716               selection.indexes[0]);
2717           // If the item is in renaming process, we don't allow to change
2718           // directory.
2719           if (!item.hasAttribute('renaming')) {
2720             event.preventDefault();
2721             this.onDirectoryAction_(selection.entries[0]);
2722           }
2723         } else if (this.dispatchSelectionAction_()) {
2724           event.preventDefault();
2725         }
2726         break;
2727     }
2728   };
2729
2730   /**
2731    * Performs a 'text search' - selects a first list entry with name
2732    * starting with entered text (case-insensitive).
2733    * @private
2734    */
2735   FileManager.prototype.onTextSearch_ = function() {
2736     var text = this.ui_.listContainer.textSearchState.text;
2737     var dm = this.directoryModel_.getFileList();
2738     for (var index = 0; index < dm.length; ++index) {
2739       var name = dm.item(index).name;
2740       if (name.substring(0, text.length).toLowerCase() == text) {
2741         this.ui.listContainer.currentList.selectionModel.selectedIndexes =
2742             [index];
2743         return;
2744       }
2745     }
2746
2747     this.ui_.listContainer.textSearchState.text = '';
2748   };
2749
2750   /**
2751    * Verifies the user entered name for file or folder to be created or
2752    * renamed to. See also util.validateFileName.
2753    *
2754    * @param {DirectoryEntry} parentEntry The URL of the parent directory entry.
2755    * @param {string} name New file or folder name.
2756    * @param {function(boolean)} onDone Function to invoke when user closes the
2757    *    warning box or immediatelly if file name is correct. If the name was
2758    *    valid it is passed true, and false otherwise.
2759    * @private
2760    */
2761   FileManager.prototype.validateFileName_ = function(
2762       parentEntry, name, onDone) {
2763     var fileNameErrorPromise = util.validateFileName(
2764         parentEntry,
2765         name,
2766         this.fileFilter_.isFilterHiddenOn());
2767     fileNameErrorPromise.then(onDone.bind(null, true), function(message) {
2768       this.alert.show(message, onDone.bind(null, false));
2769     }.bind(this)).catch(function(error) {
2770       console.error(error.stack || error);
2771     });
2772   };
2773
2774   /**
2775    * Toggle whether mobile data is used for sync.
2776    */
2777   FileManager.prototype.toggleDriveSyncSettings = function() {
2778     // If checked, the sync is disabled.
2779     var nowCellularDisabled = this.syncButton.hasAttribute('checked');
2780     var changeInfo = {cellularDisabled: !nowCellularDisabled};
2781     chrome.fileManagerPrivate.setPreferences(changeInfo);
2782   };
2783
2784   /**
2785    * Toggle whether Google Docs files are shown.
2786    */
2787   FileManager.prototype.toggleDriveHostedSettings = function() {
2788     // If checked, showing drive hosted files is enabled.
2789     var nowHostedFilesEnabled = this.hostedButton.hasAttribute('checked');
2790     var nowHostedFilesDisabled = !nowHostedFilesEnabled;
2791     /*
2792     var changeInfo = {hostedFilesDisabled: !nowHostedFilesDisabled};
2793     */
2794     var changeInfo = {};
2795     changeInfo['hostedFilesDisabled'] = !nowHostedFilesDisabled;
2796     chrome.fileManagerPrivate.setPreferences(changeInfo);
2797   };
2798
2799   FileManager.prototype.decorateSplitter = function(splitterElement) {
2800     var self = this;
2801
2802     var Splitter = cr.ui.Splitter;
2803
2804     var customSplitter = cr.ui.define('div');
2805
2806     customSplitter.prototype = {
2807       __proto__: Splitter.prototype,
2808
2809       handleSplitterDragStart: function(e) {
2810         Splitter.prototype.handleSplitterDragStart.apply(this, arguments);
2811         this.ownerDocument.documentElement.classList.add('col-resize');
2812       },
2813
2814       handleSplitterDragMove: function(deltaX) {
2815         Splitter.prototype.handleSplitterDragMove.apply(this, arguments);
2816         self.onResize_();
2817       },
2818
2819       handleSplitterDragEnd: function(e) {
2820         Splitter.prototype.handleSplitterDragEnd.apply(this, arguments);
2821         this.ownerDocument.documentElement.classList.remove('col-resize');
2822       }
2823     };
2824
2825     customSplitter.decorate(splitterElement);
2826   };
2827
2828   /**
2829    * Updates action menu item to match passed task items.
2830    *
2831    * @param {Array.<Object>=} opt_items List of items.
2832    */
2833   FileManager.prototype.updateContextMenuActionItems = function(opt_items) {
2834     var items = opt_items || [];
2835
2836     // When only one task is available, show it as default item.
2837     if (items.length === 1) {
2838       var actionItem = items[0];
2839
2840       if (actionItem.iconType) {
2841         this.actionMenuItem_.style.backgroundImage = '';
2842         this.actionMenuItem_.setAttribute('file-type-icon',
2843                                           actionItem.iconType);
2844       } else if (actionItem.iconUrl) {
2845         this.actionMenuItem_.style.backgroundImage =
2846             'url(' + actionItem.iconUrl + ')';
2847       } else {
2848         this.actionMenuItem_.style.backgroundImage = '';
2849       }
2850
2851       this.actionMenuItem_.label =
2852           actionItem.taskId === FileTasks.ZIP_UNPACKER_TASK_ID ?
2853           str('ACTION_OPEN') : actionItem.title;
2854       this.actionMenuItem_.disabled = !!actionItem.disabled;
2855       this.actionMenuItem_.taskId = actionItem.taskId;
2856     }
2857
2858     this.actionMenuItem_.hidden = items.length !== 1;
2859
2860     // When multiple tasks are available, show them in open with.
2861     this.openWithCommand_.canExecuteChange();
2862     this.openWithCommand_.setHidden(items.length < 2);
2863     this.openWithCommand_.disabled = items.length < 2;
2864
2865     // Hide default action separator when there does not exist available task.
2866     var defaultActionSeparator =
2867         this.dialogDom_.querySelector('#default-action-separator');
2868     defaultActionSeparator.hidden = items.length === 0;
2869   };
2870
2871   /**
2872    * @return {FileSelection} Selection object.
2873    */
2874   FileManager.prototype.getSelection = function() {
2875     return this.selectionHandler_.selection;
2876   };
2877
2878   /**
2879    * @return {cr.ui.ArrayDataModel} File list.
2880    */
2881   FileManager.prototype.getFileList = function() {
2882     return this.directoryModel_.getFileList();
2883   };
2884
2885   /**
2886    * @return {!cr.ui.List} Current list object.
2887    */
2888   FileManager.prototype.getCurrentList = function() {
2889     return this.ui.listContainer.currentList;
2890   };
2891
2892   /**
2893    * Retrieve the preferences of the files.app. This method caches the result
2894    * and returns it unless opt_update is true.
2895    * @param {function(Object.<string, *>)} callback Callback to get the
2896    *     preference.
2897    * @param {boolean=} opt_update If is's true, don't use the cache and
2898    *     retrieve latest preference. Default is false.
2899    * @private
2900    */
2901   FileManager.prototype.getPreferences_ = function(callback, opt_update) {
2902     if (!opt_update && this.preferences_ !== null) {
2903       callback(this.preferences_);
2904       return;
2905     }
2906
2907     chrome.fileManagerPrivate.getPreferences(function(prefs) {
2908       this.preferences_ = prefs;
2909       callback(prefs);
2910     }.bind(this));
2911   };
2912
2913   /**
2914    * @param {FileEntry} entry
2915    * @private
2916    */
2917   FileManager.prototype.doEntryAction_ = function(entry) {
2918     if (this.dialogType == DialogType.FULL_PAGE) {
2919       this.metadataCache_.get([entry], 'external', function(props) {
2920         var tasks = new FileTasks(this);
2921         tasks.init([entry], [props[0].contentMimeType || '']);
2922         tasks.executeDefault_();
2923       }.bind(this));
2924     } else {
2925       var selection = this.getSelection();
2926       if (selection.entries.length === 1 &&
2927           util.isSameEntry(selection.entries[0], entry)) {
2928         this.ui_.dialogFooter.okButton.click();
2929       }
2930     }
2931   };
2932
2933   /**
2934    * Outputs the current state for debugging.
2935    */
2936   FileManager.prototype.debugMe = function() {
2937     var out = 'Debug information.\n' +
2938         '1. fileManager.initializeQueue_.pendingTasks_\n';
2939     var keys = Object.keys(this.initializeQueue_.pendingTasks);
2940     out += 'Length: ' + keys.length + '\n';
2941     keys.forEach(function(key) {
2942       out += this.initializeQueue_.pendingTasks[key].toString() + '\n';
2943     }.bind(this));
2944
2945     out += '2. VolumeManagerWrapper\n' +
2946         this.volumeManager_.toString() + '\n';
2947
2948     out += 'End of debug information.';
2949     console.log(out);
2950   };
2951 })();