OLD | NEW |
| (Empty) |
1 // Copyright 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 * @param {HTMLElement} parentNode Node to be parent for this dialog. | |
9 * @constructor | |
10 * @extends {FileManagerDialogBase} | |
11 * @implements {ShareClient.Observer} | |
12 */ | |
13 function ShareDialog(parentNode) { | |
14 this.queue_ = new AsyncUtil.Queue(); | |
15 this.onQueueTaskFinished_ = null; | |
16 this.shareClient_ = null; | |
17 this.spinner_ = null; | |
18 this.spinnerWrapper_ = null; | |
19 this.webViewWrapper_ = null; | |
20 this.webView_ = null; | |
21 this.failureTimeout_ = null; | |
22 this.callback_ = null; | |
23 | |
24 FileManagerDialogBase.call(this, parentNode); | |
25 } | |
26 | |
27 /** | |
28 * Timeout for loading the share dialog before giving up. | |
29 * @type {number} | |
30 * @const | |
31 */ | |
32 ShareDialog.FAILURE_TIMEOUT = 10000; | |
33 | |
34 /** | |
35 * The result of opening the dialog. | |
36 * @enum {string} | |
37 * @const | |
38 */ | |
39 ShareDialog.Result = Object.freeze({ | |
40 // The dialog is closed normally. This includes user cancel. | |
41 SUCCESS: 'success', | |
42 // The dialog is closed by network error. | |
43 NETWORK_ERROR: 'networkError', | |
44 // The dialog is not opened because it is already showing. | |
45 ALREADY_SHOWING: 'alreadyShowing' | |
46 }); | |
47 | |
48 /** | |
49 * Wraps a Web View element and adds authorization headers to it. | |
50 * @param {string} urlPattern Pattern of urls to be authorized. | |
51 * @param {WebView} webView Web View element to be wrapped. | |
52 * @constructor | |
53 */ | |
54 ShareDialog.WebViewAuthorizer = function(urlPattern, webView) { | |
55 this.urlPattern_ = urlPattern; | |
56 this.webView_ = webView; | |
57 this.initialized_ = false; | |
58 this.accessToken_ = null; | |
59 }; | |
60 | |
61 /** | |
62 * Initializes the web view by installing hooks injecting the authorization | |
63 * headers. | |
64 * @param {function()} callback Completion callback. | |
65 */ | |
66 ShareDialog.WebViewAuthorizer.prototype.initialize = function(callback) { | |
67 if (this.initialized_) { | |
68 callback(); | |
69 return; | |
70 } | |
71 | |
72 var registerInjectionHooks = function() { | |
73 this.webView_.removeEventListener('loadstop', registerInjectionHooks); | |
74 this.webView_.request.onBeforeSendHeaders.addListener( | |
75 this.authorizeRequest_.bind(this), | |
76 {urls: [this.urlPattern_]}, | |
77 ['blocking', 'requestHeaders']); | |
78 this.initialized_ = true; | |
79 callback(); | |
80 }.bind(this); | |
81 | |
82 this.webView_.addEventListener('loadstop', registerInjectionHooks); | |
83 this.webView_.setAttribute('src', 'data:text/html,'); | |
84 }; | |
85 | |
86 /** | |
87 * Authorizes the web view by fetching the freshest access tokens. | |
88 * @param {function()} callback Completion callback. | |
89 */ | |
90 ShareDialog.WebViewAuthorizer.prototype.authorize = function(callback) { | |
91 // Fetch or update the access token. | |
92 chrome.fileBrowserPrivate.requestAccessToken(false, // force_refresh | |
93 function(inAccessToken) { | |
94 this.accessToken_ = inAccessToken; | |
95 callback(); | |
96 }.bind(this)); | |
97 }; | |
98 | |
99 /** | |
100 * Injects headers into the passed request. | |
101 * @param {Event} e Request event. | |
102 * @return {{requestHeaders: HttpHeaders}} Modified headers. | |
103 * @private | |
104 */ | |
105 ShareDialog.WebViewAuthorizer.prototype.authorizeRequest_ = function(e) { | |
106 e.requestHeaders.push({ | |
107 name: 'Authorization', | |
108 value: 'Bearer ' + this.accessToken_ | |
109 }); | |
110 return {requestHeaders: e.requestHeaders}; | |
111 }; | |
112 | |
113 ShareDialog.prototype = { | |
114 __proto__: FileManagerDialogBase.prototype | |
115 }; | |
116 | |
117 /** | |
118 * One-time initialization of DOM. | |
119 * @private | |
120 */ | |
121 ShareDialog.prototype.initDom_ = function() { | |
122 FileManagerDialogBase.prototype.initDom_.call(this); | |
123 this.frame_.classList.add('share-dialog-frame'); | |
124 | |
125 this.spinnerWrapper_ = this.document_.createElement('div'); | |
126 this.spinnerWrapper_.className = 'spinner-container'; | |
127 this.frame_.appendChild(this.spinnerWrapper_); | |
128 | |
129 this.spinner_ = this.document_.createElement('div'); | |
130 this.spinner_.className = 'spinner'; | |
131 this.spinnerWrapper_.appendChild(this.spinner_); | |
132 | |
133 this.webViewWrapper_ = this.document_.createElement('div'); | |
134 this.webViewWrapper_.className = 'share-dialog-webview-wrapper'; | |
135 this.cancelButton_.hidden = true; | |
136 this.okButton_.hidden = true; | |
137 this.frame_.insertBefore(this.webViewWrapper_, | |
138 this.frame_.querySelector('.cr-dialog-buttons')); | |
139 }; | |
140 | |
141 /** | |
142 * @override | |
143 */ | |
144 ShareDialog.prototype.onResized = function(width, height, callback) { | |
145 if (width && height) { | |
146 this.webViewWrapper_.style.width = width + 'px'; | |
147 this.webViewWrapper_.style.height = height + 'px'; | |
148 this.webView_.style.width = width + 'px'; | |
149 this.webView_.style.height = height + 'px'; | |
150 } | |
151 setTimeout(callback, 0); | |
152 }; | |
153 | |
154 /** | |
155 * @override | |
156 */ | |
157 ShareDialog.prototype.onClosed = function() { | |
158 this.hide(); | |
159 }; | |
160 | |
161 /** | |
162 * @override | |
163 */ | |
164 ShareDialog.prototype.onLoaded = function() { | |
165 if (this.failureTimeout_) { | |
166 clearTimeout(this.failureTimeout_); | |
167 this.failureTimeout_ = null; | |
168 } | |
169 | |
170 // Logs added temporarily to track crbug.com/288783. | |
171 console.debug('Loaded.'); | |
172 | |
173 this.okButton_.hidden = false; | |
174 this.spinnerWrapper_.hidden = true; | |
175 this.webViewWrapper_.classList.add('loaded'); | |
176 this.webView_.focus(); | |
177 }; | |
178 | |
179 /** | |
180 * @override | |
181 */ | |
182 ShareDialog.prototype.onLoadFailed = function() { | |
183 this.hideWithResult(ShareDialog.Result.NETWORK_ERROR); | |
184 }; | |
185 | |
186 /** | |
187 * @override | |
188 */ | |
189 ShareDialog.prototype.hide = function(opt_onHide) { | |
190 this.hideWithResult(ShareDialog.Result.SUCCESS, opt_onHide); | |
191 }; | |
192 | |
193 /** | |
194 * Hide the dialog with the result and the callback. | |
195 * @param {ShareDialog.Result} result Result passed to the closing callback. | |
196 * @param {function()=} opt_onHide Callback called at the end of hiding. | |
197 */ | |
198 ShareDialog.prototype.hideWithResult = function(result, opt_onHide) { | |
199 if (!this.isShowing()) | |
200 return; | |
201 | |
202 if (this.shareClient_) { | |
203 this.shareClient_.dispose(); | |
204 this.shareClient_ = null; | |
205 } | |
206 | |
207 this.webViewWrapper_.textContent = ''; | |
208 if (this.failureTimeout_) { | |
209 clearTimeout(this.failureTimeout_); | |
210 this.failureTimeout_ = null; | |
211 } | |
212 | |
213 FileManagerDialogBase.prototype.hide.call( | |
214 this, | |
215 function() { | |
216 if (opt_onHide) | |
217 opt_onHide(); | |
218 this.callback_(result); | |
219 this.callback_ = null; | |
220 }.bind(this)); | |
221 }; | |
222 | |
223 /** | |
224 * Shows the dialog. | |
225 * @param {FileEntry} entry Entry to share. | |
226 * @param {function(boolean)} callback Callback to be called when the showing | |
227 * task is completed. The argument is whether to succeed or not. Note that | |
228 * cancel is regarded as success. | |
229 */ | |
230 ShareDialog.prototype.show = function(entry, callback) { | |
231 // If the dialog is already showing, return the error. | |
232 if (this.isShowing()) { | |
233 callback(ShareDialog.Result.ALREADY_SHOWING); | |
234 return; | |
235 } | |
236 | |
237 // Initialize the variables. | |
238 this.callback_ = callback; | |
239 this.spinnerWrapper_.hidden = false; | |
240 this.webViewWrapper_.style.width = ''; | |
241 this.webViewWrapper_.style.height = ''; | |
242 | |
243 // If the embedded share dialog is not started within some time, then | |
244 // give up and show an error message. | |
245 this.failureTimeout_ = setTimeout(function() { | |
246 this.hideWithResult(ShareDialog.Result.NETWORK_ERROR); | |
247 | |
248 // Logs added temporarily to track crbug.com/288783. | |
249 console.debug('Timeout. Web View points at: ' + this.webView_.src); | |
250 }.bind(this), ShareDialog.FAILURE_TIMEOUT); | |
251 | |
252 // TODO(mtomasz): Move to initDom_() once and reuse <webview> once it gets | |
253 // fixed. See: crbug.com/260622. | |
254 this.webView_ = util.createChild( | |
255 this.webViewWrapper_, 'share-dialog-webview', 'webview'); | |
256 this.webView_.setAttribute('tabIndex', '-1'); | |
257 this.webViewAuthorizer_ = new ShareDialog.WebViewAuthorizer( | |
258 !window.IN_TEST ? (ShareClient.SHARE_TARGET + '/*') : '<all_urls>', | |
259 this.webView_); | |
260 this.webView_.addEventListener('newwindow', function(e) { | |
261 // Discard the window object and reopen in an external window. | |
262 e.window.discard(); | |
263 util.visitURL(e.targetUrl); | |
264 e.preventDefault(); | |
265 }); | |
266 var show = FileManagerDialogBase.prototype.showBlankDialog.call(this); | |
267 if (!show) { | |
268 // The code shoundn't get here, since already-showing was handled before. | |
269 console.error('ShareDialog can\'t be shown.'); | |
270 return; | |
271 } | |
272 | |
273 // Initialize and authorize the Web View tag asynchronously. | |
274 var group = new AsyncUtil.Group(); | |
275 | |
276 // Fetches an url to the sharing dialog. | |
277 var shareUrl; | |
278 group.add(function(inCallback) { | |
279 chrome.fileBrowserPrivate.getShareUrl( | |
280 entry.toURL(), | |
281 function(inShareUrl) { | |
282 if (!chrome.runtime.lastError) | |
283 shareUrl = inShareUrl; | |
284 inCallback(); | |
285 }); | |
286 }); | |
287 group.add(this.webViewAuthorizer_.initialize.bind(this.webViewAuthorizer_)); | |
288 group.add(this.webViewAuthorizer_.authorize.bind(this.webViewAuthorizer_)); | |
289 | |
290 // Loads the share widget once all the previous async calls are finished. | |
291 group.run(function() { | |
292 // If the url is not obtained, return the network error. | |
293 if (!shareUrl) { | |
294 // Logs added temporarily to track crbug.com/288783. | |
295 console.debug('URL not available.'); | |
296 | |
297 this.hideWithResult(ShareDialog.Result.NETWORK_ERROR); | |
298 return; | |
299 } | |
300 // Already inactive, therefore ignore. | |
301 if (!this.isShowing()) | |
302 return; | |
303 this.shareClient_ = new ShareClient(this.webView_, | |
304 shareUrl, | |
305 this); | |
306 this.shareClient_.load(); | |
307 }.bind(this)); | |
308 }; | |
309 | |
310 /** | |
311 * Tells whether the share dialog is showing or not. | |
312 * | |
313 * @return {boolean} True since the show method is called and until the closing | |
314 * callback is invoked. | |
315 */ | |
316 ShareDialog.prototype.isShowing = function() { | |
317 return !!this.callback_; | |
318 }; | |
OLD | NEW |