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

Side by Side Diff: chrome/browser/resources/file_manager/background/js/background.js

Issue 247123002: Move Files.app files to ui/file_manager (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix the test failure on non-chromeos Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8 * Type of a Files.app's instance launch.
9 * @enum {number}
10 */
11 var LaunchType = Object.freeze({
12 ALWAYS_CREATE: 0,
13 FOCUS_ANY_OR_CREATE: 1,
14 FOCUS_SAME_OR_CREATE: 2
15 });
16
17 /**
18 * Root class of the background page.
19 * @constructor
20 */
21 function Background() {
22 /**
23 * Map of all currently open app windows. The key is an app id.
24 * @type {Object.<string, AppWindow>}
25 */
26 this.appWindows = {};
27
28 /**
29 * Synchronous queue for asynchronous calls.
30 * @type {AsyncUtil.Queue}
31 */
32 this.queue = new AsyncUtil.Queue();
33
34 /**
35 * Progress center of the background page.
36 * @type {ProgressCenter}
37 */
38 this.progressCenter = new ProgressCenter();
39
40 /**
41 * File operation manager.
42 * @type {FileOperationManager}
43 */
44 this.fileOperationManager = new FileOperationManager();
45
46 /**
47 * Event handler for progress center.
48 * @type {FileOperationHandler}
49 * @private
50 */
51 this.fileOperationHandler_ = new FileOperationHandler(this);
52
53 /**
54 * Event handler for C++ sides notifications.
55 * @type {DeviceHandler}
56 * @private
57 */
58 this.deviceHandler_ = new DeviceHandler();
59
60 /**
61 * Drive sync handler.
62 * @type {DriveSyncHandler}
63 * @private
64 */
65 this.driveSyncHandler_ = new DriveSyncHandler(this.progressCenter);
66 this.driveSyncHandler_.addEventListener(
67 DriveSyncHandler.COMPLETED_EVENT,
68 function() { this.tryClose(); }.bind(this));
69
70 /**
71 * String assets.
72 * @type {Object.<string, string>}
73 */
74 this.stringData = null;
75
76 /**
77 * Callback list to be invoked after initialization.
78 * It turns to null after initialization.
79 *
80 * @type {Array.<function()>}
81 * @private
82 */
83 this.initializeCallbacks_ = [];
84
85 /**
86 * Last time when the background page can close.
87 *
88 * @type {number}
89 * @private
90 */
91 this.lastTimeCanClose_ = null;
92
93 // Seal self.
94 Object.seal(this);
95
96 // Initialize handlers.
97 chrome.fileBrowserHandler.onExecute.addListener(this.onExecute_.bind(this));
98 chrome.app.runtime.onLaunched.addListener(this.onLaunched_.bind(this));
99 chrome.app.runtime.onRestarted.addListener(this.onRestarted_.bind(this));
100 chrome.contextMenus.onClicked.addListener(
101 this.onContextMenuClicked_.bind(this));
102
103 // Fetch strings and initialize the context menu.
104 this.queue.run(function(callNextStep) {
105 chrome.fileBrowserPrivate.getStrings(function(strings) {
106 // Initialize string assets.
107 this.stringData = strings;
108 loadTimeData.data = strings;
109 this.initContextMenu_();
110
111 // Invoke initialize callbacks.
112 for (var i = 0; i < this.initializeCallbacks_.length; i++) {
113 this.initializeCallbacks_[i]();
114 }
115 this.initializeCallbacks_ = null;
116
117 callNextStep();
118 }.bind(this));
119 }.bind(this));
120 }
121
122 /**
123 * A number of delay milliseconds from the first call of tryClose to the actual
124 * close action.
125 * @type {number}
126 * @const
127 * @private
128 */
129 Background.CLOSE_DELAY_MS_ = 5000;
130
131 /**
132 * Make a key of window geometry preferences for the given initial URL.
133 * @param {string} url Initialize URL that the window has.
134 * @return {string} Key of window geometry preferences.
135 */
136 Background.makeGeometryKey = function(url) {
137 return 'windowGeometry' + ':' + url;
138 };
139
140 /**
141 * Key for getting and storing the last window state (maximized or not).
142 * @const
143 * @private
144 */
145 Background.MAXIMIZED_KEY_ = 'isMaximized';
146
147 /**
148 * Register callback to be invoked after initialization.
149 * If the initialization is already done, the callback is invoked immediately.
150 *
151 * @param {function()} callback Initialize callback to be registered.
152 */
153 Background.prototype.ready = function(callback) {
154 if (this.initializeCallbacks_ !== null)
155 this.initializeCallbacks_.push(callback);
156 else
157 callback();
158 };
159
160 /**
161 * Checks the current condition of background page and closes it if possible.
162 */
163 Background.prototype.tryClose = function() {
164 // If the file operation is going, the background page cannot close.
165 if (this.fileOperationManager.hasQueuedTasks() ||
166 this.driveSyncHandler_.syncing) {
167 this.lastTimeCanClose_ = null;
168 return;
169 }
170
171 var views = chrome.extension.getViews();
172 var closing = false;
173 for (var i = 0; i < views.length; i++) {
174 // If the window that is not the background page itself and it is not
175 // closing, the background page cannot close.
176 if (views[i] !== window && !views[i].closing) {
177 this.lastTimeCanClose_ = null;
178 return;
179 }
180 closing = closing || views[i].closing;
181 }
182
183 // If some windows are closing, or the background page can close but could not
184 // 5 seconds ago, We need more time for sure.
185 if (closing ||
186 this.lastTimeCanClose_ === null ||
187 Date.now() - this.lastTimeCanClose_ < Background.CLOSE_DELAY_MS_) {
188 if (this.lastTimeCanClose_ === null)
189 this.lastTimeCanClose_ = Date.now();
190 setTimeout(this.tryClose.bind(this), Background.CLOSE_DELAY_MS_);
191 return;
192 }
193
194 // Otherwise we can close the background page.
195 close();
196 };
197
198 /**
199 * Gets similar windows, it means with the same initial url.
200 * @param {string} url URL that the obtained windows have.
201 * @return {Array.<AppWindow>} List of similar windows.
202 */
203 Background.prototype.getSimilarWindows = function(url) {
204 var result = [];
205 for (var appID in this.appWindows) {
206 if (this.appWindows[appID].contentWindow.appInitialURL === url)
207 result.push(this.appWindows[appID]);
208 }
209 return result;
210 };
211
212 /**
213 * Wrapper for an app window.
214 *
215 * Expects the following from the app scripts:
216 * 1. The page load handler should initialize the app using |window.appState|
217 * and call |util.platform.saveAppState|.
218 * 2. Every time the app state changes the app should update |window.appState|
219 * and call |util.platform.saveAppState| .
220 * 3. The app may have |unload| function to persist the app state that does not
221 * fit into |window.appState|.
222 *
223 * @param {string} url App window content url.
224 * @param {string} id App window id.
225 * @param {Object} options Options object to create it.
226 * @constructor
227 */
228 function AppWindowWrapper(url, id, options) {
229 this.url_ = url;
230 this.id_ = id;
231 // Do deep copy for the template of options to assign customized params later.
232 this.options_ = JSON.parse(JSON.stringify(options));
233 this.window_ = null;
234 this.appState_ = null;
235 this.openingOrOpened_ = false;
236 this.queue = new AsyncUtil.Queue();
237 Object.seal(this);
238 }
239
240 AppWindowWrapper.prototype = {
241 /**
242 * @return {AppWindow} Wrapped application window.
243 */
244 get rawAppWindow() {
245 return this.window_;
246 }
247 };
248
249 /**
250 * Focuses the window on the specified desktop.
251 * @param {AppWindow} appWindow Application window.
252 * @param {string=} opt_profileId The profiled ID of the target window. If it is
253 * dropped, the window is focused on the current window.
254 */
255 AppWindowWrapper.focusOnDesktop = function(appWindow, opt_profileId) {
256 new Promise(function(onFulfilled, onRejected) {
257 if (opt_profileId) {
258 onFulfilled(opt_profileId);
259 } else {
260 chrome.fileBrowserPrivate.getProfiles(function(profiles,
261 currentId,
262 displayedId) {
263 onFulfilled(currentId);
264 });
265 }
266 }).then(function(profileId) {
267 appWindow.contentWindow.chrome.fileBrowserPrivate.visitDesktop(
268 profileId, function() {
269 appWindow.focus();
270 });
271 });
272 };
273
274 /**
275 * Shift distance to avoid overlapping windows.
276 * @type {number}
277 * @const
278 */
279 AppWindowWrapper.SHIFT_DISTANCE = 40;
280
281 /**
282 * Sets the icon of the window.
283 * @param {string} iconPath Path of the icon.
284 */
285 AppWindowWrapper.prototype.setIcon = function(iconPath) {
286 this.window_.setIcon(iconPath);
287 };
288
289 /**
290 * Opens the window.
291 *
292 * @param {Object} appState App state.
293 * @param {boolean} reopen True if the launching is triggered automatically.
294 * False otherwise.
295 * @param {function()=} opt_callback Completion callback.
296 */
297 AppWindowWrapper.prototype.launch = function(appState, reopen, opt_callback) {
298 // Check if the window is opened or not.
299 if (this.openingOrOpened_) {
300 console.error('The window is already opened.');
301 if (opt_callback)
302 opt_callback();
303 return;
304 }
305 this.openingOrOpened_ = true;
306
307 // Save application state.
308 this.appState_ = appState;
309
310 // Get similar windows, it means with the same initial url, eg. different
311 // main windows of Files.app.
312 var similarWindows = background.getSimilarWindows(this.url_);
313
314 // Restore maximized windows, to avoid hiding them to tray, which can be
315 // confusing for users.
316 this.queue.run(function(callback) {
317 for (var index = 0; index < similarWindows.length; index++) {
318 if (similarWindows[index].isMaximized()) {
319 var createWindowAndRemoveListener = function() {
320 similarWindows[index].onRestored.removeListener(
321 createWindowAndRemoveListener);
322 callback();
323 };
324 similarWindows[index].onRestored.addListener(
325 createWindowAndRemoveListener);
326 similarWindows[index].restore();
327 return;
328 }
329 }
330 // If no maximized windows, then create the window immediately.
331 callback();
332 });
333
334 // Obtains the last geometry and window state (maximized or not).
335 var lastBounds;
336 var isMaximized = false;
337 this.queue.run(function(callback) {
338 var boundsKey = Background.makeGeometryKey(this.url_);
339 var maximizedKey = Background.MAXIMIZED_KEY_;
340 chrome.storage.local.get([boundsKey, maximizedKey], function(preferences) {
341 if (!chrome.runtime.lastError) {
342 lastBounds = preferences[boundsKey];
343 isMaximized = preferences[maximizedKey];
344 }
345 callback();
346 });
347 }.bind(this));
348
349 // Closure creating the window, once all preprocessing tasks are finished.
350 this.queue.run(function(callback) {
351 // Apply the last bounds.
352 if (lastBounds)
353 this.options_.bounds = lastBounds;
354 if (isMaximized)
355 this.options_.state = 'maximized';
356
357 // Create a window.
358 chrome.app.window.create(this.url_, this.options_, function(appWindow) {
359 this.window_ = appWindow;
360 callback();
361 }.bind(this));
362 }.bind(this));
363
364 // After creating.
365 this.queue.run(function(callback) {
366 // If there is another window in the same position, shift the window.
367 var makeBoundsKey = function(bounds) {
368 return bounds.left + '/' + bounds.top;
369 };
370 var notAvailablePositions = {};
371 for (var i = 0; i < similarWindows.length; i++) {
372 var key = makeBoundsKey(similarWindows[i].getBounds());
373 notAvailablePositions[key] = true;
374 }
375 var candidateBounds = this.window_.getBounds();
376 while (true) {
377 var key = makeBoundsKey(candidateBounds);
378 if (!notAvailablePositions[key])
379 break;
380 // Make the position available to avoid an infinite loop.
381 notAvailablePositions[key] = false;
382 var nextLeft = candidateBounds.left + AppWindowWrapper.SHIFT_DISTANCE;
383 var nextRight = nextLeft + candidateBounds.width;
384 candidateBounds.left = nextRight >= screen.availWidth ?
385 nextRight % screen.availWidth : nextLeft;
386 var nextTop = candidateBounds.top + AppWindowWrapper.SHIFT_DISTANCE;
387 var nextBottom = nextTop + candidateBounds.height;
388 candidateBounds.top = nextBottom >= screen.availHeight ?
389 nextBottom % screen.availHeight : nextTop;
390 }
391 this.window_.moveTo(candidateBounds.left, candidateBounds.top);
392
393 // Save the properties.
394 var appWindow = this.window_;
395 background.appWindows[this.id_] = appWindow;
396 var contentWindow = appWindow.contentWindow;
397 contentWindow.appID = this.id_;
398 contentWindow.appState = this.appState_;
399 contentWindow.appReopen = reopen;
400 contentWindow.appInitialURL = this.url_;
401 if (window.IN_TEST)
402 contentWindow.IN_TEST = true;
403
404 // Register event listeners.
405 appWindow.onBoundsChanged.addListener(this.onBoundsChanged_.bind(this));
406 appWindow.onClosed.addListener(this.onClosed_.bind(this));
407
408 // Callback.
409 if (opt_callback)
410 opt_callback();
411 callback();
412 }.bind(this));
413 };
414
415 /**
416 * Handles the onClosed extension API event.
417 * @private
418 */
419 AppWindowWrapper.prototype.onClosed_ = function() {
420 // Remember the last window state (maximized or normal).
421 var preferences = {};
422 preferences[Background.MAXIMIZED_KEY_] = this.window_.isMaximized();
423 chrome.storage.local.set(preferences);
424
425 // Unload the window.
426 var appWindow = this.window_;
427 var contentWindow = this.window_.contentWindow;
428 if (contentWindow.unload)
429 contentWindow.unload();
430 this.window_ = null;
431 this.openingOrOpened_ = false;
432
433 // Updates preferences.
434 if (contentWindow.saveOnExit) {
435 contentWindow.saveOnExit.forEach(function(entry) {
436 util.AppCache.update(entry.key, entry.value);
437 });
438 }
439 chrome.storage.local.remove(this.id_); // Forget the persisted state.
440
441 // Remove the window from the set.
442 delete background.appWindows[this.id_];
443
444 // If there is no application window, reset window ID.
445 if (!Object.keys(background.appWindows).length)
446 nextFileManagerWindowID = 0;
447 background.tryClose();
448 };
449
450 /**
451 * Handles onBoundsChanged extension API event.
452 * @private
453 */
454 AppWindowWrapper.prototype.onBoundsChanged_ = function() {
455 if (!this.window_.isMaximized()) {
456 var preferences = {};
457 preferences[Background.makeGeometryKey(this.url_)] =
458 this.window_.getBounds();
459 chrome.storage.local.set(preferences);
460 }
461 };
462
463 /**
464 * Wrapper for a singleton app window.
465 *
466 * In addition to the AppWindowWrapper requirements the app scripts should
467 * have |reload| method that re-initializes the app based on a changed
468 * |window.appState|.
469 *
470 * @param {string} url App window content url.
471 * @param {Object|function()} options Options object or a function to return it.
472 * @constructor
473 */
474 function SingletonAppWindowWrapper(url, options) {
475 AppWindowWrapper.call(this, url, url, options);
476 }
477
478 /**
479 * Inherits from AppWindowWrapper.
480 */
481 SingletonAppWindowWrapper.prototype = {__proto__: AppWindowWrapper.prototype};
482
483 /**
484 * Open the window.
485 *
486 * Activates an existing window or creates a new one.
487 *
488 * @param {Object} appState App state.
489 * @param {boolean} reopen True if the launching is triggered automatically.
490 * False otherwise.
491 * @param {function()=} opt_callback Completion callback.
492 */
493 SingletonAppWindowWrapper.prototype.launch =
494 function(appState, reopen, opt_callback) {
495 // If the window is not opened yet, just call the parent method.
496 if (!this.openingOrOpened_) {
497 AppWindowWrapper.prototype.launch.call(
498 this, appState, reopen, opt_callback);
499 return;
500 }
501
502 // If the window is already opened, reload the window.
503 // The queue is used to wait until the window is opened.
504 this.queue.run(function(nextStep) {
505 this.window_.contentWindow.appState = appState;
506 this.window_.contentWindow.appReopen = reopen;
507 this.window_.contentWindow.reload();
508 if (opt_callback)
509 opt_callback();
510 nextStep();
511 }.bind(this));
512 };
513
514 /**
515 * Reopen a window if its state is saved in the local storage.
516 * @param {function()=} opt_callback Completion callback.
517 */
518 SingletonAppWindowWrapper.prototype.reopen = function(opt_callback) {
519 chrome.storage.local.get(this.id_, function(items) {
520 var value = items[this.id_];
521 if (!value) {
522 opt_callback && opt_callback();
523 return; // No app state persisted.
524 }
525
526 try {
527 var appState = JSON.parse(value);
528 } catch (e) {
529 console.error('Corrupt launch data for ' + this.id_, value);
530 opt_callback && opt_callback();
531 return;
532 }
533 this.launch(appState, true, opt_callback);
534 }.bind(this));
535 };
536
537 /**
538 * Prefix for the file manager window ID.
539 */
540 var FILES_ID_PREFIX = 'files#';
541
542 /**
543 * Regexp matching a file manager window ID.
544 */
545 var FILES_ID_PATTERN = new RegExp('^' + FILES_ID_PREFIX + '(\\d*)$');
546
547 /**
548 * Value of the next file manager window ID.
549 */
550 var nextFileManagerWindowID = 0;
551
552 /**
553 * File manager window create options.
554 * @type {Object}
555 * @const
556 */
557 var FILE_MANAGER_WINDOW_CREATE_OPTIONS = Object.freeze({
558 bounds: Object.freeze({
559 left: Math.round(window.screen.availWidth * 0.1),
560 top: Math.round(window.screen.availHeight * 0.1),
561 width: Math.round(window.screen.availWidth * 0.8),
562 height: Math.round(window.screen.availHeight * 0.8)
563 }),
564 minWidth: 480,
565 minHeight: 240,
566 frame: 'none',
567 hidden: true,
568 transparentBackground: true
569 });
570
571 /**
572 * @param {Object=} opt_appState App state.
573 * @param {number=} opt_id Window id.
574 * @param {LaunchType=} opt_type Launch type. Default: ALWAYS_CREATE.
575 * @param {function(string)=} opt_callback Completion callback with the App ID.
576 */
577 function launchFileManager(opt_appState, opt_id, opt_type, opt_callback) {
578 var type = opt_type || LaunchType.ALWAYS_CREATE;
579
580 // Wait until all windows are created.
581 background.queue.run(function(onTaskCompleted) {
582 // Check if there is already a window with the same URL. If so, then
583 // reuse it instead of opening a new one.
584 if (type == LaunchType.FOCUS_SAME_OR_CREATE ||
585 type == LaunchType.FOCUS_ANY_OR_CREATE) {
586 if (opt_appState) {
587 for (var key in background.appWindows) {
588 if (!key.match(FILES_ID_PATTERN))
589 continue;
590
591 var contentWindow = background.appWindows[key].contentWindow;
592 if (!contentWindow.appState)
593 continue;
594
595 // Different current directories.
596 if (opt_appState.currentDirectoryURL !==
597 contentWindow.appState.currentDirectoryURL) {
598 continue;
599 }
600
601 // Selection URL specified, and it is different.
602 if (opt_appState.selectionURL &&
603 opt_appState.selectionURL !==
604 contentWindow.appState.selectionURL) {
605 continue;
606 }
607
608 AppWindowWrapper.focusOnDesktop(
609 background.appWindows[key], opt_appState.displayedId);
610 if (opt_callback)
611 opt_callback(key);
612 onTaskCompleted();
613 return;
614 }
615 }
616 }
617
618 // Focus any window if none is focused. Try restored first.
619 if (type == LaunchType.FOCUS_ANY_OR_CREATE) {
620 // If there is already a focused window, then finish.
621 for (var key in background.appWindows) {
622 if (!key.match(FILES_ID_PATTERN))
623 continue;
624
625 // The isFocused() method should always be available, but in case
626 // Files.app's failed on some error, wrap it with try catch.
627 try {
628 if (background.appWindows[key].contentWindow.isFocused()) {
629 if (opt_callback)
630 opt_callback(key);
631 onTaskCompleted();
632 return;
633 }
634 } catch (e) {
635 console.error(e.message);
636 }
637 }
638 // Try to focus the first non-minimized window.
639 for (var key in background.appWindows) {
640 if (!key.match(FILES_ID_PATTERN))
641 continue;
642
643 if (!background.appWindows[key].isMinimized()) {
644 AppWindowWrapper.focusOnDesktop(
645 background.appWindows[key], (opt_appState || {}).displayedId);
646 if (opt_callback)
647 opt_callback(key);
648 onTaskCompleted();
649 return;
650 }
651 }
652 // Restore and focus any window.
653 for (var key in background.appWindows) {
654 if (!key.match(FILES_ID_PATTERN))
655 continue;
656
657 AppWindowWrapper.focusOnDesktop(
658 background.appWindows[key], (opt_appState || {}).displayedId);
659 if (opt_callback)
660 opt_callback(key);
661 onTaskCompleted();
662 return;
663 }
664 }
665
666 // Create a new instance in case of ALWAYS_CREATE type, or as a fallback
667 // for other types.
668
669 var id = opt_id || nextFileManagerWindowID;
670 nextFileManagerWindowID = Math.max(nextFileManagerWindowID, id + 1);
671 var appId = FILES_ID_PREFIX + id;
672
673 var appWindow = new AppWindowWrapper(
674 'main.html',
675 appId,
676 FILE_MANAGER_WINDOW_CREATE_OPTIONS);
677 appWindow.launch(opt_appState || {}, false, function() {
678 AppWindowWrapper.focusOnDesktop(
679 appWindow.window_, (opt_appState || {}).displayedId);
680 if (opt_callback)
681 opt_callback(appId);
682 onTaskCompleted();
683 });
684 });
685 }
686
687 /**
688 * Executes a file browser task.
689 *
690 * @param {string} action Task id.
691 * @param {Object} details Details object.
692 * @private
693 */
694 Background.prototype.onExecute_ = function(action, details) {
695 var urls = details.entries.map(function(e) { return e.toURL(); });
696
697 switch (action) {
698 case 'play':
699 launchAudioPlayer({items: urls, position: 0});
700 break;
701
702 default:
703 var launchEnable = null;
704 var queue = new AsyncUtil.Queue();
705 queue.run(function(nextStep) {
706 // If it is not auto-open (triggered by mounting external devices), we
707 // always launch Files.app.
708 if (action != 'auto-open') {
709 launchEnable = true;
710 nextStep();
711 return;
712 }
713 // If the disable-default-apps flag is on, Files.app is not opened
714 // automatically on device mount not to obstruct the manual test.
715 chrome.commandLinePrivate.hasSwitch('disable-default-apps',
716 function(flag) {
717 launchEnable = !flag;
718 nextStep();
719 });
720 });
721 queue.run(function(nextStep) {
722 if (!launchEnable) {
723 nextStep();
724 return;
725 }
726
727 // Every other action opens a Files app window.
728 var appState = {
729 params: {
730 action: action
731 },
732 // It is not allowed to call getParent() here, since there may be
733 // no permissions to access it at this stage. Therefore we are passing
734 // the selectionURL only, and the currentDirectory will be resolved
735 // later.
736 selectionURL: details.entries[0].toURL()
737 };
738 // For mounted devices just focus any Files.app window. The mounted
739 // volume will appear on the navigation list.
740 var type = action == 'auto-open' ? LaunchType.FOCUS_ANY_OR_CREATE :
741 LaunchType.FOCUS_SAME_OR_CREATE;
742 launchFileManager(appState, /* App ID */ undefined, type, nextStep);
743 });
744 break;
745 }
746 };
747
748 /**
749 * Icon of the audio player.
750 * TODO(yoshiki): Consider providing an exact size icon, instead of relying
751 * on downsampling by ash.
752 *
753 * @type {string}
754 * @const
755 */
756 var AUDIO_PLAYER_ICON = 'audio_player/icons/audio-player-64.png';
757
758 // The instance of audio player. Until it's ready, this is null.
759 var audioPlayer = null;
760
761 // Queue to serializes the initialization, launching and reloading of the audio
762 // player, so races won't happen.
763 var audioPlayerInitializationQueue = new AsyncUtil.Queue();
764
765 audioPlayerInitializationQueue.run(function(callback) {
766 // TODO(yoshiki): Remove '--file-manager-enable-new-audio-player' flag after
767 // the feature is launched.
768 var newAudioPlayerEnabled = true;
769
770 var audioPlayerHTML =
771 newAudioPlayerEnabled ? 'audio_player.html' : 'mediaplayer.html';
772
773 /**
774 * Audio player window create options.
775 * @type {Object}
776 */
777 var audioPlayerCreateOptions = Object.freeze({
778 type: 'panel',
779 hidden: true,
780 minHeight:
781 newAudioPlayerEnabled ?
782 (44 + 73) : // 44px: track, 73px: controller
783 (35 + 58), // 35px: track, 58px: controller
784 minWidth: newAudioPlayerEnabled ? 292 : 280,
785 height: newAudioPlayerEnabled ? (44 + 73) : (35 + 58), // collapsed
786 width: newAudioPlayerEnabled ? 292 : 280,
787 });
788
789 audioPlayer = new SingletonAppWindowWrapper(audioPlayerHTML,
790 audioPlayerCreateOptions);
791 callback();
792 });
793
794 /**
795 * Launches the audio player.
796 * @param {Object} playlist Playlist.
797 * @param {string=} opt_displayedId ProfileID of the desktop where the audio
798 * player should show.
799 */
800 function launchAudioPlayer(playlist, opt_displayedId) {
801 audioPlayerInitializationQueue.run(function(callback) {
802 audioPlayer.launch(playlist, false, function(appWindow) {
803 audioPlayer.setIcon(AUDIO_PLAYER_ICON);
804 AppWindowWrapper.focusOnDesktop(audioPlayer.rawAppWindow,
805 opt_displayedId);
806 });
807 callback();
808 });
809 }
810
811 /**
812 * Launches the app.
813 * @private
814 */
815 Background.prototype.onLaunched_ = function() {
816 if (nextFileManagerWindowID == 0) {
817 // The app just launched. Remove window state records that are not needed
818 // any more.
819 chrome.storage.local.get(function(items) {
820 for (var key in items) {
821 if (items.hasOwnProperty(key)) {
822 if (key.match(FILES_ID_PATTERN))
823 chrome.storage.local.remove(key);
824 }
825 }
826 });
827 }
828 launchFileManager(null, null, LaunchType.FOCUS_ANY_OR_CREATE);
829 };
830
831 /**
832 * Restarted the app, restore windows.
833 * @private
834 */
835 Background.prototype.onRestarted_ = function() {
836 // Reopen file manager windows.
837 chrome.storage.local.get(function(items) {
838 for (var key in items) {
839 if (items.hasOwnProperty(key)) {
840 var match = key.match(FILES_ID_PATTERN);
841 if (match) {
842 var id = Number(match[1]);
843 try {
844 var appState = JSON.parse(items[key]);
845 launchFileManager(appState, id);
846 } catch (e) {
847 console.error('Corrupt launch data for ' + id);
848 }
849 }
850 }
851 }
852 });
853
854 // Reopen audio player.
855 audioPlayerInitializationQueue.run(function(callback) {
856 audioPlayer.reopen(function() {
857 // If the audioPlayer is reopened, change its window's icon. Otherwise
858 // there is no reopened window so just skip the call of setIcon.
859 if (audioPlayer.rawAppWindow)
860 audioPlayer.setIcon(AUDIO_PLAYER_ICON);
861 });
862 callback();
863 });
864 };
865
866 /**
867 * Handles clicks on a custom item on the launcher context menu.
868 * @param {OnClickData} info Event details.
869 * @private
870 */
871 Background.prototype.onContextMenuClicked_ = function(info) {
872 if (info.menuItemId == 'new-window') {
873 // Find the focused window (if any) and use it's current url for the
874 // new window. If not found, then launch with the default url.
875 for (var key in background.appWindows) {
876 try {
877 if (background.appWindows[key].contentWindow.isFocused()) {
878 var appState = {
879 // Do not clone the selection url, only the current directory.
880 currentDirectoryURL: background.appWindows[key].contentWindow.
881 appState.currentDirectoryURL
882 };
883 launchFileManager(appState);
884 return;
885 }
886 } catch (ignore) {
887 // The isFocused method may not be defined during initialization.
888 // Therefore, wrapped with a try-catch block.
889 }
890 }
891
892 // Launch with the default URL.
893 launchFileManager();
894 }
895 };
896
897 /**
898 * Initializes the context menu. Recreates if already exists.
899 * @private
900 */
901 Background.prototype.initContextMenu_ = function() {
902 try {
903 // According to the spec [1], the callback is optional. But no callback
904 // causes an error for some reason, so we call it with null-callback to
905 // prevent the error. http://crbug.com/353877
906 // - [1] https://developer.chrome.com/extensions/contextMenus#method-remove
907 chrome.contextMenus.remove('new-window', function() {});
908 } catch (ignore) {
909 // There is no way to detect if the context menu is already added, therefore
910 // try to recreate it every time.
911 }
912 chrome.contextMenus.create({
913 id: 'new-window',
914 contexts: ['launcher'],
915 title: str('NEW_WINDOW_BUTTON_LABEL')
916 });
917 };
918
919 /**
920 * Singleton instance of Background.
921 * @type {Background}
922 */
923 window.background = new Background();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698