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
});