OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 * CWSWidgetContainer contains a Chrome Web Store widget that displays list of |
| 7 * apps that satisfy certain constraints (e.g. fileHandler apps that can handle |
| 8 * files with specific file extension or MIME type) and enables the user to |
| 9 * install apps directly from it. |
| 10 * CWSWidgetContainer implements client side of the widget, which handles |
| 11 * widget loading and app installation. |
| 12 */ |
| 13 |
| 14 /** |
| 15 * The width of the widget (in pixels) |
| 16 * @type {number} |
| 17 * @const |
| 18 */ |
| 19 var WEBVIEW_WIDTH = 735; |
| 20 |
| 21 /** |
| 22 * The height of the widget (in pixels). |
| 23 * @type {number} |
| 24 * @const |
| 25 */ |
| 26 var WEBVIEW_HEIGHT = 480; |
| 27 |
| 28 /** |
| 29 * The URL of the widget showing suggested apps. |
| 30 * @type {string} |
| 31 * @const |
| 32 */ |
| 33 var CWS_WIDGET_URL = |
| 34 'https://clients5.google.com/webstore/wall/cros-widget-container'; |
| 35 |
| 36 /** |
| 37 * The origin of the widget. |
| 38 * @type {string} |
| 39 * @const |
| 40 */ |
| 41 var CWS_WIDGET_ORIGIN = 'https://clients5.google.com'; |
| 42 |
| 43 /** |
| 44 * Creates the widget container element in DOM tree. |
| 45 * |
| 46 * @param {!HTMLDocument} document The document to contain this container. |
| 47 * @param {!HTMLElement} parentNode Node to be parent for this container. |
| 48 * @param {!SuggestAppDialogState} state Static state of suggest app dialog. |
| 49 * @constructor |
| 50 */ |
| 51 function CWSWidgetContainer(document, parentNode, state) { |
| 52 /** |
| 53 * The document that will contain the container. |
| 54 * @const {!HTMLDocument} |
| 55 * @private |
| 56 */ |
| 57 this.document_ = document; |
| 58 |
| 59 /** |
| 60 * The element containing the widget webview. |
| 61 * @type {!Element} |
| 62 * @private |
| 63 */ |
| 64 this.webviewContainer_ = document.createElement('div'); |
| 65 this.webviewContainer_.id = 'webview-container'; |
| 66 this.webviewContainer_.style.width = WEBVIEW_WIDTH + 'px'; |
| 67 this.webviewContainer_.style.height = WEBVIEW_HEIGHT + 'px'; |
| 68 parentNode.appendChild(this.webviewContainer_); |
| 69 |
| 70 /** |
| 71 * Element showing spinner layout in place of Web Store widget. |
| 72 * @type {!Element} |
| 73 */ |
| 74 var spinnerLayer = document.createElement('div'); |
| 75 spinnerLayer.className = 'spinner-layer'; |
| 76 this.webviewContainer_.appendChild(spinnerLayer); |
| 77 |
| 78 /** |
| 79 * The widget container's button strip. |
| 80 * @type {!Element} |
| 81 */ |
| 82 var buttons = document.createElement('div'); |
| 83 buttons.id = 'buttons'; |
| 84 parentNode.appendChild(buttons); |
| 85 |
| 86 /** |
| 87 * Button that opens the Webstore URL. |
| 88 * @const {!Element} |
| 89 * @private |
| 90 */ |
| 91 this.webstoreButton_ = document.createElement('div'); |
| 92 this.webstoreButton_.hidden = true; |
| 93 this.webstoreButton_.id = 'webstore-button'; |
| 94 this.webstoreButton_.setAttribute('role', 'button'); |
| 95 this.webstoreButton_.tabIndex = 0; |
| 96 |
| 97 /** |
| 98 * Icon for the Webstore button. |
| 99 * @type {!Element} |
| 100 */ |
| 101 var webstoreButtonIcon = this.document_.createElement('span'); |
| 102 webstoreButtonIcon.id = 'webstore-button-icon'; |
| 103 this.webstoreButton_.appendChild(webstoreButtonIcon); |
| 104 |
| 105 /** |
| 106 * The label for the Webstore button. |
| 107 * @type {!Element} |
| 108 */ |
| 109 var webstoreButtonLabel = this.document_.createElement('span'); |
| 110 webstoreButtonLabel.id = 'webstore-button-label'; |
| 111 webstoreButtonLabel.textContent = str('SUGGEST_DIALOG_LINK_TO_WEBSTORE'); |
| 112 this.webstoreButton_.appendChild(webstoreButtonLabel); |
| 113 |
| 114 this.webstoreButton_.addEventListener( |
| 115 'click', this.onWebstoreLinkActivated_.bind(this)); |
| 116 this.webstoreButton_.addEventListener( |
| 117 'keydown', this.onWebstoreLinkKeyDown_.bind(this)); |
| 118 |
| 119 buttons.appendChild(this.webstoreButton_); |
| 120 |
| 121 /** |
| 122 * The webview element containing the Chrome Web Store widget. |
| 123 * @type {?WebView} |
| 124 * @private |
| 125 */ |
| 126 this.webview_ = null; |
| 127 |
| 128 /** |
| 129 * The Chrome Web Store widget URL. |
| 130 * @const {string} |
| 131 * @private |
| 132 */ |
| 133 this.widgetUrl_ = state.overrideCwsContainerUrlForTest || CWS_WIDGET_URL; |
| 134 |
| 135 /** |
| 136 * The Chrome Web Store widget origin. |
| 137 * @const {string} |
| 138 * @private |
| 139 */ |
| 140 this.widgetOrigin_ = state.overrideCwsContainerOriginForTest || |
| 141 CWS_WIDGET_ORIGIN; |
| 142 |
| 143 /** |
| 144 * Map of options for the widget. |
| 145 * @type {?Object.<string, *>} |
| 146 * @private |
| 147 */ |
| 148 this.options_ = null; |
| 149 |
| 150 /** |
| 151 * The ID of the item being installed. Null if no items are being installed. |
| 152 * @type {?string} |
| 153 * @private |
| 154 */ |
| 155 this.installingItemId_ = null; |
| 156 |
| 157 /** |
| 158 * The ID of the the installed item. Null if no item was installed. |
| 159 * @type {?string} |
| 160 * @private |
| 161 */ |
| 162 this.installedItemId_ = null; |
| 163 |
| 164 /** |
| 165 * The current widget state. |
| 166 * @type {CWSWidgetContainer.State} |
| 167 * @private |
| 168 */ |
| 169 this.state_ = CWSWidgetContainer.State.UNINITIALIZED; |
| 170 |
| 171 /** |
| 172 * The Chrome Web Store access token to be used when communicating with the |
| 173 * Chrome Web Store widget. |
| 174 * @type {?string} |
| 175 * @private |
| 176 */ |
| 177 this.accessToken_ = null; |
| 178 |
| 179 /** |
| 180 * Called when the Chrome Web Store widget is done. It resolves the promise |
| 181 * returned by {@code this.start()}. |
| 182 * @type {?function(CWSWidgetContainer.ResolveReason)} |
| 183 * @private |
| 184 */ |
| 185 this.resolveStart_ = null; |
| 186 |
| 187 /** |
| 188 * Promise for retriving {@code this.accessToken_}. |
| 189 * @type {Promise.<string>} |
| 190 * @private |
| 191 */ |
| 192 this.tokenGetter_ = this.createTokenGetter_(); |
| 193 } |
| 194 |
| 195 /** |
| 196 * @enum {string} |
| 197 * @private |
| 198 */ |
| 199 CWSWidgetContainer.State = { |
| 200 UNINITIALIZED: 'CWSWidgetContainer.State.UNINITIALIZED', |
| 201 GETTING_ACCESS_TOKEN: 'CWSWidgetContainer.State.GETTING_ACCESS_TOKEN', |
| 202 ACCESS_TOKEN_READY: 'CWSWidgetContainer.State.ACCESS_TOKEN_READY', |
| 203 INITIALIZING: 'CWSWidgetContainer.State.INITIALIZING', |
| 204 INITIALIZE_FAILED_CLOSING: |
| 205 'CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING', |
| 206 INITIALIZED: 'CWSWidgetContainer.State.INITIALIZED', |
| 207 INSTALLING: 'CWSWidgetContainer.State.INSTALLING', |
| 208 INSTALLED_CLOSING: 'CWSWidgetContainer.State.INSTALLED_CLOSING', |
| 209 OPENING_WEBSTORE_CLOSING: 'CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING', |
| 210 CANCELED_CLOSING: 'CWSWidgetContainer.State.CANCELED_CLOSING' |
| 211 }; |
| 212 Object.freeze(CWSWidgetContainer.State); |
| 213 |
| 214 /** |
| 215 * @enum {string} |
| 216 * @const |
| 217 */ |
| 218 CWSWidgetContainer.Result = { |
| 219 /** Install is done. The install app should be opened. */ |
| 220 INSTALL_SUCCESSFUL: 'CWSWidgetContainer.Result.INSTALL_SUCCESSFUL', |
| 221 /** User cancelled the suggest app dialog. No message should be shown. */ |
| 222 USER_CANCEL: 'CWSWidgetContainer.Result.USER_CANCEL', |
| 223 /** User clicked the link to web store so the dialog is closed. */ |
| 224 WEBSTORE_LINK_OPENED: 'CWSWidgetContainer.Result.WEBSTORE_LINK_OPENED', |
| 225 /** Failed to load the widget. Error message should be shown. */ |
| 226 FAILED: 'CWSWidgetContainer.Result.FAILED' |
| 227 }; |
| 228 Object.freeze(CWSWidgetContainer.Result); |
| 229 |
| 230 /** |
| 231 * The reason due to which the container is resolving {@code this.start} |
| 232 * promise. |
| 233 * @enum {string} |
| 234 */ |
| 235 CWSWidgetContainer.ResolveReason = { |
| 236 /** The widget container ended up in its final state. */ |
| 237 DONE: 'CWSWidgetContainer.ResolveReason.DONE', |
| 238 /** The widget container is being reset. */ |
| 239 RESET: 'CWSWidgetContainer.CloserReason.RESET' |
| 240 }; |
| 241 Object.freeze(CWSWidgetContainer.ResolveReason); |
| 242 |
| 243 /** |
| 244 * @return {!Element} The element that should be focused initially. |
| 245 */ |
| 246 CWSWidgetContainer.prototype.getInitiallyFocusedElement = function() { |
| 247 return this.webviewContainer_; |
| 248 }; |
| 249 |
| 250 /** |
| 251 * Injects headers into the passed request. |
| 252 * |
| 253 * @param {!Object} e Request event. |
| 254 * @return {!BlockingResponse} Modified headers. |
| 255 * @private |
| 256 */ |
| 257 CWSWidgetContainer.prototype.authorizeRequest_ = function(e) { |
| 258 e.requestHeaders.push({ |
| 259 name: 'Authorization', |
| 260 value: 'Bearer ' + this.accessToken_ |
| 261 }); |
| 262 return /** @type {!BlockingResponse}*/ ({requestHeaders: e.requestHeaders}); |
| 263 }; |
| 264 |
| 265 /** |
| 266 * Retrieves the authorize token. |
| 267 * @return {Promise.<string>} The promise with the retrived access token. |
| 268 * @private |
| 269 */ |
| 270 CWSWidgetContainer.prototype.createTokenGetter_ = function() { |
| 271 return new Promise(function(resolve, reject) { |
| 272 if (window.IN_TEST) { |
| 273 // In test, use a dummy string as token. This must be a non-empty string. |
| 274 resolve('DUMMY_ACCESS_TOKEN_FOR_TEST'); |
| 275 return; |
| 276 } |
| 277 |
| 278 // Fetch or update the access token. |
| 279 chrome.fileManagerPrivate.requestWebStoreAccessToken( |
| 280 function(accessToken) { |
| 281 if (chrome.runtime.lastError) { |
| 282 reject('Error retriveing Web Store access token: ' + |
| 283 chrome.runtime.lastError.message); |
| 284 } |
| 285 resolve(accessToken) |
| 286 }); |
| 287 }); |
| 288 }; |
| 289 |
| 290 /** |
| 291 * Ensures that the widget container is in the state where it can properly |
| 292 * handle showing the Chrome Web Store webview. |
| 293 * @return {Promise} Resolved when the container is ready to be used. |
| 294 */ |
| 295 CWSWidgetContainer.prototype.ready = function() { |
| 296 return new Promise(function(resolve, reject) { |
| 297 if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) { |
| 298 reject('Invalid state.'); |
| 299 return; |
| 300 } |
| 301 |
| 302 CWSWidgetContainer.Metrics.recordShowDialog(); |
| 303 CWSWidgetContainer.Metrics.startLoad(); |
| 304 |
| 305 this.state_ = CWSWidgetContainer.State.GETTING_ACCESS_TOKEN; |
| 306 |
| 307 this.tokenGetter_.then(function(accessToken) { |
| 308 this.state_ = CWSWidgetContainer.State.ACCESS_TOKEN_READY; |
| 309 this.accessToken_ = accessToken; |
| 310 resolve(); |
| 311 }.bind(this), function(error) { |
| 312 this.state_ = CWSWidgetContainer.State.UNINITIALIZED; |
| 313 reject('Failed to get Web Store access token: ' + error); |
| 314 }.bind(this)); |
| 315 }.bind(this)); |
| 316 }; |
| 317 |
| 318 /** |
| 319 * Initializes and starts loading the Chrome Web Store widget webview. |
| 320 * Must not be called before {@code this.ready()} is resolved. |
| 321 * |
| 322 * @param {!Object<string, *>} options Map of options for the dialog. |
| 323 * @param {?string} webStoreUrl Url for more results. Null if not supported. |
| 324 * @return {Promise.<CWSWidgetContainer.ResolveReason>} Resolved when app |
| 325 * installation is done, or the installation is cancelled. |
| 326 */ |
| 327 CWSWidgetContainer.prototype.start = function(options, webStoreUrl) { |
| 328 return new Promise(function(resolve, reject) { |
| 329 if (this.state_ !== CWSWidgetContainer.State.ACCESS_TOKEN_READY) { |
| 330 this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING; |
| 331 reject('Invalid state in |start|.'); |
| 332 return; |
| 333 } |
| 334 |
| 335 if (!this.accessToken_) { |
| 336 this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING; |
| 337 reject('No access token.'); |
| 338 return; |
| 339 } |
| 340 |
| 341 this.resolveStart_ = resolve; |
| 342 |
| 343 this.state_ = CWSWidgetContainer.State.INITIALIZING; |
| 344 |
| 345 this.webStoreUrl_ = webStoreUrl; |
| 346 this.options_ = options; |
| 347 |
| 348 this.webstoreButton_.hidden = (webStoreUrl === null); |
| 349 |
| 350 this.webview_ = |
| 351 /** @type {!WebView} */(this.document_.createElement('webview')); |
| 352 this.webview_.id = 'cws-widget'; |
| 353 this.webview_.partition = 'persist:cwswidgets'; |
| 354 this.webview_.style.width = WEBVIEW_WIDTH + 'px'; |
| 355 this.webview_.style.height = WEBVIEW_HEIGHT + 'px'; |
| 356 this.webview_.request.onBeforeSendHeaders.addListener( |
| 357 this.authorizeRequest_.bind(this), |
| 358 /** @type {!RequestFilter}*/ ({urls: [this.widgetOrigin_ + '/*']}), |
| 359 ['blocking', 'requestHeaders']); |
| 360 this.webview_.addEventListener('newwindow', function(event) { |
| 361 event = /** @type {NewWindowEvent} */ (event); |
| 362 // Discard the window object and reopen in an external window. |
| 363 event.window.discard(); |
| 364 window.open(event.targetUrl); |
| 365 event.preventDefault(); |
| 366 }); |
| 367 this.webviewContainer_.appendChild(this.webview_); |
| 368 |
| 369 this.webviewContainer_.classList.add('show-spinner'); |
| 370 |
| 371 this.webviewClient_ = new CWSContainerClient( |
| 372 this.webview_, |
| 373 WEBVIEW_WIDTH, |
| 374 WEBVIEW_HEIGHT, |
| 375 this.widgetUrl_, |
| 376 this.widgetOrigin_, |
| 377 this.options_); |
| 378 this.webviewClient_.addEventListener(CWSContainerClient.Events.LOADED, |
| 379 this.onWidgetLoaded_.bind(this)); |
| 380 this.webviewClient_.addEventListener(CWSContainerClient.Events.LOAD_FAILED, |
| 381 this.onWidgetLoadFailed_.bind(this)); |
| 382 this.webviewClient_.addEventListener( |
| 383 CWSContainerClient.Events.REQUEST_INSTALL, |
| 384 this.onInstallRequest_.bind(this)); |
| 385 this.webviewClient_.load(); |
| 386 }.bind(this)); |
| 387 }; |
| 388 |
| 389 /** |
| 390 * Called when the 'See more...' button is activated. It opens |
| 391 * {@code this.webstoreUrl_}. |
| 392 * @param {Event} e The event that activated the link. Either mouse click or |
| 393 * key down event. |
| 394 * @private |
| 395 */ |
| 396 CWSWidgetContainer.prototype.onWebstoreLinkActivated_ = function(e) { |
| 397 if (!this.webStoreUrl_) |
| 398 return; |
| 399 util.visitURL(this.webStoreUrl_); |
| 400 this.state_ = CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING; |
| 401 this.reportDone_(); |
| 402 }; |
| 403 |
| 404 /** |
| 405 * Key down event handler for webstore button element. If the key is enter, it |
| 406 * activates the button. |
| 407 * @param {Event} e The event |
| 408 * @private |
| 409 */ |
| 410 CWSWidgetContainer.prototype.onWebstoreLinkKeyDown_ = function(e) { |
| 411 if (e.keyCode !== 13 /* Enter */) |
| 412 return; |
| 413 this.onWebstoreLinkActivated_(e); |
| 414 }; |
| 415 |
| 416 /** |
| 417 * Called when the widget is loaded successfully. |
| 418 * @param {Event} event Event. |
| 419 * @private |
| 420 */ |
| 421 CWSWidgetContainer.prototype.onWidgetLoaded_ = function(event) { |
| 422 CWSWidgetContainer.Metrics.finishLoad(); |
| 423 CWSWidgetContainer.Metrics.recordLoad( |
| 424 CWSWidgetContainer.Metrics.LOAD.SUCCEEDED); |
| 425 |
| 426 this.webviewContainer_.classList.remove('show-spinner'); |
| 427 this.state_ = CWSWidgetContainer.State.INITIALIZED; |
| 428 |
| 429 this.webview_.focus(); |
| 430 }; |
| 431 |
| 432 /** |
| 433 * Called when the widget is failed to load. |
| 434 * @param {Event} event Event. |
| 435 * @private |
| 436 */ |
| 437 CWSWidgetContainer.prototype.onWidgetLoadFailed_ = function(event) { |
| 438 CWSWidgetContainer.Metrics.recordLoad(CWSWidgetContainer.Metrics.LOAD.FAILED); |
| 439 |
| 440 this.webviewContainer_.classList.remove('show-spinner'); |
| 441 this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING; |
| 442 this.reportDone_(); |
| 443 }; |
| 444 |
| 445 /** |
| 446 * Called when the connection status is changed to offline. |
| 447 */ |
| 448 CWSWidgetContainer.prototype.onConnectionLost = function() { |
| 449 if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) { |
| 450 this.state_ = CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING; |
| 451 this.reportDone_(); |
| 452 } |
| 453 }; |
| 454 |
| 455 /** |
| 456 * Called when receiving the install request from the webview client. |
| 457 * @param {Event} e Event. |
| 458 * @private |
| 459 */ |
| 460 CWSWidgetContainer.prototype.onInstallRequest_ = function(e) { |
| 461 var itemId = e.itemId; |
| 462 this.installingItemId_ = itemId; |
| 463 |
| 464 this.appInstaller_ = new AppInstaller(itemId); |
| 465 this.appInstaller_.install(this.onInstallCompleted_.bind(this)); |
| 466 |
| 467 this.webviewContainer_.classList.add('show-spinner'); |
| 468 this.state_ = CWSWidgetContainer.State.INSTALLING; |
| 469 }; |
| 470 |
| 471 /** |
| 472 * Called when the installation is completed from the app installer. |
| 473 * @param {AppInstaller.Result} result Result of the installation. |
| 474 * @param {string} error Detail of the error. |
| 475 * @private |
| 476 */ |
| 477 CWSWidgetContainer.prototype.onInstallCompleted_ = function(result, error) { |
| 478 var success = (result === AppInstaller.Result.SUCCESS); |
| 479 |
| 480 this.webviewContainer_.classList.remove('show-spinner'); |
| 481 this.state_ = success ? |
| 482 CWSWidgetContainer.State.INSTALLED_CLOSING : |
| 483 CWSWidgetContainer.State.INITIALIZED; // Back to normal state. |
| 484 this.webviewClient_.onInstallCompleted(success, this.installingItemId_); |
| 485 this.installedItemId_ = this.installingItemId_; |
| 486 this.installingItemId_ = null; |
| 487 |
| 488 switch (result) { |
| 489 case AppInstaller.Result.SUCCESS: |
| 490 CWSWidgetContainer.Metrics.recordInstall( |
| 491 CWSWidgetContainer.Metrics.INSTALL.SUCCEEDED); |
| 492 this.reportDone_(); |
| 493 break; |
| 494 case AppInstaller.Result.CANCELLED: |
| 495 CWSWidgetContainer.Metrics.recordInstall( |
| 496 CWSWidgetContainer.Metrics.INSTALL.CANCELLED); |
| 497 // User cancelled the installation. Do nothing. |
| 498 break; |
| 499 case AppInstaller.Result.ERROR: |
| 500 CWSWidgetContainer.Metrics.recordInstall( |
| 501 CWSWidgetContainer.Metrics.INSTALL.FAILED); |
| 502 // TODO(tbarzic): Remove dialog showing call from this class. |
| 503 fileManager.ui.errorDialog.show( |
| 504 str('SUGGEST_DIALOG_INSTALLATION_FAILED'), |
| 505 null, |
| 506 null, |
| 507 null); |
| 508 break; |
| 509 } |
| 510 }; |
| 511 |
| 512 /** |
| 513 * Resolves the promise returned by {@code this.start} when widget is done with |
| 514 * installing apps. |
| 515 * @private |
| 516 */ |
| 517 CWSWidgetContainer.prototype.reportDone_ = function() { |
| 518 if (this.resolveStart_) |
| 519 this.resolveStart_(CWSWidgetContainer.ResolveReason.DONE); |
| 520 this.resolveStart_ = null; |
| 521 }; |
| 522 |
| 523 /** |
| 524 * Finalizes the widget container state and returns the final app instalation |
| 525 * result. The widget should not be used after calling this. If called before |
| 526 * promise returned by {@code this.start} is resolved, the reported result will |
| 527 * be as if the widget was cancelled. |
| 528 * @return {{result: CWSWidgetContainer.Result, installedItemId: ?string}} |
| 529 */ |
| 530 CWSWidgetContainer.prototype.finalizeAndGetResult = function() { |
| 531 switch (this.state_) { |
| 532 case CWSWidgetContainer.State.INSTALLING: |
| 533 // Install is being aborted. Send the failure result. |
| 534 // Cancels the install. |
| 535 if (this.webviewClient_) |
| 536 this.webviewClient_.onInstallCompleted(false, this.installingItemId_); |
| 537 this.installingItemId_ = null; |
| 538 |
| 539 // Assumes closing the dialog as canceling the install. |
| 540 this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING; |
| 541 break; |
| 542 case CWSWidgetContainer.State.GETTING_ACCESS_TOKEN: |
| 543 case CWSWidgetContainer.State.ACCESS_TOKEN_READY: |
| 544 case CWSWidgetContainer.State.INITIALIZING: |
| 545 CWSWidgetContainer.Metrics.recordLoad( |
| 546 CWSWidgetContainer.Metrics.LOAD.CANCELLED); |
| 547 this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING; |
| 548 break; |
| 549 case CWSWidgetContainer.State.INSTALLED_CLOSING: |
| 550 case CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING: |
| 551 case CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING: |
| 552 // Do nothing. |
| 553 break; |
| 554 case CWSWidgetContainer.State.INITIALIZED: |
| 555 this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING; |
| 556 break; |
| 557 default: |
| 558 this.state_ = CWSWidgetContainer.State.CANCELED_CLOSING; |
| 559 console.error('Invalid state.'); |
| 560 } |
| 561 |
| 562 var result; |
| 563 switch (this.state_) { |
| 564 case CWSWidgetContainer.State.INSTALLED_CLOSING: |
| 565 result = CWSWidgetContainer.Result.INSTALL_SUCCESSFUL; |
| 566 CWSWidgetContainer.Metrics.recordCloseDialog( |
| 567 CWSWidgetContainer.Metrics.CLOSE_DIALOG.ITEM_INSTALLED); |
| 568 break; |
| 569 case CWSWidgetContainer.State.INITIALIZE_FAILED_CLOSING: |
| 570 result = CWSWidgetContainer.Result.FAILED; |
| 571 break; |
| 572 case CWSWidgetContainer.State.CANCELED_CLOSING: |
| 573 result = CWSWidgetContainer.Result.USER_CANCEL; |
| 574 CWSWidgetContainer.Metrics.recordCloseDialog( |
| 575 CWSWidgetContainer.Metrics.CLOSE_DIALOG.USER_CANCELLED); |
| 576 break; |
| 577 case CWSWidgetContainer.State.OPENING_WEBSTORE_CLOSING: |
| 578 result = CWSWidgetContainer.Result.WEBSTORE_LINK_OPENED; |
| 579 CWSWidgetContainer.Metrics.recordCloseDialog( |
| 580 CWSWidgetContainer.Metrics.CLOSE_DIALOG.WEBSTORE_LINK_OPENED); |
| 581 break; |
| 582 default: |
| 583 result = CWSWidgetContainer.Result.USER_CANCEL; |
| 584 CWSWidgetContainer.Metrics.recordCloseDialog( |
| 585 CWSWidgetContainer.Metrics.CLOSE_DIALOG.UNKNOWN_ERROR); |
| 586 } |
| 587 |
| 588 this.state_ = CWSWidgetContainer.State.UNINITIALIZED; |
| 589 |
| 590 this.reset_(); |
| 591 |
| 592 return {result: result, installedItemId: this.installedItemId_}; |
| 593 }; |
| 594 |
| 595 /** |
| 596 * Resets the widget. |
| 597 * @private |
| 598 */ |
| 599 CWSWidgetContainer.prototype.reset_ = function () { |
| 600 if (this.state_ !== CWSWidgetContainer.State.UNINITIALIZED) |
| 601 console.error('Widget reset before its state was finalized.'); |
| 602 |
| 603 if (this.resolveStart_) { |
| 604 this.resolveStart_(CWSWidgetContainer.ResolveReason.RESET); |
| 605 this.resolveStart_ = null; |
| 606 } |
| 607 |
| 608 if (this.webviewClient_) { |
| 609 this.webviewClient_.dispose(); |
| 610 this.webviewClient_ = null; |
| 611 } |
| 612 |
| 613 if (this.webview_) |
| 614 this.webviewContainer_.removeChild(this.webview_); |
| 615 this.options_ = null; |
| 616 }; |
| 617 |
| 618 /** |
| 619 * Utility methods and constants to record histograms. |
| 620 */ |
| 621 CWSWidgetContainer.Metrics = {}; |
| 622 |
| 623 /** |
| 624 * @enum {number} |
| 625 * @const |
| 626 */ |
| 627 CWSWidgetContainer.Metrics.LOAD = { |
| 628 SUCCEEDED: 0, |
| 629 CANCELLED: 1, |
| 630 FAILED: 2, |
| 631 }; |
| 632 |
| 633 /** |
| 634 * @enum {number} |
| 635 * @const |
| 636 */ |
| 637 CWSWidgetContainer.Metrics.CLOSE_DIALOG = { |
| 638 UNKNOWN_ERROR: 0, |
| 639 ITEM_INSTALLED: 1, |
| 640 USER_CANCELLED: 2, |
| 641 WEBSTORE_LINK_OPENED: 3, |
| 642 }; |
| 643 |
| 644 /** |
| 645 * @enum {number} |
| 646 * @const |
| 647 */ |
| 648 CWSWidgetContainer.Metrics.INSTALL = { |
| 649 SUCCEEDED: 0, |
| 650 CANCELLED: 1, |
| 651 FAILED: 2, |
| 652 }; |
| 653 |
| 654 /** |
| 655 * @param {number} result Result of load, which must be defined in |
| 656 * CWSWidgetContainer.Metrics.LOAD. |
| 657 */ |
| 658 CWSWidgetContainer.Metrics.recordLoad = function(result) { |
| 659 if (0 <= result && result < 3) |
| 660 metrics.recordEnum('SuggestApps.Load', result, 3); |
| 661 }; |
| 662 |
| 663 /** |
| 664 * @param {number} reason Reason of closing dialog, which must be defined in |
| 665 * CWSWidgetContainer.Metrics.CLOSE_DIALOG. |
| 666 */ |
| 667 CWSWidgetContainer.Metrics.recordCloseDialog = function(reason) { |
| 668 if (0 <= reason && reason < 4) |
| 669 metrics.recordEnum('SuggestApps.CloseDialog', reason, 4); |
| 670 }; |
| 671 |
| 672 /** |
| 673 * @param {number} result Result of installation, which must be defined in |
| 674 * CWSWidgetContainer.Metrics.INSTALL. |
| 675 */ |
| 676 CWSWidgetContainer.Metrics.recordInstall = function(result) { |
| 677 if (0 <= result && result < 3) |
| 678 metrics.recordEnum('SuggestApps.Install', result, 3); |
| 679 }; |
| 680 |
| 681 CWSWidgetContainer.Metrics.recordShowDialog = function() { |
| 682 metrics.recordUserAction('SuggestApps.ShowDialog'); |
| 683 }; |
| 684 |
| 685 CWSWidgetContainer.Metrics.startLoad = function() { |
| 686 metrics.startInterval('SuggestApps.LoadTime'); |
| 687 }; |
| 688 |
| 689 CWSWidgetContainer.Metrics.finishLoad = function() { |
| 690 metrics.recordInterval('SuggestApps.LoadTime'); |
| 691 }; |
OLD | NEW |