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

Side by Side Diff: chrome/browser/resources/file_manager/foreground/js/suggest_apps_dialog.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) 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.webviewContainer_ = this.document_.createElement('div');
53 this.webviewContainer_.id = 'webview-container';
54 this.webviewContainer_.style.width = WEBVIEW_WIDTH + 'px';
55 this.webviewContainer_.style.height = WEBVIEW_HEIGHT + 'px';
56 this.frame_.insertBefore(this.webviewContainer_, this.text_.nextSibling);
57
58 var spinnerLayer = this.document_.createElement('div');
59 spinnerLayer.className = 'spinner-layer';
60 this.webviewContainer_.appendChild(spinnerLayer);
61
62 this.buttons_ = this.document_.createElement('div');
63 this.buttons_.id = 'buttons';
64 this.frame_.appendChild(this.buttons_);
65
66 this.webstoreButton_ = this.document_.createElement('div');
67 this.webstoreButton_.id = 'webstore-button';
68 this.webstoreButton_.innerHTML = str('SUGGEST_DIALOG_LINK_TO_WEBSTORE');
69 this.webstoreButton_.addEventListener(
70 'click', this.onWebstoreLinkClicked_.bind(this));
71 this.buttons_.appendChild(this.webstoreButton_);
72
73 this.initialFocusElement_ = this.webviewContainer_;
74
75 this.webview_ = null;
76 this.accessToken_ = null;
77 this.widgetUrl_ =
78 state.overrideCwsContainerUrlForTest || CWS_WIDGET_URL;
79 this.widgetOrigin_ =
80 state.overrideCwsContainerOriginForTest || CWS_WIDGET_ORIGIN;
81
82 this.extension_ = null;
83 this.mime_ = null;
84 this.installingItemId_ = null;
85 this.state_ = SuggestAppsDialog.State.UNINITIALIZED;
86
87 this.initializationTask_ = new AsyncUtil.Group();
88 this.initializationTask_.add(this.retrieveAuthorizeToken_.bind(this));
89 this.initializationTask_.run();
90 }
91
92 SuggestAppsDialog.prototype = {
93 __proto__: FileManagerDialogBase.prototype
94 };
95
96 /**
97 * @enum {string}
98 * @const
99 */
100 SuggestAppsDialog.State = {
101 UNINITIALIZED: 'SuggestAppsDialog.State.UNINITIALIZED',
102 INITIALIZING: 'SuggestAppsDialog.State.INITIALIZING',
103 INITIALIZE_FAILED_CLOSING:
104 'SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING',
105 INITIALIZED: 'SuggestAppsDialog.State.INITIALIZED',
106 INSTALLING: 'SuggestAppsDialog.State.INSTALLING',
107 INSTALLED_CLOSING: 'SuggestAppsDialog.State.INSTALLED_CLOSING',
108 OPENING_WEBSTORE_CLOSING: 'SuggestAppsDialog.State.OPENING_WEBSTORE_CLOSING',
109 CANCELED_CLOSING: 'SuggestAppsDialog.State.CANCELED_CLOSING'
110 };
111 Object.freeze(SuggestAppsDialog.State);
112
113 /**
114 * @enum {string}
115 * @const
116 */
117 SuggestAppsDialog.Result = {
118 // Install is done. The install app should be opened.
119 INSTALL_SUCCESSFUL: 'SuggestAppsDialog.Result.INSTALL_SUCCESSFUL',
120 // User cancelled the suggest app dialog. No message should be shown.
121 USER_CANCELL: 'SuggestAppsDialog.Result.USER_CANCELL',
122 // User clicked the link to web store so the dialog is closed.
123 WEBSTORE_LINK_OPENED: 'SuggestAppsDialog.Result.WEBSTORE_LINK_OPENED',
124 // Failed to load the widget. Error message should be shown.
125 FAILED: 'SuggestAppsDialog.Result.FAILED'
126 };
127 Object.freeze(SuggestAppsDialog.Result);
128
129 /**
130 * @override
131 */
132 SuggestAppsDialog.prototype.onInputFocus = function() {
133 this.webviewContainer_.select();
134 };
135
136 /**
137 * Injects headers into the passed request.
138 *
139 * @param {Event} e Request event.
140 * @return {{requestHeaders: HttpHeaders}} Modified headers.
141 * @private
142 */
143 SuggestAppsDialog.prototype.authorizeRequest_ = function(e) {
144 e.requestHeaders.push({
145 name: 'Authorization',
146 value: 'Bearer ' + this.accessToken_
147 });
148 return {requestHeaders: e.requestHeaders};
149 };
150
151 /**
152 * Retrieves the authorize token. This method should be called in
153 * initialization of the dialog.
154 *
155 * @param {function()} callback Called when the token is retrieved.
156 * @private
157 */
158 SuggestAppsDialog.prototype.retrieveAuthorizeToken_ = function(callback) {
159 if (window.IN_TEST) {
160 // In test, use a dummy string as token. This must be a non-empty string.
161 this.accessToken_ = 'DUMMY_ACCESS_TOKEN_FOR_TEST';
162 }
163
164 if (this.accessToken_) {
165 callback();
166 return;
167 }
168
169 // Fetch or update the access token.
170 chrome.fileBrowserPrivate.requestWebStoreAccessToken(
171 function(accessToken) {
172 // In case of error, this.accessToken_ will be set to null.
173 this.accessToken_ = accessToken;
174 callback();
175 }.bind(this));
176 };
177
178 /**
179 * Dummy function for SuggestAppsDialog.show() not to be called unintentionally.
180 */
181 SuggestAppsDialog.prototype.show = function() {
182 console.error('SuggestAppsDialog.show() shouldn\'t be called directly.');
183 };
184
185 /**
186 * Shows suggest-apps dialog by file extension and mime.
187 *
188 * @param {string} extension Extension of the file.
189 * @param {string} mime Mime of the file.
190 * @param {function(boolean)} onDialogClosed Called when the dialog is closed.
191 * The argument is the result of installation: true if an app is installed,
192 * false otherwise.
193 */
194 SuggestAppsDialog.prototype.showByExtensionAndMime =
195 function(extension, mime, onDialogClosed) {
196 this.text_.hidden = true;
197 this.dialogText_ = '';
198 this.showInternal_(null, extension, mime, onDialogClosed);
199 };
200
201 /**
202 * Shows suggest-apps dialog by the filename.
203 *
204 * @param {string} filename Filename (without extension) of the file.
205 * @param {function(boolean)} onDialogClosed Called when the dialog is closed.
206 * The argument is the result of installation: true if an app is installed,
207 * false otherwise.
208 */
209 SuggestAppsDialog.prototype.showByFilename =
210 function(filename, onDialogClosed) {
211 this.text_.hidden = false;
212 this.dialogText_ = str('SUGGEST_DIALOG_MESSAGE_FOR_EXECUTABLE');
213 this.showInternal_(filename, null, null, onDialogClosed);
214 };
215
216 /**
217 * Internal method to show a dialog. This should be called only from 'Suggest.
218 * appDialog.showXxxx()' functions.
219 *
220 * @param {string} filename Filename (without extension) of the file.
221 * @param {string} extension Extension of the file.
222 * @param {string} mime Mime of the file.
223 * @param {function(boolean)} onDialogClosed Called when the dialog is closed.
224 * The argument is the result of installation: true if an app is installed,
225 * false otherwise.
226 * @private
227 */
228 SuggestAppsDialog.prototype.showInternal_ =
229 function(filename, extension, mime, onDialogClosed) {
230 if (this.state_ != SuggestAppsDialog.State.UNINITIALIZED) {
231 console.error('Invalid state.');
232 return;
233 }
234
235 this.extension_ = extension;
236 this.mimeType_ = mime;
237 this.onDialogClosed_ = onDialogClosed;
238 this.state_ = SuggestAppsDialog.State.INITIALIZING;
239
240 SuggestAppsDialog.Metrics.recordShowDialog();
241 SuggestAppsDialog.Metrics.startLoad();
242
243 // Makes it sure that the initialization is completed.
244 this.initializationTask_.run(function() {
245 if (!this.accessToken_) {
246 this.state_ = SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING;
247 this.onHide_();
248 return;
249 }
250
251 var title = str('SUGGEST_DIALOG_TITLE');
252 var show = this.dialogText_ ?
253 FileManagerDialogBase.prototype.showTitleAndTextDialog.call(
254 this, title, this.dialogText_) :
255 FileManagerDialogBase.prototype.showTitleOnlyDialog.call(
256 this, title);
257 if (!show) {
258 console.error('SuggestAppsDialog can\'t be shown');
259 this.state_ = SuggestAppsDialog.State.UNINITIALIZED;
260 this.onHide();
261 return;
262 }
263
264 this.webview_ = this.document_.createElement('webview');
265 this.webview_.id = 'cws-widget';
266 this.webview_.partition = 'persist:cwswidgets';
267 this.webview_.style.width = WEBVIEW_WIDTH + 'px';
268 this.webview_.style.height = WEBVIEW_HEIGHT + 'px';
269 this.webview_.request.onBeforeSendHeaders.addListener(
270 this.authorizeRequest_.bind(this),
271 {urls: [this.widgetOrigin_ + '/*']},
272 ['blocking', 'requestHeaders']);
273 this.webview_.addEventListener('newwindow', function(event) {
274 // Discard the window object and reopen in an external window.
275 event.window.discard();
276 util.visitURL(event.targetUrl);
277 event.preventDefault();
278 });
279 this.webviewContainer_.appendChild(this.webview_);
280
281 this.frame_.classList.add('show-spinner');
282
283 this.webviewClient_ = new CWSContainerClient(
284 this.webview_,
285 extension, mime, filename,
286 WEBVIEW_WIDTH, WEBVIEW_HEIGHT,
287 this.widgetUrl_, this.widgetOrigin_);
288 this.webviewClient_.addEventListener(CWSContainerClient.Events.LOADED,
289 this.onWidgetLoaded_.bind(this));
290 this.webviewClient_.addEventListener(CWSContainerClient.Events.LOAD_FAILED,
291 this.onWidgetLoadFailed_.bind(this));
292 this.webviewClient_.addEventListener(
293 CWSContainerClient.Events.REQUEST_INSTALL,
294 this.onInstallRequest_.bind(this));
295 this.webviewClient_.load();
296 }.bind(this));
297 };
298
299 /**
300 * Called when the 'See more...' link is clicked to be navigated to Webstore.
301 * @param {Event} e Event.
302 * @private
303 */
304 SuggestAppsDialog.prototype.onWebstoreLinkClicked_ = function(e) {
305 var webStoreUrl =
306 FileTasks.createWebStoreLink(this.extension_, this.mimeType_);
307 util.visitURL(webStoreUrl);
308 this.state_ = SuggestAppsDialog.State.OPENING_WEBSTORE_CLOSING;
309 this.hide();
310 };
311
312 /**
313 * Called when the widget is loaded successfully.
314 * @param {Event} event Event.
315 * @private
316 */
317 SuggestAppsDialog.prototype.onWidgetLoaded_ = function(event) {
318 SuggestAppsDialog.Metrics.finishLoad();
319 SuggestAppsDialog.Metrics.recordLoad(
320 SuggestAppsDialog.Metrics.LOAD.SUCCEEDED);
321
322 this.frame_.classList.remove('show-spinner');
323 this.state_ = SuggestAppsDialog.State.INITIALIZED;
324
325 this.webview_.focus();
326 };
327
328 /**
329 * Called when the widget is failed to load.
330 * @param {Event} event Event.
331 * @private
332 */
333 SuggestAppsDialog.prototype.onWidgetLoadFailed_ = function(event) {
334 SuggestAppsDialog.Metrics.recordLoad(SuggestAppsDialog.Metrics.LOAD.FAILURE);
335
336 this.frame_.classList.remove('show-spinner');
337 this.state_ = SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING;
338
339 this.hide();
340 };
341
342 /**
343 * Called when the connection status is changed.
344 * @param {util.DriveConnectionType} connectionType Current connection type.
345 */
346 SuggestAppsDialog.prototype.onDriveConnectionChanged =
347 function(connectionType) {
348 if (this.state_ !== SuggestAppsDialog.State.UNINITIALIZED &&
349 connectionType === util.DriveConnectionType.OFFLINE) {
350 this.state_ = SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING;
351 this.hide();
352 }
353 };
354
355 /**
356 * Called when receiving the install request from the webview client.
357 * @param {Event} e Event.
358 * @private
359 */
360 SuggestAppsDialog.prototype.onInstallRequest_ = function(e) {
361 var itemId = e.itemId;
362 this.installingItemId_ = itemId;
363
364 this.appInstaller_ = new AppInstaller(itemId);
365 this.appInstaller_.install(this.onInstallCompleted_.bind(this));
366
367 this.frame_.classList.add('show-spinner');
368 this.state_ = SuggestAppsDialog.State.INSTALLING;
369 };
370
371 /**
372 * Called when the installation is completed from the app installer.
373 * @param {AppInstaller.Result} result Result of the installation.
374 * @param {string} error Detail of the error.
375 * @private
376 */
377 SuggestAppsDialog.prototype.onInstallCompleted_ = function(result, error) {
378 var success = (result === AppInstaller.Result.SUCCESS);
379
380 this.frame_.classList.remove('show-spinner');
381 this.state_ = success ?
382 SuggestAppsDialog.State.INSTALLED_CLOSING :
383 SuggestAppsDialog.State.INITIALIZED; // Back to normal state.
384 this.webviewClient_.onInstallCompleted(success, this.installingItemId_);
385 this.installingItemId_ = null;
386
387 switch (result) {
388 case AppInstaller.Result.SUCCESS:
389 SuggestAppsDialog.Metrics.recordInstall(
390 SuggestAppsDialog.Metrics.INSTALL.SUCCESS);
391 this.hide();
392 break;
393 case AppInstaller.Result.CANCELLED:
394 SuggestAppsDialog.Metrics.recordInstall(
395 SuggestAppsDialog.Metrics.INSTALL.CANCELLED);
396 // User cancelled the installation. Do nothing.
397 break;
398 case AppInstaller.Result.ERROR:
399 SuggestAppsDialog.Metrics.recordInstall(
400 SuggestAppsDialog.Metrics.INSTALL.FAILED);
401 fileManager.error.show(str('SUGGEST_DIALOG_INSTALLATION_FAILED'));
402 break;
403 }
404 };
405
406 /**
407 * @override
408 */
409 SuggestAppsDialog.prototype.hide = function(opt_originalOnHide) {
410 switch (this.state_) {
411 case SuggestAppsDialog.State.INSTALLING:
412 // Install is being aborted. Send the failure result.
413 // Cancels the install.
414 if (this.webviewClient_)
415 this.webviewClient_.onInstallCompleted(false, this.installingItemId_);
416 this.installingItemId_ = null;
417
418 // Assumes closing the dialog as canceling the install.
419 this.state_ = SuggestAppsDialog.State.CANCELED_CLOSING;
420 break;
421 case SuggestAppsDialog.State.INITIALIZING:
422 SuggestAppsDialog.Metrics.recordLoad(
423 SuggestAppsDialog.Metrics.LOAD.CANCELLED);
424 this.state_ = SuggestAppsDialog.State.CANCELED_CLOSING;
425 break;
426 case SuggestAppsDialog.State.INSTALLED_CLOSING:
427 case SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING:
428 case SuggestAppsDialog.State.OPENING_WEBSTORE_CLOSING:
429 // Do nothing.
430 break;
431 case SuggestAppsDialog.State.INITIALIZED:
432 this.state_ = SuggestAppsDialog.State.CANCELED_CLOSING;
433 break;
434 default:
435 this.state_ = SuggestAppsDialog.State.CANCELED_CLOSING;
436 console.error('Invalid state.');
437 }
438
439 if (this.webviewClient_) {
440 this.webviewClient_.dispose();
441 this.webviewClient_ = null;
442 }
443
444 this.webviewContainer_.removeChild(this.webview_);
445 this.webview_ = null;
446 this.extension_ = null;
447 this.mime_ = null;
448
449 FileManagerDialogBase.prototype.hide.call(
450 this,
451 this.onHide_.bind(this, opt_originalOnHide));
452 };
453
454 /**
455 * @param {function()=} opt_originalOnHide Original onHide function passed to
456 * SuggestAppsDialog.hide().
457 * @private
458 */
459 SuggestAppsDialog.prototype.onHide_ = function(opt_originalOnHide) {
460 // Calls the callback after the dialog hides.
461 if (opt_originalOnHide)
462 opt_originalOnHide();
463
464 var result;
465 switch (this.state_) {
466 case SuggestAppsDialog.State.INSTALLED_CLOSING:
467 result = SuggestAppsDialog.Result.INSTALL_SUCCESSFUL;
468 SuggestAppsDialog.Metrics.recordCloseDialog(
469 SuggestAppsDialog.Metrics.CLOSE_DIALOG.ITEM_INSTALLED);
470 break;
471 case SuggestAppsDialog.State.INITIALIZE_FAILED_CLOSING:
472 result = SuggestAppsDialog.Result.FAILED;
473 break;
474 case SuggestAppsDialog.State.CANCELED_CLOSING:
475 result = SuggestAppsDialog.Result.USER_CANCELL;
476 SuggestAppsDialog.Metrics.recordCloseDialog(
477 SuggestAppsDialog.Metrics.CLOSE_DIALOG.USER_CANCELL);
478 break;
479 case SuggestAppsDialog.State.OPENING_WEBSTORE_CLOSING:
480 result = SuggestAppsDialog.Result.WEBSTORE_LINK_OPENED;
481 SuggestAppsDialog.Metrics.recordCloseDialog(
482 SuggestAppsDialog.Metrics.CLOSE_DIALOG.WEB_STORE_LINK);
483 break;
484 default:
485 result = SuggestAppsDialog.Result.USER_CANCELL;
486 SuggestAppsDialog.Metrics.recordCloseDialog(
487 SuggestAppsDialog.Metrics.CLOSE_DIALOG.UNKNOWN_ERROR);
488 console.error('Invalid state.');
489 }
490 this.state_ = SuggestAppsDialog.State.UNINITIALIZED;
491
492 this.onDialogClosed_(result);
493 };
494
495 /**
496 * Utility methods and constants to record histograms.
497 */
498 SuggestAppsDialog.Metrics = Object.freeze({
499 LOAD: Object.freeze({
500 SUCCEEDED: 0,
501 CANCELLED: 1,
502 FAILED: 2,
503 }),
504
505 /**
506 * @param {SuggestAppsDialog.Metrics.LOAD} result Result of load.
507 */
508 recordLoad: function(result) {
509 if (0 <= result && result < 3)
510 metrics.recordEnum('SuggestApps.Load', result, 3);
511 },
512
513 CLOSE_DIALOG: Object.freeze({
514 UNKOWN_ERROR: 0,
515 ITEM_INSTALLED: 1,
516 USER_CANCELLED: 2,
517 WEBSTORE_LINK_OPENED: 3,
518 }),
519
520 /**
521 * @param {SuggestAppsDialog.Metrics.CLOSE_DIALOG} reason Reason of closing
522 * dialog.
523 */
524 recordCloseDialog: function(reason) {
525 if (0 <= reason && reason < 4)
526 metrics.recordEnum('SuggestApps.CloseDialog', reason, 4);
527 },
528
529 INSTALL: Object.freeze({
530 SUCCEEDED: 0,
531 CANCELLED: 1,
532 FAILED: 2,
533 }),
534
535 /**
536 * @param {SuggestAppsDialog.Metrics.INSTALL} result Result of installation.
537 */
538 recordInstall: function(result) {
539 if (0 <= result && result < 3)
540 metrics.recordEnum('SuggestApps.Install', result, 3);
541 },
542
543 recordShowDialog: function() {
544 metrics.recordUserAction('SuggestApps.ShowDialog');
545 },
546
547 startLoad: function() {
548 metrics.startInterval('SuggestApps.LoadTime');
549 },
550
551 finishLoad: function() {
552 metrics.recordInterval('SuggestApps.LoadTime');
553 },
554 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698