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('print_preview', function() {
9 * A data store that stores destinations and dispatches events when the data
11 * @param {!print_preview.NativeLayer} nativeLayer Used to fetch local print
13 * @param {!print_preview.UserInfo} userInfo User information repository.
14 * @param {!print_preview.AppState} appState Application state.
16 * @extends {cr.EventTarget}
18 function DestinationStore(nativeLayer, userInfo, appState) {
19 cr.EventTarget.call(this);
22 * Used to fetch local print destinations.
23 * @type {!print_preview.NativeLayer}
26 this.nativeLayer_ = nativeLayer;
29 * User information repository.
30 * @type {!print_preview.UserInfo}
33 this.userInfo_ = userInfo;
36 * Used to load and persist the selected destination.
37 * @type {!print_preview.AppState}
40 this.appState_ = appState;
43 * Used to track metrics.
44 * @type {!print_preview.DestinationSearchMetricsContext}
47 this.metrics_ = new print_preview.DestinationSearchMetricsContext();
50 * Internal backing store for the data store.
51 * @type {!Array<!print_preview.Destination>}
54 this.destinations_ = [];
57 * Cache used for constant lookup of destinations by origin and id.
58 * @type {Object<!print_preview.Destination>}
61 this.destinationMap_ = {};
64 * Currently selected destination.
65 * @type {print_preview.Destination}
68 this.selectedDestination_ = null;
71 * Whether the destination store will auto select the destination that
72 * matches the last used destination stored in appState_.
76 this.isInAutoSelectMode_ = false;
79 * Event tracker used to track event listeners of the destination store.
80 * @type {!EventTracker}
83 this.tracker_ = new EventTracker();
86 * Whether PDF printer is enabled. It's disabled, for example, in App Kiosk
91 this.pdfPrinterEnabled_ = false;
94 * Used to fetch cloud-based print destinations.
95 * @type {cloudprint.CloudPrintInterface}
98 this.cloudPrintInterface_ = null;
101 * Maps user account to the list of origins for which destinations are
103 * @type {!Object<Array<print_preview.Destination.Origin>>}
106 this.loadedCloudOrigins_ = {};
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
116 this.autoSelectTimeout_ = null;
119 * Whether a search for local destinations is in progress.
123 this.isLocalDestinationSearchInProgress_ = false;
126 * Whether the destination store has already loaded or is loading all local
131 this.hasLoadedAllLocalDestinations_ = false;
134 * Whether a search for privet destinations is in progress.
138 this.isPrivetDestinationSearchInProgress_ = false;
141 * Whether the destination store has already loaded or is loading all privet
146 this.hasLoadedAllPrivetDestinations_ = false;
149 * ID of a timeout after the start of a privet search to end that privet
154 this.privetSearchTimeout_ = null;
157 * Whether a search for extension destinations is in progress.
161 this.isExtensionDestinationSearchInProgress_ = false;
164 * Whether the destination store has already loaded all extension
169 this.hasLoadedAllExtensionDestinations_ = false;
172 * ID of a timeout set at the start of an extension destination search. The
173 * timeout ends the search.
177 this.extensionSearchTimeout_ = null;
180 * MDNS service name of destination that we are waiting to register.
184 this.waitForRegisterDestination_ = null;
186 this.addEventListeners_();
191 * Event types dispatched by the data store.
194 DestinationStore.EventType = {
195 DESTINATION_SEARCH_DONE:
196 'print_preview.DestinationStore.DESTINATION_SEARCH_DONE',
197 DESTINATION_SEARCH_STARTED:
198 'print_preview.DestinationStore.DESTINATION_SEARCH_STARTED',
199 DESTINATION_SELECT: 'print_preview.DestinationStore.DESTINATION_SELECT',
200 DESTINATIONS_INSERTED:
201 'print_preview.DestinationStore.DESTINATIONS_INSERTED',
202 PROVISIONAL_DESTINATION_RESOLVED:
203 'print_preview.DestinationStore.PROVISIONAL_DESTINATION_RESOLVED',
204 CACHED_SELECTED_DESTINATION_INFO_READY:
205 'print_preview.DestinationStore.CACHED_SELECTED_DESTINATION_INFO_READY',
206 SELECTED_DESTINATION_CAPABILITIES_READY:
207 'print_preview.DestinationStore.SELECTED_DESTINATION_CAPABILITIES_READY'
211 * Delay in milliseconds before the destination store ignores the initial
212 * destination ID and just selects any printer (since the initial destination
218 DestinationStore.AUTO_SELECT_TIMEOUT_ = 15000;
221 * Amount of time spent searching for privet destination, in milliseconds.
226 DestinationStore.PRIVET_SEARCH_DURATION_ = 5000;
229 * Maximum amount of time spent searching for extension destinations, in
235 DestinationStore.EXTENSION_SEARCH_DURATION_ = 5000;
238 * Localizes printer capabilities.
239 * @param {!Object} capabilities Printer capabilities to localize.
240 * @return {!Object} Localized capabilities.
243 DestinationStore.localizeCapabilities_ = function(capabilities) {
244 var mediaSize = capabilities.printer.media_size;
246 var mediaDisplayNames = {
249 'NA_LETTER': 'Letter',
251 'NA_LEDGER': 'Tabloid'
253 for (var i = 0, media; media = mediaSize.option[i]; i++) {
254 // No need to patch capabilities with localized names provided.
255 if (!media.custom_display_name_localized) {
256 media.custom_display_name =
257 media.custom_display_name ||
258 mediaDisplayNames[media.name] ||
266 DestinationStore.prototype = {
267 __proto__: cr.EventTarget.prototype,
270 * @param {string=} opt_account Account to filter destinations by. When
271 * omitted, all destinations are returned.
272 * @return {!Array<!print_preview.Destination>} List of destinations
273 * accessible by the {@code account}.
275 destinations: function(opt_account) {
277 return this.destinations_.filter(function(destination) {
278 return !destination.account || destination.account == opt_account;
281 return this.destinations_.slice(0);
286 * @return {print_preview.Destination} The currently selected destination or
287 * {@code null} if none is selected.
289 get selectedDestination() {
290 return this.selectedDestination_;
293 /** @return {boolean} Whether destination selection is pending or not. */
294 get isAutoSelectDestinationInProgress() {
295 return this.selectedDestination_ == null &&
296 this.autoSelectTimeout_ != null;
300 * @return {boolean} Whether a search for local destinations is in progress.
302 get isLocalDestinationSearchInProgress() {
303 return this.isLocalDestinationSearchInProgress_ ||
304 this.isPrivetDestinationSearchInProgress_ ||
305 this.isExtensionDestinationSearchInProgress_;
309 * @return {boolean} Whether a search for cloud destinations is in progress.
311 get isCloudDestinationSearchInProgress() {
312 return !!this.cloudPrintInterface_ &&
313 this.cloudPrintInterface_.isCloudDestinationSearchInProgress;
317 * Initializes the destination store. Sets the initially selected
318 * destination. If any inserted destinations match this ID, that destination
319 * will be automatically selected. This method must be called after the
320 * print_preview.AppState has been initialized.
321 * @param {boolean} isInAppKioskMode Whether the print preview is in App
324 init: function(isInAppKioskMode) {
325 this.pdfPrinterEnabled_ = !isInAppKioskMode;
326 this.isInAutoSelectMode_ = true;
327 this.createLocalPdfPrintDestination_();
328 if (!this.appState_.selectedDestinationId ||
329 !this.appState_.selectedDestinationOrigin) {
330 this.selectDefaultDestination_();
332 var key = this.getDestinationKey_(
333 this.appState_.selectedDestinationOrigin,
334 this.appState_.selectedDestinationId,
335 this.appState_.selectedDestinationAccount || '');
336 var candidate = this.destinationMap_[key];
337 if (candidate != null) {
338 this.selectDestination(candidate);
339 } else if (this.appState_.selectedDestinationOrigin ==
340 print_preview.Destination.Origin.LOCAL) {
341 this.nativeLayer_.startGetLocalDestinationCapabilities(
342 this.appState_.selectedDestinationId);
343 } else if (this.cloudPrintInterface_ &&
344 (this.appState_.selectedDestinationOrigin ==
345 print_preview.Destination.Origin.COOKIES ||
346 this.appState_.selectedDestinationOrigin ==
347 print_preview.Destination.Origin.DEVICE)) {
348 this.cloudPrintInterface_.printer(
349 this.appState_.selectedDestinationId,
350 this.appState_.selectedDestinationOrigin,
351 this.appState_.selectedDestinationAccount || '');
352 } else if (this.appState_.selectedDestinationOrigin ==
353 print_preview.Destination.Origin.PRIVET) {
354 // TODO(noamsml): Resolve a specific printer instead of listing all
355 // privet printers in this case.
356 this.nativeLayer_.startGetPrivetDestinations();
358 var destinationName = this.appState_.selectedDestinationName || '';
360 // Create a fake selectedDestination_ that is not actually in the
361 // destination store. When the real destination is created, this
362 // destination will be overwritten.
363 this.selectedDestination_ = new print_preview.Destination(
364 this.appState_.selectedDestinationId,
365 print_preview.Destination.Type.LOCAL,
366 print_preview.Destination.Origin.PRIVET,
369 print_preview.Destination.ConnectionStatus.ONLINE);
370 this.selectedDestination_.capabilities =
371 this.appState_.selectedDestinationCapabilities;
373 cr.dispatchSimpleEvent(
375 DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY);
376 } else if (this.appState_.selectedDestinationOrigin ==
377 print_preview.Destination.Origin.EXTENSION) {
378 // TODO(tbarzic): Add support for requesting a single extension's
380 this.startLoadExtensionDestinations();
382 this.selectedDestination_ =
383 print_preview.ExtensionDestinationParser.parse({
384 extensionId: this.appState_.selectedDestinationExtensionId,
385 extensionName: this.appState_.selectedDestinationExtensionName,
386 id: this.appState_.selectedDestinationId,
387 name: this.appState_.selectedDestinationName || ''
390 if (this.appState_.selectedDestinationCapabilities) {
391 this.selectedDestination_.capabilities =
392 this.appState_.selectedDestinationCapabilities;
394 cr.dispatchSimpleEvent(
396 DestinationStore.EventType
397 .CACHED_SELECTED_DESTINATION_INFO_READY);
400 this.selectDefaultDestination_();
406 * Sets the destination store's Google Cloud Print interface.
407 * @param {!cloudprint.CloudPrintInterface} cloudPrintInterface Interface
410 setCloudPrintInterface: function(cloudPrintInterface) {
411 this.cloudPrintInterface_ = cloudPrintInterface;
413 this.cloudPrintInterface_,
414 cloudprint.CloudPrintInterface.EventType.SEARCH_DONE,
415 this.onCloudPrintSearchDone_.bind(this));
417 this.cloudPrintInterface_,
418 cloudprint.CloudPrintInterface.EventType.SEARCH_FAILED,
419 this.onCloudPrintSearchDone_.bind(this));
421 this.cloudPrintInterface_,
422 cloudprint.CloudPrintInterface.EventType.PRINTER_DONE,
423 this.onCloudPrintPrinterDone_.bind(this));
425 this.cloudPrintInterface_,
426 cloudprint.CloudPrintInterface.EventType.PRINTER_FAILED,
427 this.onCloudPrintPrinterFailed_.bind(this));
429 this.cloudPrintInterface_,
430 cloudprint.CloudPrintInterface.EventType.PROCESS_INVITE_DONE,
431 this.onCloudPrintProcessInviteDone_.bind(this));
435 * @return {boolean} Whether only default cloud destinations have been
438 hasOnlyDefaultCloudDestinations: function() {
439 // TODO: Move the logic to print_preview.
440 return this.destinations_.every(function(dest) {
441 return dest.isLocal ||
442 dest.id == print_preview.Destination.GooglePromotedId.DOCS ||
443 dest.id == print_preview.Destination.GooglePromotedId.FEDEX;
448 * @param {print_preview.Destination} destination Destination to select.
450 selectDestination: function(destination) {
451 this.isInAutoSelectMode_ = false;
452 // When auto select expires, DESTINATION_SELECT event has to be dispatched
453 // anyway (see isAutoSelectDestinationInProgress() logic).
454 if (this.autoSelectTimeout_) {
455 clearTimeout(this.autoSelectTimeout_);
456 this.autoSelectTimeout_ = null;
457 } else if (destination == this.selectedDestination_) {
460 if (destination == null) {
461 this.selectedDestination_ = null;
462 cr.dispatchSimpleEvent(
463 this, DestinationStore.EventType.DESTINATION_SELECT);
467 assert(!destination.isProvisional,
468 'Unable to select provisonal destinations');
470 // Update and persist selected destination.
471 this.selectedDestination_ = destination;
472 this.selectedDestination_.isRecent = true;
473 if (destination.id == print_preview.Destination.GooglePromotedId.FEDEX &&
474 !destination.isTosAccepted) {
475 assert(this.cloudPrintInterface_ != null,
476 'Selected FedEx destination, but GCP API is not available');
477 destination.isTosAccepted = true;
478 this.cloudPrintInterface_.updatePrinterTosAcceptance(destination, true);
480 this.appState_.persistSelectedDestination(this.selectedDestination_);
482 if (destination.cloudID &&
483 this.destinations_.some(function(otherDestination) {
484 return otherDestination.cloudID == destination.cloudID &&
485 otherDestination != destination;
487 this.metrics_.record(destination.isPrivet ?
488 print_preview.Metrics.DestinationSearchBucket.
489 PRIVET_DUPLICATE_SELECTED :
490 print_preview.Metrics.DestinationSearchBucket.
491 CLOUD_DUPLICATE_SELECTED);
493 // Notify about selected destination change.
494 cr.dispatchSimpleEvent(
495 this, DestinationStore.EventType.DESTINATION_SELECT);
496 // Request destination capabilities, of not known yet.
497 if (destination.capabilities == null) {
498 if (destination.isPrivet) {
499 this.nativeLayer_.startGetPrivetDestinationCapabilities(
501 } else if (destination.isExtension) {
502 this.nativeLayer_.startGetExtensionDestinationCapabilities(
504 } else if (destination.isLocal) {
505 this.nativeLayer_.startGetLocalDestinationCapabilities(
508 assert(this.cloudPrintInterface_ != null,
509 'Cloud destination selected, but GCP is not enabled');
510 this.cloudPrintInterface_.printer(
511 destination.id, destination.origin, destination.account);
514 cr.dispatchSimpleEvent(
516 DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
521 * Attempts to resolve a provisional destination.
522 * @param {!print_preview.Destination} destinaion Provisional destination
523 * that should be resolved.
525 resolveProvisionalDestination: function(destination) {
527 destination.provisionalType ==
528 print_preview.Destination.ProvisionalType.NEEDS_USB_PERMISSION,
529 'Provisional type cannot be resolved.');
530 this.nativeLayer_.grantExtensionPrinterAccess(destination.id);
534 * Selects 'Save to PDF' destination (since it always exists).
537 selectDefaultDestination_: function() {
538 var saveToPdfKey = this.getDestinationKey_(
539 print_preview.Destination.Origin.LOCAL,
540 print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
542 this.selectDestination(
543 this.destinationMap_[saveToPdfKey] || this.destinations_[0] || null);
546 /** Initiates loading of local print destinations. */
547 startLoadLocalDestinations: function() {
548 if (!this.hasLoadedAllLocalDestinations_) {
549 this.hasLoadedAllLocalDestinations_ = true;
550 this.nativeLayer_.startGetLocalDestinations();
551 this.isLocalDestinationSearchInProgress_ = true;
552 cr.dispatchSimpleEvent(
553 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
557 /** Initiates loading of privet print destinations. */
558 startLoadPrivetDestinations: function() {
559 if (!this.hasLoadedAllPrivetDestinations_) {
560 if (this.privetDestinationSearchInProgress_)
561 clearTimeout(this.privetSearchTimeout_);
562 this.isPrivetDestinationSearchInProgress_ = true;
563 this.nativeLayer_.startGetPrivetDestinations();
564 cr.dispatchSimpleEvent(
565 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
566 this.privetSearchTimeout_ = setTimeout(
567 this.endPrivetPrinterSearch_.bind(this),
568 DestinationStore.PRIVET_SEARCH_DURATION_);
572 /** Initializes loading of extension managed print destinations. */
573 startLoadExtensionDestinations: function() {
574 if (this.hasLoadedAllExtensionDestinations_)
577 if (this.isExtensionDestinationSearchInProgress_)
578 clearTimeout(this.extensionSearchTimeout_);
580 this.isExtensionDestinationSearchInProgress_ = true;
581 this.nativeLayer_.startGetExtensionDestinations();
582 cr.dispatchSimpleEvent(
583 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
584 this.extensionSearchTimeout_ = setTimeout(
585 this.endExtensionPrinterSearch_.bind(this),
586 DestinationStore.EXTENSION_SEARCH_DURATION_);
590 * Initiates loading of cloud destinations.
591 * @param {print_preview.Destination.Origin=} opt_origin Search destinations
592 * for the specified origin only.
594 startLoadCloudDestinations: function(opt_origin) {
595 if (this.cloudPrintInterface_ != null) {
596 var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || [];
597 if (origins.length == 0 ||
598 (opt_origin && origins.indexOf(opt_origin) < 0)) {
599 this.cloudPrintInterface_.search(
600 this.userInfo_.activeUser, opt_origin);
601 cr.dispatchSimpleEvent(
602 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
607 /** Requests load of COOKIE based cloud destinations. */
608 reloadUserCookieBasedDestinations: function() {
609 var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || [];
610 if (origins.indexOf(print_preview.Destination.Origin.COOKIES) >= 0) {
611 cr.dispatchSimpleEvent(
612 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
614 this.startLoadCloudDestinations(
615 print_preview.Destination.Origin.COOKIES);
619 /** Initiates loading of all known destination types. */
620 startLoadAllDestinations: function() {
621 this.startLoadCloudDestinations();
622 this.startLoadLocalDestinations();
623 this.startLoadPrivetDestinations();
624 this.startLoadExtensionDestinations();
628 * Wait for a privet device to be registered.
630 waitForRegister: function(id) {
631 this.nativeLayer_.startGetPrivetDestinations();
632 this.waitForRegisterDestination_ = id;
636 * Event handler for {@code
637 * print_preview.NativeLayer.EventType.PROVISIONAL_DESTINATION_RESOLVED}.
638 * Currently assumes the provisional destination is an extension
640 * Called when a provisional destination resolvement attempt finishes.
641 * The provisional destination is removed from the store and replaced with
642 * a destination created from the resolved destination properties, if any
644 * Emits {@code DestinationStore.EventType.PROVISIONAL_DESTINATION_RESOLVED}
646 * @param {!Event} The event containing the provisional destination ID and
647 * resolved destination description. If the destination was not
648 * successfully resolved, the description will not be set.
651 handleProvisionalDestinationResolved_: function(evt) {
652 var provisionalDestinationIndex = -1;
653 var provisionalDestination = null;
654 for (var i = 0; i < this.destinations_.length; ++i) {
655 if (evt.provisionalId == this.destinations_[i].id) {
656 provisionalDestinationIndex = i;
657 provisionalDestination = this.destinations_[i];
662 if (!provisionalDestination)
665 this.destinations_.splice(provisionalDestinationIndex, 1);
666 delete this.destinationMap_[this.getKey_(provisionalDestination)];
668 var destination = evt.destination ?
669 print_preview.ExtensionDestinationParser.parse(evt.destination) :
673 this.insertIntoStore_(destination);
675 var event = new Event(
676 DestinationStore.EventType.PROVISIONAL_DESTINATION_RESOLVED);
677 event.provisionalId = evt.provisionalId;
678 event.destination = destination;
679 this.dispatchEvent(event);
683 * Inserts {@code destination} to the data store and dispatches a
684 * DESTINATIONS_INSERTED event.
685 * @param {!print_preview.Destination} destination Print destination to
689 insertDestination_: function(destination) {
690 if (this.insertIntoStore_(destination)) {
691 this.destinationsInserted_(destination);
696 * Inserts multiple {@code destinations} to the data store and dispatches
697 * single DESTINATIONS_INSERTED event.
698 * @param {!Array<print_preview.Destination>} destinations Print
699 * destinations to insert.
702 insertDestinations_: function(destinations) {
703 var inserted = false;
704 destinations.forEach(function(destination) {
705 inserted = this.insertIntoStore_(destination) || inserted;
708 this.destinationsInserted_();
713 * Dispatches DESTINATIONS_INSERTED event. In auto select mode, tries to
714 * update selected destination to match {@code appState_} settings.
715 * @param {print_preview.Destination=} opt_destination The only destination
716 * that was changed or skipped if possibly more than one destination was
717 * changed. Used as a hint to limit destination search scope in
718 * {@code isInAutoSelectMode_).
720 destinationsInserted_: function(opt_destination) {
721 cr.dispatchSimpleEvent(
722 this, DestinationStore.EventType.DESTINATIONS_INSERTED);
723 if (this.isInAutoSelectMode_) {
724 var destinationsToSearch =
725 opt_destination && [opt_destination] || this.destinations_;
726 destinationsToSearch.some(function(destination) {
727 if (this.matchPersistedDestination_(destination)) {
728 this.selectDestination(destination);
736 * Updates an existing print destination with capabilities and display name
737 * information. If the destination doesn't already exist, it will be added.
738 * @param {!print_preview.Destination} destination Destination to update.
739 * @return {!print_preview.Destination} The existing destination that was
740 * updated or {@code null} if it was the new destination.
743 updateDestination_: function(destination) {
744 assert(destination.constructor !== Array, 'Single printer expected');
745 var existingDestination = this.destinationMap_[this.getKey_(destination)];
746 if (existingDestination != null) {
747 existingDestination.capabilities = destination.capabilities;
749 this.insertDestination_(destination);
752 if (existingDestination == this.selectedDestination_ ||
753 destination == this.selectedDestination_) {
754 this.appState_.persistSelectedDestination(this.selectedDestination_);
755 cr.dispatchSimpleEvent(
757 DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
760 return existingDestination;
764 * Called when the search for Privet printers is done.
767 endPrivetPrinterSearch_: function() {
768 this.nativeLayer_.stopGetPrivetDestinations();
769 this.isPrivetDestinationSearchInProgress_ = false;
770 this.hasLoadedAllPrivetDestinations_ = true;
771 cr.dispatchSimpleEvent(
772 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
776 * Called when loading of extension managed printers is done.
779 endExtensionPrinterSearch_: function() {
780 this.isExtensionDestinationSearchInProgress_ = false;
781 this.hasLoadedAllExtensionDestinations_ = true;
782 cr.dispatchSimpleEvent(
783 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
784 // Clear initially selected (cached) extension destination if it hasn't
785 // been found among reported extension destinations.
786 if (this.isInAutoSelectMode_ && this.selectedDestination_.isExtension)
787 this.selectDefaultDestination_();
791 * Inserts a destination into the store without dispatching any events.
792 * @return {boolean} Whether the inserted destination was not already in the
796 insertIntoStore_: function(destination) {
797 var key = this.getKey_(destination);
798 var existingDestination = this.destinationMap_[key];
799 if (existingDestination == null) {
800 this.destinations_.push(destination);
801 this.destinationMap_[key] = destination;
803 } else if (existingDestination.connectionStatus ==
804 print_preview.Destination.ConnectionStatus.UNKNOWN &&
805 destination.connectionStatus !=
806 print_preview.Destination.ConnectionStatus.UNKNOWN) {
807 existingDestination.connectionStatus = destination.connectionStatus;
815 * Binds handlers to events.
818 addEventListeners_: function() {
821 print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET,
822 this.onLocalDestinationsSet_.bind(this));
825 print_preview.NativeLayer.EventType.CAPABILITIES_SET,
826 this.onLocalDestinationCapabilitiesSet_.bind(this));
829 print_preview.NativeLayer.EventType.GET_CAPABILITIES_FAIL,
830 this.onGetCapabilitiesFail_.bind(this));
833 print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD,
834 this.onDestinationsReload_.bind(this));
837 print_preview.NativeLayer.EventType.PRIVET_PRINTER_CHANGED,
838 this.onPrivetPrinterAdded_.bind(this));
841 print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET,
842 this.onPrivetCapabilitiesSet_.bind(this));
845 print_preview.NativeLayer.EventType.EXTENSION_PRINTERS_ADDED,
846 this.onExtensionPrintersAdded_.bind(this));
849 print_preview.NativeLayer.EventType.EXTENSION_CAPABILITIES_SET,
850 this.onExtensionCapabilitiesSet_.bind(this));
853 print_preview.NativeLayer.EventType.PROVISIONAL_DESTINATION_RESOLVED,
854 this.handleProvisionalDestinationResolved_.bind(this));
858 * Creates a local PDF print destination.
859 * @return {!print_preview.Destination} Created print destination.
862 createLocalPdfPrintDestination_: function() {
863 // TODO(alekseys): Create PDF printer in the native code and send its
864 // capabilities back with other local printers.
865 if (this.pdfPrinterEnabled_) {
866 this.insertDestination_(new print_preview.Destination(
867 print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
868 print_preview.Destination.Type.LOCAL,
869 print_preview.Destination.Origin.LOCAL,
870 loadTimeData.getString('printToPDF'),
872 print_preview.Destination.ConnectionStatus.ONLINE));
877 * Resets the state of the destination store to its initial state.
881 this.destinations_ = [];
882 this.destinationMap_ = {};
883 this.selectDestination(null);
884 this.loadedCloudOrigins_ = {};
885 this.hasLoadedAllLocalDestinations_ = false;
886 this.hasLoadedAllPrivetDestinations_ = false;
887 this.hasLoadedAllExtensionDestinations_ = false;
889 clearTimeout(this.autoSelectTimeout_);
890 this.autoSelectTimeout_ = setTimeout(
891 this.selectDefaultDestination_.bind(this),
892 DestinationStore.AUTO_SELECT_TIMEOUT_);
896 * Called when the local destinations have been got from the native layer.
897 * @param {Event} event Contains the local destinations.
900 onLocalDestinationsSet_: function(event) {
901 var localDestinations = event.destinationInfos.map(function(destInfo) {
902 return print_preview.LocalDestinationParser.parse(destInfo);
904 this.insertDestinations_(localDestinations);
905 this.isLocalDestinationSearchInProgress_ = false;
906 cr.dispatchSimpleEvent(
907 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
911 * Called when the native layer retrieves the capabilities for the selected
912 * local destination. Updates the destination with new capabilities if the
913 * destination already exists, otherwise it creates a new destination and
914 * then updates its capabilities.
915 * @param {Event} event Contains the capabilities of the local print
919 onLocalDestinationCapabilitiesSet_: function(event) {
920 var destinationId = event.settingsInfo['printerId'];
921 var key = this.getDestinationKey_(
922 print_preview.Destination.Origin.LOCAL,
925 var destination = this.destinationMap_[key];
926 var capabilities = DestinationStore.localizeCapabilities_(
927 event.settingsInfo.capabilities);
928 // Special case for PDF printer (until local printers capabilities are
929 // reported in CDD format too).
931 print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) {
933 destination.capabilities = capabilities;
937 // In case there were multiple capabilities request for this local
938 // destination, just ignore the later ones.
939 if (destination.capabilities != null) {
942 destination.capabilities = capabilities;
944 // TODO(rltoscano): This makes the assumption that the "deviceName" is
945 // the same as "printerName". We should include the "printerName" in
946 // the response. See http://crbug.com/132831.
947 destination = print_preview.LocalDestinationParser.parse(
948 {deviceName: destinationId, printerName: destinationId});
949 destination.capabilities = capabilities;
950 this.insertDestination_(destination);
953 if (this.selectedDestination_ &&
954 this.selectedDestination_.id == destinationId) {
955 cr.dispatchSimpleEvent(this,
956 DestinationStore.EventType.
957 SELECTED_DESTINATION_CAPABILITIES_READY);
962 * Called when a request to get a local destination's print capabilities
963 * fails. If the destination is the initial destination, auto-select another
964 * destination instead.
965 * @param {Event} event Contains the destination ID that failed.
968 onGetCapabilitiesFail_: function(event) {
969 console.error('Failed to get print capabilities for printer ' +
970 event.destinationId);
971 if (this.isInAutoSelectMode_ &&
972 this.sameAsPersistedDestination_(event.destinationId,
973 event.destinationOrigin)) {
974 this.selectDefaultDestination_();
979 * Called when the /search call completes, either successfully or not.
980 * In case of success, stores fetched destinations.
981 * @param {Event} event Contains the request result.
984 onCloudPrintSearchDone_: function(event) {
985 if (event.printers) {
986 this.insertDestinations_(event.printers);
988 if (event.searchDone) {
989 var origins = this.loadedCloudOrigins_[event.user] || [];
990 if (origins.indexOf(event.origin) < 0) {
991 this.loadedCloudOrigins_[event.user] = origins.concat([event.origin]);
994 cr.dispatchSimpleEvent(
995 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
999 * Called when /printer call completes. Updates the specified destination's
1000 * print capabilities.
1001 * @param {Event} event Contains detailed information about the
1005 onCloudPrintPrinterDone_: function(event) {
1006 this.updateDestination_(event.printer);
1010 * Called when the Google Cloud Print interface fails to lookup a
1011 * destination. Selects another destination if the failed destination was
1012 * the initial destination.
1013 * @param {Object} event Contains the ID of the destination that was failed
1017 onCloudPrintPrinterFailed_: function(event) {
1018 if (this.isInAutoSelectMode_ &&
1019 this.sameAsPersistedDestination_(event.destinationId,
1020 event.destinationOrigin)) {
1022 'Failed to fetch last used printer caps: ' + event.destinationId);
1023 this.selectDefaultDestination_();
1028 * Called when printer sharing invitation was processed successfully.
1029 * @param {Event} event Contains detailed information about the invite and
1030 * newly accepted destination (if known).
1033 onCloudPrintProcessInviteDone_: function(event) {
1034 if (event.accept && event.printer) {
1035 // Hint the destination list to promote this new destination.
1036 event.printer.isRecent = true;
1037 this.insertDestination_(event.printer);
1042 * Called when a Privet printer is added to the local network.
1043 * @param {Object} event Contains information about the added printer.
1046 onPrivetPrinterAdded_: function(event) {
1047 if (event.printer.serviceName == this.waitForRegisterDestination_ &&
1048 !event.printer.isUnregistered) {
1049 this.waitForRegisterDestination_ = null;
1050 this.onDestinationsReload_();
1052 this.insertDestinations_(
1053 print_preview.PrivetDestinationParser.parse(event.printer));
1058 * Called when capabilities for a privet printer are set.
1059 * @param {Object} event Contains the capabilities and printer ID.
1062 onPrivetCapabilitiesSet_: function(event) {
1064 print_preview.PrivetDestinationParser.parse(event.printer);
1065 destinations.forEach(function(dest) {
1066 dest.capabilities = event.capabilities;
1067 this.updateDestination_(dest);
1072 * Called when an extension responds to a getExtensionDestinations
1074 * @param {Object} event Contains information about list of printers
1075 * reported by the extension.
1076 * {@code done} parameter is set iff this is the final list of printers
1077 * returned as part of getExtensionDestinations request.
1080 onExtensionPrintersAdded_: function(event) {
1081 this.insertDestinations_(event.printers.map(function(printer) {
1082 return print_preview.ExtensionDestinationParser.parse(printer);
1085 if (event.done && this.isExtensionDestinationSearchInProgress_) {
1086 clearTimeout(this.extensionSearchTimeout_);
1087 this.endExtensionPrinterSearch_();
1092 * Called when capabilities for an extension managed printer are set.
1093 * @param {Object} event Contains the printer's capabilities and ID.
1096 onExtensionCapabilitiesSet_: function(event) {
1097 var destinationKey = this.getDestinationKey_(
1098 print_preview.Destination.Origin.EXTENSION,
1101 var destination = this.destinationMap_[destinationKey];
1104 destination.capabilities = event.capabilities;
1105 this.updateDestination_(destination);
1109 * Called from native layer after the user was requested to sign in, and did
1113 onDestinationsReload_: function() {
1115 this.isInAutoSelectMode_ = true;
1116 this.createLocalPdfPrintDestination_();
1117 this.startLoadAllDestinations();
1120 // TODO(vitalybuka): Remove three next functions replacing Destination.id
1121 // and Destination.origin by complex ID.
1123 * Returns key to be used with {@code destinationMap_}.
1124 * @param {!print_preview.Destination.Origin} origin Destination origin.
1125 * @param {string} id Destination id.
1126 * @param {string} account User account destination is registered for.
1129 getDestinationKey_: function(origin, id, account) {
1130 return origin + '/' + id + '/' + account;
1134 * Returns key to be used with {@code destinationMap_}.
1135 * @param {!print_preview.Destination} destination Destination.
1138 getKey_: function(destination) {
1139 return this.getDestinationKey_(
1140 destination.origin, destination.id, destination.account);
1144 * @param {!print_preview.Destination} destination Destination to match.
1145 * @return {boolean} Whether {@code destination} matches the last user
1149 matchPersistedDestination_: function(destination) {
1150 return !this.appState_.selectedDestinationId ||
1151 !this.appState_.selectedDestinationOrigin ||
1152 this.sameAsPersistedDestination_(
1153 destination.id, destination.origin);
1157 * @param {?string} id Id of the destination.
1158 * @param {?string} origin Oring of the destination.
1159 * @return {boolean} Whether destination is the same as initial.
1162 sameAsPersistedDestination_: function(id, origin) {
1163 return id == this.appState_.selectedDestinationId &&
1164 origin == this.appState_.selectedDestinationOrigin;
1170 DestinationStore: DestinationStore