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. 4 5cr.define('print_preview', function() { 6 'use strict'; 7 8 /** 9 * A data store that stores destinations and dispatches events when the data 10 * store changes. 11 * @param {!print_preview.NativeLayer} nativeLayer Used to fetch local print 12 * destinations. 13 * @param {!print_preview.UserInfo} userInfo User information repository. 14 * @param {!print_preview.AppState} appState Application state. 15 * @constructor 16 * @extends {cr.EventTarget} 17 */ 18 function DestinationStore(nativeLayer, userInfo, appState) { 19 cr.EventTarget.call(this); 20 21 /** 22 * Used to fetch local print destinations. 23 * @type {!print_preview.NativeLayer} 24 * @private 25 */ 26 this.nativeLayer_ = nativeLayer; 27 28 /** 29 * User information repository. 30 * @type {!print_preview.UserInfo} 31 * @private 32 */ 33 this.userInfo_ = userInfo; 34 35 /** 36 * Used to load and persist the selected destination. 37 * @type {!print_preview.AppState} 38 * @private 39 */ 40 this.appState_ = appState; 41 42 /** 43 * Used to track metrics. 44 * @type {!print_preview.DestinationSearchMetricsContext} 45 * @private 46 */ 47 this.metrics_ = new print_preview.DestinationSearchMetricsContext(); 48 49 /** 50 * Internal backing store for the data store. 51 * @type {!Array.<!print_preview.Destination>} 52 * @private 53 */ 54 this.destinations_ = []; 55 56 /** 57 * Cache used for constant lookup of destinations by origin and id. 58 * @type {object.<string, !print_preview.Destination>} 59 * @private 60 */ 61 this.destinationMap_ = {}; 62 63 /** 64 * Currently selected destination. 65 * @type {print_preview.Destination} 66 * @private 67 */ 68 this.selectedDestination_ = null; 69 70 /** 71 * Whether the destination store will auto select the destination that 72 * matches the last used destination stored in appState_. 73 * @type {boolean} 74 * @private 75 */ 76 this.isInAutoSelectMode_ = false; 77 78 /** 79 * Event tracker used to track event listeners of the destination store. 80 * @type {!EventTracker} 81 * @private 82 */ 83 this.tracker_ = new EventTracker(); 84 85 /** 86 * Whether PDF printer is enabled. It's disabled, for example, in App Kiosk 87 * mode. 88 * @type {boolean} 89 * @private 90 */ 91 this.pdfPrinterEnabled_ = false; 92 93 /** 94 * Used to fetch cloud-based print destinations. 95 * @type {print_preview.CloudPrintInterface} 96 * @private 97 */ 98 this.cloudPrintInterface_ = null; 99 100 /** 101 * Maps user account to the list of origins for which destinations are 102 * already loaded. 103 * @type {!Object.<string, Array.<print_preview.Destination.Origin>>} 104 * @private 105 */ 106 this.loadedCloudOrigins_ = {}; 107 108 /** 109 * ID of a timeout after the initial destination ID is set. If no inserted 110 * destination matches the initial destination ID after the specified 111 * timeout, the first destination in the store will be automatically 112 * selected. 113 * @type {?number} 114 * @private 115 */ 116 this.autoSelectTimeout_ = null; 117 118 /** 119 * Whether a search for local destinations is in progress. 120 * @type {boolean} 121 * @private 122 */ 123 this.isLocalDestinationSearchInProgress_ = false; 124 125 /** 126 * Whether the destination store has already loaded or is loading all local 127 * destinations. 128 * @type {boolean} 129 * @private 130 */ 131 this.hasLoadedAllLocalDestinations_ = false; 132 133 /** 134 * Whether a search for privet destinations is in progress. 135 * @type {boolean} 136 * @private 137 */ 138 this.isPrivetDestinationSearchInProgress_ = false; 139 140 /** 141 * Whether the destination store has already loaded or is loading all privet 142 * destinations. 143 * @type {boolean} 144 * @private 145 */ 146 this.hasLoadedAllPrivetDestinations_ = false; 147 148 /** 149 * ID of a timeout after the start of a privet search to end that privet 150 * search. 151 * @type {?number} 152 * @private 153 */ 154 this.privetSearchTimeout_ = null; 155 156 /** 157 * MDNS service name of destination that we are waiting to register. 158 * @type {?string} 159 * @private 160 */ 161 this.waitForRegisterDestination_ = null; 162 163 this.addEventListeners_(); 164 this.reset_(); 165 }; 166 167 /** 168 * Event types dispatched by the data store. 169 * @enum {string} 170 */ 171 DestinationStore.EventType = { 172 DESTINATION_SEARCH_DONE: 173 'print_preview.DestinationStore.DESTINATION_SEARCH_DONE', 174 DESTINATION_SEARCH_STARTED: 175 'print_preview.DestinationStore.DESTINATION_SEARCH_STARTED', 176 DESTINATION_SELECT: 'print_preview.DestinationStore.DESTINATION_SELECT', 177 DESTINATIONS_INSERTED: 178 'print_preview.DestinationStore.DESTINATIONS_INSERTED', 179 CACHED_SELECTED_DESTINATION_INFO_READY: 180 'print_preview.DestinationStore.CACHED_SELECTED_DESTINATION_INFO_READY', 181 SELECTED_DESTINATION_CAPABILITIES_READY: 182 'print_preview.DestinationStore.SELECTED_DESTINATION_CAPABILITIES_READY' 183 }; 184 185 /** 186 * Delay in milliseconds before the destination store ignores the initial 187 * destination ID and just selects any printer (since the initial destination 188 * was not found). 189 * @type {number} 190 * @const 191 * @private 192 */ 193 DestinationStore.AUTO_SELECT_TIMEOUT_ = 15000; 194 195 /** 196 * Amount of time spent searching for privet destination, in milliseconds. 197 * @type {number} 198 * @const 199 * @private 200 */ 201 DestinationStore.PRIVET_SEARCH_DURATION_ = 2000; 202 203 /** 204 * Localizes printer capabilities. 205 * @param {!Object} capabilities Printer capabilities to localize. 206 * @return {!Object} Localized capabilities. 207 * @private 208 */ 209 DestinationStore.localizeCapabilities_ = function(capabilities) { 210 var mediaSize = capabilities.printer.media_size; 211 if (mediaSize) { 212 var mediaDisplayNames = { 213 'ISO_A4': 'A4', 214 'ISO_A3': 'A3', 215 'NA_LETTER': 'Letter', 216 'NA_LEGAL': 'Legal', 217 'NA_LEDGER': 'Tabloid' 218 }; 219 for (var i = 0, media; media = mediaSize.option[i]; i++) { 220 media.custom_display_name = 221 media.custom_display_name || 222 mediaDisplayNames[media.name] || 223 media.name; 224 } 225 } 226 return capabilities; 227 }; 228 229 DestinationStore.prototype = { 230 __proto__: cr.EventTarget.prototype, 231 232 /** 233 * @param {string=} opt_account Account to filter destinations by. When 234 * omitted, all destinations are returned. 235 * @return {!Array.<!print_preview.Destination>} List of destinations 236 * accessible by the {@code account}. 237 */ 238 destinations: function(opt_account) { 239 if (opt_account) { 240 return this.destinations_.filter(function(destination) { 241 return !destination.account || destination.account == opt_account; 242 }); 243 } else { 244 return this.destinations_.slice(0); 245 } 246 }, 247 248 /** 249 * @return {print_preview.Destination} The currently selected destination or 250 * {@code null} if none is selected. 251 */ 252 get selectedDestination() { 253 return this.selectedDestination_; 254 }, 255 256 /** @return {boolean} Whether destination selection is pending or not. */ 257 get isAutoSelectDestinationInProgress() { 258 return this.selectedDestination_ == null && 259 this.autoSelectTimeout_ != null; 260 }, 261 262 /** 263 * @return {boolean} Whether a search for local destinations is in progress. 264 */ 265 get isLocalDestinationSearchInProgress() { 266 return this.isLocalDestinationSearchInProgress_ || 267 this.isPrivetDestinationSearchInProgress_; 268 }, 269 270 /** 271 * @return {boolean} Whether a search for cloud destinations is in progress. 272 */ 273 get isCloudDestinationSearchInProgress() { 274 return this.cloudPrintInterface_ && 275 this.cloudPrintInterface_.isCloudDestinationSearchInProgress; 276 }, 277 278 /** 279 * Initializes the destination store. Sets the initially selected 280 * destination. If any inserted destinations match this ID, that destination 281 * will be automatically selected. This method must be called after the 282 * print_preview.AppState has been initialized. 283 * @param {boolean} isInAppKioskMode Whether the print preview is in App 284 * Kiosk mode. 285 */ 286 init: function(isInAppKioskMode) { 287 this.pdfPrinterEnabled_ = !isInAppKioskMode; 288 this.isInAutoSelectMode_ = true; 289 this.createLocalPdfPrintDestination_(); 290 if (!this.appState_.selectedDestinationId || 291 !this.appState_.selectedDestinationOrigin) { 292 this.selectDefaultDestination_(); 293 } else { 294 var key = this.getDestinationKey_( 295 this.appState_.selectedDestinationOrigin, 296 this.appState_.selectedDestinationId, 297 this.appState_.selectedDestinationAccount); 298 var candidate = this.destinationMap_[key]; 299 if (candidate != null) { 300 this.selectDestination(candidate); 301 } else if (this.appState_.selectedDestinationOrigin == 302 print_preview.Destination.Origin.LOCAL) { 303 this.nativeLayer_.startGetLocalDestinationCapabilities( 304 this.appState_.selectedDestinationId); 305 } else if (this.cloudPrintInterface_ && 306 (this.appState_.selectedDestinationOrigin == 307 print_preview.Destination.Origin.COOKIES || 308 this.appState_.selectedDestinationOrigin == 309 print_preview.Destination.Origin.DEVICE)) { 310 this.cloudPrintInterface_.printer( 311 this.appState_.selectedDestinationId, 312 this.appState_.selectedDestinationOrigin, 313 this.appState_.selectedDestinationAccount); 314 } else if (this.appState_.selectedDestinationOrigin == 315 print_preview.Destination.Origin.PRIVET) { 316 // TODO(noamsml): Resolve a specific printer instead of listing all 317 // privet printers in this case. 318 this.nativeLayer_.startGetPrivetDestinations(); 319 320 var destinationName = this.appState_.selectedDestinationName || ''; 321 322 // Create a fake selectedDestination_ that is not actually in the 323 // destination store. When the real destination is created, this 324 // destination will be overwritten. 325 this.selectedDestination_ = new print_preview.Destination( 326 this.appState_.selectedDestinationId, 327 print_preview.Destination.Type.LOCAL, 328 print_preview.Destination.Origin.PRIVET, 329 destinationName, 330 false /*isRecent*/, 331 print_preview.Destination.ConnectionStatus.ONLINE); 332 this.selectedDestination_.capabilities = 333 this.appState_.selectedDestinationCapabilities; 334 335 cr.dispatchSimpleEvent( 336 this, 337 DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY); 338 } else { 339 this.selectDefaultDestination_(); 340 } 341 } 342 }, 343 344 /** 345 * Sets the destination store's Google Cloud Print interface. 346 * @param {!print_preview.CloudPrintInterface} cloudPrintInterface Interface 347 * to set. 348 */ 349 setCloudPrintInterface: function(cloudPrintInterface) { 350 this.cloudPrintInterface_ = cloudPrintInterface; 351 this.tracker_.add( 352 this.cloudPrintInterface_, 353 cloudprint.CloudPrintInterface.EventType.SEARCH_DONE, 354 this.onCloudPrintSearchDone_.bind(this)); 355 this.tracker_.add( 356 this.cloudPrintInterface_, 357 cloudprint.CloudPrintInterface.EventType.SEARCH_FAILED, 358 this.onCloudPrintSearchDone_.bind(this)); 359 this.tracker_.add( 360 this.cloudPrintInterface_, 361 cloudprint.CloudPrintInterface.EventType.PRINTER_DONE, 362 this.onCloudPrintPrinterDone_.bind(this)); 363 this.tracker_.add( 364 this.cloudPrintInterface_, 365 cloudprint.CloudPrintInterface.EventType.PRINTER_FAILED, 366 this.onCloudPrintPrinterFailed_.bind(this)); 367 this.tracker_.add( 368 this.cloudPrintInterface_, 369 cloudprint.CloudPrintInterface.EventType.PROCESS_INVITE_DONE, 370 this.onCloudPrintProcessInviteDone_.bind(this)); 371 }, 372 373 /** 374 * @return {boolean} Whether only default cloud destinations have been 375 * loaded. 376 */ 377 hasOnlyDefaultCloudDestinations: function() { 378 // TODO: Move the logic to print_preview. 379 return this.destinations_.every(function(dest) { 380 return dest.isLocal || 381 dest.id == print_preview.Destination.GooglePromotedId.DOCS || 382 dest.id == print_preview.Destination.GooglePromotedId.FEDEX; 383 }); 384 }, 385 386 /** 387 * @param {!print_preview.Destination} destination Destination to select. 388 */ 389 selectDestination: function(destination) { 390 this.isInAutoSelectMode_ = false; 391 // When auto select expires, DESTINATION_SELECT event has to be dispatched 392 // anyway (see isAutoSelectDestinationInProgress() logic). 393 if (this.autoSelectTimeout_) { 394 clearTimeout(this.autoSelectTimeout_); 395 this.autoSelectTimeout_ = null; 396 } else if (destination == this.selectedDestination_) { 397 return; 398 } 399 if (destination == null) { 400 this.selectedDestination_ = null; 401 cr.dispatchSimpleEvent( 402 this, DestinationStore.EventType.DESTINATION_SELECT); 403 return; 404 } 405 // Update and persist selected destination. 406 this.selectedDestination_ = destination; 407 this.selectedDestination_.isRecent = true; 408 if (destination.id == print_preview.Destination.GooglePromotedId.FEDEX && 409 !destination.isTosAccepted) { 410 assert(this.cloudPrintInterface_ != null, 411 'Selected FedEx destination, but GCP API is not available'); 412 destination.isTosAccepted = true; 413 this.cloudPrintInterface_.updatePrinterTosAcceptance(destination, true); 414 } 415 this.appState_.persistSelectedDestination(this.selectedDestination_); 416 // Adjust metrics. 417 if (destination.cloudID && 418 this.destinations_.some(function(otherDestination) { 419 return otherDestination.cloudID == destination.cloudID && 420 otherDestination != destination; 421 })) { 422 this.metrics_.record(destination.isPrivet ? 423 print_preview.Metrics.DestinationSearchBucket. 424 PRIVET_DUPLICATE_SELECTED : 425 print_preview.Metrics.DestinationSearchBucket. 426 CLOUD_DUPLICATE_SELECTED); 427 } 428 // Notify about selected destination change. 429 cr.dispatchSimpleEvent( 430 this, DestinationStore.EventType.DESTINATION_SELECT); 431 // Request destination capabilities, of not known yet. 432 if (destination.capabilities == null) { 433 if (destination.isPrivet) { 434 this.nativeLayer_.startGetPrivetDestinationCapabilities( 435 destination.id); 436 } 437 else if (destination.isLocal) { 438 this.nativeLayer_.startGetLocalDestinationCapabilities( 439 destination.id); 440 } else { 441 assert(this.cloudPrintInterface_ != null, 442 'Cloud destination selected, but GCP is not enabled'); 443 this.cloudPrintInterface_.printer( 444 destination.id, destination.origin, destination.account); 445 } 446 } else { 447 cr.dispatchSimpleEvent( 448 this, 449 DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY); 450 } 451 }, 452 453 /** 454 * Selects 'Save to PDF' destination (since it always exists). 455 * @private 456 */ 457 selectDefaultDestination_: function() { 458 var saveToPdfKey = this.getDestinationKey_( 459 print_preview.Destination.Origin.LOCAL, 460 print_preview.Destination.GooglePromotedId.SAVE_AS_PDF, 461 ''); 462 this.selectDestination( 463 this.destinationMap_[saveToPdfKey] || this.destinations_[0] || null); 464 }, 465 466 /** Initiates loading of local print destinations. */ 467 startLoadLocalDestinations: function() { 468 if (!this.hasLoadedAllLocalDestinations_) { 469 this.hasLoadedAllLocalDestinations_ = true; 470 this.nativeLayer_.startGetLocalDestinations(); 471 this.isLocalDestinationSearchInProgress_ = true; 472 cr.dispatchSimpleEvent( 473 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED); 474 } 475 }, 476 477 /** Initiates loading of privet print destinations. */ 478 startLoadPrivetDestinations: function() { 479 if (!this.hasLoadedAllPrivetDestinations_) { 480 this.isPrivetDestinationSearchInProgress_ = true; 481 this.nativeLayer_.startGetPrivetDestinations(); 482 cr.dispatchSimpleEvent( 483 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED); 484 this.privetSearchTimeout_ = setTimeout( 485 this.endPrivetPrinterSearch_.bind(this), 486 DestinationStore.PRIVET_SEARCH_DURATION_); 487 } 488 }, 489 490 /** 491 * Initiates loading of cloud destinations. 492 * @param {print_preview.Destination.Origin=} opt_origin Search destinations 493 * for the specified origin only. 494 */ 495 startLoadCloudDestinations: function(opt_origin) { 496 if (this.cloudPrintInterface_ != null) { 497 var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || []; 498 if (origins.length == 0 || 499 (opt_origin && origins.indexOf(opt_origin) < 0)) { 500 this.cloudPrintInterface_.search( 501 this.userInfo_.activeUser, opt_origin); 502 cr.dispatchSimpleEvent( 503 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED); 504 } 505 } 506 }, 507 508 /** Requests load of COOKIE based cloud destinations. */ 509 reloadUserCookieBasedDestinations: function() { 510 var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || []; 511 if (origins.indexOf(print_preview.Destination.Origin.COOKIES) >= 0) { 512 cr.dispatchSimpleEvent( 513 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE); 514 } else { 515 this.startLoadCloudDestinations( 516 print_preview.Destination.Origin.COOKIES); 517 } 518 }, 519 520 /** Initiates loading of all known destination types. */ 521 startLoadAllDestinations: function() { 522 this.startLoadCloudDestinations(); 523 this.startLoadLocalDestinations(); 524 this.startLoadPrivetDestinations(); 525 }, 526 527 /** 528 * Wait for a privet device to be registered. 529 */ 530 waitForRegister: function(id) { 531 this.nativeLayer_.startGetPrivetDestinations(); 532 this.waitForRegisterDestination_ = id; 533 }, 534 535 /** 536 * Inserts {@code destination} to the data store and dispatches a 537 * DESTINATIONS_INSERTED event. 538 * @param {!print_preview.Destination} destination Print destination to 539 * insert. 540 * @private 541 */ 542 insertDestination_: function(destination) { 543 if (this.insertIntoStore_(destination)) { 544 this.destinationsInserted_(destination); 545 } 546 }, 547 548 /** 549 * Inserts multiple {@code destinations} to the data store and dispatches 550 * single DESTINATIONS_INSERTED event. 551 * @param {!Array.<print_preview.Destination>} destinations Print 552 * destinations to insert. 553 * @private 554 */ 555 insertDestinations_: function(destinations) { 556 var inserted = false; 557 destinations.forEach(function(destination) { 558 inserted = this.insertIntoStore_(destination) || inserted; 559 }, this); 560 if (inserted) { 561 this.destinationsInserted_(); 562 } 563 }, 564 565 /** 566 * Dispatches DESTINATIONS_INSERTED event. In auto select mode, tries to 567 * update selected destination to match {@code appState_} settings. 568 * @param {print_preview.Destination=} opt_destination The only destination 569 * that was changed or skipped if possibly more than one destination was 570 * changed. Used as a hint to limit destination search scope in 571 * {@code isInAutoSelectMode_). 572 */ 573 destinationsInserted_: function(opt_destination) { 574 cr.dispatchSimpleEvent( 575 this, DestinationStore.EventType.DESTINATIONS_INSERTED); 576 if (this.isInAutoSelectMode_) { 577 var destinationsToSearch = 578 opt_destination && [opt_destination] || this.destinations_; 579 destinationsToSearch.some(function(destination) { 580 if (this.matchPersistedDestination_(destination)) { 581 this.selectDestination(destination); 582 return true; 583 } 584 }, this); 585 } 586 }, 587 588 /** 589 * Updates an existing print destination with capabilities and display name 590 * information. If the destination doesn't already exist, it will be added. 591 * @param {!print_preview.Destination} destination Destination to update. 592 * @return {!print_preview.Destination} The existing destination that was 593 * updated or {@code null} if it was the new destination. 594 * @private 595 */ 596 updateDestination_: function(destination) { 597 assert(destination.constructor !== Array, 'Single printer expected'); 598 var existingDestination = this.destinationMap_[this.getKey_(destination)]; 599 if (existingDestination != null) { 600 existingDestination.capabilities = destination.capabilities; 601 } else { 602 this.insertDestination_(destination); 603 } 604 605 if (existingDestination == this.selectedDestination_ || 606 destination == this.selectedDestination_) { 607 this.appState_.persistSelectedDestination(this.selectedDestination_); 608 cr.dispatchSimpleEvent( 609 this, 610 DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY); 611 } 612 613 return existingDestination; 614 }, 615 616 /** 617 * Called when the search for Privet printers is done. 618 * @private 619 */ 620 endPrivetPrinterSearch_: function() { 621 this.nativeLayer_.stopGetPrivetDestinations(); 622 this.isPrivetDestinationSearchInProgress_ = false; 623 this.hasLoadedAllPrivetDestinations_ = true; 624 cr.dispatchSimpleEvent( 625 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE); 626 }, 627 628 /** 629 * Inserts a destination into the store without dispatching any events. 630 * @return {boolean} Whether the inserted destination was not already in the 631 * store. 632 * @private 633 */ 634 insertIntoStore_: function(destination) { 635 var key = this.getKey_(destination); 636 var existingDestination = this.destinationMap_[key]; 637 if (existingDestination == null) { 638 this.destinations_.push(destination); 639 this.destinationMap_[key] = destination; 640 return true; 641 } else if (existingDestination.connectionStatus == 642 print_preview.Destination.ConnectionStatus.UNKNOWN && 643 destination.connectionStatus != 644 print_preview.Destination.ConnectionStatus.UNKNOWN) { 645 existingDestination.connectionStatus = destination.connectionStatus; 646 return true; 647 } else { 648 return false; 649 } 650 }, 651 652 /** 653 * Binds handlers to events. 654 * @private 655 */ 656 addEventListeners_: function() { 657 this.tracker_.add( 658 this.nativeLayer_, 659 print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET, 660 this.onLocalDestinationsSet_.bind(this)); 661 this.tracker_.add( 662 this.nativeLayer_, 663 print_preview.NativeLayer.EventType.CAPABILITIES_SET, 664 this.onLocalDestinationCapabilitiesSet_.bind(this)); 665 this.tracker_.add( 666 this.nativeLayer_, 667 print_preview.NativeLayer.EventType.GET_CAPABILITIES_FAIL, 668 this.onGetCapabilitiesFail_.bind(this)); 669 this.tracker_.add( 670 this.nativeLayer_, 671 print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD, 672 this.onDestinationsReload_.bind(this)); 673 this.tracker_.add( 674 this.nativeLayer_, 675 print_preview.NativeLayer.EventType.PRIVET_PRINTER_CHANGED, 676 this.onPrivetPrinterAdded_.bind(this)); 677 this.tracker_.add( 678 this.nativeLayer_, 679 print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET, 680 this.onPrivetCapabilitiesSet_.bind(this)); 681 }, 682 683 /** 684 * Creates a local PDF print destination. 685 * @return {!print_preview.Destination} Created print destination. 686 * @private 687 */ 688 createLocalPdfPrintDestination_: function() { 689 // TODO(alekseys): Create PDF printer in the native code and send its 690 // capabilities back with other local printers. 691 if (this.pdfPrinterEnabled_) { 692 this.insertDestination_(new print_preview.Destination( 693 print_preview.Destination.GooglePromotedId.SAVE_AS_PDF, 694 print_preview.Destination.Type.LOCAL, 695 print_preview.Destination.Origin.LOCAL, 696 loadTimeData.getString('printToPDF'), 697 false /*isRecent*/, 698 print_preview.Destination.ConnectionStatus.ONLINE)); 699 } 700 }, 701 702 /** 703 * Resets the state of the destination store to its initial state. 704 * @private 705 */ 706 reset_: function() { 707 this.destinations_ = []; 708 this.destinationMap_ = {}; 709 this.selectDestination(null); 710 this.loadedCloudOrigins_ = {}; 711 this.hasLoadedAllLocalDestinations_ = false; 712 713 clearTimeout(this.autoSelectTimeout_); 714 this.autoSelectTimeout_ = setTimeout( 715 this.selectDefaultDestination_.bind(this), 716 DestinationStore.AUTO_SELECT_TIMEOUT_); 717 }, 718 719 /** 720 * Called when the local destinations have been got from the native layer. 721 * @param {Event} event Contains the local destinations. 722 * @private 723 */ 724 onLocalDestinationsSet_: function(event) { 725 var localDestinations = event.destinationInfos.map(function(destInfo) { 726 return print_preview.LocalDestinationParser.parse(destInfo); 727 }); 728 this.insertDestinations_(localDestinations); 729 this.isLocalDestinationSearchInProgress_ = false; 730 cr.dispatchSimpleEvent( 731 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE); 732 }, 733 734 /** 735 * Called when the native layer retrieves the capabilities for the selected 736 * local destination. Updates the destination with new capabilities if the 737 * destination already exists, otherwise it creates a new destination and 738 * then updates its capabilities. 739 * @param {Event} event Contains the capabilities of the local print 740 * destination. 741 * @private 742 */ 743 onLocalDestinationCapabilitiesSet_: function(event) { 744 var destinationId = event.settingsInfo['printerId']; 745 var key = this.getDestinationKey_( 746 print_preview.Destination.Origin.LOCAL, 747 destinationId, 748 ''); 749 var destination = this.destinationMap_[key]; 750 var capabilities = DestinationStore.localizeCapabilities_( 751 event.settingsInfo.capabilities); 752 // Special case for PDF printer (until local printers capabilities are 753 // reported in CDD format too). 754 if (destinationId == 755 print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) { 756 if (destination) { 757 destination.capabilities = capabilities; 758 } 759 } else { 760 if (destination) { 761 // In case there were multiple capabilities request for this local 762 // destination, just ignore the later ones. 763 if (destination.capabilities != null) { 764 return; 765 } 766 destination.capabilities = capabilities; 767 } else { 768 // TODO(rltoscano): This makes the assumption that the "deviceName" is 769 // the same as "printerName". We should include the "printerName" in 770 // the response. See http://crbug.com/132831. 771 destination = print_preview.LocalDestinationParser.parse( 772 {deviceName: destinationId, printerName: destinationId}); 773 destination.capabilities = capabilities; 774 this.insertDestination_(destination); 775 } 776 } 777 if (this.selectedDestination_ && 778 this.selectedDestination_.id == destinationId) { 779 cr.dispatchSimpleEvent(this, 780 DestinationStore.EventType. 781 SELECTED_DESTINATION_CAPABILITIES_READY); 782 } 783 }, 784 785 /** 786 * Called when a request to get a local destination's print capabilities 787 * fails. If the destination is the initial destination, auto-select another 788 * destination instead. 789 * @param {Event} event Contains the destination ID that failed. 790 * @private 791 */ 792 onGetCapabilitiesFail_: function(event) { 793 console.error('Failed to get print capabilities for printer ' + 794 event.destinationId); 795 if (this.isInAutoSelectMode_ && 796 this.sameAsPersistedDestination_(event.destinationId, 797 event.destinationOrigin)) { 798 this.selectDefaultDestination_(); 799 } 800 }, 801 802 /** 803 * Called when the /search call completes, either successfully or not. 804 * In case of success, stores fetched destinations. 805 * @param {Event} event Contains the request result. 806 * @private 807 */ 808 onCloudPrintSearchDone_: function(event) { 809 if (event.printers) { 810 this.insertDestinations_(event.printers); 811 } 812 if (event.searchDone) { 813 var origins = this.loadedCloudOrigins_[event.user] || []; 814 if (origins.indexOf(event.origin) < 0) { 815 this.loadedCloudOrigins_[event.user] = origins.concat([event.origin]); 816 } 817 } 818 cr.dispatchSimpleEvent( 819 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE); 820 }, 821 822 /** 823 * Called when /printer call completes. Updates the specified destination's 824 * print capabilities. 825 * @param {Event} event Contains detailed information about the 826 * destination. 827 * @private 828 */ 829 onCloudPrintPrinterDone_: function(event) { 830 this.updateDestination_(event.printer); 831 }, 832 833 /** 834 * Called when the Google Cloud Print interface fails to lookup a 835 * destination. Selects another destination if the failed destination was 836 * the initial destination. 837 * @param {object} event Contains the ID of the destination that was failed 838 * to be looked up. 839 * @private 840 */ 841 onCloudPrintPrinterFailed_: function(event) { 842 if (this.isInAutoSelectMode_ && 843 this.sameAsPersistedDestination_(event.destinationId, 844 event.destinationOrigin)) { 845 console.error( 846 'Failed to fetch last used printer caps: ' + event.destinationId); 847 this.selectDefaultDestination_(); 848 } 849 }, 850 851 /** 852 * Called when printer sharing invitation was processed successfully. 853 * @param {Event} event Contains detailed information about the invite and 854 * newly accepted destination (if known). 855 * @private 856 */ 857 onCloudPrintProcessInviteDone_: function(event) { 858 if (event.accept && event.printer) { 859 // Hint the destination list to promote this new destination. 860 event.printer.isRecent = true; 861 this.insertDestination_(event.printer); 862 } 863 }, 864 865 /** 866 * Called when a Privet printer is added to the local network. 867 * @param {object} event Contains information about the added printer. 868 * @private 869 */ 870 onPrivetPrinterAdded_: function(event) { 871 if (event.printer.serviceName == this.waitForRegisterDestination_ && 872 !event.printer.isUnregistered) { 873 this.waitForRegisterDestination_ = null; 874 this.onDestinationsReload_(); 875 } else { 876 this.insertDestinations_( 877 print_preview.PrivetDestinationParser.parse(event.printer)); 878 } 879 }, 880 881 /** 882 * Called when capabilities for a privet printer are set. 883 * @param {object} event Contains the capabilities and printer ID. 884 * @private 885 */ 886 onPrivetCapabilitiesSet_: function(event) { 887 var destinationId = event.printerId; 888 var destinations = 889 print_preview.PrivetDestinationParser.parse(event.printer); 890 destinations.forEach(function(dest) { 891 dest.capabilities = event.capabilities; 892 this.updateDestination_(dest); 893 }, this); 894 }, 895 896 /** 897 * Called from native layer after the user was requested to sign in, and did 898 * so successfully. 899 * @private 900 */ 901 onDestinationsReload_: function() { 902 this.reset_(); 903 this.isInAutoSelectMode_ = true; 904 this.createLocalPdfPrintDestination_(); 905 this.startLoadAllDestinations(); 906 }, 907 908 // TODO(vitalybuka): Remove three next functions replacing Destination.id 909 // and Destination.origin by complex ID. 910 /** 911 * Returns key to be used with {@code destinationMap_}. 912 * @param {!print_preview.Destination.Origin} origin Destination origin. 913 * @return {string} id Destination id. 914 * @return {string} account User account destination is registered for. 915 * @private 916 */ 917 getDestinationKey_: function(origin, id, account) { 918 return origin + '/' + id + '/' + account; 919 }, 920 921 /** 922 * Returns key to be used with {@code destinationMap_}. 923 * @param {!print_preview.Destination} destination Destination. 924 * @private 925 */ 926 getKey_: function(destination) { 927 return this.getDestinationKey_( 928 destination.origin, destination.id, destination.account); 929 }, 930 931 /** 932 * @param {!print_preview.Destination} destination Destination to match. 933 * @return {boolean} Whether {@code destination} matches the last user 934 * selected one. 935 * @private 936 */ 937 matchPersistedDestination_: function(destination) { 938 return !this.appState_.selectedDestinationId || 939 !this.appState_.selectedDestinationOrigin || 940 this.sameAsPersistedDestination_( 941 destination.id, destination.origin); 942 }, 943 944 /** 945 * @param {?string} id Id of the destination. 946 * @param {?string} origin Oring of the destination. 947 * @return {boolean} Whether destination is the same as initial. 948 * @private 949 */ 950 sameAsPersistedDestination_: function(id, origin) { 951 return id == this.appState_.selectedDestinationId && 952 origin == this.appState_.selectedDestinationOrigin; 953 } 954 }; 955 956 // Export 957 return { 958 DestinationStore: DestinationStore 959 }; 960}); 961