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