| OLD | NEW |
| (Empty) |
| 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 */ | |
| 5 | |
| 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 */ | |
| 11 | |
| 12 /** @suppress {duplicate} */ | |
| 13 var remoting = remoting || {}; | |
| 14 | |
| 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 | |
| 31 }; | |
| 32 | |
| 33 (function() { | |
| 34 | |
| 35 'use strict'; | |
| 36 | |
| 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; | |
| 45 | |
| 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; | |
| 59 | |
| 60 /** @private {remoting.WindowShape} */ | |
| 61 this.windowShape_ = windowShape; | |
| 62 | |
| 63 /** @private {!remoting.ConnectedView} */ | |
| 64 this.connectedView_ = connectedView; | |
| 65 | |
| 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); | |
| 70 | |
| 71 /** @private {string} */ | |
| 72 this.accessToken_ = ''; | |
| 73 | |
| 74 /** @private {base.WindowMessageDispatcher} */ | |
| 75 this.windowMessageDispatcher_ = windowMessageDispatcher; | |
| 76 | |
| 77 this.windowMessageDispatcher_.registerMessageHandler( | |
| 78 'cloud-print-dialog', this.onMessage_.bind(this)); | |
| 79 | |
| 80 /** @private {base.RepeatingTimer} */ | |
| 81 this.timer_ = new base.RepeatingTimer( | |
| 82 this.cacheAccessToken_.bind(this), | |
| 83 CLOUD_PRINT_DIALOG_TOKEN_REFRESH_INTERVAL_MS, true); | |
| 84 | |
| 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']); | |
| 91 }; | |
| 92 | |
| 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?'; | |
| 97 | |
| 98 remoting.CloudPrintDialogContainer.prototype.dispose = function() { | |
| 99 this.windowMessageDispatcher_.unregisterMessageHandler('cloud-print-dialog'); | |
| 100 this.timer_.dispose(); | |
| 101 }; | |
| 102 | |
| 103 /** | |
| 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 })); | |
| 117 }; | |
| 118 | |
| 119 /** | |
| 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}); | |
| 130 }; | |
| 131 | |
| 132 /** | |
| 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_); | |
| 141 }; | |
| 142 | |
| 143 /** | |
| 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(); | |
| 150 } | |
| 151 | |
| 152 /** | |
| 153 * Event handler to process messages from the webview. | |
| 154 * | |
| 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'); | |
| 162 | |
| 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; | |
| 169 | |
| 170 case 'cp-dialog-on-close::': | |
| 171 this.hideCloudPrintUI(); | |
| 172 break; | |
| 173 | |
| 174 default: | |
| 175 console.error('Unexpected message:', event.data['command'], event.data); | |
| 176 } | |
| 177 }; | |
| 178 | |
| 179 /** | |
| 180 * Retrieve the file specified by |fileName| in the app package and pass its | |
| 181 * content to the onDone callback. | |
| 182 * | |
| 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}); | |
| 191 | |
| 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 }); | |
| 199 }; | |
| 200 | |
| 201 /** | |
| 202 * Lanunch the cloud print dialog to print the document. | |
| 203 * | |
| 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) + '&'; | |
| 212 | |
| 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 } | |
| 230 | |
| 231 // TODO (weitaosu); Consider moving the event registration to the ctor | |
| 232 // and add unregistration in the dtor. | |
| 233 var that = this; | |
| 234 | |
| 235 /** @param {string} script */ | |
| 236 var showDialog = function(script) { | |
| 237 /** @type {Webview} */ | |
| 238 var webview = that.webview_; | |
| 239 | |
| 240 var sendHandshake = function() { | |
| 241 webview.contentWindow.postMessage('app-remoting-handshake', '*'); | |
| 242 } | |
| 243 | |
| 244 /** @param {Event} event */ | |
| 245 var redirectConsoleOutput = function(event) { | |
| 246 | |
| 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 + '"}'; | |
| 253 | |
| 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 } | |
| 272 | |
| 273 webview.addEventListener('consolemessage', redirectConsoleOutput); | |
| 274 | |
| 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 }); | |
| 285 | |
| 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 } | |
| 291 | |
| 292 /** @param {*} errorMsg */ | |
| 293 var onError = function(errorMsg) { | |
| 294 console.error('Failed to retrieve the script: ', errorMsg); | |
| 295 } | |
| 296 | |
| 297 remoting.CloudPrintDialogContainer.readFile( | |
| 298 remoting.CloudPrintDialogContainer.INJECTED_SCRIPT, showDialog, onError); | |
| 299 }; | |
| 300 | |
| 301 /** | |
| 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. | |
| 305 * | |
| 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); | |
| 314 | |
| 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 } | |
| 320 | |
| 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 }); | |
| 327 | |
| 328 return /** @type {!BlockingResponse} */ ({'requestHeaders': newHeaders}); | |
| 329 }; | |
| 330 | |
| 331 })(); | |
| OLD | NEW |