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