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.
8 * The application side of the application/cloud print dialog interface, used
9 * by the application to exchange messages with the dialog.
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
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
22 remoting.ConsoleMessageLevel = {
25 // console.info or console.log
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.
44 var CLOUD_PRINT_DIALOG_TOKEN_REFRESH_INTERVAL_MS = 30 * 60 * 1000;
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
52 * @implements {remoting.WindowShape.ClientUI}
53 * @implements {base.Disposable}
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.
107 remoting.CloudPrintDialogContainer.prototype.cacheAccessToken_ = function() {
108 /** @type {remoting.CloudPrintDialogContainer} */
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());
120 * @param {Array<{left: number, top: number, width: number, height: number}>}
121 * rects List of rectangles.
123 remoting.CloudPrintDialogContainer.prototype.addToRegion = function(rects) {
125 /** @type {ClientRect} */(this.webview_.getBoundingClientRect());
126 rects.push({left: rect.left,
129 height: rect.height});
133 * Show the cloud print dialog.
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.
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
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
170 case 'cp-dialog-on-close::':
171 this.hideCloudPrintUI();
175 console.error('Unexpected message:', event.data['command'], event.data);
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.
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());
196 onError('xhr.status = ' + response.status);
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.
208 remoting.CloudPrintDialogContainer.prototype.printDocument =
209 function(title, type, data) {
210 var dialogUrl = remoting.CloudPrintDialogContainer.CLOUD_PRINT_DIALOG_URL;
211 dialogUrl += 'title=' + encodeURIComponent(title) + '&';
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);
220 dialogUrl += 'type=url&url=' + encodeURIComponent(data);
223 // 'data' should contain the doc id of the gdrive document to be printed.
224 dialogUrl += 'type=google.drive&content=' + encodeURIComponent(data);
227 console.error('Unknown content type for the printDocument command.');
231 // TODO (weitaosu); Consider moving the event registration to the ctor
232 // and add unregistration in the dtor.
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', '*');
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);
258 case remoting.ConsoleMessageLevel.INFO:
259 console.info(message);
261 case remoting.ConsoleMessageLevel.WARNING:
262 console.warn(message);
264 case remoting.ConsoleMessageLevel.ERROR:
265 console.error(message);
268 console.error('unrecognized message level. ' + message);
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
281 webview.executeScript(
282 {code: script + ' //# sourceURL=cloud_print_dialog_injected.js'},
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();
292 /** @param {*} errorMsg */
293 var onError = function(errorMsg) {
294 console.error('Failed to retrieve the script: ', errorMsg);
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.
310 remoting.CloudPrintDialogContainer.prototype.setAuthHeaders_ =
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});
321 // Make fresh copy of the headers array.
322 var newHeaders = /** @type {Array} */ (headers.slice());
324 'name': 'Authorization',
325 'value': 'Bearer ' + this.accessToken_
328 return /** @type {!BlockingResponse} */ ({'requestHeaders': newHeaders});