Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / print_preview / data / destination_store.js
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
5 cr.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 {cloudprint.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         assert(typeof this.appState_.selectedDestinationAccount == 'string');
295         var key = this.getDestinationKey_(
296             this.appState_.selectedDestinationOrigin,
297             this.appState_.selectedDestinationId,
298             this.appState_.selectedDestinationAccount);
299         var candidate = this.destinationMap_[key];
300         if (candidate != null) {
301           this.selectDestination(candidate);
302         } else if (this.appState_.selectedDestinationOrigin ==
303                    print_preview.Destination.Origin.LOCAL) {
304           this.nativeLayer_.startGetLocalDestinationCapabilities(
305               this.appState_.selectedDestinationId);
306         } else if (this.cloudPrintInterface_ &&
307                    (this.appState_.selectedDestinationOrigin ==
308                         print_preview.Destination.Origin.COOKIES ||
309                     this.appState_.selectedDestinationOrigin ==
310                         print_preview.Destination.Origin.DEVICE)) {
311           this.cloudPrintInterface_.printer(
312               this.appState_.selectedDestinationId,
313               this.appState_.selectedDestinationOrigin,
314               this.appState_.selectedDestinationAccount);
315         } else if (this.appState_.selectedDestinationOrigin ==
316                    print_preview.Destination.Origin.PRIVET) {
317           // TODO(noamsml): Resolve a specific printer instead of listing all
318           // privet printers in this case.
319           this.nativeLayer_.startGetPrivetDestinations();
320
321           var destinationName = this.appState_.selectedDestinationName || '';
322
323           // Create a fake selectedDestination_ that is not actually in the
324           // destination store. When the real destination is created, this
325           // destination will be overwritten.
326           this.selectedDestination_ = new print_preview.Destination(
327               this.appState_.selectedDestinationId,
328               print_preview.Destination.Type.LOCAL,
329               print_preview.Destination.Origin.PRIVET,
330               destinationName,
331               false /*isRecent*/,
332               print_preview.Destination.ConnectionStatus.ONLINE);
333           this.selectedDestination_.capabilities =
334               this.appState_.selectedDestinationCapabilities;
335
336           cr.dispatchSimpleEvent(
337             this,
338             DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY);
339         } else {
340           this.selectDefaultDestination_();
341         }
342       }
343     },
344
345     /**
346      * Sets the destination store's Google Cloud Print interface.
347      * @param {!cloudprint.CloudPrintInterface} cloudPrintInterface Interface
348      *     to set.
349      */
350     setCloudPrintInterface: function(cloudPrintInterface) {
351       this.cloudPrintInterface_ = cloudPrintInterface;
352       this.tracker_.add(
353           this.cloudPrintInterface_,
354           cloudprint.CloudPrintInterface.EventType.SEARCH_DONE,
355           this.onCloudPrintSearchDone_.bind(this));
356       this.tracker_.add(
357           this.cloudPrintInterface_,
358           cloudprint.CloudPrintInterface.EventType.SEARCH_FAILED,
359           this.onCloudPrintSearchDone_.bind(this));
360       this.tracker_.add(
361           this.cloudPrintInterface_,
362           cloudprint.CloudPrintInterface.EventType.PRINTER_DONE,
363           this.onCloudPrintPrinterDone_.bind(this));
364       this.tracker_.add(
365           this.cloudPrintInterface_,
366           cloudprint.CloudPrintInterface.EventType.PRINTER_FAILED,
367           this.onCloudPrintPrinterFailed_.bind(this));
368       this.tracker_.add(
369           this.cloudPrintInterface_,
370           cloudprint.CloudPrintInterface.EventType.PROCESS_INVITE_DONE,
371           this.onCloudPrintProcessInviteDone_.bind(this));
372     },
373
374     /**
375      * @return {boolean} Whether only default cloud destinations have been
376      *     loaded.
377      */
378     hasOnlyDefaultCloudDestinations: function() {
379       // TODO: Move the logic to print_preview.
380       return this.destinations_.every(function(dest) {
381         return dest.isLocal ||
382             dest.id == print_preview.Destination.GooglePromotedId.DOCS ||
383             dest.id == print_preview.Destination.GooglePromotedId.FEDEX;
384       });
385     },
386
387     /**
388      * @param {print_preview.Destination} destination Destination to select.
389      */
390     selectDestination: function(destination) {
391       this.isInAutoSelectMode_ = false;
392       // When auto select expires, DESTINATION_SELECT event has to be dispatched
393       // anyway (see isAutoSelectDestinationInProgress() logic).
394       if (this.autoSelectTimeout_) {
395         clearTimeout(this.autoSelectTimeout_);
396         this.autoSelectTimeout_ = null;
397       } else if (destination == this.selectedDestination_) {
398         return;
399       }
400       if (destination == null) {
401         this.selectedDestination_ = null;
402         cr.dispatchSimpleEvent(
403             this, DestinationStore.EventType.DESTINATION_SELECT);
404         return;
405       }
406       // Update and persist selected destination.
407       this.selectedDestination_ = destination;
408       this.selectedDestination_.isRecent = true;
409       if (destination.id == print_preview.Destination.GooglePromotedId.FEDEX &&
410           !destination.isTosAccepted) {
411         assert(this.cloudPrintInterface_ != null,
412                'Selected FedEx destination, but GCP API is not available');
413         destination.isTosAccepted = true;
414         this.cloudPrintInterface_.updatePrinterTosAcceptance(destination, true);
415       }
416       this.appState_.persistSelectedDestination(this.selectedDestination_);
417       // Adjust metrics.
418       if (destination.cloudID &&
419           this.destinations_.some(function(otherDestination) {
420             return otherDestination.cloudID == destination.cloudID &&
421                 otherDestination != destination;
422           })) {
423         this.metrics_.record(destination.isPrivet ?
424             print_preview.Metrics.DestinationSearchBucket.
425                 PRIVET_DUPLICATE_SELECTED :
426             print_preview.Metrics.DestinationSearchBucket.
427                 CLOUD_DUPLICATE_SELECTED);
428       }
429       // Notify about selected destination change.
430       cr.dispatchSimpleEvent(
431           this, DestinationStore.EventType.DESTINATION_SELECT);
432       // Request destination capabilities, of not known yet.
433       if (destination.capabilities == null) {
434         if (destination.isPrivet) {
435           this.nativeLayer_.startGetPrivetDestinationCapabilities(
436               destination.id);
437         }
438         else if (destination.isLocal) {
439           this.nativeLayer_.startGetLocalDestinationCapabilities(
440               destination.id);
441         } else {
442           assert(this.cloudPrintInterface_ != null,
443                  'Cloud destination selected, but GCP is not enabled');
444           this.cloudPrintInterface_.printer(
445               destination.id, destination.origin, destination.account);
446         }
447       } else {
448         cr.dispatchSimpleEvent(
449             this,
450             DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
451       }
452     },
453
454     /**
455      * Selects 'Save to PDF' destination (since it always exists).
456      * @private
457      */
458     selectDefaultDestination_: function() {
459       var saveToPdfKey = this.getDestinationKey_(
460           print_preview.Destination.Origin.LOCAL,
461           print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
462           '');
463       this.selectDestination(
464           this.destinationMap_[saveToPdfKey] || this.destinations_[0] || null);
465     },
466
467     /** Initiates loading of local print destinations. */
468     startLoadLocalDestinations: function() {
469       if (!this.hasLoadedAllLocalDestinations_) {
470         this.hasLoadedAllLocalDestinations_ = true;
471         this.nativeLayer_.startGetLocalDestinations();
472         this.isLocalDestinationSearchInProgress_ = true;
473         cr.dispatchSimpleEvent(
474             this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
475       }
476     },
477
478     /** Initiates loading of privet print destinations. */
479     startLoadPrivetDestinations: function() {
480       if (!this.hasLoadedAllPrivetDestinations_) {
481         this.isPrivetDestinationSearchInProgress_ = true;
482         this.nativeLayer_.startGetPrivetDestinations();
483         cr.dispatchSimpleEvent(
484             this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
485         this.privetSearchTimeout_ = setTimeout(
486             this.endPrivetPrinterSearch_.bind(this),
487             DestinationStore.PRIVET_SEARCH_DURATION_);
488       }
489     },
490
491     /**
492      * Initiates loading of cloud destinations.
493      * @param {print_preview.Destination.Origin=} opt_origin Search destinations
494      *     for the specified origin only.
495      */
496     startLoadCloudDestinations: function(opt_origin) {
497       if (this.cloudPrintInterface_ != null) {
498         var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || [];
499         if (origins.length == 0 ||
500             (opt_origin && origins.indexOf(opt_origin) < 0)) {
501           this.cloudPrintInterface_.search(
502               this.userInfo_.activeUser, opt_origin);
503           cr.dispatchSimpleEvent(
504               this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
505         }
506       }
507     },
508
509     /** Requests load of COOKIE based cloud destinations. */
510     reloadUserCookieBasedDestinations: function() {
511       var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || [];
512       if (origins.indexOf(print_preview.Destination.Origin.COOKIES) >= 0) {
513         cr.dispatchSimpleEvent(
514             this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
515       } else {
516         this.startLoadCloudDestinations(
517             print_preview.Destination.Origin.COOKIES);
518       }
519     },
520
521     /** Initiates loading of all known destination types. */
522     startLoadAllDestinations: function() {
523       this.startLoadCloudDestinations();
524       this.startLoadLocalDestinations();
525       this.startLoadPrivetDestinations();
526     },
527
528     /**
529      * Wait for a privet device to be registered.
530      */
531     waitForRegister: function(id) {
532       this.nativeLayer_.startGetPrivetDestinations();
533       this.waitForRegisterDestination_ = id;
534     },
535
536     /**
537      * Inserts {@code destination} to the data store and dispatches a
538      * DESTINATIONS_INSERTED event.
539      * @param {!print_preview.Destination} destination Print destination to
540      *     insert.
541      * @private
542      */
543     insertDestination_: function(destination) {
544       if (this.insertIntoStore_(destination)) {
545         this.destinationsInserted_(destination);
546       }
547     },
548
549     /**
550      * Inserts multiple {@code destinations} to the data store and dispatches
551      * single DESTINATIONS_INSERTED event.
552      * @param {!Array.<print_preview.Destination>} destinations Print
553      *     destinations to insert.
554      * @private
555      */
556     insertDestinations_: function(destinations) {
557       var inserted = false;
558       destinations.forEach(function(destination) {
559         inserted = this.insertIntoStore_(destination) || inserted;
560       }, this);
561       if (inserted) {
562         this.destinationsInserted_();
563       }
564     },
565
566     /**
567      * Dispatches DESTINATIONS_INSERTED event. In auto select mode, tries to
568      * update selected destination to match {@code appState_} settings.
569      * @param {print_preview.Destination=} opt_destination The only destination
570      *     that was changed or skipped if possibly more than one destination was
571      *     changed. Used as a hint to limit destination search scope in
572      *     {@code isInAutoSelectMode_).
573      */
574     destinationsInserted_: function(opt_destination) {
575       cr.dispatchSimpleEvent(
576           this, DestinationStore.EventType.DESTINATIONS_INSERTED);
577       if (this.isInAutoSelectMode_) {
578         var destinationsToSearch =
579             opt_destination && [opt_destination] || this.destinations_;
580         destinationsToSearch.some(function(destination) {
581           if (this.matchPersistedDestination_(destination)) {
582             this.selectDestination(destination);
583             return true;
584           }
585         }, this);
586       }
587     },
588
589     /**
590      * Updates an existing print destination with capabilities and display name
591      * information. If the destination doesn't already exist, it will be added.
592      * @param {!print_preview.Destination} destination Destination to update.
593      * @return {!print_preview.Destination} The existing destination that was
594      *     updated or {@code null} if it was the new destination.
595      * @private
596      */
597     updateDestination_: function(destination) {
598       assert(destination.constructor !== Array, 'Single printer expected');
599       var existingDestination = this.destinationMap_[this.getKey_(destination)];
600       if (existingDestination != null) {
601         existingDestination.capabilities = destination.capabilities;
602       } else {
603         this.insertDestination_(destination);
604       }
605
606       if (existingDestination == this.selectedDestination_ ||
607           destination == this.selectedDestination_) {
608         this.appState_.persistSelectedDestination(this.selectedDestination_);
609         cr.dispatchSimpleEvent(
610             this,
611             DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
612       }
613
614       return existingDestination;
615     },
616
617     /**
618      * Called when the search for Privet printers is done.
619      * @private
620      */
621     endPrivetPrinterSearch_: function() {
622       this.nativeLayer_.stopGetPrivetDestinations();
623       this.isPrivetDestinationSearchInProgress_ = false;
624       this.hasLoadedAllPrivetDestinations_ = true;
625       cr.dispatchSimpleEvent(
626           this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
627     },
628
629     /**
630      * Inserts a destination into the store without dispatching any events.
631      * @return {boolean} Whether the inserted destination was not already in the
632      *     store.
633      * @private
634      */
635     insertIntoStore_: function(destination) {
636       var key = this.getKey_(destination);
637       var existingDestination = this.destinationMap_[key];
638       if (existingDestination == null) {
639         this.destinations_.push(destination);
640         this.destinationMap_[key] = destination;
641         return true;
642       } else if (existingDestination.connectionStatus ==
643                      print_preview.Destination.ConnectionStatus.UNKNOWN &&
644                  destination.connectionStatus !=
645                      print_preview.Destination.ConnectionStatus.UNKNOWN) {
646         existingDestination.connectionStatus = destination.connectionStatus;
647         return true;
648       } else {
649         return false;
650       }
651     },
652
653     /**
654      * Binds handlers to events.
655      * @private
656      */
657     addEventListeners_: function() {
658       this.tracker_.add(
659           this.nativeLayer_,
660           print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET,
661           this.onLocalDestinationsSet_.bind(this));
662       this.tracker_.add(
663           this.nativeLayer_,
664           print_preview.NativeLayer.EventType.CAPABILITIES_SET,
665           this.onLocalDestinationCapabilitiesSet_.bind(this));
666       this.tracker_.add(
667           this.nativeLayer_,
668           print_preview.NativeLayer.EventType.GET_CAPABILITIES_FAIL,
669           this.onGetCapabilitiesFail_.bind(this));
670       this.tracker_.add(
671           this.nativeLayer_,
672           print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD,
673           this.onDestinationsReload_.bind(this));
674       this.tracker_.add(
675           this.nativeLayer_,
676           print_preview.NativeLayer.EventType.PRIVET_PRINTER_CHANGED,
677           this.onPrivetPrinterAdded_.bind(this));
678       this.tracker_.add(
679           this.nativeLayer_,
680           print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET,
681           this.onPrivetCapabilitiesSet_.bind(this));
682     },
683
684     /**
685      * Creates a local PDF print destination.
686      * @return {!print_preview.Destination} Created print destination.
687      * @private
688      */
689     createLocalPdfPrintDestination_: function() {
690       // TODO(alekseys): Create PDF printer in the native code and send its
691       // capabilities back with other local printers.
692       if (this.pdfPrinterEnabled_) {
693         this.insertDestination_(new print_preview.Destination(
694             print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
695             print_preview.Destination.Type.LOCAL,
696             print_preview.Destination.Origin.LOCAL,
697             loadTimeData.getString('printToPDF'),
698             false /*isRecent*/,
699             print_preview.Destination.ConnectionStatus.ONLINE));
700       }
701     },
702
703     /**
704      * Resets the state of the destination store to its initial state.
705      * @private
706      */
707     reset_: function() {
708       this.destinations_ = [];
709       this.destinationMap_ = {};
710       this.selectDestination(null);
711       this.loadedCloudOrigins_ = {};
712       this.hasLoadedAllLocalDestinations_ = false;
713
714       clearTimeout(this.autoSelectTimeout_);
715       this.autoSelectTimeout_ = setTimeout(
716           this.selectDefaultDestination_.bind(this),
717           DestinationStore.AUTO_SELECT_TIMEOUT_);
718     },
719
720     /**
721      * Called when the local destinations have been got from the native layer.
722      * @param {Event} event Contains the local destinations.
723      * @private
724      */
725     onLocalDestinationsSet_: function(event) {
726       var localDestinations = event.destinationInfos.map(function(destInfo) {
727         return print_preview.LocalDestinationParser.parse(destInfo);
728       });
729       this.insertDestinations_(localDestinations);
730       this.isLocalDestinationSearchInProgress_ = false;
731       cr.dispatchSimpleEvent(
732           this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
733     },
734
735     /**
736      * Called when the native layer retrieves the capabilities for the selected
737      * local destination. Updates the destination with new capabilities if the
738      * destination already exists, otherwise it creates a new destination and
739      * then updates its capabilities.
740      * @param {Event} event Contains the capabilities of the local print
741      *     destination.
742      * @private
743      */
744     onLocalDestinationCapabilitiesSet_: function(event) {
745       var destinationId = event.settingsInfo['printerId'];
746       var key = this.getDestinationKey_(
747           print_preview.Destination.Origin.LOCAL,
748           destinationId,
749           '');
750       var destination = this.destinationMap_[key];
751       var capabilities = DestinationStore.localizeCapabilities_(
752           event.settingsInfo.capabilities);
753       // Special case for PDF printer (until local printers capabilities are
754       // reported in CDD format too).
755       if (destinationId ==
756           print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) {
757         if (destination) {
758           destination.capabilities = capabilities;
759         }
760       } else {
761         if (destination) {
762           // In case there were multiple capabilities request for this local
763           // destination, just ignore the later ones.
764           if (destination.capabilities != null) {
765             return;
766           }
767           destination.capabilities = capabilities;
768         } else {
769           // TODO(rltoscano): This makes the assumption that the "deviceName" is
770           // the same as "printerName". We should include the "printerName" in
771           // the response. See http://crbug.com/132831.
772           destination = print_preview.LocalDestinationParser.parse(
773               {deviceName: destinationId, printerName: destinationId});
774           destination.capabilities = capabilities;
775           this.insertDestination_(destination);
776         }
777       }
778       if (this.selectedDestination_ &&
779           this.selectedDestination_.id == destinationId) {
780         cr.dispatchSimpleEvent(this,
781                                DestinationStore.EventType.
782                                    SELECTED_DESTINATION_CAPABILITIES_READY);
783       }
784     },
785
786     /**
787      * Called when a request to get a local destination's print capabilities
788      * fails. If the destination is the initial destination, auto-select another
789      * destination instead.
790      * @param {Event} event Contains the destination ID that failed.
791      * @private
792      */
793     onGetCapabilitiesFail_: function(event) {
794       console.error('Failed to get print capabilities for printer ' +
795                     event.destinationId);
796       if (this.isInAutoSelectMode_ &&
797           this.sameAsPersistedDestination_(event.destinationId,
798                                            event.destinationOrigin)) {
799         this.selectDefaultDestination_();
800       }
801     },
802
803     /**
804      * Called when the /search call completes, either successfully or not.
805      * In case of success, stores fetched destinations.
806      * @param {Event} event Contains the request result.
807      * @private
808      */
809     onCloudPrintSearchDone_: function(event) {
810       if (event.printers) {
811         this.insertDestinations_(event.printers);
812       }
813       if (event.searchDone) {
814         var origins = this.loadedCloudOrigins_[event.user] || [];
815         if (origins.indexOf(event.origin) < 0) {
816           this.loadedCloudOrigins_[event.user] = origins.concat([event.origin]);
817         }
818       }
819       cr.dispatchSimpleEvent(
820           this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
821     },
822
823     /**
824      * Called when /printer call completes. Updates the specified destination's
825      * print capabilities.
826      * @param {Event} event Contains detailed information about the
827      *     destination.
828      * @private
829      */
830     onCloudPrintPrinterDone_: function(event) {
831       this.updateDestination_(event.printer);
832     },
833
834     /**
835      * Called when the Google Cloud Print interface fails to lookup a
836      * destination. Selects another destination if the failed destination was
837      * the initial destination.
838      * @param {Object} event Contains the ID of the destination that was failed
839      *     to be looked up.
840      * @private
841      */
842     onCloudPrintPrinterFailed_: function(event) {
843       if (this.isInAutoSelectMode_ &&
844           this.sameAsPersistedDestination_(event.destinationId,
845                                            event.destinationOrigin)) {
846         console.error(
847             'Failed to fetch last used printer caps: ' + event.destinationId);
848         this.selectDefaultDestination_();
849       }
850     },
851
852     /**
853      * Called when printer sharing invitation was processed successfully.
854      * @param {Event} event Contains detailed information about the invite and
855      *     newly accepted destination (if known).
856      * @private
857      */
858     onCloudPrintProcessInviteDone_: function(event) {
859       if (event.accept && event.printer) {
860         // Hint the destination list to promote this new destination.
861         event.printer.isRecent = true;
862         this.insertDestination_(event.printer);
863       }
864     },
865
866     /**
867      * Called when a Privet printer is added to the local network.
868      * @param {Object} event Contains information about the added printer.
869      * @private
870      */
871     onPrivetPrinterAdded_: function(event) {
872       if (event.printer.serviceName == this.waitForRegisterDestination_ &&
873           !event.printer.isUnregistered) {
874         this.waitForRegisterDestination_ = null;
875         this.onDestinationsReload_();
876       } else {
877         this.insertDestinations_(
878             print_preview.PrivetDestinationParser.parse(event.printer));
879       }
880     },
881
882     /**
883      * Called when capabilities for a privet printer are set.
884      * @param {Object} event Contains the capabilities and printer ID.
885      * @private
886      */
887     onPrivetCapabilitiesSet_: function(event) {
888       var destinationId = event.printerId;
889       var destinations =
890           print_preview.PrivetDestinationParser.parse(event.printer);
891       destinations.forEach(function(dest) {
892         dest.capabilities = event.capabilities;
893         this.updateDestination_(dest);
894       }, this);
895     },
896
897     /**
898      * Called from native layer after the user was requested to sign in, and did
899      * so successfully.
900      * @private
901      */
902     onDestinationsReload_: function() {
903       this.reset_();
904       this.isInAutoSelectMode_ = true;
905       this.createLocalPdfPrintDestination_();
906       this.startLoadAllDestinations();
907     },
908
909     // TODO(vitalybuka): Remove three next functions replacing Destination.id
910     //    and Destination.origin by complex ID.
911     /**
912      * Returns key to be used with {@code destinationMap_}.
913      * @param {!print_preview.Destination.Origin} origin Destination origin.
914      * @param {string} id Destination id.
915      * @param {string} account User account destination is registered for.
916      * @private
917      */
918     getDestinationKey_: function(origin, id, account) {
919       return origin + '/' + id + '/' + account;
920     },
921
922     /**
923      * Returns key to be used with {@code destinationMap_}.
924      * @param {!print_preview.Destination} destination Destination.
925      * @private
926      */
927     getKey_: function(destination) {
928       return this.getDestinationKey_(
929           destination.origin, destination.id, destination.account);
930     },
931
932     /**
933      * @param {!print_preview.Destination} destination Destination to match.
934      * @return {boolean} Whether {@code destination} matches the last user
935      *     selected one.
936      * @private
937      */
938     matchPersistedDestination_: function(destination) {
939       return !this.appState_.selectedDestinationId ||
940              !this.appState_.selectedDestinationOrigin ||
941              this.sameAsPersistedDestination_(
942                  destination.id, destination.origin);
943     },
944
945     /**
946      * @param {?string} id Id of the destination.
947      * @param {?string} origin Oring of the destination.
948      * @return {boolean} Whether destination is the same as initial.
949      * @private
950      */
951     sameAsPersistedDestination_: function(id, origin) {
952       return id == this.appState_.selectedDestinationId &&
953              origin == this.appState_.selectedDestinationOrigin;
954     }
955   };
956
957   // Export
958   return {
959     DestinationStore: DestinationStore
960   };
961 });