Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(408)

Side by Side Diff: chrome/browser/resources/print_preview/data/destination_store.js

Issue 900503002: List printers managed by extensions in print preview (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: . Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 cr.define('print_preview', function() { 5 cr.define('print_preview', function() {
6 'use strict'; 6 'use strict';
7 7
8 /** 8 /**
9 * A data store that stores destinations and dispatches events when the data 9 * A data store that stores destinations and dispatches events when the data
10 * store changes. 10 * store changes.
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after
147 147
148 /** 148 /**
149 * ID of a timeout after the start of a privet search to end that privet 149 * ID of a timeout after the start of a privet search to end that privet
150 * search. 150 * search.
151 * @type {?number} 151 * @type {?number}
152 * @private 152 * @private
153 */ 153 */
154 this.privetSearchTimeout_ = null; 154 this.privetSearchTimeout_ = null;
155 155
156 /** 156 /**
157 * Whether a search for extension destinations is in progress.
158 * @type {boolean}
159 * @private
160 */
161 this.isExtensionDestinationSearchInProgress_ = false;
162
163 /**
164 * Whether the destination store has already loaded all extension
165 * destinations.
166 * @type {boolean}
167 * @private
168 */
169 this.hasLoadedAllExtensionDestinations_ = false;
170
171 /**
172 * ID of a timeout set at the start of an extension destination search. The
173 * timeout ends the search.
174 * @type {?number}
175 * @private
176 */
177 this.extensionSearchTimeout_ = null;
178
179 /**
157 * MDNS service name of destination that we are waiting to register. 180 * MDNS service name of destination that we are waiting to register.
158 * @type {?string} 181 * @type {?string}
159 * @private 182 * @private
160 */ 183 */
161 this.waitForRegisterDestination_ = null; 184 this.waitForRegisterDestination_ = null;
162 185
163 this.addEventListeners_(); 186 this.addEventListeners_();
164 this.reset_(); 187 this.reset_();
165 }; 188 };
166 189
(...skipping 27 matching lines...) Expand all
194 217
195 /** 218 /**
196 * Amount of time spent searching for privet destination, in milliseconds. 219 * Amount of time spent searching for privet destination, in milliseconds.
197 * @type {number} 220 * @type {number}
198 * @const 221 * @const
199 * @private 222 * @private
200 */ 223 */
201 DestinationStore.PRIVET_SEARCH_DURATION_ = 2000; 224 DestinationStore.PRIVET_SEARCH_DURATION_ = 2000;
202 225
203 /** 226 /**
227 * Maximum amount of time spent searching for extension destinations, in
228 * milliseconds.
229 * @type {number}
230 * @const
231 * @private
232 */
233 DestinationStore.EXTENSION_SEARCH_DURATION_ = 5000;
234
235 /**
204 * Localizes printer capabilities. 236 * Localizes printer capabilities.
205 * @param {!Object} capabilities Printer capabilities to localize. 237 * @param {!Object} capabilities Printer capabilities to localize.
206 * @return {!Object} Localized capabilities. 238 * @return {!Object} Localized capabilities.
207 * @private 239 * @private
208 */ 240 */
209 DestinationStore.localizeCapabilities_ = function(capabilities) { 241 DestinationStore.localizeCapabilities_ = function(capabilities) {
210 var mediaSize = capabilities.printer.media_size; 242 var mediaSize = capabilities.printer.media_size;
211 if (mediaSize) { 243 if (mediaSize) {
212 var mediaDisplayNames = { 244 var mediaDisplayNames = {
213 'ISO_A4': 'A4', 245 'ISO_A4': 'A4',
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
260 get isAutoSelectDestinationInProgress() { 292 get isAutoSelectDestinationInProgress() {
261 return this.selectedDestination_ == null && 293 return this.selectedDestination_ == null &&
262 this.autoSelectTimeout_ != null; 294 this.autoSelectTimeout_ != null;
263 }, 295 },
264 296
265 /** 297 /**
266 * @return {boolean} Whether a search for local destinations is in progress. 298 * @return {boolean} Whether a search for local destinations is in progress.
267 */ 299 */
268 get isLocalDestinationSearchInProgress() { 300 get isLocalDestinationSearchInProgress() {
269 return this.isLocalDestinationSearchInProgress_ || 301 return this.isLocalDestinationSearchInProgress_ ||
270 this.isPrivetDestinationSearchInProgress_; 302 this.isPrivetDestinationSearchInProgress_ ||
303 this.isExtensionDestinationSearchInProgress_;
271 }, 304 },
272 305
273 /** 306 /**
274 * @return {boolean} Whether a search for cloud destinations is in progress. 307 * @return {boolean} Whether a search for cloud destinations is in progress.
275 */ 308 */
276 get isCloudDestinationSearchInProgress() { 309 get isCloudDestinationSearchInProgress() {
277 return !!this.cloudPrintInterface_ && 310 return !!this.cloudPrintInterface_ &&
278 this.cloudPrintInterface_.isCloudDestinationSearchInProgress; 311 this.cloudPrintInterface_.isCloudDestinationSearchInProgress;
279 }, 312 },
280 313
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
332 print_preview.Destination.Origin.PRIVET, 365 print_preview.Destination.Origin.PRIVET,
333 destinationName, 366 destinationName,
334 false /*isRecent*/, 367 false /*isRecent*/,
335 print_preview.Destination.ConnectionStatus.ONLINE); 368 print_preview.Destination.ConnectionStatus.ONLINE);
336 this.selectedDestination_.capabilities = 369 this.selectedDestination_.capabilities =
337 this.appState_.selectedDestinationCapabilities; 370 this.appState_.selectedDestinationCapabilities;
338 371
339 cr.dispatchSimpleEvent( 372 cr.dispatchSimpleEvent(
340 this, 373 this,
341 DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY); 374 DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY);
375 } else if (this.appState_.selectedDestinationOrigin ==
376 print_preview.Destination.Origin.EXTENSION) {
377 // TODO(tbarzic): Add support for requesting a single extension's
378 // printer list.
379 this.startLoadExtensionDestinations();
380
381 this.selectedDestination_ =
382 print_preview.ExtensionDestinationParser.parse({
383 extensionId: this.appState_.selectedDestinationExtensionId,
384 id: this.appState_.selectedDestinationId,
385 name: this.appState_.selectedDestinationName || ''
386 });
387
388 if (this.appState_.selectedDestinationCapabilities) {
389 this.selectedDestination_.capabilities =
390 this.appState_.selectedDestinationCapabilities;
391
392 cr.dispatchSimpleEvent(
393 this,
394 DestinationStore.EventType
395 .CACHED_SELECTED_DESTINATION_INFO_READY);
396 }
342 } else { 397 } else {
343 this.selectDefaultDestination_(); 398 this.selectDefaultDestination_();
344 } 399 }
345 } 400 }
346 }, 401 },
347 402
348 /** 403 /**
349 * Sets the destination store's Google Cloud Print interface. 404 * Sets the destination store's Google Cloud Print interface.
350 * @param {!cloudprint.CloudPrintInterface} cloudPrintInterface Interface 405 * @param {!cloudprint.CloudPrintInterface} cloudPrintInterface Interface
351 * to set. 406 * to set.
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
430 CLOUD_DUPLICATE_SELECTED); 485 CLOUD_DUPLICATE_SELECTED);
431 } 486 }
432 // Notify about selected destination change. 487 // Notify about selected destination change.
433 cr.dispatchSimpleEvent( 488 cr.dispatchSimpleEvent(
434 this, DestinationStore.EventType.DESTINATION_SELECT); 489 this, DestinationStore.EventType.DESTINATION_SELECT);
435 // Request destination capabilities, of not known yet. 490 // Request destination capabilities, of not known yet.
436 if (destination.capabilities == null) { 491 if (destination.capabilities == null) {
437 if (destination.isPrivet) { 492 if (destination.isPrivet) {
438 this.nativeLayer_.startGetPrivetDestinationCapabilities( 493 this.nativeLayer_.startGetPrivetDestinationCapabilities(
439 destination.id); 494 destination.id);
440 } 495 } else if (destination.isExtension) {
441 else if (destination.isLocal) { 496 this.nativeLayer_.startGetExtensionDestinationCapabilities(
497 destination.id);
498 } else if (destination.isLocal) {
442 this.nativeLayer_.startGetLocalDestinationCapabilities( 499 this.nativeLayer_.startGetLocalDestinationCapabilities(
443 destination.id); 500 destination.id);
444 } else { 501 } else {
445 assert(this.cloudPrintInterface_ != null, 502 assert(this.cloudPrintInterface_ != null,
446 'Cloud destination selected, but GCP is not enabled'); 503 'Cloud destination selected, but GCP is not enabled');
447 this.cloudPrintInterface_.printer( 504 this.cloudPrintInterface_.printer(
448 destination.id, destination.origin, destination.account); 505 destination.id, destination.origin, destination.account);
449 } 506 }
450 } else { 507 } else {
451 cr.dispatchSimpleEvent( 508 cr.dispatchSimpleEvent(
(...skipping 22 matching lines...) Expand all
474 this.nativeLayer_.startGetLocalDestinations(); 531 this.nativeLayer_.startGetLocalDestinations();
475 this.isLocalDestinationSearchInProgress_ = true; 532 this.isLocalDestinationSearchInProgress_ = true;
476 cr.dispatchSimpleEvent( 533 cr.dispatchSimpleEvent(
477 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED); 534 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
478 } 535 }
479 }, 536 },
480 537
481 /** Initiates loading of privet print destinations. */ 538 /** Initiates loading of privet print destinations. */
482 startLoadPrivetDestinations: function() { 539 startLoadPrivetDestinations: function() {
483 if (!this.hasLoadedAllPrivetDestinations_) { 540 if (!this.hasLoadedAllPrivetDestinations_) {
541 if (this.privetDestinationSearchInProgress_)
542 clearTimeout(this.privetSearchTimeout_);
484 this.isPrivetDestinationSearchInProgress_ = true; 543 this.isPrivetDestinationSearchInProgress_ = true;
485 this.nativeLayer_.startGetPrivetDestinations(); 544 this.nativeLayer_.startGetPrivetDestinations();
486 cr.dispatchSimpleEvent( 545 cr.dispatchSimpleEvent(
487 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED); 546 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
488 if (this.privetDestinationSearchInProgress_)
489 clearTimeout(this.privetSearchTimeout_);
490 this.privetSearchTimeout_ = setTimeout( 547 this.privetSearchTimeout_ = setTimeout(
491 this.endPrivetPrinterSearch_.bind(this), 548 this.endPrivetPrinterSearch_.bind(this),
492 DestinationStore.PRIVET_SEARCH_DURATION_); 549 DestinationStore.PRIVET_SEARCH_DURATION_);
493 } 550 }
494 }, 551 },
495 552
553 /** Initializes loading of extension managed print destinations. */
554 startLoadExtensionDestinations: function() {
555 if (this.hasLoadedAllExtensionDestinations_)
556 return;
557
558 if (this.isExtensionDestinationSearchInProgress_)
559 clearTimeout(this.extensionSearchTimeout_);
560
561 this.isExtensionDestinationSearchInProgress_ = true;
562 this.nativeLayer_.startGetExtensionDestinations();
563 cr.dispatchSimpleEvent(
564 this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
565 this.extensionSearchTimeout_ = setTimeout(
566 this.endExtensionPrinterSearch_.bind(this),
567 DestinationStore.EXTENSION_SEARCH_DURATION_);
568 },
569
496 /** 570 /**
497 * Initiates loading of cloud destinations. 571 * Initiates loading of cloud destinations.
498 * @param {print_preview.Destination.Origin=} opt_origin Search destinations 572 * @param {print_preview.Destination.Origin=} opt_origin Search destinations
499 * for the specified origin only. 573 * for the specified origin only.
500 */ 574 */
501 startLoadCloudDestinations: function(opt_origin) { 575 startLoadCloudDestinations: function(opt_origin) {
502 if (this.cloudPrintInterface_ != null) { 576 if (this.cloudPrintInterface_ != null) {
503 var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || []; 577 var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || [];
504 if (origins.length == 0 || 578 if (origins.length == 0 ||
505 (opt_origin && origins.indexOf(opt_origin) < 0)) { 579 (opt_origin && origins.indexOf(opt_origin) < 0)) {
(...skipping 15 matching lines...) Expand all
521 this.startLoadCloudDestinations( 595 this.startLoadCloudDestinations(
522 print_preview.Destination.Origin.COOKIES); 596 print_preview.Destination.Origin.COOKIES);
523 } 597 }
524 }, 598 },
525 599
526 /** Initiates loading of all known destination types. */ 600 /** Initiates loading of all known destination types. */
527 startLoadAllDestinations: function() { 601 startLoadAllDestinations: function() {
528 this.startLoadCloudDestinations(); 602 this.startLoadCloudDestinations();
529 this.startLoadLocalDestinations(); 603 this.startLoadLocalDestinations();
530 this.startLoadPrivetDestinations(); 604 this.startLoadPrivetDestinations();
605 this.startLoadExtensionDestinations();
531 }, 606 },
532 607
533 /** 608 /**
534 * Wait for a privet device to be registered. 609 * Wait for a privet device to be registered.
535 */ 610 */
536 waitForRegister: function(id) { 611 waitForRegister: function(id) {
537 this.nativeLayer_.startGetPrivetDestinations(); 612 this.nativeLayer_.startGetPrivetDestinations();
538 this.waitForRegisterDestination_ = id; 613 this.waitForRegisterDestination_ = id;
539 }, 614 },
540 615
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
625 */ 700 */
626 endPrivetPrinterSearch_: function() { 701 endPrivetPrinterSearch_: function() {
627 this.nativeLayer_.stopGetPrivetDestinations(); 702 this.nativeLayer_.stopGetPrivetDestinations();
628 this.isPrivetDestinationSearchInProgress_ = false; 703 this.isPrivetDestinationSearchInProgress_ = false;
629 this.hasLoadedAllPrivetDestinations_ = true; 704 this.hasLoadedAllPrivetDestinations_ = true;
630 cr.dispatchSimpleEvent( 705 cr.dispatchSimpleEvent(
631 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE); 706 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
632 }, 707 },
633 708
634 /** 709 /**
710 * Called when loading of extension managed printers is done.
711 * @private
712 */
713 endExtensionPrinterSearch_: function() {
714 this.isExtensionDestinationSearchInProgress_ = false;
715 this.hasLoadedAllExtensionDestinations_ = true;
716 cr.dispatchSimpleEvent(
717 this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
718 // Clear initially selected (cached) extension destination if it hasn't
719 // been found among reported extension destinations.
720 if (this.isInAutoSelectMode_ && this.selectedDestination_.isExtension)
721 this.selectDefaultDestination_();
722 },
723
724 /**
635 * Inserts a destination into the store without dispatching any events. 725 * Inserts a destination into the store without dispatching any events.
636 * @return {boolean} Whether the inserted destination was not already in the 726 * @return {boolean} Whether the inserted destination was not already in the
637 * store. 727 * store.
638 * @private 728 * @private
639 */ 729 */
640 insertIntoStore_: function(destination) { 730 insertIntoStore_: function(destination) {
641 var key = this.getKey_(destination); 731 var key = this.getKey_(destination);
642 var existingDestination = this.destinationMap_[key]; 732 var existingDestination = this.destinationMap_[key];
643 if (existingDestination == null) { 733 if (existingDestination == null) {
644 this.destinations_.push(destination); 734 this.destinations_.push(destination);
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
677 print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD, 767 print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD,
678 this.onDestinationsReload_.bind(this)); 768 this.onDestinationsReload_.bind(this));
679 this.tracker_.add( 769 this.tracker_.add(
680 this.nativeLayer_, 770 this.nativeLayer_,
681 print_preview.NativeLayer.EventType.PRIVET_PRINTER_CHANGED, 771 print_preview.NativeLayer.EventType.PRIVET_PRINTER_CHANGED,
682 this.onPrivetPrinterAdded_.bind(this)); 772 this.onPrivetPrinterAdded_.bind(this));
683 this.tracker_.add( 773 this.tracker_.add(
684 this.nativeLayer_, 774 this.nativeLayer_,
685 print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET, 775 print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET,
686 this.onPrivetCapabilitiesSet_.bind(this)); 776 this.onPrivetCapabilitiesSet_.bind(this));
777 this.tracker_.add(
778 this.nativeLayer_,
779 print_preview.NativeLayer.EventType.EXTENSION_PRINTERS_ADDED,
780 this.onExtensionPrintersAdded_.bind(this));
781 this.tracker_.add(
782 this.nativeLayer_,
783 print_preview.NativeLayer.EventType.EXTENSION_CAPABILITIES_SET,
784 this.onExtensionCapabilitiesSet_.bind(this));
687 }, 785 },
688 786
689 /** 787 /**
690 * Creates a local PDF print destination. 788 * Creates a local PDF print destination.
691 * @return {!print_preview.Destination} Created print destination. 789 * @return {!print_preview.Destination} Created print destination.
692 * @private 790 * @private
693 */ 791 */
694 createLocalPdfPrintDestination_: function() { 792 createLocalPdfPrintDestination_: function() {
695 // TODO(alekseys): Create PDF printer in the native code and send its 793 // TODO(alekseys): Create PDF printer in the native code and send its
696 // capabilities back with other local printers. 794 // capabilities back with other local printers.
(...skipping 12 matching lines...) Expand all
709 * Resets the state of the destination store to its initial state. 807 * Resets the state of the destination store to its initial state.
710 * @private 808 * @private
711 */ 809 */
712 reset_: function() { 810 reset_: function() {
713 this.destinations_ = []; 811 this.destinations_ = [];
714 this.destinationMap_ = {}; 812 this.destinationMap_ = {};
715 this.selectDestination(null); 813 this.selectDestination(null);
716 this.loadedCloudOrigins_ = {}; 814 this.loadedCloudOrigins_ = {};
717 this.hasLoadedAllLocalDestinations_ = false; 815 this.hasLoadedAllLocalDestinations_ = false;
718 this.hasLoadedAllPrivetDestinations_ = false; 816 this.hasLoadedAllPrivetDestinations_ = false;
817 this.hasLoadedAllExtensionDestinations_ = false;
719 818
720 clearTimeout(this.autoSelectTimeout_); 819 clearTimeout(this.autoSelectTimeout_);
721 this.autoSelectTimeout_ = setTimeout( 820 this.autoSelectTimeout_ = setTimeout(
722 this.selectDefaultDestination_.bind(this), 821 this.selectDefaultDestination_.bind(this),
723 DestinationStore.AUTO_SELECT_TIMEOUT_); 822 DestinationStore.AUTO_SELECT_TIMEOUT_);
724 }, 823 },
725 824
726 /** 825 /**
727 * Called when the local destinations have been got from the native layer. 826 * Called when the local destinations have been got from the native layer.
728 * @param {Event} event Contains the local destinations. 827 * @param {Event} event Contains the local destinations.
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
884 print_preview.PrivetDestinationParser.parse(event.printer)); 983 print_preview.PrivetDestinationParser.parse(event.printer));
885 } 984 }
886 }, 985 },
887 986
888 /** 987 /**
889 * Called when capabilities for a privet printer are set. 988 * Called when capabilities for a privet printer are set.
890 * @param {Object} event Contains the capabilities and printer ID. 989 * @param {Object} event Contains the capabilities and printer ID.
891 * @private 990 * @private
892 */ 991 */
893 onPrivetCapabilitiesSet_: function(event) { 992 onPrivetCapabilitiesSet_: function(event) {
894 var destinationId = event.printerId;
895 var destinations = 993 var destinations =
896 print_preview.PrivetDestinationParser.parse(event.printer); 994 print_preview.PrivetDestinationParser.parse(event.printer);
897 destinations.forEach(function(dest) { 995 destinations.forEach(function(dest) {
898 dest.capabilities = event.capabilities; 996 dest.capabilities = event.capabilities;
899 this.updateDestination_(dest); 997 this.updateDestination_(dest);
900 }, this); 998 }, this);
901 }, 999 },
902 1000
903 /** 1001 /**
1002 * Called when an extension responds to a getExtensionDestinations
1003 * request.
1004 * @param {Object} event Contains information about list of printers
1005 * reported by the extension.
1006 * {@code done} parameter is set iff this is the final list of printers
1007 * returned as part of getExtensionDestinations request.
1008 * @private
1009 */
1010 onExtensionPrintersAdded_: function(event) {
1011 this.insertDestinations_(event.printers.map(function(printer) {
1012 return print_preview.ExtensionDestinationParser.parse(printer);
1013 }));
1014
1015 if (event.done && this.isExtensionDestinationSearchInProgress_) {
1016 clearTimeout(this.extensionSearchTimeout_);
1017 this.endExtensionPrinterSearch_();
1018 }
1019 },
1020
1021 /**
1022 * Called when capabilities for an extension managed printer are set.
1023 * @param {Object} event Contains the printer's capabilities and ID.
1024 * @private
1025 */
1026 onExtensionCapabilitiesSet_: function(event) {
1027 var destinationKey = this.getDestinationKey_(
1028 print_preview.Destination.Origin.EXTENSION,
1029 event.printerId,
1030 '' /* account */);
1031 var destination = this.destinationMap_[destinationKey];
1032 if (!destination)
1033 return;
1034 destination.capabilities = event.capabilities;
1035 this.updateDestination_(destination);
1036 },
1037
1038 /**
904 * Called from native layer after the user was requested to sign in, and did 1039 * Called from native layer after the user was requested to sign in, and did
905 * so successfully. 1040 * so successfully.
906 * @private 1041 * @private
907 */ 1042 */
908 onDestinationsReload_: function() { 1043 onDestinationsReload_: function() {
909 this.reset_(); 1044 this.reset_();
910 this.isInAutoSelectMode_ = true; 1045 this.isInAutoSelectMode_ = true;
911 this.createLocalPdfPrintDestination_(); 1046 this.createLocalPdfPrintDestination_();
912 this.startLoadAllDestinations(); 1047 this.startLoadAllDestinations();
913 }, 1048 },
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
958 return id == this.appState_.selectedDestinationId && 1093 return id == this.appState_.selectedDestinationId &&
959 origin == this.appState_.selectedDestinationOrigin; 1094 origin == this.appState_.selectedDestinationOrigin;
960 } 1095 }
961 }; 1096 };
962 1097
963 // Export 1098 // Export
964 return { 1099 return {
965 DestinationStore: DestinationStore 1100 DestinationStore: DestinationStore
966 }; 1101 };
967 }); 1102 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698