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 |