Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / print_preview / search / destination_search.js
blob0922bca647f034762bde81a0348f09dd9ac175b6
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    * Component used for searching for a print destination.
10    * This is a modal dialog that allows the user to search and select a
11    * destination to print to. When a destination is selected, it is written to
12    * the destination store.
13    * @param {!print_preview.DestinationStore} destinationStore Data store
14    *     containing the destinations to search through.
15    * @param {!print_preview.InvitationStore} invitationStore Data store
16    *     holding printer sharing invitations.
17    * @param {!print_preview.UserInfo} userInfo Event target that contains
18    *     information about the logged in user.
19    * @constructor
20    * @extends {print_preview.Overlay}
21    */
22   function DestinationSearch(destinationStore, invitationStore, userInfo) {
23     print_preview.Overlay.call(this);
25     /**
26      * Data store containing the destinations to search through.
27      * @type {!print_preview.DestinationStore}
28      * @private
29      */
30     this.destinationStore_ = destinationStore;
32     /**
33      * Data store holding printer sharing invitations.
34      * @type {!print_preview.DestinationStore}
35      * @private
36      */
37     this.invitationStore_ = invitationStore;
39     /**
40      * Event target that contains information about the logged in user.
41      * @type {!print_preview.UserInfo}
42      * @private
43      */
44     this.userInfo_ = userInfo;
46     /**
47      * Currently displayed printer sharing invitation.
48      * @type {print_preview.Invitation}
49      * @private
50      */
51     this.invitation_ = null;
53     /**
54      * Used to record usage statistics.
55      * @type {!print_preview.DestinationSearchMetricsContext}
56      * @private
57      */
58     this.metrics_ = new print_preview.DestinationSearchMetricsContext();
60     /**
61      * Whether or not a UMA histogram for the register promo being shown was
62      * already recorded.
63      * @type {boolean}
64      * @private
65      */
66     this.registerPromoShownMetricRecorded_ = false;
68     /**
69      * Child overlay used for resolving a provisional destination. The overlay
70      * is shown when the user attempts to select a provisional destination.
71      * Set only when a destination is being resolved.
72      * @private {?print_preview.ProvisionalDestinationResolver}
73      */
74     this.provisionalDestinationResolver_ = null;
76     /**
77      * Search box used to search through the destination lists.
78      * @type {!print_preview.SearchBox}
79      * @private
80      */
81     this.searchBox_ = new print_preview.SearchBox(
82         loadTimeData.getString('searchBoxPlaceholder'));
83     this.addChild(this.searchBox_);
85     /**
86      * Destination list containing recent destinations.
87      * @type {!print_preview.DestinationList}
88      * @private
89      */
90     this.recentList_ = new print_preview.RecentDestinationList(this);
91     this.addChild(this.recentList_);
93     /**
94      * Destination list containing local destinations.
95      * @type {!print_preview.DestinationList}
96      * @private
97      */
98     this.localList_ = new print_preview.DestinationList(
99         this,
100         loadTimeData.getString('localDestinationsTitle'),
101         cr.isChromeOS ? null : loadTimeData.getString('manage'));
102     this.addChild(this.localList_);
104     /**
105      * Destination list containing cloud destinations.
106      * @type {!print_preview.DestinationList}
107      * @private
108      */
109     this.cloudList_ = new print_preview.CloudDestinationList(this);
110     this.addChild(this.cloudList_);
111   };
113   /**
114    * Event types dispatched by the component.
115    * @enum {string}
116    */
117   DestinationSearch.EventType = {
118     // Dispatched when user requests to sign-in into another Google account.
119     ADD_ACCOUNT: 'print_preview.DestinationSearch.ADD_ACCOUNT',
121     // Dispatched when the user requests to manage their cloud destinations.
122     MANAGE_CLOUD_DESTINATIONS:
123         'print_preview.DestinationSearch.MANAGE_CLOUD_DESTINATIONS',
125     // Dispatched when the user requests to manage their local destinations.
126     MANAGE_LOCAL_DESTINATIONS:
127         'print_preview.DestinationSearch.MANAGE_LOCAL_DESTINATIONS',
129     // Dispatched when the user requests to sign-in to their Google account.
130     SIGN_IN: 'print_preview.DestinationSearch.SIGN_IN'
131   };
133   /**
134    * Number of unregistered destinations that may be promoted to the top.
135    * @type {number}
136    * @const
137    * @private
138    */
139   DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_ = 2;
141   DestinationSearch.prototype = {
142     __proto__: print_preview.Overlay.prototype,
144     /** @override */
145     onSetVisibleInternal: function(isVisible) {
146       if (isVisible) {
147         this.searchBox_.focus();
148         if (getIsVisible(this.getChildElement('.cloudprint-promo'))) {
149           this.metrics_.record(
150               print_preview.Metrics.DestinationSearchBucket.SIGNIN_PROMPT);
151         }
152         if (this.userInfo_.initialized)
153           this.onUsersChanged_();
154         this.reflowLists_();
155         this.metrics_.record(
156             print_preview.Metrics.DestinationSearchBucket.DESTINATION_SHOWN);
158         this.destinationStore_.startLoadAllDestinations();
159         this.invitationStore_.startLoadingInvitations();
160       } else {
161         // Collapse all destination lists
162         this.localList_.setIsShowAll(false);
163         this.cloudList_.setIsShowAll(false);
164         if (this.provisionalDestinationResolver_)
165           this.provisionalDestinationResolver_.cancel();
166         this.resetSearch_();
167       }
168     },
170     /** @override */
171     onCancelInternal: function() {
172       this.metrics_.record(print_preview.Metrics.DestinationSearchBucket.
173           DESTINATION_CLOSED_UNCHANGED);
174     },
176     /** Shows the Google Cloud Print promotion banner. */
177     showCloudPrintPromo: function() {
178       setIsVisible(this.getChildElement('.cloudprint-promo'), true);
179       if (this.getIsVisible()) {
180         this.metrics_.record(
181             print_preview.Metrics.DestinationSearchBucket.SIGNIN_PROMPT);
182       }
183       this.reflowLists_();
184     },
186     /** @override */
187     enterDocument: function() {
188       print_preview.Overlay.prototype.enterDocument.call(this);
190       this.tracker.add(
191           this.getChildElement('.account-select'),
192           'change',
193           this.onAccountChange_.bind(this));
195       this.tracker.add(
196           this.getChildElement('.sign-in'),
197           'click',
198           this.onSignInActivated_.bind(this));
200       this.tracker.add(
201           this.getChildElement('.invitation-accept-button'),
202           'click',
203           this.onInvitationProcessButtonClick_.bind(this, true /*accept*/));
204       this.tracker.add(
205           this.getChildElement('.invitation-reject-button'),
206           'click',
207           this.onInvitationProcessButtonClick_.bind(this, false /*accept*/));
209       this.tracker.add(
210           this.getChildElement('.cloudprint-promo > .close-button'),
211           'click',
212           this.onCloudprintPromoCloseButtonClick_.bind(this));
213       this.tracker.add(
214           this.searchBox_,
215           print_preview.SearchBox.EventType.SEARCH,
216           this.onSearch_.bind(this));
217       this.tracker.add(
218           this,
219           print_preview.DestinationListItem.EventType.SELECT,
220           this.onDestinationSelect_.bind(this));
221       this.tracker.add(
222           this,
223           print_preview.DestinationListItem.EventType.REGISTER_PROMO_CLICKED,
224           function() {
225             this.metrics_.record(print_preview.Metrics.DestinationSearchBucket.
226                 REGISTER_PROMO_SELECTED);
227           }.bind(this));
229       this.tracker.add(
230           this.destinationStore_,
231           print_preview.DestinationStore.EventType.DESTINATIONS_INSERTED,
232           this.onDestinationsInserted_.bind(this));
233       this.tracker.add(
234           this.destinationStore_,
235           print_preview.DestinationStore.EventType.DESTINATION_SELECT,
236           this.onDestinationStoreSelect_.bind(this));
237       this.tracker.add(
238           this.destinationStore_,
239           print_preview.DestinationStore.EventType.DESTINATION_SEARCH_STARTED,
240           this.updateThrobbers_.bind(this));
241       this.tracker.add(
242           this.destinationStore_,
243           print_preview.DestinationStore.EventType.DESTINATION_SEARCH_DONE,
244           this.onDestinationSearchDone_.bind(this));
245       this.tracker.add(
246           this.destinationStore_,
247           print_preview.DestinationStore.EventType
248               .PROVISIONAL_DESTINATION_RESOLVED,
249           this.onDestinationsInserted_.bind(this));
251       this.tracker.add(
252           this.invitationStore_,
253           print_preview.InvitationStore.EventType.INVITATION_SEARCH_DONE,
254           this.updateInvitations_.bind(this));
255       this.tracker.add(
256           this.invitationStore_,
257           print_preview.InvitationStore.EventType.INVITATION_PROCESSED,
258           this.updateInvitations_.bind(this));
260       this.tracker.add(
261           this.localList_,
262           print_preview.DestinationList.EventType.ACTION_LINK_ACTIVATED,
263           this.onManageLocalDestinationsActivated_.bind(this));
264       this.tracker.add(
265           this.cloudList_,
266           print_preview.DestinationList.EventType.ACTION_LINK_ACTIVATED,
267           this.onManageCloudDestinationsActivated_.bind(this));
269       this.tracker.add(
270           this.userInfo_,
271           print_preview.UserInfo.EventType.USERS_CHANGED,
272           this.onUsersChanged_.bind(this));
274       this.tracker.add(
275           this.getChildElement('.button-strip .cancel-button'),
276           'click',
277           this.cancel.bind(this));
279       this.tracker.add(window, 'resize', this.onWindowResize_.bind(this));
281       this.updateThrobbers_();
283       // Render any destinations already in the store.
284       this.renderDestinations_();
285     },
287     /** @override */
288     decorateInternal: function() {
289       this.searchBox_.render(this.getChildElement('.search-box-container'));
290       this.recentList_.render(this.getChildElement('.recent-list'));
291       this.localList_.render(this.getChildElement('.local-list'));
292       this.cloudList_.render(this.getChildElement('.cloud-list'));
293       this.getChildElement('.promo-text').innerHTML = loadTimeData.getStringF(
294           'cloudPrintPromotion',
295           '<a is="action-link" class="sign-in">',
296           '</a>');
297       this.getChildElement('.account-select-label').textContent =
298           loadTimeData.getString('accountSelectTitle');
299     },
301     /**
302      * @return {number} Height available for destination lists, in pixels.
303      * @private
304      */
305     getAvailableListsHeight_: function() {
306       var elStyle = window.getComputedStyle(this.getElement());
307       return this.getElement().offsetHeight -
308           parseInt(elStyle.getPropertyValue('padding-top'), 10) -
309           parseInt(elStyle.getPropertyValue('padding-bottom'), 10) -
310           this.getChildElement('.lists').offsetTop -
311           this.getChildElement('.invitation-container').offsetHeight -
312           this.getChildElement('.cloudprint-promo').offsetHeight -
313           this.getChildElement('.action-area').offsetHeight;
314     },
316     /**
317      * Filters all destination lists with the given query.
318      * @param {RegExp} query Query to filter destination lists by.
319      * @private
320      */
321     filterLists_: function(query) {
322       this.recentList_.updateSearchQuery(query);
323       this.localList_.updateSearchQuery(query);
324       this.cloudList_.updateSearchQuery(query);
325     },
327     /**
328      * Resets the search query.
329      * @private
330      */
331     resetSearch_: function() {
332       this.searchBox_.setQuery(null);
333       this.filterLists_(null);
334     },
336     /**
337      * Renders all of the destinations in the destination store.
338      * @private
339      */
340     renderDestinations_: function() {
341       var recentDestinations = [];
342       var localDestinations = [];
343       var cloudDestinations = [];
344       var unregisteredCloudDestinations = [];
346       var destinations =
347           this.destinationStore_.destinations(this.userInfo_.activeUser);
348       destinations.forEach(function(destination) {
349         if (destination.isRecent) {
350           recentDestinations.push(destination);
351         }
352         if (destination.isLocal ||
353             destination.origin == print_preview.Destination.Origin.DEVICE) {
354           localDestinations.push(destination);
355         } else {
356           if (destination.connectionStatus ==
357                 print_preview.Destination.ConnectionStatus.UNREGISTERED) {
358             unregisteredCloudDestinations.push(destination);
359           } else {
360             cloudDestinations.push(destination);
361           }
362         }
363       });
365       if (unregisteredCloudDestinations.length != 0 &&
366           !this.registerPromoShownMetricRecorded_) {
367         this.metrics_.record(
368             print_preview.Metrics.DestinationSearchBucket.REGISTER_PROMO_SHOWN);
369         this.registerPromoShownMetricRecorded_ = true;
370       }
372       var finalCloudDestinations = unregisteredCloudDestinations.slice(
373         0, DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_).concat(
374           cloudDestinations,
375           unregisteredCloudDestinations.slice(
376             DestinationSearch.MAX_PROMOTED_UNREGISTERED_PRINTERS_));
378       this.recentList_.updateDestinations(recentDestinations);
379       this.localList_.updateDestinations(localDestinations);
380       this.cloudList_.updateDestinations(finalCloudDestinations);
381     },
383     /**
384      * Reflows the destination lists according to the available height.
385      * @private
386      */
387     reflowLists_: function() {
388       if (!this.getIsVisible()) {
389         return;
390       }
392       var hasCloudList = getIsVisible(this.getChildElement('.cloud-list'));
393       var lists = [this.recentList_, this.localList_];
394       if (hasCloudList) {
395         lists.push(this.cloudList_);
396       }
398       var getListsTotalHeight = function(lists, counts) {
399         return lists.reduce(function(sum, list, index) {
400           var container = list.getContainerElement();
401           return sum + list.getEstimatedHeightInPixels(counts[index]) +
402               parseInt(window.getComputedStyle(container).paddingBottom, 10);
403         }, 0);
404       };
405       var getCounts = function(lists, count) {
406         return lists.map(function(list) { return count; });
407       };
409       var availableHeight = this.getAvailableListsHeight_();
410       var listsEl = this.getChildElement('.lists');
411       listsEl.style.maxHeight = availableHeight + 'px';
413       var maxListLength = lists.reduce(function(prevCount, list) {
414         return Math.max(prevCount, list.getDestinationsCount());
415       }, 0);
416       for (var i = 1; i <= maxListLength; i++) {
417         if (getListsTotalHeight(lists, getCounts(lists, i)) > availableHeight) {
418           i--;
419           break;
420         }
421       }
422       var counts = getCounts(lists, i);
423       // Fill up the possible n-1 free slots left by the previous loop.
424       if (getListsTotalHeight(lists, counts) < availableHeight) {
425         for (var countIndex = 0; countIndex < counts.length; countIndex++) {
426           counts[countIndex]++;
427           if (getListsTotalHeight(lists, counts) > availableHeight) {
428             counts[countIndex]--;
429             break;
430           }
431         }
432       }
434       lists.forEach(function(list, index) {
435         list.updateShortListSize(counts[index]);
436       });
438       // Set height of the list manually so that search filter doesn't change
439       // lists height.
440       var listsHeight = getListsTotalHeight(lists, counts) + 'px';
441       if (listsHeight != listsEl.style.height) {
442         // Try to close account select if there's a possibility it's open now.
443         var accountSelectEl = this.getChildElement('.account-select');
444         if (!accountSelectEl.disabled) {
445           accountSelectEl.disabled = true;
446           accountSelectEl.disabled = false;
447         }
448         listsEl.style.height = listsHeight;
449       }
450     },
452     /**
453      * Updates whether the throbbers for the various destination lists should be
454      * shown or hidden.
455      * @private
456      */
457     updateThrobbers_: function() {
458       this.localList_.setIsThrobberVisible(
459           this.destinationStore_.isLocalDestinationSearchInProgress);
460       this.cloudList_.setIsThrobberVisible(
461           this.destinationStore_.isCloudDestinationSearchInProgress);
462       this.recentList_.setIsThrobberVisible(
463           this.destinationStore_.isLocalDestinationSearchInProgress &&
464           this.destinationStore_.isCloudDestinationSearchInProgress);
465       this.reflowLists_();
466     },
468     /**
469      * Updates printer sharing invitations UI.
470      * @private
471      */
472     updateInvitations_: function() {
473       var invitations = this.userInfo_.activeUser ?
474           this.invitationStore_.invitations(this.userInfo_.activeUser) : [];
475       if (invitations.length > 0) {
476         if (this.invitation_ != invitations[0]) {
477           this.metrics_.record(print_preview.Metrics.DestinationSearchBucket.
478               INVITATION_AVAILABLE);
479         }
480         this.invitation_ = invitations[0];
481         this.showInvitation_(this.invitation_);
482       } else {
483         this.invitation_ = null;
484       }
485       setIsVisible(
486           this.getChildElement('.invitation-container'), !!this.invitation_);
487       this.reflowLists_();
488     },
490     /**
491      * @param {!printe_preview.Invitation} invitation Invitation to show.
492      * @private
493      */
494     showInvitation_: function(invitation) {
495       var invitationText = '';
496       if (invitation.asGroupManager) {
497         invitationText = loadTimeData.getStringF(
498             'groupPrinterSharingInviteText',
499             HTMLEscape(invitation.sender),
500             HTMLEscape(invitation.destination.displayName),
501             HTMLEscape(invitation.receiver));
502       } else {
503         invitationText = loadTimeData.getStringF(
504             'printerSharingInviteText',
505             HTMLEscape(invitation.sender),
506             HTMLEscape(invitation.destination.displayName));
507       }
508       this.getChildElement('.invitation-text').innerHTML = invitationText;
510       var acceptButton = this.getChildElement('.invitation-accept-button');
511       acceptButton.textContent = loadTimeData.getString(
512           invitation.asGroupManager ? 'acceptForGroup' : 'accept');
513       acceptButton.disabled = !!this.invitationStore_.invitationInProgress;
514       this.getChildElement('.invitation-reject-button').disabled =
515           !!this.invitationStore_.invitationInProgress;
516       setIsVisible(
517           this.getChildElement('#invitation-process-throbber'),
518           !!this.invitationStore_.invitationInProgress);
519     },
521     /**
522      * Called when user's logged in accounts change. Updates the UI.
523      * @private
524      */
525     onUsersChanged_: function() {
526       var loggedIn = this.userInfo_.loggedIn;
527       if (loggedIn) {
528         var accountSelectEl = this.getChildElement('.account-select');
529         accountSelectEl.innerHTML = '';
530         this.userInfo_.users.forEach(function(account) {
531           var option = document.createElement('option');
532           option.text = account;
533           option.value = account;
534           accountSelectEl.add(option);
535         });
536         var option = document.createElement('option');
537         option.text = loadTimeData.getString('addAccountTitle');
538         option.value = '';
539         accountSelectEl.add(option);
541         accountSelectEl.selectedIndex =
542             this.userInfo_.users.indexOf(this.userInfo_.activeUser);
543       }
545       setIsVisible(this.getChildElement('.user-info'), loggedIn);
546       setIsVisible(this.getChildElement('.cloud-list'), loggedIn);
547       setIsVisible(this.getChildElement('.cloudprint-promo'), !loggedIn);
548       this.updateInvitations_();
549     },
551     /**
552      * Called when a destination search should be executed. Filters the
553      * destination lists with the given query.
554      * @param {Event} evt Contains the search query.
555      * @private
556      */
557     onSearch_: function(evt) {
558       this.filterLists_(evt.queryRegExp);
559     },
561     /**
562      * Handler for {@code print_preview.DestinationListItem.EventType.SELECT}
563      * event, which is called when a destinationi list item is selected.
564      * @param {Event} evt Contains the selected destination.
565      * @private
566      */
567     onDestinationSelect_: function(evt) {
568       this.handleOnDestinationSelect_(evt.destination);
569     },
571     /**
572      * Called when a destination is selected. Clears the search and hides the
573      * widget. If The destination is provisional, it runs provisional
574      * destination resolver first.
575      * @param {!print_preview.Destination} destination The selected destination.
576      * @private
577      */
578     handleOnDestinationSelect_: function(destination) {
579       if (destination.isProvisional) {
580         assert(!this.provisionalDestinationResolver_,
581                'Provisional destination resolver already exists.');
582         this.provisionalDestinationResolver_ =
583             print_preview.ProvisionalDestinationResolver.create(
584                 this.destinationStore_, destination);
585         assert(!!this.provisionalDestinationResolver_,
586                'Unable to create provisional destination resolver');
588         var lastFocusedElement = document.activeElement;
589         this.addChild(this.provisionalDestinationResolver_);
590         this.provisionalDestinationResolver_.run(this.getElement())
591             .then(
592                 /**
593                  * @param {!print_preview.Destination} resolvedDestination
594                  *    Destination to which the provisional destination was
595                  *    resolved.
596                  */
597                 function(resolvedDestination) {
598                   this.handleOnDestinationSelect_(resolvedDestination);
599                 }.bind(this))
600             .catch(
601                 function() {
602                   console.log('Failed to resolve provisional destination: ' +
603                               destination.id);
604                 })
605             .then(
606                 function() {
607                   this.removeChild(this.provisionalDestinationResolver_);
608                   this.provisionalDestinationResolver_ = null;
610                   // Restore focus to the previosly focused element if it's
611                   // still shown in the search.
612                   if (lastFocusedElement &&
613                       this.getIsVisible() &&
614                       getIsVisible(lastFocusedElement) &&
615                       this.getElement().contains(lastFocusedElement)) {
616                     lastFocusedElement.focus();
617                   }
618                 }.bind(this));
619         return;
620       }
622       this.setIsVisible(false);
623       this.destinationStore_.selectDestination(destination);
624       this.metrics_.record(print_preview.Metrics.DestinationSearchBucket.
625           DESTINATION_CLOSED_CHANGED);
626     },
628     /**
629      * Called when a destination is selected. Selected destination are marked as
630      * recent, so we have to update our recent destinations list.
631      * @private
632      */
633     onDestinationStoreSelect_: function() {
634       var destinations =
635           this.destinationStore_.destinations(this.userInfo_.activeUser);
636       var recentDestinations = [];
637       destinations.forEach(function(destination) {
638         if (destination.isRecent) {
639           recentDestinations.push(destination);
640         }
641       });
642       this.recentList_.updateDestinations(recentDestinations);
643       this.reflowLists_();
644     },
646     /**
647      * Called when destinations are inserted into the store. Rerenders
648      * destinations.
649      * @private
650      */
651     onDestinationsInserted_: function() {
652       this.renderDestinations_();
653       this.reflowLists_();
654     },
656     /**
657      * Called when destinations are inserted into the store. Rerenders
658      * destinations.
659      * @private
660      */
661     onDestinationSearchDone_: function() {
662       this.updateThrobbers_();
663       this.renderDestinations_();
664       this.reflowLists_();
665       // In case user account information was retrieved with this search
666       // (knowing current user account is required to fetch invitations).
667       this.invitationStore_.startLoadingInvitations();
668     },
670     /**
671      * Called when the manage cloud printers action is activated.
672      * @private
673      */
674     onManageCloudDestinationsActivated_: function() {
675       cr.dispatchSimpleEvent(
676           this,
677           print_preview.DestinationSearch.EventType.MANAGE_CLOUD_DESTINATIONS);
678     },
680     /**
681      * Called when the manage local printers action is activated.
682      * @private
683      */
684     onManageLocalDestinationsActivated_: function() {
685       cr.dispatchSimpleEvent(
686           this,
687           print_preview.DestinationSearch.EventType.MANAGE_LOCAL_DESTINATIONS);
688     },
690     /**
691      * Called when the "Sign in" link on the Google Cloud Print promo is
692      * activated.
693      * @private
694      */
695     onSignInActivated_: function() {
696       cr.dispatchSimpleEvent(this, DestinationSearch.EventType.SIGN_IN);
697       this.metrics_.record(
698           print_preview.Metrics.DestinationSearchBucket.SIGNIN_TRIGGERED);
699     },
701     /**
702      * Called when item in the Accounts list is selected. Initiates active user
703      * switch or, for 'Add account...' item, opens Google sign-in page.
704      * @private
705      */
706     onAccountChange_: function() {
707       var accountSelectEl = this.getChildElement('.account-select');
708       var account =
709           accountSelectEl.options[accountSelectEl.selectedIndex].value;
710       if (account) {
711         this.userInfo_.activeUser = account;
712         this.destinationStore_.reloadUserCookieBasedDestinations();
713         this.invitationStore_.startLoadingInvitations();
714         this.metrics_.record(
715             print_preview.Metrics.DestinationSearchBucket.ACCOUNT_CHANGED);
716       } else {
717         cr.dispatchSimpleEvent(this, DestinationSearch.EventType.ADD_ACCOUNT);
718         // Set selection back to the active user.
719         for (var i = 0; i < accountSelectEl.options.length; i++) {
720           if (accountSelectEl.options[i].value == this.userInfo_.activeUser) {
721             accountSelectEl.selectedIndex = i;
722             break;
723           }
724         }
725         this.metrics_.record(
726             print_preview.Metrics.DestinationSearchBucket.ADD_ACCOUNT_SELECTED);
727       }
728     },
730     /**
731      * Called when the printer sharing invitation Accept/Reject button is
732      * clicked.
733      * @private
734      */
735     onInvitationProcessButtonClick_: function(accept) {
736       this.metrics_.record(accept ?
737           print_preview.Metrics.DestinationSearchBucket.INVITATION_ACCEPTED :
738           print_preview.Metrics.DestinationSearchBucket.INVITATION_REJECTED);
739       this.invitationStore_.processInvitation(this.invitation_, accept);
740       this.updateInvitations_();
741     },
743     /**
744      * Called when the close button on the cloud print promo is clicked. Hides
745      * the promo.
746      * @private
747      */
748     onCloudprintPromoCloseButtonClick_: function() {
749       setIsVisible(this.getChildElement('.cloudprint-promo'), false);
750       this.reflowLists_();
751     },
753     /**
754      * Called when the window is resized. Reflows layout of destination lists.
755      * @private
756      */
757     onWindowResize_: function() {
758       this.reflowLists_();
759     }
760   };
762   // Export
763   return {
764     DestinationSearch: DestinationSearch
765   };