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