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

Unified Diff: ui/file_manager/file_manager/background/js/background.js

Issue 563743004: Revert of Files.app: Split background.js (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 3 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 side-by-side diff with in-line comments
Download patch
Index: ui/file_manager/file_manager/background/js/background.js
diff --git a/ui/file_manager/file_manager/background/js/background.js b/ui/file_manager/file_manager/background/js/background.js
index f6e574a06cb96943b5e91f1a490a08a1752a27a5..ef7a4a07fc667e4adab2ca84b6fc085bb32b3c15 100644
--- a/ui/file_manager/file_manager/background/js/background.js
+++ b/ui/file_manager/file_manager/background/js/background.js
@@ -17,10 +17,13 @@
/**
* Root class of the background page.
* @constructor
- * @extends {BackgroundBase}
- */
-function FileBrowserBackground() {
- BackgroundBase.call(this);
+ */
+function Background() {
+ /**
+ * Map of all currently open app windows. The key is an app ID.
+ * @type {Object.<string, AppWindow>}
+ */
+ this.appWindows = {};
/**
* Map of all currently open file dialogs. The key is an app ID.
@@ -140,11 +143,23 @@
* @const
* @private
*/
-FileBrowserBackground.CLOSE_DELAY_MS_ = 5000;
-
-FileBrowserBackground.prototype = {
- __proto__: BackgroundBase.prototype
-};
+Background.CLOSE_DELAY_MS_ = 5000;
+
+/**
+ * Make a key of window geometry preferences for the given initial URL.
+ * @param {string} url Initialize URL that the window has.
+ * @return {string} Key of window geometry preferences.
+ */
+Background.makeGeometryKey = function(url) {
+ return 'windowGeometry' + ':' + url;
+};
+
+/**
+ * Key for getting and storing the last window state (maximized or not).
+ * @const
+ * @private
+ */
+Background.MAXIMIZED_KEY_ = 'isMaximized';
/**
* Register callback to be invoked after initialization.
@@ -152,20 +167,19 @@
*
* @param {function()} callback Initialize callback to be registered.
*/
-FileBrowserBackground.prototype.ready = function(callback) {
+Background.prototype.ready = function(callback) {
this.stringDataPromise.then(callback);
};
/**
- * Checks the current condition of background page.
- * @return {boolean} True if the background page is closable, false if not.
- */
-FileBrowserBackground.prototype.canClose = function() {
+ * Checks the current condition of background page and closes it if possible.
+ */
+Background.prototype.tryClose = function() {
// If the file operation is going, the background page cannot close.
if (this.fileOperationManager.hasQueuedTasks() ||
this.driveSyncHandler_.syncing) {
this.lastTimeCanClose_ = null;
- return false;
+ return;
}
var views = chrome.extension.getViews();
@@ -175,7 +189,7 @@
// closing, the background page cannot close.
if (views[i] !== window && !views[i].closing) {
this.lastTimeCanClose_ = null;
- return false;
+ return;
}
closing = closing || views[i].closing;
}
@@ -184,23 +198,36 @@
// 5 seconds ago, We need more time for sure.
if (closing ||
this.lastTimeCanClose_ === null ||
- (Date.now() - this.lastTimeCanClose_ <
- FileBrowserBackground.CLOSE_DELAY_MS_)) {
+ Date.now() - this.lastTimeCanClose_ < Background.CLOSE_DELAY_MS_) {
if (this.lastTimeCanClose_ === null)
this.lastTimeCanClose_ = Date.now();
- setTimeout(this.tryClose.bind(this), FileBrowserBackground.CLOSE_DELAY_MS_);
- return false;
+ setTimeout(this.tryClose.bind(this), Background.CLOSE_DELAY_MS_);
+ return;
}
// Otherwise we can close the background page.
- return true;
+ close();
+};
+
+/**
+ * Gets similar windows, it means with the same initial url.
+ * @param {string} url URL that the obtained windows have.
+ * @return {Array.<AppWindow>} List of similar windows.
+ */
+Background.prototype.getSimilarWindows = function(url) {
+ var result = [];
+ for (var appID in this.appWindows) {
+ if (this.appWindows[appID].contentWindow.appInitialURL === url)
+ result.push(this.appWindows[appID]);
+ }
+ return result;
};
/**
* Opens the root directory of the volume in Files.app.
* @param {string} volumeId ID of a volume to be opened.
*/
-FileBrowserBackground.prototype.navigateToVolume = function(volumeId) {
+Background.prototype.navigateToVolume = function(volumeId) {
VolumeManager.getInstance().then(function(volumeManager) {
var volumeInfoList = volumeManager.volumeInfoList;
var index = volumeInfoList.findIndex(volumeId);
@@ -214,6 +241,331 @@
}).catch(function(error) {
console.error(error.stack || error);
});
+};
+
+/**
+ * Wrapper for an app window.
+ *
+ * Expects the following from the app scripts:
+ * 1. The page load handler should initialize the app using |window.appState|
+ * and call |util.saveAppState|.
+ * 2. Every time the app state changes the app should update |window.appState|
+ * and call |util.saveAppState| .
+ * 3. The app may have |unload| function to persist the app state that does not
+ * fit into |window.appState|.
+ *
+ * @param {string} url App window content url.
+ * @param {string} id App window id.
+ * @param {Object} options Options object to create it.
+ * @constructor
+ */
+function AppWindowWrapper(url, id, options) {
+ this.url_ = url;
+ this.id_ = id;
+ // Do deep copy for the template of options to assign customized params later.
+ this.options_ = JSON.parse(JSON.stringify(options));
+ this.window_ = null;
+ this.appState_ = null;
+ this.openingOrOpened_ = false;
+ this.queue = new AsyncUtil.Queue();
+ Object.seal(this);
+}
+
+AppWindowWrapper.prototype = {
+ /**
+ * @return {AppWindow} Wrapped application window.
+ */
+ get rawAppWindow() {
+ return this.window_;
+ }
+};
+
+/**
+ * Focuses the window on the specified desktop.
+ * @param {AppWindow} appWindow Application window.
+ * @param {string=} opt_profileId The profiled ID of the target window. If it is
+ * dropped, the window is focused on the current window.
+ */
+AppWindowWrapper.focusOnDesktop = function(appWindow, opt_profileId) {
+ new Promise(function(onFulfilled, onRejected) {
+ if (opt_profileId) {
+ onFulfilled(opt_profileId);
+ } else {
+ chrome.fileManagerPrivate.getProfiles(function(profiles,
+ currentId,
+ displayedId) {
+ onFulfilled(currentId);
+ });
+ }
+ }).then(function(profileId) {
+ appWindow.contentWindow.chrome.fileManagerPrivate.visitDesktop(
+ profileId, function() {
+ appWindow.focus();
+ });
+ });
+};
+
+/**
+ * Shift distance to avoid overlapping windows.
+ * @type {number}
+ * @const
+ */
+AppWindowWrapper.SHIFT_DISTANCE = 40;
+
+/**
+ * Sets the icon of the window.
+ * @param {string} iconPath Path of the icon.
+ */
+AppWindowWrapper.prototype.setIcon = function(iconPath) {
+ this.window_.setIcon(iconPath);
+};
+
+/**
+ * Opens the window.
+ *
+ * @param {Object} appState App state.
+ * @param {boolean} reopen True if the launching is triggered automatically.
+ * False otherwise.
+ * @param {function()=} opt_callback Completion callback.
+ */
+AppWindowWrapper.prototype.launch = function(appState, reopen, opt_callback) {
+ // Check if the window is opened or not.
+ if (this.openingOrOpened_) {
+ console.error('The window is already opened.');
+ if (opt_callback)
+ opt_callback();
+ return;
+ }
+ this.openingOrOpened_ = true;
+
+ // Save application state.
+ this.appState_ = appState;
+
+ // Get similar windows, it means with the same initial url, eg. different
+ // main windows of Files.app.
+ var similarWindows = background.getSimilarWindows(this.url_);
+
+ // Restore maximized windows, to avoid hiding them to tray, which can be
+ // confusing for users.
+ this.queue.run(function(callback) {
+ for (var index = 0; index < similarWindows.length; index++) {
+ if (similarWindows[index].isMaximized()) {
+ var createWindowAndRemoveListener = function() {
+ similarWindows[index].onRestored.removeListener(
+ createWindowAndRemoveListener);
+ callback();
+ };
+ similarWindows[index].onRestored.addListener(
+ createWindowAndRemoveListener);
+ similarWindows[index].restore();
+ return;
+ }
+ }
+ // If no maximized windows, then create the window immediately.
+ callback();
+ });
+
+ // Obtains the last geometry and window state (maximized or not).
+ var lastBounds;
+ var isMaximized = false;
+ this.queue.run(function(callback) {
+ var boundsKey = Background.makeGeometryKey(this.url_);
+ var maximizedKey = Background.MAXIMIZED_KEY_;
+ chrome.storage.local.get([boundsKey, maximizedKey], function(preferences) {
+ if (!chrome.runtime.lastError) {
+ lastBounds = preferences[boundsKey];
+ isMaximized = preferences[maximizedKey];
+ }
+ callback();
+ });
+ }.bind(this));
+
+ // Closure creating the window, once all preprocessing tasks are finished.
+ this.queue.run(function(callback) {
+ // Apply the last bounds.
+ if (lastBounds)
+ this.options_.bounds = lastBounds;
+ if (isMaximized)
+ this.options_.state = 'maximized';
+
+ // Create a window.
+ chrome.app.window.create(this.url_, this.options_, function(appWindow) {
+ this.window_ = appWindow;
+ callback();
+ }.bind(this));
+ }.bind(this));
+
+ // After creating.
+ this.queue.run(function(callback) {
+ // If there is another window in the same position, shift the window.
+ var makeBoundsKey = function(bounds) {
+ return bounds.left + '/' + bounds.top;
+ };
+ var notAvailablePositions = {};
+ for (var i = 0; i < similarWindows.length; i++) {
+ var key = makeBoundsKey(similarWindows[i].getBounds());
+ notAvailablePositions[key] = true;
+ }
+ var candidateBounds = this.window_.getBounds();
+ while (true) {
+ var key = makeBoundsKey(candidateBounds);
+ if (!notAvailablePositions[key])
+ break;
+ // Make the position available to avoid an infinite loop.
+ notAvailablePositions[key] = false;
+ var nextLeft = candidateBounds.left + AppWindowWrapper.SHIFT_DISTANCE;
+ var nextRight = nextLeft + candidateBounds.width;
+ candidateBounds.left = nextRight >= screen.availWidth ?
+ nextRight % screen.availWidth : nextLeft;
+ var nextTop = candidateBounds.top + AppWindowWrapper.SHIFT_DISTANCE;
+ var nextBottom = nextTop + candidateBounds.height;
+ candidateBounds.top = nextBottom >= screen.availHeight ?
+ nextBottom % screen.availHeight : nextTop;
+ }
+ this.window_.moveTo(candidateBounds.left, candidateBounds.top);
+
+ // Save the properties.
+ var appWindow = this.window_;
+ background.appWindows[this.id_] = appWindow;
+ var contentWindow = appWindow.contentWindow;
+ contentWindow.appID = this.id_;
+ contentWindow.appState = this.appState_;
+ contentWindow.appReopen = reopen;
+ contentWindow.appInitialURL = this.url_;
+ if (window.IN_TEST)
+ contentWindow.IN_TEST = true;
+
+ // Register event listeners.
+ appWindow.onBoundsChanged.addListener(this.onBoundsChanged_.bind(this));
+ appWindow.onClosed.addListener(this.onClosed_.bind(this));
+
+ // Callback.
+ if (opt_callback)
+ opt_callback();
+ callback();
+ }.bind(this));
+};
+
+/**
+ * Handles the onClosed extension API event.
+ * @private
+ */
+AppWindowWrapper.prototype.onClosed_ = function() {
+ // Remember the last window state (maximized or normal).
+ var preferences = {};
+ preferences[Background.MAXIMIZED_KEY_] = this.window_.isMaximized();
+ chrome.storage.local.set(preferences);
+
+ // Unload the window.
+ var appWindow = this.window_;
+ var contentWindow = this.window_.contentWindow;
+ if (contentWindow.unload)
+ contentWindow.unload();
+ this.window_ = null;
+ this.openingOrOpened_ = false;
+
+ // Updates preferences.
+ if (contentWindow.saveOnExit) {
+ contentWindow.saveOnExit.forEach(function(entry) {
+ util.AppCache.update(entry.key, entry.value);
+ });
+ }
+ chrome.storage.local.remove(this.id_); // Forget the persisted state.
+
+ // Remove the window from the set.
+ delete background.appWindows[this.id_];
+
+ // If there is no application window, reset window ID.
+ if (!Object.keys(background.appWindows).length)
+ nextFileManagerWindowID = 0;
+ background.tryClose();
+};
+
+/**
+ * Handles onBoundsChanged extension API event.
+ * @private
+ */
+AppWindowWrapper.prototype.onBoundsChanged_ = function() {
+ if (!this.window_.isMaximized()) {
+ var preferences = {};
+ preferences[Background.makeGeometryKey(this.url_)] =
+ this.window_.getBounds();
+ chrome.storage.local.set(preferences);
+ }
+};
+
+/**
+ * Wrapper for a singleton app window.
+ *
+ * In addition to the AppWindowWrapper requirements the app scripts should
+ * have |reload| method that re-initializes the app based on a changed
+ * |window.appState|.
+ *
+ * @param {string} url App window content url.
+ * @param {Object|function()} options Options object or a function to return it.
+ * @constructor
+ */
+function SingletonAppWindowWrapper(url, options) {
+ AppWindowWrapper.call(this, url, url, options);
+}
+
+/**
+ * Inherits from AppWindowWrapper.
+ */
+SingletonAppWindowWrapper.prototype = {__proto__: AppWindowWrapper.prototype};
+
+/**
+ * Open the window.
+ *
+ * Activates an existing window or creates a new one.
+ *
+ * @param {Object} appState App state.
+ * @param {boolean} reopen True if the launching is triggered automatically.
+ * False otherwise.
+ * @param {function()=} opt_callback Completion callback.
+ */
+SingletonAppWindowWrapper.prototype.launch =
+ function(appState, reopen, opt_callback) {
+ // If the window is not opened yet, just call the parent method.
+ if (!this.openingOrOpened_) {
+ AppWindowWrapper.prototype.launch.call(
+ this, appState, reopen, opt_callback);
+ return;
+ }
+
+ // If the window is already opened, reload the window.
+ // The queue is used to wait until the window is opened.
+ this.queue.run(function(nextStep) {
+ this.window_.contentWindow.appState = appState;
+ this.window_.contentWindow.appReopen = reopen;
+ this.window_.contentWindow.reload();
+ if (opt_callback)
+ opt_callback();
+ nextStep();
+ }.bind(this));
+};
+
+/**
+ * Reopen a window if its state is saved in the local storage.
+ * @param {function()=} opt_callback Completion callback.
+ */
+SingletonAppWindowWrapper.prototype.reopen = function(opt_callback) {
+ chrome.storage.local.get(this.id_, function(items) {
+ var value = items[this.id_];
+ if (!value) {
+ opt_callback && opt_callback();
+ return; // No app state persisted.
+ }
+
+ try {
+ var appState = JSON.parse(value);
+ } catch (e) {
+ console.error('Corrupt launch data for ' + this.id_, value);
+ opt_callback && opt_callback();
+ return;
+ }
+ this.launch(appState, true, opt_callback);
+ }.bind(this));
};
/**
@@ -402,7 +754,7 @@
* @param {Object} details Details object.
* @private
*/
-FileBrowserBackground.prototype.onExecute_ = function(action, details) {
+Background.prototype.onExecute_ = function(action, details) {
switch (action) {
case 'play':
var urls = util.entriesToURLs(details.entries);
@@ -487,7 +839,7 @@
* Launches the app.
* @private
*/
-FileBrowserBackground.prototype.onLaunched_ = function() {
+Background.prototype.onLaunched_ = function() {
if (nextFileManagerWindowID == 0) {
// The app just launched. Remove window state records that are not needed
// any more.
@@ -507,7 +859,7 @@
* Restarted the app, restore windows.
* @private
*/
-FileBrowserBackground.prototype.onRestarted_ = function() {
+Background.prototype.onRestarted_ = function() {
// Reopen file manager windows.
chrome.storage.local.get(function(items) {
for (var key in items) {
@@ -543,7 +895,7 @@
* @param {OnClickData} info Event details.
* @private
*/
-FileBrowserBackground.prototype.onContextMenuClicked_ = function(info) {
+Background.prototype.onContextMenuClicked_ = function(info) {
if (info.menuItemId == 'new-window') {
// Find the focused window (if any) and use it's current url for the
// new window. If not found, then launch with the default url.
@@ -573,7 +925,7 @@
* Initializes the context menu. Recreates if already exists.
* @private
*/
-FileBrowserBackground.prototype.initContextMenu_ = function() {
+Background.prototype.initContextMenu_ = function() {
try {
// According to the spec [1], the callback is optional. But no callback
// causes an error for some reason, so we call it with null-callback to
@@ -593,6 +945,6 @@
/**
* Singleton instance of Background.
- * @type {FileBrowserBackground}
- */
-window.background = new FileBrowserBackground();
+ * @type {Background}
+ */
+window.background = new Background();

Powered by Google App Engine
This is Rietveld 408576698