Fix infinite recursion on hiding panel when created during fullscreen mode.
[chromium-blink-merge.git] / chrome / browser / resources / print_preview / cloud_print_interface.js
blobb418063953fe1d8e0ad51a7417293ca2c5c9ac9d
1 // Copyright (c) 2012 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.
5 cr.define('cloudprint', function() {
6 'use strict';
8 /**
9 * API to the Google Cloud Print service.
10 * @param {string} baseUrl Base part of the Google Cloud Print service URL
11 * with no trailing slash. For example,
12 * 'https://www.google.com/cloudprint'.
13 * @param {!print_preview.NativeLayer} nativeLayer Native layer used to get
14 * Auth2 tokens.
15 * @constructor
16 * @extends {cr.EventTarget}
18 function CloudPrintInterface(baseUrl, nativeLayer) {
19 /**
20 * The base URL of the Google Cloud Print API.
21 * @type {string}
22 * @private
24 this.baseUrl_ = baseUrl;
26 /**
27 * Used to get Auth2 tokens.
28 * @type {!print_preview.NativeLayer}
29 * @private
31 this.nativeLayer_ = nativeLayer;
33 /**
34 * Last received XSRF token. Sent as a parameter in every request.
35 * @type {string}
36 * @private
38 this.xsrfToken_ = '';
40 /**
41 * Pending requests delayed until we get access token.
42 * @type {!Array.<!CloudPrintRequest>}
43 * @private
45 this.requestQueue_ = [];
47 /**
48 * Number of outstanding cloud destination search requests.
49 * @type {number}
50 * @private
52 this.outstandingCloudSearchRequestCount_ = 0;
54 /**
55 * Event tracker used to keep track of native layer events.
56 * @type {!EventTracker}
57 * @private
59 this.tracker_ = new EventTracker();
61 this.addEventListeners_();
64 /**
65 * Event types dispatched by the interface.
66 * @enum {string}
68 CloudPrintInterface.EventType = {
69 PRINTER_DONE: 'cloudprint.CloudPrintInterface.PRINTER_DONE',
70 PRINTER_FAILED: 'cloudprint.CloudPrintInterface.PRINTER_FAILED',
71 SEARCH_DONE: 'cloudprint.CloudPrintInterface.SEARCH_DONE',
72 SEARCH_FAILED: 'cloudprint.CloudPrintInterface.SEARCH_FAILED',
73 SUBMIT_DONE: 'cloudprint.CloudPrintInterface.SUBMIT_DONE',
74 SUBMIT_FAILED: 'cloudprint.CloudPrintInterface.SUBMIT_FAILED',
75 UPDATE_PRINTER_TOS_ACCEPTANCE_FAILED:
76 'cloudprint.CloudPrintInterface.UPDATE_PRINTER_TOS_ACCEPTANCE_FAILED'
79 /**
80 * Content type header value for a URL encoded HTTP request.
81 * @type {string}
82 * @const
83 * @private
85 CloudPrintInterface.URL_ENCODED_CONTENT_TYPE_ =
86 'application/x-www-form-urlencoded';
88 /**
89 * Multi-part POST request boundary used in communication with Google
90 * Cloud Print.
91 * @type {string}
92 * @const
93 * @private
95 CloudPrintInterface.MULTIPART_BOUNDARY_ =
96 '----CloudPrintFormBoundaryjc9wuprokl8i';
98 /**
99 * Content type header value for a multipart HTTP request.
100 * @type {string}
101 * @const
102 * @private
104 CloudPrintInterface.MULTIPART_CONTENT_TYPE_ =
105 'multipart/form-data; boundary=' +
106 CloudPrintInterface.MULTIPART_BOUNDARY_;
109 * Regex that extracts Chrome's version from the user-agent string.
110 * @type {!RegExp}
111 * @const
112 * @private
114 CloudPrintInterface.VERSION_REGEXP_ = /.*Chrome\/([\d\.]+)/i;
117 * Enumeration of JSON response fields from Google Cloud Print API.
118 * @enum {string}
119 * @private
121 CloudPrintInterface.JsonFields_ = {
122 PRINTER: 'printer'
126 * Could Print origins used to search printers.
127 * @type {!Array.<!print_preview.Destination.Origin>}
128 * @const
129 * @private
131 CloudPrintInterface.CLOUD_ORIGINS_ = [
132 print_preview.Destination.Origin.COOKIES,
133 print_preview.Destination.Origin.DEVICE
134 // TODO(vitalybuka): Enable when implemented.
135 // ready print_preview.Destination.Origin.PROFILE
138 CloudPrintInterface.prototype = {
139 __proto__: cr.EventTarget.prototype,
141 /** @return {string} Base URL of the Google Cloud Print service. */
142 get baseUrl() {
143 return this.baseUrl_;
147 * @return {boolean} Whether a search for cloud destinations is in progress.
149 get isCloudDestinationSearchInProgress() {
150 return this.outstandingCloudSearchRequestCount_ > 0;
154 * Sends a Google Cloud Print search API request.
155 * @param {boolean} isRecent Whether to search for only recently used
156 * printers.
158 search: function(isRecent) {
159 var params = [
160 new HttpParam('connection_status', 'ALL'),
161 new HttpParam('client', 'chrome'),
162 new HttpParam('use_cdd', 'true')
164 if (isRecent) {
165 params.push(new HttpParam('q', '^recent'));
167 CloudPrintInterface.CLOUD_ORIGINS_.forEach(function(origin) {
168 ++this.outstandingCloudSearchRequestCount_;
169 var cpRequest =
170 this.buildRequest_('GET', 'search', params, origin,
171 this.onSearchDone_.bind(this, isRecent));
172 this.sendOrQueueRequest_(cpRequest);
173 }, this);
177 * Sends a Google Cloud Print submit API request.
178 * @param {!print_preview.Destination} destination Cloud destination to
179 * print to.
180 * @param {!print_preview.PrintTicketStore} printTicketStore Contains the
181 * print ticket to print.
182 * @param {!print_preview.DocumentInfo} documentInfo Document data model.
183 * @param {string} data Base64 encoded data of the document.
185 submit: function(destination, printTicketStore, documentInfo, data) {
186 var result =
187 CloudPrintInterface.VERSION_REGEXP_.exec(navigator.userAgent);
188 var chromeVersion = 'unknown';
189 if (result && result.length == 2) {
190 chromeVersion = result[1];
192 var params = [
193 new HttpParam('printerid', destination.id),
194 new HttpParam('contentType', 'dataUrl'),
195 new HttpParam('title', documentInfo.title),
196 new HttpParam('ticket',
197 printTicketStore.createPrintTicket(destination)),
198 new HttpParam('content', 'data:application/pdf;base64,' + data),
199 new HttpParam('tag',
200 '__google__chrome_version=' + chromeVersion),
201 new HttpParam('tag', '__google__os=' + navigator.platform)
203 var cpRequest = this.buildRequest_('POST', 'submit', params,
204 destination.origin,
205 this.onSubmitDone_.bind(this));
206 this.sendOrQueueRequest_(cpRequest);
210 * Sends a Google Cloud Print printer API request.
211 * @param {string} printerId ID of the printer to lookup.
212 * @param {!print_preview.Destination.Origin} origin Origin of the printer.
214 printer: function(printerId, origin) {
215 var params = [
216 new HttpParam('printerid', printerId),
217 new HttpParam('use_cdd', 'true'),
218 new HttpParam('printer_connection_status', 'true')
220 var cpRequest =
221 this.buildRequest_('GET', 'printer', params, origin,
222 this.onPrinterDone_.bind(this, printerId));
223 this.sendOrQueueRequest_(cpRequest);
227 * Sends a Google Cloud Print update API request to accept (or reject) the
228 * terms-of-service of the given printer.
229 * @param {string} printerId ID of the printer to accept the
230 * terms-of-service for.
231 * @param {!print_preview.Destination.Origin} origin Origin of the printer.
232 * @param {boolean} isAccepted Whether the user accepted the
233 * terms-of-service.
235 updatePrinterTosAcceptance: function(printerId, origin, isAccepted) {
236 var params = [
237 new HttpParam('printerid', printerId),
238 new HttpParam('is_tos_accepted', isAccepted)
240 var cpRequest =
241 this.buildRequest_('POST', 'update', params, origin,
242 this.onUpdatePrinterTosAcceptanceDone_.bind(this));
243 this.sendOrQueueRequest_(cpRequest);
247 * Adds event listeners to the relevant native layer events.
248 * @private
250 addEventListeners_: function() {
251 this.tracker_.add(
252 this.nativeLayer_,
253 print_preview.NativeLayer.EventType.ACCESS_TOKEN_READY,
254 this.onAccessTokenReady_.bind(this));
258 * Builds request to the Google Cloud Print API.
259 * @param {string} method HTTP method of the request.
260 * @param {string} action Google Cloud Print action to perform.
261 * @param {Array.<!HttpParam>} params HTTP parameters to include in the
262 * request.
263 * @param {!print_preview.Destination.Origin} origin Origin for destination.
264 * @param {function(number, Object, !print_preview.Destination.Origin)}
265 * callback Callback to invoke when request completes.
266 * @return {!CloudPrintRequest} Partially prepared request.
267 * @private
269 buildRequest_: function(method, action, params, origin, callback) {
270 var url = this.baseUrl_ + '/' + action + '?xsrf=';
271 if (origin == print_preview.Destination.Origin.COOKIES) {
272 if (!this.xsrfToken_) {
273 // TODO(rltoscano): Should throw an error if not a read-only action or
274 // issue an xsrf token request.
275 } else {
276 url = url + this.xsrfToken_;
279 var body = null;
280 if (params) {
281 if (method == 'GET') {
282 url = params.reduce(function(partialUrl, param) {
283 return partialUrl + '&' + param.name + '=' +
284 encodeURIComponent(param.value);
285 }, url);
286 } else if (method == 'POST') {
287 body = params.reduce(function(partialBody, param) {
288 return partialBody + 'Content-Disposition: form-data; name=\"' +
289 param.name + '\"\r\n\r\n' + param.value + '\r\n--' +
290 CloudPrintInterface.MULTIPART_BOUNDARY_ + '\r\n';
291 }, '--' + CloudPrintInterface.MULTIPART_BOUNDARY_ + '\r\n');
295 var headers = {};
296 headers['X-CloudPrint-Proxy'] = 'ChromePrintPreview';
297 if (method == 'GET') {
298 headers['Content-Type'] = CloudPrintInterface.URL_ENCODED_CONTENT_TYPE_;
299 } else if (method == 'POST') {
300 headers['Content-Type'] = CloudPrintInterface.MULTIPART_CONTENT_TYPE_;
303 var xhr = new XMLHttpRequest();
304 xhr.open(method, url, true);
305 xhr.withCredentials =
306 (origin == print_preview.Destination.Origin.COOKIES);
307 for (var header in headers) {
308 xhr.setRequestHeader(header, headers[header]);
311 return new CloudPrintRequest(xhr, body, origin, callback);
315 * Sends a request to the Google Cloud Print API or queues if it needs to
316 * wait OAuth2 access token.
317 * @param {!CloudPrintRequest} request Request to send or queue.
318 * @private
320 sendOrQueueRequest_: function(request) {
321 if (request.origin == print_preview.Destination.Origin.COOKIES) {
322 return this.sendRequest_(request);
323 } else {
324 this.requestQueue_.push(request);
325 this.nativeLayer_.startGetAccessToken(request.origin);
330 * Sends a request to the Google Cloud Print API.
331 * @param {!CloudPrintRequest} request Request to send.
332 * @private
334 sendRequest_: function(request) {
335 request.xhr.onreadystatechange =
336 this.onReadyStateChange_.bind(this, request);
337 request.xhr.send(request.body);
341 * Creates a Google Cloud Print interface error that is ready to dispatch.
342 * @param {!CloudPrintInterface.EventType} type Type of the error.
343 * @param {!CloudPrintRequest} request Request that has been completed.
344 * @return {!Event} Google Cloud Print interface error event.
345 * @private
347 createErrorEvent_: function(type, request) {
348 var errorEvent = new Event(type);
349 errorEvent.status = request.xhr.status;
350 if (request.xhr.status == 200) {
351 errorEvent.errorCode = request.result['errorCode'];
352 errorEvent.message = request.result['message'];
353 } else {
354 errorEvent.errorCode = 0;
355 errorEvent.message = '';
357 errorEvent.origin = request.origin;
358 return errorEvent;
362 * Called when a native layer receives access token.
363 * @param {Event} evt Contains the authetication type and access token.
364 * @private
366 onAccessTokenReady_: function(event) {
367 // TODO(vitalybuka): remove when other Origins implemented.
368 assert(event.authType == print_preview.Destination.Origin.DEVICE);
369 this.requestQueue_ = this.requestQueue_.filter(function(request) {
370 assert(request.origin == print_preview.Destination.Origin.DEVICE);
371 if (request.origin != event.authType) {
372 return true;
374 if (event.accessToken) {
375 request.xhr.setRequestHeader('Authorization',
376 'Bearer ' + event.accessToken);
377 this.sendRequest_(request);
378 } else { // No valid token.
379 // Without abort status does not exists.
380 request.xhr.abort();
381 request.callback(request);
383 return false;
384 }, this);
388 * Called when the ready-state of a XML http request changes.
389 * Calls the successCallback with the result or dispatches an ERROR event.
390 * @param {!CloudPrintRequest} request Request that was changed.
391 * @private
393 onReadyStateChange_: function(request) {
394 if (request.xhr.readyState == 4) {
395 if (request.xhr.status == 200) {
396 request.result = JSON.parse(request.xhr.responseText);
397 if (request.origin == print_preview.Destination.Origin.COOKIES &&
398 request.result['success']) {
399 this.xsrfToken_ = request.result['xsrf_token'];
402 request.status = request.xhr.status;
403 request.callback(request);
408 * Called when the search request completes.
409 * @param {boolean} isRecent Whether the search request was for recent
410 * destinations.
411 * @param {!CloudPrintRequest} request Request that has been completed.
412 * @private
414 onSearchDone_: function(isRecent, request) {
415 --this.outstandingCloudSearchRequestCount_;
416 if (request.xhr.status == 200 && request.result['success']) {
417 var printerListJson = request.result['printers'] || [];
418 var printerList = [];
419 printerListJson.forEach(function(printerJson) {
420 try {
421 printerList.push(
422 cloudprint.CloudDestinationParser.parse(printerJson,
423 request.origin));
424 } catch (err) {
425 console.error('Unable to parse cloud print destination: ' + err);
428 var searchDoneEvent =
429 new Event(CloudPrintInterface.EventType.SEARCH_DONE);
430 searchDoneEvent.printers = printerList;
431 searchDoneEvent.origin = request.origin;
432 searchDoneEvent.isRecent = isRecent;
433 searchDoneEvent.email = request.result['request']['user'];
434 this.dispatchEvent(searchDoneEvent);
435 } else {
436 var errorEvent = this.createErrorEvent_(
437 CloudPrintInterface.EventType.SEARCH_FAILED, request);
438 this.dispatchEvent(errorEvent);
443 * Called when the submit request completes.
444 * @param {!CloudPrintRequest} request Request that has been completed.
445 * @private
447 onSubmitDone_: function(request) {
448 if (request.xhr.status == 200 && request.result['success']) {
449 var submitDoneEvent = new Event(
450 CloudPrintInterface.EventType.SUBMIT_DONE);
451 submitDoneEvent.jobId = request.result['job']['id'];
452 this.dispatchEvent(submitDoneEvent);
453 } else {
454 var errorEvent = this.createErrorEvent_(
455 CloudPrintInterface.EventType.SUBMIT_FAILED, request);
456 this.dispatchEvent(errorEvent);
461 * Called when the printer request completes.
462 * @param {string} destinationId ID of the destination that was looked up.
463 * @param {!CloudPrintRequest} request Request that has been completed.
464 * @private
466 onPrinterDone_: function(destinationId, request) {
467 if (request.xhr.status == 200 && request.result['success']) {
468 var printerJson = request.result['printers'][0];
469 var printer;
470 try {
471 printer = cloudprint.CloudDestinationParser.parse(printerJson,
472 request.origin);
473 } catch (err) {
474 console.error('Failed to parse cloud print destination: ' +
475 JSON.stringify(printerJson));
476 return;
478 var printerDoneEvent =
479 new Event(CloudPrintInterface.EventType.PRINTER_DONE);
480 printerDoneEvent.printer = printer;
481 this.dispatchEvent(printerDoneEvent);
482 } else {
483 var errorEvent = this.createErrorEvent_(
484 CloudPrintInterface.EventType.PRINTER_FAILED, request);
485 errorEvent.destinationId = destinationId;
486 errorEvent.destinationOrigin = request.origin;
487 this.dispatchEvent(errorEvent, request.origin);
492 * Called when the update printer TOS acceptance request completes.
493 * @param {!CloudPrintRequest} request Request that has been completed.
494 * @private
496 onUpdatePrinterTosAcceptanceDone_: function(request) {
497 if (request.xhr.status == 200 && request.result['success']) {
498 // Do nothing.
499 } else {
500 var errorEvent = this.createErrorEvent_(
501 CloudPrintInterface.EventType.SUBMIT_FAILED, request);
502 this.dispatchEvent(errorEvent);
508 * Data structure that holds data for Cloud Print requests.
509 * @param {!XMLHttpRequest} xhr Partially prepared http request.
510 * @param {string} body Data to send with POST requests.
511 * @param {!print_preview.Destination.Origin} origin Origin for destination.
512 * @param {function(!CloudPrintRequest)} callback Callback to invoke when
513 * request completes.
514 * @constructor
516 function CloudPrintRequest(xhr, body, origin, callback) {
518 * Partially prepared http request.
519 * @type {!XMLHttpRequest}
521 this.xhr = xhr;
524 * Data to send with POST requests.
525 * @type {string}
527 this.body = body;
530 * Origin for destination.
531 * @type {!print_preview.Destination.Origin}
533 this.origin = origin;
536 * Callback to invoke when request completes.
537 * @type {function(!CloudPrintRequest)}
539 this.callback = callback;
542 * Result for requests.
543 * @type {Object} JSON response.
545 this.result = null;
549 * Data structure that represents an HTTP parameter.
550 * @param {string} name Name of the parameter.
551 * @param {string} value Value of the parameter.
552 * @constructor
554 function HttpParam(name, value) {
556 * Name of the parameter.
557 * @type {string}
559 this.name = name;
562 * Name of the value.
563 * @type {string}
565 this.value = value;
568 // Export
569 return {
570 CloudPrintInterface: CloudPrintInterface