| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 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 * SuggestAppsDialog contains a list box to select an app to be opened the file | |
| 9 * with. This dialog should be used as action picker for file operations. | |
| 10 */ | |
| 11 | |
| 12 /** | |
| 13 * The width of the widget (in pixel). | |
| 14 * @type {number} | |
| 15 * @const | |
| 16 */ | |
| 17 var WEBVIEW_WIDTH = 735; | |
| 18 /** | |
| 19 * The height of the widget (in pixel). | |
| 20 * @type {number} | |
| 21 * @const | |
| 22 */ | |
| 23 var WEBVIEW_HEIGHT = 480; | |
| 24 | |
| 25 /** | |
| 26 * The URL of the widget. | |
| 27 * @type {string} | |
| 28 * @const | |
| 29 */ | |
| 30 var CWS_WIDGET_URL = | |
| 31 'https://clients5.google.com/webstore/wall/cros-widget-container'; | |
| 32 /** | |
| 33 * The origin of the widget. | |
| 34 * @type {string} | |
| 35 * @const | |
| 36 */ | |
| 37 var CWS_WIDGET_ORIGIN = 'https://clients5.google.com'; | |
| 38 | |
| 39 /** | |
| 40 * Creates dialog in DOM tree. | |
| 41 * | |
| 42 * @param {HTMLElement} parentNode Node to be parent for this dialog. | |
| 43 * @param {Object} state Static state of suggest app dialog. | |
| 44 * @constructor | |
| 45 * @extends {FileManagerDialogBase} | |
| 46 */ | |
| 47 function SuggestAppsDialog(parentNode, state) { | |
| 48 FileManagerDialogBase.call(this, parentNode); | |
| 49 | |
| 50 this.frame_.id = 'suggest-app-dialog'; | |
| 51 | |
| 52 this.spinner_ = this.document_.createElement('div'); | |
| 53 this.spinner_.className = 'spinner'; | |
| 54 | |
| 55 this.spinnerWrapper_ = this.document_.createElement('div'); | |
| 56 this.spinnerWrapper_.className = 'spinner-container'; | |
| 57 this.spinnerWrapper_.style.width = WEBVIEW_WIDTH + 'px'; | |
| 58 this.spinnerWrapper_.style.height = WEBVIEW_HEIGHT + 'px'; | |
| 59 this.spinnerWrapper_.appendChild(this.spinner_); | |
| 60 this.frame_.insertBefore(this.spinnerWrapper_, this.text_.nextSibling); | |
| 61 | |
| 62 this.webviewContainer_ = this.document_.createElement('div'); | |
| 63 this.webviewContainer_.id = 'webview-container'; | |
| 64 this.webviewContainer_.style.width = WEBVIEW_WIDTH + 'px'; | |
| 65 this.webviewContainer_.style.height = WEBVIEW_HEIGHT + 'px'; | |
| 66 this.frame_.insertBefore(this.webviewContainer_, this.text_.nextSibling); | |
| 67 | |
| 68 this.buttons_ = this.document_.createElement('div'); | |
| 69 this.buttons_.id = 'buttons'; | |
| 70 this.frame_.appendChild(this.buttons_); | |
| 71 | |
| 72 this.webstoreButton_ = this.document_.createElement('div'); | |
| 73 this.webstoreButton_.id = 'webstore-button'; | |
| 74 this.webstoreButton_.innerHTML = str('SUGGEST_DIALOG_LINK_TO_WEBSTORE'); | |
| 75 this.webstoreButton_.addEventListener( | |
| 76 'click', this.onWebstoreLinkClicked_.bind(this)); | |
| 77 this.buttons_.appendChild(this.webstoreButton_); | |
| 78 | |
| 79 this.initialFocusElement_ = this.webviewContainer_; | |
| 80 | |
| 81 this.webview_ = null; | |
| 82 this.accessToken_ = null; | |
| 83 this.widgetUrl_ = | |
| 84 state.overrideCwsContainerUrlForTest || CWS_WIDGET_URL; | |
| 85 this.widgetOrigin_ = | |
| 86 state.overrideCwsContainerOriginForTest || CWS_WIDGET_ORIGIN; | |
| 87 | |
| 88 this.extension_ = null; | |
| 89 this.mime_ = null; | |
| 90 this.installingItemId_ = null; | |
| 91 this.state_ = SuggestAppsDialog.State.UNINITIALIZED; | |
| 92 | |
| 93 this.initializationTask_ = new AsyncUtil.Group(); | |
| 94 this.initializationTask_.add(this.retrieveAuthorizeToken_.bind(this)); | |
| 95 this.initializationTask_.run(); | |
| 96 } | |
| 97 | |
| 98 SuggestAppsDialog.prototype = { | |
| 99 __proto__: FileManagerDialogBase.prototype | |
| 100 }; | |
| 101 | |
| 102 /** | |
| 103 * @enum {string} | |
| 104 * @const | |
| 105 */ | |
| 106 SuggestAppsDialog.State = { | |
| 107 UNINITIALIZED: 'SuggestAppsDialog.State.UNINITIALIZED', | |
| 108 INITIALIZING: 'SuggestAppsDialog.State.INITIALIZING', | |
| 109 INITIALIZE_FAILED_CLOSING: | |
| 110 'SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING', | |
| 111 INITIALIZED: 'SuggestAppsDialog.State.INITIALIZED', | |
| 112 INSTALLING: 'SuggestAppsDialog.State.INSTALLING', | |
| 113 INSTALLED_CLOSING: 'SuggestAppsDialog.State.INSTALLED_CLOSING', | |
| 114 CANCELED_CLOSING: 'SuggestAppsDialog.State.CANCELED_CLOSING' | |
| 115 }; | |
| 116 Object.freeze(SuggestAppsDialog.State); | |
| 117 | |
| 118 /** | |
| 119 * @enum {string} | |
| 120 * @const | |
| 121 */ | |
| 122 SuggestAppsDialog.Result = { | |
| 123 // Install is done. The install app should be opened. | |
| 124 INSTALL_SUCCESSFUL: 'SuggestAppsDialog.Result.INSTALL_SUCCESSFUL', | |
| 125 // User cancelled the suggest app dialog. No message should be shown. | |
| 126 USER_CANCELL: 'SuggestAppsDialog.Result.USER_CANCELL', | |
| 127 // Failed to load the widget. Error message should be shown. | |
| 128 FAILED: 'SuggestAppsDialog.Result.FAILED' | |
| 129 }; | |
| 130 Object.freeze(SuggestAppsDialog.Result); | |
| 131 | |
| 132 /** | |
| 133 * @override | |
| 134 */ | |
| 135 SuggestAppsDialog.prototype.onInputFocus = function() { | |
| 136 this.webviewContainer_.select(); | |
| 137 }; | |
| 138 | |
| 139 /** | |
| 140 * Injects headers into the passed request. | |
| 141 * | |
| 142 * @param {Event} e Request event. | |
| 143 * @return {{requestHeaders: HttpHeaders}} Modified headers. | |
| 144 * @private | |
| 145 */ | |
| 146 SuggestAppsDialog.prototype.authorizeRequest_ = function(e) { | |
| 147 e.requestHeaders.push({ | |
| 148 name: 'Authorization', | |
| 149 value: 'Bearer ' + this.accessToken_ | |
| 150 }); | |
| 151 return {requestHeaders: e.requestHeaders}; | |
| 152 }; | |
| 153 | |
| 154 /** | |
| 155 * Retrieves the authorize token. This method should be called in | |
| 156 * initialization of the dialog. | |
| 157 * | |
| 158 * @param {function()} callback Called when the token is retrieved. | |
| 159 * @private | |
| 160 */ | |
| 161 SuggestAppsDialog.prototype.retrieveAuthorizeToken_ = function(callback) { | |
| 162 if (window.IN_TEST) { | |
| 163 // In test, use a dummy string as token. This must be a non-empty string. | |
| 164 this.accessToken_ = 'DUMMY_ACCESS_TOKEN_FOR_TEST'; | |
| 165 } | |
| 166 | |
| 167 if (this.accessToken_) { | |
| 168 callback(); | |
| 169 return; | |
| 170 } | |
| 171 | |
| 172 // Fetch or update the access token. | |
| 173 chrome.fileBrowserPrivate.requestWebStoreAccessToken( | |
| 174 function(accessToken) { | |
| 175 // In case of error, this.accessToken_ will be set to null. | |
| 176 this.accessToken_ = accessToken; | |
| 177 callback(); | |
| 178 }.bind(this)); | |
| 179 }; | |
| 180 | |
| 181 /** | |
| 182 * Shows dialog. | |
| 183 * | |
| 184 * @param {string} extension Extension of the file. | |
| 185 * @param {string} mime Mime of the file. | |
| 186 * @param {function(boolean)} onDialogClosed Called when the dialog is closed. | |
| 187 * The argument is the result of installation: true if an app is installed, | |
| 188 * false otherwise. | |
| 189 */ | |
| 190 SuggestAppsDialog.prototype.show = function(extension, mime, onDialogClosed) { | |
| 191 if (this.state_ != SuggestAppsDialog.State.UNINITIALIZED) { | |
| 192 console.error('Invalid state.'); | |
| 193 return; | |
| 194 } | |
| 195 | |
| 196 this.extension_ = extension; | |
| 197 this.mimeType_ = mime; | |
| 198 this.onDialogClosed_ = onDialogClosed; | |
| 199 this.state_ = SuggestAppsDialog.State.INITIALIZING; | |
| 200 | |
| 201 // Makes it sure that the initialization is completed. | |
| 202 this.initializationTask_.run(function() { | |
| 203 if (!this.accessToken_) { | |
| 204 this.state_ = SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING; | |
| 205 this.onHide_(); | |
| 206 return; | |
| 207 } | |
| 208 | |
| 209 var title = str('SUGGEST_DIALOG_TITLE'); | |
| 210 | |
| 211 var show = | |
| 212 FileManagerDialogBase.prototype.showTitleOnlyDialog.call(this, title); | |
| 213 if (!show) { | |
| 214 console.error('SuggestAppsDialog can\'t be shown'); | |
| 215 this.state_ = SuggestAppsDialog.State.UNINITIALIZED; | |
| 216 this.onHide(); | |
| 217 return; | |
| 218 } | |
| 219 | |
| 220 this.webviewContainer_.innerHTML = | |
| 221 '<webview id="cws-widget" partition="persist:cwswidgets"></webview>'; | |
| 222 | |
| 223 this.webview_ = this.container_.querySelector('#cws-widget'); | |
| 224 this.webview_.style.width = WEBVIEW_WIDTH + 'px'; | |
| 225 this.webview_.style.height = WEBVIEW_HEIGHT + 'px'; | |
| 226 this.webview_.request.onBeforeSendHeaders.addListener( | |
| 227 this.authorizeRequest_.bind(this), | |
| 228 {urls: [this.widgetOrigin_ + '/*']}, | |
| 229 ['blocking', 'requestHeaders']); | |
| 230 this.webview_.addEventListener('newwindow', function(event) { | |
| 231 // Discard the window object and reopen in an external window. | |
| 232 event.window.discard(); | |
| 233 util.visitURL(event.targetUrl); | |
| 234 event.preventDefault(); | |
| 235 }); | |
| 236 | |
| 237 this.frame_.classList.add('show-spinner'); | |
| 238 | |
| 239 this.webviewClient_ = new CWSContainerClient( | |
| 240 this.webview_, | |
| 241 extension, mime, | |
| 242 WEBVIEW_WIDTH, WEBVIEW_HEIGHT, | |
| 243 this.widgetUrl_, this.widgetOrigin_); | |
| 244 this.webviewClient_.addEventListener(CWSContainerClient.Events.LOADED, | |
| 245 this.onWidgetLoaded_.bind(this)); | |
| 246 this.webviewClient_.addEventListener(CWSContainerClient.Events.LOAD_FAILED, | |
| 247 this.onWidgetLoadFailed_.bind(this)); | |
| 248 this.webviewClient_.addEventListener( | |
| 249 CWSContainerClient.Events.REQUEST_INSTALL, | |
| 250 this.onInstallRequest_.bind(this)); | |
| 251 this.webviewClient_.load(); | |
| 252 }.bind(this)); | |
| 253 }; | |
| 254 | |
| 255 /** | |
| 256 * Called when the 'See more...' link is clicked to be navigated to Webstore. | |
| 257 * @param {Event} e Event. | |
| 258 * @private | |
| 259 */ | |
| 260 SuggestAppsDialog.prototype.onWebstoreLinkClicked_ = function(e) { | |
| 261 var webStoreUrl = | |
| 262 FileTasks.createWebStoreLink(this.extension_, this.mimeType_); | |
| 263 chrome.windows.create({url: webStoreUrl}); | |
| 264 this.hide(); | |
| 265 }; | |
| 266 | |
| 267 /** | |
| 268 * Called when the widget is loaded successfully. | |
| 269 * @param {Event} event Event. | |
| 270 * @private | |
| 271 */ | |
| 272 SuggestAppsDialog.prototype.onWidgetLoaded_ = function(event) { | |
| 273 this.frame_.classList.remove('show-spinner'); | |
| 274 this.state_ = SuggestAppsDialog.State.INITIALIZED; | |
| 275 | |
| 276 this.webview_.focus(); | |
| 277 }; | |
| 278 | |
| 279 /** | |
| 280 * Called when the widget is failed to load. | |
| 281 * @param {Event} event Event. | |
| 282 * @private | |
| 283 */ | |
| 284 SuggestAppsDialog.prototype.onWidgetLoadFailed_ = function(event) { | |
| 285 this.frame_.classList.remove('show-spinner'); | |
| 286 this.state_ = SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING; | |
| 287 | |
| 288 this.hide(); | |
| 289 }; | |
| 290 | |
| 291 /** | |
| 292 * Called when the connection status is changed. | |
| 293 * @param {util.DriveConnectionType} connectionType Current connection type. | |
| 294 */ | |
| 295 SuggestAppsDialog.prototype.onDriveConnectionChanged = | |
| 296 function(connectionType) { | |
| 297 if (this.state_ !== SuggestAppsDialog.State.UNINITIALIZED && | |
| 298 connectionType === util.DriveConnectionType.OFFLINE) { | |
| 299 this.state_ = SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING; | |
| 300 this.hide(); | |
| 301 } | |
| 302 }; | |
| 303 | |
| 304 /** | |
| 305 * Called when receiving the install request from the webview client. | |
| 306 * @param {Event} e Event. | |
| 307 * @private | |
| 308 */ | |
| 309 SuggestAppsDialog.prototype.onInstallRequest_ = function(e) { | |
| 310 var itemId = e.itemId; | |
| 311 this.installingItemId_ = itemId; | |
| 312 | |
| 313 this.appInstaller_ = new AppInstaller(itemId); | |
| 314 this.appInstaller_.install(this.onInstallCompleted_.bind(this)); | |
| 315 | |
| 316 this.frame_.classList.add('show-spinner'); | |
| 317 this.state_ = SuggestAppsDialog.State.INSTALLING; | |
| 318 }; | |
| 319 | |
| 320 /** | |
| 321 * Called when the installation is completed from the app installer. | |
| 322 * @param {AppInstaller.Result} result Result of the installation. | |
| 323 * @param {string} error Detail of the error. | |
| 324 * @private | |
| 325 */ | |
| 326 SuggestAppsDialog.prototype.onInstallCompleted_ = function(result, error) { | |
| 327 var success = (result === AppInstaller.Result.SUCCESS); | |
| 328 | |
| 329 this.frame_.classList.remove('show-spinner'); | |
| 330 this.state_ = success ? | |
| 331 SuggestAppsDialog.State.INSTALLED_CLOSING : | |
| 332 SuggestAppsDialog.State.INITIALIZED; // Back to normal state. | |
| 333 this.webviewClient_.onInstallCompleted(success, this.installingItemId_); | |
| 334 this.installingItemId_ = null; | |
| 335 | |
| 336 switch (result) { | |
| 337 case AppInstaller.Result.SUCCESS: | |
| 338 this.hide(); | |
| 339 break; | |
| 340 case AppInstaller.Result.CANCELLED: | |
| 341 // User cancelled the installation. Do nothing. | |
| 342 break; | |
| 343 case AppInstaller.Result.ERROR: | |
| 344 fileManager.error.show(str('SUGGEST_DIALOG_INSTALLATION_FAILED')); | |
| 345 break; | |
| 346 } | |
| 347 }; | |
| 348 | |
| 349 /** | |
| 350 * @override | |
| 351 */ | |
| 352 SuggestAppsDialog.prototype.hide = function(opt_originalOnHide) { | |
| 353 switch (this.state_) { | |
| 354 case SuggestAppsDialog.State.INSTALLING: | |
| 355 // Install is being aborted. Send the failure result. | |
| 356 // Cancels the install. | |
| 357 if (this.webviewClient_) | |
| 358 this.webviewClient_.onInstallCompleted(false, this.installingItemId_); | |
| 359 this.installingItemId_ = null; | |
| 360 | |
| 361 // Assumes closing the dialog as canceling the install. | |
| 362 this.state_ = SuggestAppsDialog.State.CANCELED_CLOSING; | |
| 363 break; | |
| 364 case SuggestAppsDialog.State.INSTALLED_CLOSING: | |
| 365 case SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING: | |
| 366 // Do nothing. | |
| 367 break; | |
| 368 case SuggestAppsDialog.State.INITIALIZED: | |
| 369 this.state_ = SuggestAppsDialog.State.CANCELED_CLOSING; | |
| 370 break; | |
| 371 default: | |
| 372 this.state_ = SuggestAppsDialog.State.CANCELED_CLOSING; | |
| 373 console.error('Invalid state.'); | |
| 374 } | |
| 375 | |
| 376 if (this.webviewClient_) { | |
| 377 this.webviewClient_.dispose(); | |
| 378 this.webviewClient_ = null; | |
| 379 } | |
| 380 | |
| 381 this.webviewContainer_.innerHTML = ''; | |
| 382 this.extension_ = null; | |
| 383 this.mime_ = null; | |
| 384 | |
| 385 FileManagerDialogBase.prototype.hide.call( | |
| 386 this, | |
| 387 this.onHide_.bind(this, opt_originalOnHide)); | |
| 388 }; | |
| 389 | |
| 390 /** | |
| 391 * @param {function()=} opt_originalOnHide Original onHide function passed to | |
| 392 * SuggestAppsDialog.hide(). | |
| 393 * @private | |
| 394 */ | |
| 395 SuggestAppsDialog.prototype.onHide_ = function(opt_originalOnHide) { | |
| 396 // Calls the callback after the dialog hides. | |
| 397 if (opt_originalOnHide) | |
| 398 opt_originalOnHide(); | |
| 399 | |
| 400 var result; | |
| 401 switch (this.state_) { | |
| 402 case SuggestAppsDialog.State.INSTALLED_CLOSING: | |
| 403 result = SuggestAppsDialog.Result.INSTALL_SUCCESSFUL; | |
| 404 break; | |
| 405 case SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING: | |
| 406 result = SuggestAppsDialog.Result.FAILED; | |
| 407 break; | |
| 408 case SuggestAppsDialog.State.CANCELED_CLOSING: | |
| 409 result = SuggestAppsDialog.Result.USER_CANCELL; | |
| 410 break; | |
| 411 default: | |
| 412 result = SuggestAppsDialog.Result.USER_CANCELL; | |
| 413 console.error('Invalid state.'); | |
| 414 } | |
| 415 this.state_ = SuggestAppsDialog.State.UNINITIALIZED; | |
| 416 | |
| 417 this.onDialogClosed_(result); | |
| 418 }; | |
| OLD | NEW |