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