• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
13function 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 */
32ShareDialog.FAILURE_TIMEOUT = 10000;
33
34/**
35 * The result of opening the dialog.
36 * @enum {string}
37 * @const
38 */
39ShareDialog.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 */
54ShareDialog.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 */
66ShareDialog.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 */
90ShareDialog.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 */
105ShareDialog.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
113ShareDialog.prototype = {
114  __proto__: FileManagerDialogBase.prototype
115};
116
117/**
118 * One-time initialization of DOM.
119 * @private
120 */
121ShareDialog.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 */
140ShareDialog.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 */
153ShareDialog.prototype.onClosed = function() {
154  this.hide();
155};
156
157/**
158 * @override
159 */
160ShareDialog.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 */
178ShareDialog.prototype.onLoadFailed = function() {
179  this.hideWithResult(ShareDialog.Result.NETWORK_ERROR);
180};
181
182/**
183 * @override
184 */
185ShareDialog.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 */
194ShareDialog.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 */
226ShareDialog.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 */
312ShareDialog.prototype.isShowing = function() {
313  return !!this.callback_;
314};
315