Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / print_preview / data / destination_store.js
blob59539f27eeb5f50d7f185d866bf7b521b4db9477
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() {
6   'use strict';
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.AppState} appState Application state.
14    * @param {!print_preview.Metrics} metrics Metrics.
15    * @constructor
16    * @extends {cr.EventTarget}
17    */
18   function DestinationStore(nativeLayer, appState, metrics) {
19     cr.EventTarget.call(this);
21     /**
22      * Used to fetch local print destinations.
23      * @type {!print_preview.NativeLayer}
24      * @private
25      */
26     this.nativeLayer_ = nativeLayer;
28     /**
29      * Used to load and persist the selected destination.
30      * @type {!print_preview.AppState}
31      * @private
32      */
33     this.appState_ = appState;
35     /**
36      * Used to track metrics.
37      * @type {!print_preview.AppState}
38      * @private
39      */
40     this.metrics_ = metrics;
42     /**
43      * Internal backing store for the data store.
44      * @type {!Array.<!print_preview.Destination>}
45      * @private
46      */
47     this.destinations_ = [];
49     /**
50      * Cache used for constant lookup of destinations by origin and id.
51      * @type {object.<string, !print_preview.Destination>}
52      * @private
53      */
54     this.destinationMap_ = {};
56     /**
57      * Currently selected destination.
58      * @type {print_preview.Destination}
59      * @private
60      */
61     this.selectedDestination_ = null;
63     /**
64      * Initial destination ID used to auto-select the first inserted destination
65      * that matches. If {@code null}, the first destination inserted into the
66      * store will be selected.
67      * @type {?string}
68      * @private
69      */
70     this.initialDestinationId_ = null;
72     /**
73      * Initial origin used to auto-select destination.
74      * @type {print_preview.Destination.Origin}
75      * @private
76      */
77     this.initialDestinationOrigin_ = print_preview.Destination.Origin.LOCAL;
79     /**
80      * Whether the destination store will auto select the destination that
81      * matches the initial destination.
82      * @type {boolean}
83      * @private
84      */
85     this.isInAutoSelectMode_ = false;
87     /**
88      * Event tracker used to track event listeners of the destination store.
89      * @type {!EventTracker}
90      * @private
91      */
92     this.tracker_ = new EventTracker();
94     /**
95      * Used to fetch cloud-based print destinations.
96      * @type {print_preview.CloudPrintInterface}
97      * @private
98      */
99     this.cloudPrintInterface_ = null;
101     /**
102      * Whether the destination store has already loaded or is loading all cloud
103      * destinations.
104      * @type {boolean}
105      * @private
106      */
107     this.hasLoadedAllCloudDestinations_ = false;
109     /**
110      * ID of a timeout after the initial destination ID is set. If no inserted
111      * destination matches the initial destination ID after the specified
112      * timeout, the first destination in the store will be automatically
113      * selected.
114      * @type {?number}
115      * @private
116      */
117     this.autoSelectTimeout_ = null;
119     /**
120      * Whether a search for local destinations is in progress.
121      * @type {boolean}
122      * @private
123      */
124     this.isLocalDestinationSearchInProgress_ = false;
126     /**
127      * Whether the destination store has already loaded or is loading all local
128      * destinations.
129      * @type {boolean}
130      * @private
131      */
132     this.hasLoadedAllLocalDestinations_ = false;
134     /**
135      * Whether a search for privet destinations is in progress.
136      * @type {boolean}
137      * @private
138      */
139     this.isPrivetDestinationSearchInProgress_ = false;
141     /**
142      * Whether the destination store has already loaded or is loading all privet
143      * destinations.
144      * @type {boolean}
145      * @private
146      */
147     this.hasLoadedAllPrivetDestinations_ = false;
149     /**
150      * ID of a timeout after the start of a privet search to end that privet
151      * search.
152      * @type {?number}
153      * @private
154      */
155     this.privetSearchTimeout_ = null;
157     /**
158      * Map set of IDs of privet register promo destinations click.
159      * @type {!Object.<string, bool>}
160      * @private
161      */
162     this.waitForRegisterDestination_ = null;
164     this.addEventListeners_();
165     this.reset_();
166   };
168   /**
169    * Event types dispatched by the data store.
170    * @enum {string}
171    */
172   DestinationStore.EventType = {
173     DESTINATION_SEARCH_DONE:
174         'print_preview.DestinationStore.DESTINATION_SEARCH_DONE',
175     DESTINATION_SEARCH_STARTED:
176         'print_preview.DestinationStore.DESTINATION_SEARCH_STARTED',
177     DESTINATION_SELECT: 'print_preview.DestinationStore.DESTINATION_SELECT',
178     DESTINATIONS_INSERTED:
179         'print_preview.DestinationStore.DESTINATIONS_INSERTED',
180     CACHED_SELECTED_DESTINATION_INFO_READY:
181         'print_preview.DestinationStore.CACHED_SELECTED_DESTINATION_INFO_READY',
182     SELECTED_DESTINATION_CAPABILITIES_READY:
183         'print_preview.DestinationStore.SELECTED_DESTINATION_CAPABILITIES_READY'
184   };
186   /**
187    * Delay in milliseconds before the destination store ignores the initial
188    * destination ID and just selects any printer (since the initial destination
189    * was not found).
190    * @type {number}
191    * @const
192    * @private
193    */
194   DestinationStore.AUTO_SELECT_TIMEOUT_ = 15000;
196   /**
197    * Amount of time spent searching for privet destination, in milliseconds.
198    * @type {number}
199    * @const
200    * @private
201    */
202   DestinationStore.PRIVET_SEARCH_DURATION_ = 2000;
204   /**
205    * Creates a local PDF print destination.
206    * @return {!print_preview.Destination} Created print destination.
207    * @private
208    */
209   DestinationStore.createLocalPdfPrintDestination_ = function() {
210     var dest = new print_preview.Destination(
211         print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
212         print_preview.Destination.Type.LOCAL,
213         print_preview.Destination.Origin.LOCAL,
214         localStrings.getString('printToPDF'),
215         false /*isRecent*/,
216         print_preview.Destination.ConnectionStatus.ONLINE);
217     dest.capabilities = {
218       version: '1.0',
219       printer: {
220         page_orientation: {
221           option: [
222             {type: 'AUTO', is_default: true},
223             {type: 'PORTRAIT'},
224             {type: 'LANDSCAPE'}
225           ]
226         },
227         color: { option: [{type: 'STANDARD_COLOR', is_default: true}] }
228       }
229     };
230     return dest;
231   };
233   DestinationStore.prototype = {
234     __proto__: cr.EventTarget.prototype,
236     /**
237      * @return {!Array.<!print_preview.Destination>} List of destinations in
238      *     the store.
239      */
240     get destinations() {
241       return this.destinations_.slice(0);
242     },
244     /**
245      * @return {print_preview.Destination} The currently selected destination or
246      *     {@code null} if none is selected.
247      */
248     get selectedDestination() {
249       return this.selectedDestination_;
250     },
252     /**
253      * @return {boolean} Whether a search for local destinations is in progress.
254      */
255     get isLocalDestinationSearchInProgress() {
256       return this.isLocalDestinationSearchInProgress_ ||
257         this.isPrivetDestinationSearchInProgress_;
258     },
260     /**
261      * @return {boolean} Whether a search for cloud destinations is in progress.
262      */
263     get isCloudDestinationSearchInProgress() {
264       return this.cloudPrintInterface_ &&
265              this.cloudPrintInterface_.isCloudDestinationSearchInProgress;
266     },
268     /**
269      * Initializes the destination store. Sets the initially selected
270      * destination. If any inserted destinations match this ID, that destination
271      * will be automatically selected. This method must be called after the
272      * print_preview.AppState has been initialized.
273      * @param {?string} systemDefaultDestinationId ID of the system default
274      *     destination.
275      * @private
276      */
277     init: function(systemDefaultDestinationId) {
278       if (this.appState_.selectedDestinationId &&
279           this.appState_.selectedDestinationOrigin) {
280         this.initialDestinationId_ = this.appState_.selectedDestinationId;
281         this.initialDestinationOrigin_ =
282             this.appState_.selectedDestinationOrigin;
283       } else if (systemDefaultDestinationId) {
284         this.initialDestinationId_ = systemDefaultDestinationId;
285         this.initialDestinationOrigin_ = print_preview.Destination.Origin.LOCAL;
286       }
287       this.isInAutoSelectMode_ = true;
288       if (!this.initialDestinationId_ || !this.initialDestinationOrigin_) {
289         this.onAutoSelectFailed_();
290       } else {
291         var key = this.getDestinationKey_(this.initialDestinationOrigin_,
292                                           this.initialDestinationId_);
293         var candidate = this.destinationMap_[key];
294         if (candidate != null) {
295           this.selectDestination(candidate);
296         } else if (this.initialDestinationOrigin_ ==
297                    print_preview.Destination.Origin.LOCAL) {
298           this.nativeLayer_.startGetLocalDestinationCapabilities(
299               this.initialDestinationId_);
300         } else if (this.cloudPrintInterface_ &&
301                    (this.initialDestinationOrigin_ ==
302                     print_preview.Destination.Origin.COOKIES ||
303                     this.initialDestinationOrigin_ ==
304                     print_preview.Destination.Origin.DEVICE)) {
305           this.cloudPrintInterface_.printer(this.initialDestinationId_,
306                                             this.initialDestinationOrigin_);
307         } else if (this.initialDestinationOrigin_ ==
308                    print_preview.Destination.Origin.PRIVET) {
309           // TODO(noamsml): Resolve a specific printer instead of listing all
310           // privet printers in this case.
311           this.nativeLayer_.startGetPrivetDestinations();
313           var destinationName = this.appState_.selectedDestinationName || '';
315           // Create a fake selectedDestination_ that is not actually in the
316           // destination store. When the real destination is created, this
317           // destination will be overwritten.
318           this.selectedDestination_ = new print_preview.Destination(
319               this.initialDestinationId_,
320               print_preview.Destination.Type.LOCAL,
321               print_preview.Destination.Origin.PRIVET,
322               destinationName,
323               false /*isRecent*/,
324               print_preview.Destination.ConnectionStatus.ONLINE);
325           this.selectedDestination_.capabilities =
326               this.appState_.selectedDestinationCapabilities;
328           cr.dispatchSimpleEvent(
329             this,
330             DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY);
332         } else {
333           this.onAutoSelectFailed_();
334         }
335       }
336     },
338     /**
339      * Sets the destination store's Google Cloud Print interface.
340      * @param {!print_preview.CloudPrintInterface} cloudPrintInterface Interface
341      *     to set.
342      */
343     setCloudPrintInterface: function(cloudPrintInterface) {
344       this.cloudPrintInterface_ = cloudPrintInterface;
345       this.tracker_.add(
346           this.cloudPrintInterface_,
347           cloudprint.CloudPrintInterface.EventType.SEARCH_DONE,
348           this.onCloudPrintSearchDone_.bind(this));
349       this.tracker_.add(
350           this.cloudPrintInterface_,
351           cloudprint.CloudPrintInterface.EventType.SEARCH_FAILED,
352           this.onCloudPrintSearchFailed_.bind(this));
353       this.tracker_.add(
354           this.cloudPrintInterface_,
355           cloudprint.CloudPrintInterface.EventType.PRINTER_DONE,
356           this.onCloudPrintPrinterDone_.bind(this));
357       this.tracker_.add(
358           this.cloudPrintInterface_,
359           cloudprint.CloudPrintInterface.EventType.PRINTER_FAILED,
360           this.onCloudPrintPrinterFailed_.bind(this));
361     },
363     /**
364      * @return {boolean} Whether only default cloud destinations have been
365      *     loaded.
366      */
367     hasOnlyDefaultCloudDestinations: function() {
368       return this.destinations_.every(function(dest) {
369         return dest.isLocal ||
370             dest.id == print_preview.Destination.GooglePromotedId.DOCS ||
371             dest.id == print_preview.Destination.GooglePromotedId.FEDEX;
372       });
373     },
375     /** @param {!print_preview.Destination} Destination to select. */
376     selectDestination: function(destination) {
377       this.selectedDestination_ = destination;
378       this.selectedDestination_.isRecent = true;
379       this.isInAutoSelectMode_ = false;
380       if (this.autoSelectTimeout_ != null) {
381         clearTimeout(this.autoSelectTimeout_);
382         this.autoSelectTimeout_ = null;
383       }
384       if (destination.id == print_preview.Destination.GooglePromotedId.FEDEX &&
385           !destination.isTosAccepted) {
386         assert(this.cloudPrintInterface_ != null,
387                'Selected FedEx Office destination, but Google Cloud Print is ' +
388                'not enabled');
389         destination.isTosAccepted = true;
390         this.cloudPrintInterface_.updatePrinterTosAcceptance(destination.id,
391                                                              destination.origin,
392                                                              true);
393       }
394       this.appState_.persistSelectedDestination(this.selectedDestination_);
396       if (destination.cloudID &&
397           this.destinations.some(function(otherDestination) {
398             return otherDestination.cloudID == destination.cloudID &&
399                 otherDestination != destination;
400             })) {
401         if (destination.isPrivet) {
402           this.metrics_.incrementDestinationSearchBucket(
403               print_preview.Metrics.DestinationSearchBucket.
404                   PRIVET_DUPLICATE_SELECTED);
405         } else {
406           this.metrics_.incrementDestinationSearchBucket(
407               print_preview.Metrics.DestinationSearchBucket.
408                   CLOUD_DUPLICATE_SELECTED);
409         }
410       }
412       cr.dispatchSimpleEvent(
413           this, DestinationStore.EventType.DESTINATION_SELECT);
414       if (destination.capabilities == null) {
415         if (destination.isPrivet) {
416           this.nativeLayer_.startGetPrivetDestinationCapabilities(
417               destination.id);
418         }
419         else if (destination.isLocal) {
420           this.nativeLayer_.startGetLocalDestinationCapabilities(
421               destination.id);
422         } else {
423           assert(this.cloudPrintInterface_ != null,
424                  'Selected destination is a cloud destination, but Google ' +
425                  'Cloud Print is not enabled');
426           this.cloudPrintInterface_.printer(destination.id,
427                                             destination.origin);
428         }
429       } else {
430         cr.dispatchSimpleEvent(
431             this,
432             DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
433       }
434     },
436     /**
437      * Inserts a print destination to the data store and dispatches a
438      * DESTINATIONS_INSERTED event. If the destination matches the initial
439      * destination ID, then the destination will be automatically selected.
440      * @param {!print_preview.Destination} destination Print destination to
441      *     insert.
442      */
443     insertDestination: function(destination) {
444       if (this.insertDestination_(destination)) {
445         cr.dispatchSimpleEvent(
446             this, DestinationStore.EventType.DESTINATIONS_INSERTED);
447         if (this.isInAutoSelectMode_ &&
448             this.matchInitialDestination_(destination.id, destination.origin)) {
449           this.selectDestination(destination);
450         }
451       }
452     },
454     /**
455      * Inserts multiple print destinations to the data store and dispatches one
456      * DESTINATIONS_INSERTED event. If any of the destinations match the initial
457      * destination ID, then that destination will be automatically selected.
458      * @param {!Array.<print_preview.Destination>} destinations Print
459      *     destinations to insert.
460      */
461     insertDestinations: function(destinations) {
462       var insertedDestination = false;
463       var destinationToAutoSelect = null;
464       destinations.forEach(function(dest) {
465         if (this.insertDestination_(dest)) {
466           insertedDestination = true;
467           if (this.isInAutoSelectMode_ &&
468               destinationToAutoSelect == null &&
469               this.matchInitialDestination_(dest.id, dest.origin)) {
470             destinationToAutoSelect = dest;
471           }
472         }
473       }, this);
474       if (insertedDestination) {
475         cr.dispatchSimpleEvent(
476             this, DestinationStore.EventType.DESTINATIONS_INSERTED);
477       }
478       if (destinationToAutoSelect != null) {
479         this.selectDestination(destinationToAutoSelect);
480       }
481     },
483     /**
484      * Updates an existing print destination with capabilities and display name
485      * information. If the destination doesn't already exist, it will be added.
486      * @param {!print_preview.Destination} destination Destination to update.
487      * @return {!print_preview.Destination} The existing destination that was
488      *     updated or {@code null} if it was the new destination.
489      */
490     updateDestination: function(destination) {
491       assert(destination.constructor !== Array, 'Single printer expected');
492       var key = this.getDestinationKey_(destination.origin, destination.id);
493       var existingDestination = this.destinationMap_[key];
494       if (existingDestination != null) {
495         existingDestination.capabilities = destination.capabilities;
496       } else {
497         this.insertDestination(destination);
498       }
500       if (existingDestination == this.selectedDestination_ ||
501           destination == this.selectedDestination_) {
502         this.appState_.persistSelectedDestination(this.selectedDestination_);
503         cr.dispatchSimpleEvent(
504             this,
505             DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
506       }
508       return existingDestination;
509     },
511     /** Initiates loading of local print destinations. */
512     startLoadLocalDestinations: function() {
513       if (!this.hasLoadedAllLocalDestinations_) {
514         this.hasLoadedAllLocalDestinations_ = true;
515         this.nativeLayer_.startGetLocalDestinations();
516         this.isLocalDestinationSearchInProgress_ = true;
517         cr.dispatchSimpleEvent(
518             this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
519       }
520     },
522     /** Initiates loading of privet print destinations. */
523     startLoadPrivetDestinations: function() {
524       if (!this.hasLoadedAllPrivetDestinations_) {
525         this.isPrivetDestinationSearchInProgress_ = true;
526         this.nativeLayer_.startGetPrivetDestinations();
527         cr.dispatchSimpleEvent(
528             this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
529         this.privetSearchTimeout_ = setTimeout(
530             this.endPrivetPrinterSearch_.bind(this),
531             DestinationStore.PRIVET_SEARCH_DURATION_);
532       }
533     },
535     /**
536      * Initiates loading of cloud destinations.
537      */
538     startLoadCloudDestinations: function() {
539       if (this.cloudPrintInterface_ != null &&
540           !this.hasLoadedAllCloudDestinations_) {
541         this.hasLoadedAllCloudDestinations_ = true;
542         this.cloudPrintInterface_.search(true);
543         this.cloudPrintInterface_.search(false);
544         cr.dispatchSimpleEvent(
545             this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
546       }
547     },
549     /**
550      * Wait for a privet device to be registered.
551      */
552     waitForRegister: function(id) {
553       this.nativeLayer_.startGetPrivetDestinations();
554       this.waitForRegisterDestination_ = id;
555     },
557     /**
558      * Called when the search for Privet printers is done.
559      * @private
560      */
561     endPrivetPrinterSearch_: function() {
562       this.nativeLayer_.stopGetPrivetDestinations();
563       this.isPrivetDestinationSearchInProgress_ = false;
564       this.hasLoadedAllPrivetDestinations_ = true;
565       cr.dispatchSimpleEvent(
566           this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
567     },
570     /**
571      * Inserts a destination into the store without dispatching any events.
572      * @return {boolean} Whether the inserted destination was not already in the
573      *     store.
574      * @private
575      */
576     insertDestination_: function(destination) {
577       var key = this.getDestinationKey_(destination.origin, destination.id);
578       var existingDestination = this.destinationMap_[key];
579       if (existingDestination == null) {
580         this.destinations_.push(destination);
581         this.destinationMap_[key] = destination;
582         return true;
583       } else if (existingDestination.connectionStatus ==
584                      print_preview.Destination.ConnectionStatus.UNKNOWN &&
585                  destination.connectionStatus !=
586                      print_preview.Destination.ConnectionStatus.UNKNOWN) {
587         existingDestination.connectionStatus = destination.connectionStatus;
588         return true;
589       } else {
590         return false;
591       }
592     },
594     /**
595      * Binds handlers to events.
596      * @private
597      */
598     addEventListeners_: function() {
599       this.tracker_.add(
600           this.nativeLayer_,
601           print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET,
602           this.onLocalDestinationsSet_.bind(this));
603       this.tracker_.add(
604           this.nativeLayer_,
605           print_preview.NativeLayer.EventType.CAPABILITIES_SET,
606           this.onLocalDestinationCapabilitiesSet_.bind(this));
607       this.tracker_.add(
608           this.nativeLayer_,
609           print_preview.NativeLayer.EventType.GET_CAPABILITIES_FAIL,
610           this.onGetCapabilitiesFail_.bind(this));
611       this.tracker_.add(
612           this.nativeLayer_,
613           print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD,
614           this.onDestinationsReload_.bind(this));
615       this.tracker_.add(
616           this.nativeLayer_,
617           print_preview.NativeLayer.EventType.PRIVET_PRINTER_CHANGED,
618           this.onPrivetPrinterAdded_.bind(this));
619       this.tracker_.add(
620           this.nativeLayer_,
621           print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET,
622           this.onPrivetCapabilitiesSet_.bind(this));
623     },
625     /**
626      * Resets the state of the destination store to its initial state.
627      * @private
628      */
629     reset_: function() {
630       this.destinations_ = [];
631       this.destinationMap_ = {};
632       this.selectedDestination_ = null;
633       this.hasLoadedAllCloudDestinations_ = false;
634       this.hasLoadedAllLocalDestinations_ = false;
635       this.insertDestination(
636           DestinationStore.createLocalPdfPrintDestination_());
637       this.autoSelectTimeout_ =
638           setTimeout(this.onAutoSelectFailed_.bind(this),
639                      DestinationStore.AUTO_SELECT_TIMEOUT_);
640     },
642     /**
643      * Called when the local destinations have been got from the native layer.
644      * @param {Event} Contains the local destinations.
645      * @private
646      */
647     onLocalDestinationsSet_: function(event) {
648       var localDestinations = event.destinationInfos.map(function(destInfo) {
649         return print_preview.LocalDestinationParser.parse(destInfo);
650       });
651       this.insertDestinations(localDestinations);
652       this.isLocalDestinationSearchInProgress_ = false;
653       cr.dispatchSimpleEvent(
654           this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
655     },
657     /**
658      * Called when the native layer retrieves the capabilities for the selected
659      * local destination. Updates the destination with new capabilities if the
660      * destination already exists, otherwise it creates a new destination and
661      * then updates its capabilities.
662      * @param {Event} event Contains the capabilities of the local print
663      *     destination.
664      * @private
665      */
666     onLocalDestinationCapabilitiesSet_: function(event) {
667       var destinationId = event.settingsInfo['printerId'];
668       var key =
669           this.getDestinationKey_(print_preview.Destination.Origin.LOCAL,
670                                   destinationId);
671       var destination = this.destinationMap_[key];
672       var capabilities = print_preview.LocalCapabilitiesParser.parse(
673             event.settingsInfo);
674       if (destination) {
675         // In case there were multiple capabilities request for this local
676         // destination, just ignore the later ones.
677         if (destination.capabilities != null) {
678           return;
679         }
680         destination.capabilities = capabilities;
681       } else {
682         // TODO(rltoscano): This makes the assumption that the "deviceName" is
683         // the same as "printerName". We should include the "printerName" in the
684         // response. See http://crbug.com/132831.
685         destination = print_preview.LocalDestinationParser.parse(
686             {deviceName: destinationId, printerName: destinationId});
687         destination.capabilities = capabilities;
688         this.insertDestination(destination);
689       }
690       if (this.selectedDestination_ &&
691           this.selectedDestination_.id == destinationId) {
692         cr.dispatchSimpleEvent(this,
693                                DestinationStore.EventType.
694                                    SELECTED_DESTINATION_CAPABILITIES_READY);
695       }
696     },
698     /**
699      * Called when a request to get a local destination's print capabilities
700      * fails. If the destination is the initial destination, auto-select another
701      * destination instead.
702      * @param {Event} event Contains the destination ID that failed.
703      * @private
704      */
705     onGetCapabilitiesFail_: function(event) {
706       console.error('Failed to get print capabilities for printer ' +
707                     event.destinationId);
708       if (this.isInAutoSelectMode_ &&
709           this.matchInitialDestinationStrict_(event.destinationId,
710                                               event.destinationOrigin)) {
711         assert(this.destinations_.length > 0,
712                'No destinations were loaded when failed to get initial ' +
713                'destination');
714         this.selectDestination(this.destinations_[0]);
715       }
716     },
718     /**
719      * Called when the /search call completes. Adds the fetched destinations to
720      * the destination store.
721      * @param {Event} event Contains the fetched destinations.
722      * @private
723      */
724     onCloudPrintSearchDone_: function(event) {
725       this.insertDestinations(event.printers);
726       cr.dispatchSimpleEvent(
727           this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
728     },
730     /**
731      * Called when the /search call fails. Updates outstanding request count and
732      * dispatches CLOUD_DESTINATIONS_LOADED event.
733      * @private
734      */
735     onCloudPrintSearchFailed_: function() {
736       cr.dispatchSimpleEvent(
737           this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
738     },
740     /**
741      * Called when /printer call completes. Updates the specified destination's
742      * print capabilities.
743      * @param {Event} event Contains detailed information about the
744      *     destination.
745      * @private
746      */
747     onCloudPrintPrinterDone_: function(event) {
748       this.updateDestination(event.printer);
749     },
751     /**
752      * Called when the Google Cloud Print interface fails to lookup a
753      * destination. Selects another destination if the failed destination was
754      * the initial destination.
755      * @param {object} event Contains the ID of the destination that was failed
756      *     to be looked up.
757      * @private
758      */
759     onCloudPrintPrinterFailed_: function(event) {
760       if (this.isInAutoSelectMode_ &&
761           this.matchInitialDestinationStrict_(event.destinationId,
762                                               event.destinationOrigin)) {
763         console.error('Could not find initial printer: ' + event.destinationId);
764         assert(this.destinations_.length > 0,
765                'No destinations were loaded when failed to get initial ' +
766                'destination');
767         this.selectDestination(this.destinations_[0]);
768       }
769     },
771     /**
772      * Called when a Privet printer is added to the local network.
773      * @param {object} event Contains information about the added printer.
774      * @private
775      */
776     onPrivetPrinterAdded_: function(event) {
777       if (event.printer.serviceName == this.waitForRegisterDestination_ &&
778           !event.printer.isUnregistered) {
779         this.waitForRegisterDestination_ = null;
780         this.onDestinationsReload_();
781       } else {
782         this.insertDestinations(
783             print_preview.PrivetDestinationParser.parse(event.printer));
784       }
785     },
787     /**
788      * Called when capabilities for a privet printer are set.
789      * @param {object} event Contains the capabilities and printer ID.
790      * @private
791      */
792     onPrivetCapabilitiesSet_: function(event) {
793       var destinationId = event.printerId;
794       var destinations =
795           print_preview.PrivetDestinationParser.parse(event.printer);
796       destinations.forEach(function(dest) {
797         dest.capabilities = event.capabilities;
798         this.updateDestination(dest);
799       }, this);
800     },
802     /**
803      * Called from native layer after the user was requested to sign in, and did
804      * so successfully.
805      * @private
806      */
807     onDestinationsReload_: function() {
808       this.reset_();
809       this.isInAutoSelectMode_ = true;
810       this.startLoadLocalDestinations();
811       this.startLoadCloudDestinations();
812       this.startLoadPrivetDestinations();
813     },
815     /**
816      * Called when auto-selection fails. Selects the first destination in store.
817      * @private
818      */
819     onAutoSelectFailed_: function() {
820       this.autoSelectTimeout_ = null;
821       assert(this.destinations_.length > 0,
822              'No destinations were loaded before auto-select timeout expired');
823       this.selectDestination(this.destinations_[0]);
824     },
826     // TODO(vitalybuka): Remove three next functions replacing Destination.id
827     //    and Destination.origin by complex ID.
828     /**
829      * Returns key to be used with {@code destinationMap_}.
830      * @param {!print_preview.Destination.Origin} origin Destination origin.
831      * @return {!string} id Destination id.
832      * @private
833      */
834     getDestinationKey_: function(origin, id) {
835       return origin + '/' + id;
836     },
838     /**
839      * @param {?string} id Id of the destination.
840      * @param {?string} origin Oring of the destination.
841      * @return {boolean} Whether a initial destination matches provided.
842      * @private
843      */
844     matchInitialDestination_: function(id, origin) {
845       return this.initialDestinationId_ == null ||
846              this.initialDestinationOrigin_ == null ||
847              this.matchInitialDestinationStrict_(id, origin);
848     },
850     /**
851      * @param {?string} id Id of the destination.
852      * @param {?string} origin Oring of the destination.
853      * @return {boolean} Whether destination is the same as initial.
854      * @private
855      */
856     matchInitialDestinationStrict_: function(id, origin) {
857       return id == this.initialDestinationId_ &&
858              origin == this.initialDestinationOrigin_;
859     }
860   };
862   // Export
863   return {
864     DestinationStore: DestinationStore
865   };