Chromium Code Reviews| Index: chrome/browser/resources/print_preview/data/destination_store.js |
| diff --git a/chrome/browser/resources/print_preview/data/destination_store.js b/chrome/browser/resources/print_preview/data/destination_store.js |
| index 69f766ad9c88fde6636ea1d7cb1b367fc266c3bb..09a0d54a5b268d7670ea1d9e73b391ca72853fb4 100644 |
| --- a/chrome/browser/resources/print_preview/data/destination_store.js |
| +++ b/chrome/browser/resources/print_preview/data/destination_store.js |
| @@ -8,20 +8,36 @@ cr.define('print_preview', function() { |
| /** |
| * A data store that stores destinations and dispatches events when the data |
| * store changes. |
| + * @param {!print_preview.NativeLayer} nativeLayer Used to fetch local print |
| + * destinations. |
| * @constructor |
| * @extends {cr.EventTarget} |
| */ |
| - function DestinationStore() { |
| + function DestinationStore(nativeLayer) { |
| cr.EventTarget.call(this); |
| /** |
| + * Used to fetch local print destinations. |
| + * @type {!print_preview.NativeLayer} |
| + * @private |
| + */ |
| + this.nativeLayer_ = nativeLayer; |
| + |
| + /** |
| * Internal backing store for the data store. |
| - * @type {!Array.<print_preview.Destination>} |
| + * @type {!Array.<!print_preview.Destination>} |
| * @private |
| */ |
| this.destinations_ = []; |
| /** |
| + * Cache used for constant lookup of printers. |
| + * @type {object.<string, !print_preview.Destination>} |
| + * @private |
| + */ |
| + this.destinationMap_ = {}; |
| + |
| + /** |
| * Currently selected destination. |
| * @type {print_preview.Destination} |
| * @private |
| @@ -44,6 +60,39 @@ cr.define('print_preview', function() { |
| * @private |
| */ |
| this.isInAutoSelectMode_ = false; |
| + |
| + /** |
| + * Event tracker used to track event listeners of the destination store. |
| + * @type {!EventTracker} |
| + * @private |
| + */ |
| + this.tracker_ = new EventTracker(); |
| + |
| + /** |
| + * Used to fetch cloud-based print destinations. |
| + * @type {!print_preview.CloudPrintInterface} |
|
dpapad
2012/05/25 16:18:03
cant use
@type {!Type}
this.blah_ = null;
! means
Robert Toscano
2012/05/25 19:47:01
Whoops copy paste error. Done.
|
| + * @private |
| + */ |
| + this.cloudPrintInterface_ = null; |
| + |
| + /** |
| + * Whether the destination store has already loaded or is loading all cloud |
| + * destinations. |
| + * @type {boolean} |
| + * @private |
| + */ |
| + this.hasLoadedAllCloudDestinations_ = false; |
| + |
| + /** |
| + * Timeout object that fires after the initial destination is set, but not |
| + * matched with a destination within a timeout. |
| + * @type {Object} |
| + * @private |
| + */ |
| + this.autoSelectTimeout_ = null; |
|
dpapad
2012/05/25 16:18:03
Isn't this just a timeout id, therefore a number?
Robert Toscano
2012/05/25 19:47:01
I never looked up what exactly was returned by set
|
| + |
| + this.addEventListeners_(); |
| + this.reset_(); |
| }; |
| /** |
| @@ -53,7 +102,44 @@ cr.define('print_preview', function() { |
| DestinationStore.EventType = { |
| DESTINATIONS_INSERTED: |
| 'print_preview.DestinationStore.DESTINATIONS_INSERTED', |
| - DESTINATION_SELECT: 'print_preview.DestinationStore.DESTINATION_SELECT' |
| + DESTINATION_SELECT: 'print_preview.DestinationStore.DESTINATION_SELECT', |
| + SELECTED_DESTINATION_CAPABILITIES_READY: |
| + 'print_preview.DestinationStore.SELECTED_DESTINATION_CAPABILITIES_READY' |
| + }; |
| + |
| + /** |
| + * Delay in milliseconds before the destination store ignores the initial |
| + * destination ID and just selects any printer (since the initial destination |
| + * was not found). |
| + * @type {number} |
| + * @const |
| + * @private |
| + */ |
| + DestinationStore.AUTO_SELECT_TIMEOUT_ = 2000; |
| + |
| + /** |
| + * Creates a local PDF print destination. |
| + * @return {!print_preview.Destination} Created print destination. |
| + * @private |
| + */ |
| + DestinationStore.createLocalPdfPrintDestination_ = function() { |
| + var dest = new print_preview.Destination( |
| + print_preview.Destination.GooglePromotedId.SAVE_AS_PDF, |
| + print_preview.Destination.Type.LOCAL, |
| + localStrings.getString('printToPDF'), |
| + false /*isRecent*/); |
| + dest.capabilities = new print_preview.ChromiumCapabilities( |
| + false /*hasCopiesCapability*/, |
| + '1' /*defaultCopiesStr*/, |
| + false /*hasCollateCapability*/, |
| + false /*defaultIsCollateEnabled*/, |
| + false /*hasDuplexCapability*/, |
| + false /*defaultIsDuplexEnabled*/, |
| + true /*hasOrientationCapability*/, |
| + false /*defaultIsLandscapeEnabled*/, |
| + true /*hasColorCapability*/, |
| + true /*defaultIsColorEnabled*/); |
| + return dest; |
| }; |
| DestinationStore.prototype = { |
| @@ -80,31 +166,68 @@ cr.define('print_preview', function() { |
| * match this ID, that destination will be automatically selected. This |
| * occurs only once for every time this setter is called or if the store is |
| * cleared. |
| - * @param {string} ID of the destination that should be selected |
| - * automatically when added to the store. |
| + * @param {?string} ID of the destination that should be selected |
| + * automatically when added to the store or {@code null} if the first |
| + * destination that is inserted should be selected. |
| */ |
| setInitialDestinationId: function(initialDestinationId) { |
| this.initialDestinationId_ = initialDestinationId; |
| this.isInAutoSelectMode_ = true; |
| - if (this.initialDestinationId_ == null && this.destinations_.length > 0) { |
| + if (this.initialDestinationId_ == null) { |
| + assert(this.destinations_.length > 0, |
| + 'No destinations available to select'); |
| this.selectDestination(this.destinations_[0]); |
| - } else if (this.initialDestinationId_ != null) { |
| - for (var dest, i = 0; dest = this.destinations_[i]; i++) { |
| - if (dest.id == initialDestinationId) { |
| - this.selectDestination(dest); |
| - break; |
| - } |
| + } else { |
| + var candidate = this.destinationMap_[this.initialDestinationId_]; |
| + if (candidate != null) { |
| + this.selectDestination(candidate); |
| } |
| } |
| }, |
| + /** |
| + * Sets the destination store's Google Cloud Print interface. |
| + * @param {!print_preview.CloudPrintInterface} cloudPrintInterface Interface |
| + * to set. |
| + */ |
| + setCloudPrintInterface: function(cloudPrintInterface) { |
| + this.cloudPrintInterface_ = cloudPrintInterface; |
| + this.tracker_.add( |
| + this.cloudPrintInterface_, |
| + cloudprint.CloudPrintInterface.EventType.SEARCH_DONE, |
| + this.onCloudPrintSearchDone_.bind(this)); |
| + this.tracker_.add( |
| + this.cloudPrintInterface_, |
| + cloudprint.CloudPrintInterface.EventType.PRINTER_DONE, |
| + this.onCloudPrintPrinterDone_.bind(this)); |
| + }, |
| + |
| /** @param {!print_preview.Destination} Destination to select. */ |
| selectDestination: function(destination) { |
| this.selectedDestination_ = destination; |
| this.selectedDestination_.isRecent = true; |
| this.isInAutoSelectMode_ = false; |
| + if (this.autoSelectTimeout_ != null) { |
| + clearTimeout(this.autoSelectTimeout_); |
| + this.autoSelectTimeout_ = null; |
| + } |
| cr.dispatchSimpleEvent( |
| this, DestinationStore.EventType.DESTINATION_SELECT); |
| + if (destination.capabilities == null) { |
| + if (destination.type == print_preview.Destination.Type.LOCAL) { |
| + this.nativeLayer_.startGetLocalDestinationCapabilities( |
| + destination.id); |
| + } else { |
| + assert(this.cloudPrintInterface_ != null, |
| + 'Selected destination is a cloud destination, but Google ' + |
| + 'Cloud Print is not enabled'); |
| + this.cloudPrintInterface_.printer(destination.id); |
| + } |
| + } else { |
| + cr.dispatchSimpleEvent( |
| + this, |
| + DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY); |
| + } |
| }, |
| /** |
| @@ -115,16 +238,13 @@ cr.define('print_preview', function() { |
| * insert. |
| */ |
| insertDestination: function(destination) { |
| - this.destinations_.push(destination); |
| - cr.dispatchSimpleEvent( |
| - this, DestinationStore.EventType.DESTINATIONS_INSERTED); |
| - if (this.isInAutoSelectMode_) { |
| - if (this.initialDestinationId_ == null) { |
| + if (this.insertDestination_(destination)) { |
| + cr.dispatchSimpleEvent( |
| + this, DestinationStore.EventType.DESTINATIONS_INSERTED); |
| + if (this.isInAutoSelectMode_ && |
| + (this.initialDestinationId_ == null || |
| + destination.id == this.initialDestinationId_)) { |
| this.selectDestination(destination); |
| - } else { |
| - if (destination.id == this.initialDestinationId_) { |
| - this.selectDestination(destination); |
| - } |
| } |
| } |
| }, |
| @@ -137,21 +257,26 @@ cr.define('print_preview', function() { |
| * destinations to insert. |
| */ |
| insertDestinations: function(destinations) { |
| - this.destinations_ = this.destinations_.concat(destinations); |
| - cr.dispatchSimpleEvent( |
| - this, DestinationStore.EventType.DESTINATIONS_INSERTED); |
| - if (this.isInAutoSelectMode_) { |
| - if (this.initialDestinationId_ == null && destinations.length > 0) { |
| - this.selectDestination(destinations[0]); |
| - } else if (this.initialDestinationId_ != null) { |
| - for (var dest, i = 0; dest = destinations[i]; i++) { |
| - if (dest.id == this.initialDestinationId_) { |
| - this.selectDestination(dest); |
| - break; |
| - } |
| + var insertedDestination = false; |
| + var destinationToAutoSelect = null; |
| + for (var dest, i = 0; dest = destinations[i]; i++) { |
|
dpapad
2012/05/25 16:18:03
This can probably be simplified if you use forEach
Robert Toscano
2012/05/25 19:47:01
Thanks! Didn't know about this one. Done.
|
| + if (this.insertDestination_(dest)) { |
|
dpapad
2012/05/25 16:18:03
You can merge these 2 nested if statements as foll
Robert Toscano
2012/05/25 19:47:01
Kausalya recommended not using assignments in "if"
|
| + insertedDestination = true; |
| + if (this.isInAutoSelectMode_ && |
| + destinationToAutoSelect == null && |
| + (this.initialDestinationId_ == null || |
| + dest.id == this.initialDestinationId_)) { |
| + destinationToAutoSelect = dest; |
| } |
| } |
| } |
| + if (insertedDestination) { |
| + cr.dispatchSimpleEvent( |
| + this, DestinationStore.EventType.DESTINATIONS_INSERTED); |
| + } |
| + if (destinationToAutoSelect != null) { |
| + this.selectDestination(destinationToAutoSelect); |
| + } |
| }, |
| /** |
| @@ -162,14 +287,8 @@ cr.define('print_preview', function() { |
| * updated. |
| */ |
| updateDestination: function(destination) { |
| - var existingDestination = null; |
| - for (var d, i = 0; d = this.destinations_[i]; i++) { |
| - if (destination.id == d.id) { |
| - existingDestination = d; |
| - break; |
| - } |
| - } |
| - if (existingDestination) { |
| + var existingDestination = this.destinationMap_[destination.id]; |
| + if (existingDestination != null) { |
| existingDestination.capabilities = destination.capabilities; |
| return existingDestination; |
| } else { |
| @@ -177,11 +296,164 @@ cr.define('print_preview', function() { |
| } |
| }, |
| - /** Clears all print destinations. */ |
| - clear: function() { |
| + /** Initiates loading of local print destinations. */ |
| + startLoadLocalDestinations: function() { |
| + this.nativeLayer_.startGetLocalDestinations(); |
| + }, |
| + |
| + /** Initiates loading of recent cloud destinations. */ |
| + startLoadRecentCloudDestinations: function() { |
| + if (this.cloudPrintInterface_ != null) { |
| + this.cloudPrintInterface_.search(true /*isRecent*/); |
| + } |
| + }, |
| + |
| + /** Initiates loading of all cloud destinations. */ |
| + startLoadAllCloudDestinations: function() { |
| + if (this.cloudPrintInterface_ != null && |
| + !this.hasLoadedAllCloudDestinations_) { |
| + this.cloudPrintInterface_.search(false /*isRecent*/); |
| + this.hasLoadedAllCloudDestinations_ = true; |
| + } |
| + }, |
| + |
| + /** |
| + * Inserts a destination into the store without dispatching any events. |
| + * @return {boolean} Whether the inserted destination was not already in the |
| + * store. |
| + * @private |
| + */ |
| + insertDestination_: function(destination) { |
| + if (this.destinationMap_[destination.id] == null) { |
| + this.destinations_.push(destination); |
| + this.destinationMap_[destination.id] = destination; |
| + return true; |
| + } else { |
| + return false; |
| + } |
| + }, |
| + |
| + /** |
| + * Binds handlers to events. |
| + * @private |
| + */ |
| + addEventListeners_: function() { |
| + this.tracker_.add( |
| + this.nativeLayer_, |
| + print_preview.NativeLayer.EventType.LOCAL_DESTINATIONS_SET, |
| + this.onLocalDestinationsSet_.bind(this)); |
| + this.tracker_.add( |
| + this.nativeLayer_, |
| + print_preview.NativeLayer.EventType.CAPABILITIES_SET, |
| + this.onLocalDestinationCapabilitiesSet_.bind(this)); |
| + this.tracker_.add( |
| + this.nativeLayer_, |
| + print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD, |
| + this.onDestinationsReload_.bind(this)); |
| + }, |
| + |
| + /** |
| + * Resets the state of the destination store to its initial state. |
| + * @private |
| + */ |
| + reset_: function() { |
| this.destinations_ = []; |
| + this.destinationMap_ = {}; |
| this.selectedDestination_ = null; |
| this.isInAutoSelectMode_ = true; |
| + this.hasLoadedAllCloudDestinations_ = false; |
| + this.insertDestination( |
| + DestinationStore.createLocalPdfPrintDestination_()); |
| + this.autoSelectTimeout_ = setTimeout( |
| + this.onAutoSelectTimeoutExpired_.bind(this), |
| + DestinationStore.AUTO_SELECT_TIMEOUT_); |
| + }, |
| + |
| + /** |
| + * Called when the local destinations have been got from the native layer. |
| + * @param {cr.Event} Contains the local destinations. |
| + * @private |
| + */ |
| + onLocalDestinationsSet_: function(event) { |
| + var localDestinations = []; |
| + for (var destInfo, i = 0; destInfo = event.destinationInfos[i]; i++) { |
|
dpapad
2012/05/25 16:18:03
use forEach.
Robert Toscano
2012/05/25 19:47:01
Done.
|
| + localDestinations.push( |
| + print_preview.LocalDestinationParser.parse(destInfo)); |
| + } |
| + this.insertDestinations(localDestinations); |
| + }, |
| + |
| + /** |
| + * Called when the native layer retrieves the capabilities for the selected |
| + * local destination. |
| + * @param {cr.Event} event Contains the capabilities of the local print |
| + * destination. |
| + * @private |
| + */ |
| + onLocalDestinationCapabilitiesSet_: function(event) { |
| + // TODO There may be a race condition here. This method is assumed to |
| + // return capabilities for the currently selected printer. But between the |
| + // time the local printer was selected and the capabilities were |
| + // retrieved, the selected printer can change. One way to address this is |
| + // to include the destination ID in the event.settingsInfo parameter. |
| + if (this.selectedDestination_ && this.selectedDestination_.isLocal) { |
| + var capabilities = print_preview.LocalCapabilitiesParser.parse( |
| + event.settingsInfo); |
| + this.selectedDestination_.capabilities = capabilities; |
| + cr.dispatchSimpleEvent( |
| + this, |
| + DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY); |
| + } |
| + }, |
| + |
| + /** |
| + * Called when the /search call completes. Adds the fetched printers to the |
| + * destination store. |
| + * @param {cr.Event} event Contains the fetched printers. |
| + * @private |
| + */ |
| + onCloudPrintSearchDone_: function(event) { |
| + this.insertDestinations(event.printers); |
| + }, |
| + |
| + /** |
| + * Called when /printer call completes. Updates the specified destination's |
| + * print capabilities. |
| + * @param {cr.Event} event Contains detailed information about the |
| + * destination. |
| + * @private |
| + */ |
| + onCloudPrintPrinterDone_: function(event) { |
| + var dest = this.updateDestination(event.printer); |
| + if (this.selectedDestination_ == dest) { |
| + cr.dispatchSimpleEvent( |
| + this, |
| + DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY); |
| + } |
| + }, |
| + |
| + /** |
| + * Called from native layer after the user was requested to sign in, and did |
| + * so successfully. |
| + * @private |
| + */ |
| + onDestinationsReload_: function() { |
| + this.reset_(); |
| + this.startLoadLocalDestinations(); |
| + this.startLoadRecentCloudDestinations(); |
| + this.startLoadAllCloudDestinations(); |
| + }, |
| + |
| + /** |
| + * Called when no destination was auto-selected after some timeout. Selects |
| + * the first destination in store. |
| + * @private |
| + */ |
| + onAutoSelectTimeoutExpired_: function() { |
| + this.autoSelectTimeout_ = null; |
| + assert(this.destinations_.length > 0, |
| + 'No destinations were loaded before auto-select timeout expired'); |
| + this.selectDestination(this.destinations_[0]); |
| } |
| }; |