Roll src/third_party/WebKit 3529d49:06e8485 (svn 202554:202555)
[chromium-blink-merge.git] / remoting / webapp / app_remoting / js / cloud_print_dialog_container.js
bloba927ec6fa4df65aaac70cf4775246fde03bf87a2
1 /* Copyright 2015 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  */
6 /**
7  * @fileoverview
8  * The application side of the application/cloud print dialog interface, used
9  * by the application to exchange messages with the dialog.
10  */
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
16  * According to https://developer.chrome.com/apps/tags/webview, the level
17  * ranges from 0 to 4. But in real life they are -1 (debug), 0 (log and info),
18  * 1 (warn), and 2 (error). See crbug.com/499408
19  *
20  * @enum {number}
21  */
22 remoting.ConsoleMessageLevel = {
23   // console.debug
24   VERBOSE: -1,
25   // console.info or console.log
26   INFO: 0,
27   // console.warn
28   WARNING: 1,
29   //console.err
30   ERROR: 2
33 (function() {
35 'use strict';
37 /**
38  * Interval to refresh the access token used by the cloud print dialog.
39  * Refreshing the token every 30 minutes should be good enough because the
40  * access token will be valid for an hour.
41  *
42  * @const {number}
43  */
44 var CLOUD_PRINT_DIALOG_TOKEN_REFRESH_INTERVAL_MS = 30 * 60 * 1000;
46 /**
47  * @param {!Webview} webview The webview hosting the cloud cloud print dialog.
48  * @param {remoting.WindowShape} windowShape
49  * @param {base.WindowMessageDispatcher} windowMessageDispatcher
50  * @param {!remoting.ConnectedView} connectedView
51  * @constructor
52  * @implements {remoting.WindowShape.ClientUI}
53  * @implements {base.Disposable}
54  */
55 remoting.CloudPrintDialogContainer =
56     function(webview, windowShape, windowMessageDispatcher, connectedView) {
57   /** @private {!Webview} */
58   this.webview_ = webview;
60   /** @private {remoting.WindowShape} */
61   this.windowShape_ = windowShape;
63   /** @private {!remoting.ConnectedView} */
64   this.connectedView_ = connectedView;
66   // TODO (weitaosu): This is only needed if the cloud print webview is on the
67   // same page as the plugin. We should remove it if we move the webview to a
68   // standalone message window.
69   this.connectedView_.allowFocus(webview);
71   /** @private {string} */
72   this.accessToken_ = '';
74   /** @private {base.WindowMessageDispatcher} */
75   this.windowMessageDispatcher_ = windowMessageDispatcher;
77   this.windowMessageDispatcher_.registerMessageHandler(
78       'cloud-print-dialog', this.onMessage_.bind(this));
80   /** @private {base.RepeatingTimer} */
81   this.timer_ = new base.RepeatingTimer(
82       this.cacheAccessToken_.bind(this),
83       CLOUD_PRINT_DIALOG_TOKEN_REFRESH_INTERVAL_MS, true);
85   // Adding a synchronous (blocking) handler so that the reqeust headers can
86   // be modified on the spot.
87   webview.request.onBeforeSendHeaders.addListener(
88       this.setAuthHeaders_.bind(this),
89       /** @type {!RequestFilter} */ ({urls: ['https://*.google.com/*']}),
90       ['requestHeaders','blocking']);
93 remoting.CloudPrintDialogContainer.INJECTED_SCRIPT =
94     '_modules/koejkfhmphamcgafjmkellhnekdkopod/cloud_print_dialog_injected.js';
95 remoting.CloudPrintDialogContainer.CLOUD_PRINT_DIALOG_URL =
96     'https://www.google.com/cloudprint/dialog.html?';
98 remoting.CloudPrintDialogContainer.prototype.dispose = function() {
99   this.windowMessageDispatcher_.unregisterMessageHandler('cloud-print-dialog');
100   this.timer_.dispose();
104  * Timer callback to cache the access token.
105  * @private
106  */
107 remoting.CloudPrintDialogContainer.prototype.cacheAccessToken_ = function() {
108   /** @type {remoting.CloudPrintDialogContainer} */
109   var that = this;
110   remoting.identity.getNewToken().then(
111     function(/** string */ token){
112       console.assert(token !== that.accessToken_);
113       that.accessToken_ = token;
114   }).catch(remoting.Error.handler(function(/** remoting.Error */ error) {
115     console.log('Failed to refresh access token: ' + error.toString());
116   }));
120  * @param {Array<{left: number, top: number, width: number, height: number}>}
121  *     rects List of rectangles.
122  */
123 remoting.CloudPrintDialogContainer.prototype.addToRegion = function(rects) {
124   var rect =
125       /** @type {ClientRect} */(this.webview_.getBoundingClientRect());
126   rects.push({left: rect.left,
127               top: rect.top,
128               width: rect.width,
129               height: rect.height});
133  * Show the cloud print dialog.
134  */
135 remoting.CloudPrintDialogContainer.prototype.showCloudPrintUI = function() {
136   // TODO (weitaosu): Considering showing the cloud print dialog in a separate
137   // window or using remoting.Html5ModalDialog to show the modal dialog.
138   this.webview_.hidden = false;
139   this.windowShape_.registerClientUI(this);
140   this.windowShape_.centerToDesktop(this.webview_);
144  * Hide the cloud print dialog.
145  */
146 remoting.CloudPrintDialogContainer.prototype.hideCloudPrintUI = function() {
147   this.webview_.hidden = true;
148   this.windowShape_.unregisterClientUI(this);
149   this.connectedView_.returnFocusToPlugin();
153  * Event handler to process messages from the webview.
155  * @param {Event} event
156  * @private
157  */
158 remoting.CloudPrintDialogContainer.prototype.onMessage_ = function(event) {
159   var data = event.data;
160   console.assert(typeof data === 'object' &&
161                  data['source'] == 'cloud-print-dialog');
163   switch (event.data['command']) {
164     case 'cp-dialog-on-init::':
165       // We actually never receive this message because the cloud print dialog
166       // has already been initialized by the time the injected script finishes
167       // executing.
168       break;
170     case 'cp-dialog-on-close::':
171       this.hideCloudPrintUI();
172       break;
174     default:
175       console.error('Unexpected message:', event.data['command'], event.data);
176   }
180  * Retrieve the file specified by |fileName| in the app package and pass its
181  * content to the onDone callback.
183  * @param {string} fileName Name of the file in the app package to be read.
184  * @param {!function(string):void} onDone Callback to be invoked on success.
185  * @param {!function(*):void} onError Callback to be invoked on failure.
186  */
187 remoting.CloudPrintDialogContainer.readFile =
188     function(fileName, onDone, onError) {
189   var fileUrl = chrome.runtime.getURL(fileName);
190   var xhr = new remoting.Xhr({ method: 'GET', url: fileUrl});
192   xhr.start().then(function(/** !remoting.Xhr.Response */ response) {
193     if (response.status == 200) {
194       onDone(response.getText());
195     } else {
196       onError('xhr.status = ' + response.status);
197     }
198   });
202  * Lanunch the cloud print dialog to print the document.
204  * @param {string} title Title of the print job.
205  * @param {string} type Type of the storage of the document (url, gdrive, etc).
206  * @param {string} data Meaning of this field depends on the |type| parameter.
207  */
208 remoting.CloudPrintDialogContainer.prototype.printDocument =
209     function(title, type, data) {
210   var dialogUrl = remoting.CloudPrintDialogContainer.CLOUD_PRINT_DIALOG_URL;
211   dialogUrl += 'title=' + encodeURIComponent(title) + '&';
213   switch (type) {
214     case 'url':
215       // 'data' should contain the url to the document to be printed.
216       if (data.substr(0, 7) !== 'http://' && data.substr(0, 8) !== 'https://') {
217         console.error('Bad URL: ' + data);
218         return;
219       }
220       dialogUrl += 'type=url&url=' + encodeURIComponent(data);
221       break;
222     case 'google.drive':
223       // 'data' should contain the doc id of the gdrive document to be printed.
224       dialogUrl += 'type=google.drive&content=' + encodeURIComponent(data);
225       break;
226     default:
227       console.error('Unknown content type for the printDocument command.');
228       return;
229   }
231   // TODO (weitaosu); Consider moving the event registration to the ctor
232   // and add unregistration in the dtor.
233   var that = this;
235   /** @param {string} script */
236   var showDialog = function(script) {
237     /** @type {Webview} */
238     var webview = that.webview_;
240     var sendHandshake = function() {
241       webview.contentWindow.postMessage('app-remoting-handshake', '*');
242     }
244     /** @param {Event} event */
245     var redirectConsoleOutput = function(event) {
247       var e = /** @type {chrome.ConsoleMessageBrowserEvent} */ (event);
248       var message = 'console message from webviwe: {' +
249                     'level=' + e.level + ', ' +
250                     'source=' + e.sourceId + ', ' +
251                     'line=' + e.line + ', ' +
252                     'message="' + e.message + '"}';
254       switch (e['level']) {
255         case remoting.ConsoleMessageLevel.VERBOSE:
256           console.debug(message);
257           break;
258         case remoting.ConsoleMessageLevel.INFO:
259           console.info(message);
260           break;
261         case remoting.ConsoleMessageLevel.WARNING:
262           console.warn(message);
263           break;
264         case remoting.ConsoleMessageLevel.ERROR:
265           console.error(message);
266           break;
267         default:
268           console.error('unrecognized message level. ' + message);
269           break;
270       }
271     }
273     webview.addEventListener('consolemessage', redirectConsoleOutput);
275     // Inject the script and send a handshake message to the cloud print dialog
276     // after the injected script has been executed.
277     webview.addEventListener("loadstart", function(event) {
278         console.log('"loadstart" captured in webview containier.');
279         // TODO (weitaosu): Consider switching to addContentScripts when M44
280         // is released.
281         webview.executeScript(
282           {code: script + ' //# sourceURL=cloud_print_dialog_injected.js'},
283           sendHandshake);
284     });
286     // We need to show the cloud print UI here because we will never receive
287     // the 'cp-dialog-on-init::' message.
288     webview.src = dialogUrl;
289     that.showCloudPrintUI();
290   }
292   /** @param {*} errorMsg */
293   var onError = function(errorMsg) {
294     console.error('Failed to retrieve the script: ', errorMsg);
295   }
297   remoting.CloudPrintDialogContainer.readFile(
298       remoting.CloudPrintDialogContainer.INJECTED_SCRIPT, showDialog, onError);
302  * Handler of the onBeforeSendHeaders event for the webview. It adds the auth
303  * header to the request being send. Note that this handler is synchronous so
304  * modifications to the requst must happen before it returns.
306  * @param {Object} details Details of the WebRequest.
307  * @return {!BlockingResponse} The modified request headers.
308  * @private
309  */
310 remoting.CloudPrintDialogContainer.prototype.setAuthHeaders_ =
311     function(details) {
312   var url = /** @type {string} */ (details['url']);
313   console.log('Setting auth token for request: ', url);
315   var headers = /** @type {Array} */ (details['requestHeaders']) || [];
316   if (this.accessToken_.length == 0) {
317     console.error('No auth token available for the request: ', url);
318     return /** @type {!BlockingResponse} */ ({'requestHeaders': headers});
319   }
321   // Make fresh copy of the headers array.
322   var newHeaders = /** @type {Array} */ (headers.slice());
323   newHeaders.push({
324     'name': 'Authorization',
325     'value': 'Bearer ' + this.accessToken_
326   });
328   return /** @type {!BlockingResponse} */ ({'requestHeaders': newHeaders});
331 })();