| 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.spinnerLayer_ = 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.spinnerLayer_ = this.document_.createElement('div'); | |
| 126 this.spinnerLayer_.className = 'spinner-layer'; | |
| 127 this.frame_.appendChild(this.spinnerLayer_); | |
| 128 | |
| 129 this.webViewWrapper_ = this.document_.createElement('div'); | |
| 130 this.webViewWrapper_.className = 'share-dialog-webview-wrapper'; | |
| 131 this.cancelButton_.hidden = true; | |
| 132 this.okButton_.hidden = true; | |
| 133 this.frame_.insertBefore(this.webViewWrapper_, | |
| 134 this.frame_.querySelector('.cr-dialog-buttons')); | |
| 135 }; | |
| 136 | |
| 137 /** | |
| 138 * @override | |
| 139 */ | |
| 140 ShareDialog.prototype.onResized = function(width, height, callback) { | |
| 141 if (width && height) { | |
| 142 this.webViewWrapper_.style.width = width + 'px'; | |
| 143 this.webViewWrapper_.style.height = height + 'px'; | |
| 144 this.webView_.style.width = width + 'px'; | |
| 145 this.webView_.style.height = height + 'px'; | |
| 146 } | |
| 147 setTimeout(callback, 0); | |
| 148 }; | |
| 149 | |
| 150 /** | |
| 151 * @override | |
| 152 */ | |
| 153 ShareDialog.prototype.onClosed = function() { | |
| 154 this.hide(); | |
| 155 }; | |
| 156 | |
| 157 /** | |
| 158 * @override | |
| 159 */ | |
| 160 ShareDialog.prototype.onLoaded = function() { | |
| 161 if (this.failureTimeout_) { | |
| 162 clearTimeout(this.failureTimeout_); | |
| 163 this.failureTimeout_ = null; | |
| 164 } | |
| 165 | |
| 166 // Logs added temporarily to track crbug.com/288783. | |
| 167 console.debug('Loaded.'); | |
| 168 | |
| 169 this.okButton_.hidden = false; | |
| 170 this.spinnerLayer_.hidden = true; | |
| 171 this.webViewWrapper_.classList.add('loaded'); | |
| 172 this.webView_.focus(); | |
| 173 }; | |
| 174 | |
| 175 /** | |
| 176 * @override | |
| 177 */ | |
| 178 ShareDialog.prototype.onLoadFailed = function() { | |
| 179 this.hideWithResult(ShareDialog.Result.NETWORK_ERROR); | |
| 180 }; | |
| 181 | |
| 182 /** | |
| 183 * @override | |
| 184 */ | |
| 185 ShareDialog.prototype.hide = function(opt_onHide) { | |
| 186 this.hideWithResult(ShareDialog.Result.SUCCESS, opt_onHide); | |
| 187 }; | |
| 188 | |
| 189 /** | |
| 190 * Hide the dialog with the result and the callback. | |
| 191 * @param {ShareDialog.Result} result Result passed to the closing callback. | |
| 192 * @param {function()=} opt_onHide Callback called at the end of hiding. | |
| 193 */ | |
| 194 ShareDialog.prototype.hideWithResult = function(result, opt_onHide) { | |
| 195 if (!this.isShowing()) | |
| 196 return; | |
| 197 | |
| 198 if (this.shareClient_) { | |
| 199 this.shareClient_.dispose(); | |
| 200 this.shareClient_ = null; | |
| 201 } | |
| 202 | |
| 203 this.webViewWrapper_.textContent = ''; | |
| 204 if (this.failureTimeout_) { | |
| 205 clearTimeout(this.failureTimeout_); | |
| 206 this.failureTimeout_ = null; | |
| 207 } | |
| 208 | |
| 209 FileManagerDialogBase.prototype.hide.call( | |
| 210 this, | |
| 211 function() { | |
| 212 if (opt_onHide) | |
| 213 opt_onHide(); | |
| 214 this.callback_(result); | |
| 215 this.callback_ = null; | |
| 216 }.bind(this)); | |
| 217 }; | |
| 218 | |
| 219 /** | |
| 220 * Shows the dialog. | |
| 221 * @param {FileEntry} entry Entry to share. | |
| 222 * @param {function(boolean)} callback Callback to be called when the showing | |
| 223 * task is completed. The argument is whether to succeed or not. Note that | |
| 224 * cancel is regarded as success. | |
| 225 */ | |
| 226 ShareDialog.prototype.show = function(entry, callback) { | |
| 227 // If the dialog is already showing, return the error. | |
| 228 if (this.isShowing()) { | |
| 229 callback(ShareDialog.Result.ALREADY_SHOWING); | |
| 230 return; | |
| 231 } | |
| 232 | |
| 233 // Initialize the variables. | |
| 234 this.callback_ = callback; | |
| 235 this.spinnerLayer_.hidden = false; | |
| 236 this.webViewWrapper_.style.width = ''; | |
| 237 this.webViewWrapper_.style.height = ''; | |
| 238 | |
| 239 // If the embedded share dialog is not started within some time, then | |
| 240 // give up and show an error message. | |
| 241 this.failureTimeout_ = setTimeout(function() { | |
| 242 this.hideWithResult(ShareDialog.Result.NETWORK_ERROR); | |
| 243 | |
| 244 // Logs added temporarily to track crbug.com/288783. | |
| 245 console.debug('Timeout. Web View points at: ' + this.webView_.src); | |
| 246 }.bind(this), ShareDialog.FAILURE_TIMEOUT); | |
| 247 | |
| 248 // TODO(mtomasz): Move to initDom_() once and reuse <webview> once it gets | |
| 249 // fixed. See: crbug.com/260622. | |
| 250 this.webView_ = util.createChild( | |
| 251 this.webViewWrapper_, 'share-dialog-webview', 'webview'); | |
| 252 this.webView_.setAttribute('tabIndex', '-1'); | |
| 253 this.webViewAuthorizer_ = new ShareDialog.WebViewAuthorizer( | |
| 254 !window.IN_TEST ? (ShareClient.SHARE_TARGET + '/*') : '<all_urls>', | |
| 255 this.webView_); | |
| 256 this.webView_.addEventListener('newwindow', function(e) { | |
| 257 // Discard the window object and reopen in an external window. | |
| 258 e.window.discard(); | |
| 259 util.visitURL(e.targetUrl); | |
| 260 e.preventDefault(); | |
| 261 }); | |
| 262 var show = FileManagerDialogBase.prototype.showBlankDialog.call(this); | |
| 263 if (!show) { | |
| 264 // The code shoundn't get here, since already-showing was handled before. | |
| 265 console.error('ShareDialog can\'t be shown.'); | |
| 266 return; | |
| 267 } | |
| 268 | |
| 269 // Initialize and authorize the Web View tag asynchronously. | |
| 270 var group = new AsyncUtil.Group(); | |
| 271 | |
| 272 // Fetches an url to the sharing dialog. | |
| 273 var shareUrl; | |
| 274 group.add(function(inCallback) { | |
| 275 chrome.fileBrowserPrivate.getShareUrl( | |
| 276 entry.toURL(), | |
| 277 function(inShareUrl) { | |
| 278 if (!chrome.runtime.lastError) | |
| 279 shareUrl = inShareUrl; | |
| 280 inCallback(); | |
| 281 }); | |
| 282 }); | |
| 283 group.add(this.webViewAuthorizer_.initialize.bind(this.webViewAuthorizer_)); | |
| 284 group.add(this.webViewAuthorizer_.authorize.bind(this.webViewAuthorizer_)); | |
| 285 | |
| 286 // Loads the share widget once all the previous async calls are finished. | |
| 287 group.run(function() { | |
| 288 // If the url is not obtained, return the network error. | |
| 289 if (!shareUrl) { | |
| 290 // Logs added temporarily to track crbug.com/288783. | |
| 291 console.debug('URL not available.'); | |
| 292 | |
| 293 this.hideWithResult(ShareDialog.Result.NETWORK_ERROR); | |
| 294 return; | |
| 295 } | |
| 296 // Already inactive, therefore ignore. | |
| 297 if (!this.isShowing()) | |
| 298 return; | |
| 299 this.shareClient_ = new ShareClient(this.webView_, | |
| 300 shareUrl, | |
| 301 this); | |
| 302 this.shareClient_.load(); | |
| 303 }.bind(this)); | |
| 304 }; | |
| 305 | |
| 306 /** | |
| 307 * Tells whether the share dialog is showing or not. | |
| 308 * | |
| 309 * @return {boolean} True since the show method is called and until the closing | |
| 310 * callback is invoked. | |
| 311 */ | |
| 312 ShareDialog.prototype.isShowing = function() { | |
| 313 return !!this.callback_; | |
| 314 }; | |
| OLD | NEW |