blob: ef00b68646241803581709486942ff60d80981fe [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('print_preview', function() {
'use strict';
/**
* A data store that stores destinations and dispatches events when the data
* store changes.
* @param {!Array<!print_preview.Destination.Origin>} origins Match
* destinations from these origins.
* @param {RegExp} idRegExp Match destination's id.
* @param {RegExp} displayNameRegExp Match destination's displayName.
* @param {boolean} skipVirtualDestinations Whether to ignore virtual
* destinations, for example, Save as PDF.
* @constructor
*/
function DestinationMatch(
origins, idRegExp, displayNameRegExp, skipVirtualDestinations) {
/** @private {!Array<!print_preview.Destination.Origin>} */
this.origins_ = origins;
/** @private {RegExp} */
this.idRegExp_ = idRegExp;
/** @private {RegExp} */
this.displayNameRegExp_ = displayNameRegExp;
/** @private {boolean} */
this.skipVirtualDestinations_ = skipVirtualDestinations;
};
DestinationMatch.prototype = {
/**
* @param {!print_preview.Destination.Origin} origin Origin to match.
* @return {boolean} Whether the origin is one of the {@code origins_}.
*/
matchOrigin: function(origin) {
return arrayContains(this.origins_, origin);
},
/**
* @param {string} id Id of the destination.
* @param {string} origin Origin of the destination.
* @return {boolean} Whether destination is the same as initial.
*/
matchIdAndOrigin: function(id, origin) {
return this.matchOrigin(origin) &&
this.idRegExp_ &&
this.idRegExp_.test(id);
},
/**
* @param {!print_preview.Destination} destination Destination to match.
* @return {boolean} Whether {@code destination} matches the last user
* selected one.
*/
match: function(destination) {
if (!this.matchOrigin(destination.origin)) {
return false;
}
if (this.idRegExp_ && !this.idRegExp_.test(destination.id)) {
return false;
}
if (this.displayNameRegExp_ &&
!this.displayNameRegExp_.test(destination.displayName)) {
return false;
}
if (this.skipVirtualDestinations_ &&
this.isVirtualDestination_(destination)) {
return false;
}
return true;
},
/**
* @param {!print_preview.Destination} destination Destination to check.
* @return {boolean} Whether {@code destination} is virtual, in terms of
* destination selection.
* @private
*/
isVirtualDestination_: function(destination) {
if (destination.origin == print_preview.Destination.Origin.LOCAL) {
return arrayContains(
[print_preview.Destination.GooglePromotedId.SAVE_AS_PDF],
destination.id);
}
return arrayContains(
[print_preview.Destination.GooglePromotedId.DOCS,
print_preview.Destination.GooglePromotedId.FEDEX],
destination.id);
}
};
/**
* 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.
* @param {!print_preview.UserInfo} userInfo User information repository.
* @param {!print_preview.AppState} appState Application state.
* @constructor
* @extends {cr.EventTarget}
*/
function DestinationStore(nativeLayer, userInfo, appState) {
cr.EventTarget.call(this);
/**
* Used to fetch local print destinations.
* @type {!print_preview.NativeLayer}
* @private
*/
this.nativeLayer_ = nativeLayer;
/**
* User information repository.
* @type {!print_preview.UserInfo}
* @private
*/
this.userInfo_ = userInfo;
/**
* Used to load and persist the selected destination.
* @type {!print_preview.AppState}
* @private
*/
this.appState_ = appState;
/**
* Used to track metrics.
* @type {!print_preview.DestinationSearchMetricsContext}
* @private
*/
this.metrics_ = new print_preview.DestinationSearchMetricsContext();
/**
* Internal backing store for the data store.
* @type {!Array<!print_preview.Destination>}
* @private
*/
this.destinations_ = [];
/**
* Cache used for constant lookup of destinations by origin and id.
* @type {Object<!print_preview.Destination>}
* @private
*/
this.destinationMap_ = {};
/**
* Currently selected destination.
* @type {print_preview.Destination}
* @private
*/
this.selectedDestination_ = null;
/**
* Whether the destination store will auto select the destination that
* matches this set of parameters.
* @type {print_preview.DestinationMatch}
* @private
*/
this.autoSelectMatchingDestination_ = null;
/**
* Event tracker used to track event listeners of the destination store.
* @type {!EventTracker}
* @private
*/
this.tracker_ = new EventTracker();
/**
* Whether PDF printer is enabled. It's disabled, for example, in App Kiosk
* mode.
* @type {boolean}
* @private
*/
this.pdfPrinterEnabled_ = false;
/**
* ID of the system default destination.
* @type {?string}
* @private
*/
this.systemDefaultDestinationId_ = null;
/**
* Used to fetch cloud-based print destinations.
* @type {cloudprint.CloudPrintInterface}
* @private
*/
this.cloudPrintInterface_ = null;
/**
* Maps user account to the list of origins for which destinations are
* already loaded.
* @type {!Object<Array<print_preview.Destination.Origin>>}
* @private
*/
this.loadedCloudOrigins_ = {};
/**
* ID of a timeout after the initial destination ID is set. If no inserted
* destination matches the initial destination ID after the specified
* timeout, the first destination in the store will be automatically
* selected.
* @type {?number}
* @private
*/
this.autoSelectTimeout_ = null;
/**
* Whether a search for local destinations is in progress.
* @type {boolean}
* @private
*/
this.isLocalDestinationSearchInProgress_ = false;
/**
* Whether the destination store has already loaded or is loading all local
* destinations.
* @type {boolean}
* @private
*/
this.hasLoadedAllLocalDestinations_ = false;
/**
* Whether a search for privet destinations is in progress.
* @type {boolean}
* @private
*/
this.isPrivetDestinationSearchInProgress_ = false;
/**
* Whether the destination store has already loaded or is loading all privet
* destinations.
* @type {boolean}
* @private
*/
this.hasLoadedAllPrivetDestinations_ = false;
/**
* ID of a timeout after the start of a privet search to end that privet
* search.
* @type {?number}
* @private
*/
this.privetSearchTimeout_ = null;
/**
* Whether a search for extension destinations is in progress.
* @type {boolean}
* @private
*/
this.isExtensionDestinationSearchInProgress_ = false;
/**
* Whether the destination store has already loaded all extension
* destinations.
* @type {boolean}
* @private
*/
this.hasLoadedAllExtensionDestinations_ = false;
/**
* ID of a timeout set at the start of an extension destination search. The
* timeout ends the search.
* @type {?number}
* @private
*/
this.extensionSearchTimeout_ = null;
/**
* MDNS service name of destination that we are waiting to register.
* @type {?string}
* @private
*/
this.waitForRegisterDestination_ = null;
this.addEventListeners_();
this.reset_();
};
/**
* Event types dispatched by the data store.
* @enum {string}
*/
DestinationStore.EventType = {
DESTINATION_SEARCH_DONE:
'print_preview.DestinationStore.DESTINATION_SEARCH_DONE',
DESTINATION_SEARCH_STARTED:
'print_preview.DestinationStore.DESTINATION_SEARCH_STARTED',
DESTINATION_SELECT: 'print_preview.DestinationStore.DESTINATION_SELECT',
DESTINATIONS_INSERTED:
'print_preview.DestinationStore.DESTINATIONS_INSERTED',
PROVISIONAL_DESTINATION_RESOLVED:
'print_preview.DestinationStore.PROVISIONAL_DESTINATION_RESOLVED',
CACHED_SELECTED_DESTINATION_INFO_READY:
'print_preview.DestinationStore.CACHED_SELECTED_DESTINATION_INFO_READY',
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_ = 15000;
/**
* Amount of time spent searching for privet destination, in milliseconds.
* @type {number}
* @const
* @private
*/
DestinationStore.PRIVET_SEARCH_DURATION_ = 5000;
/**
* Maximum amount of time spent searching for extension destinations, in
* milliseconds.
* @type {number}
* @const
* @private
*/
DestinationStore.EXTENSION_SEARCH_DURATION_ = 5000;
/**
* Localizes printer capabilities.
* @param {!Object} capabilities Printer capabilities to localize.
* @return {!Object} Localized capabilities.
* @private
*/
DestinationStore.localizeCapabilities_ = function(capabilities) {
var mediaSize = capabilities.printer.media_size;
if (mediaSize) {
var mediaDisplayNames = {
'ISO_A0': 'A0',
'ISO_A1': 'A1',
'ISO_A2': 'A2',
'ISO_A3': 'A3',
'ISO_A4': 'A4',
'ISO_A5': 'A5',
'NA_LEGAL': 'Legal',
'NA_LETTER': 'Letter',
'NA_LEDGER': 'Tabloid'
};
for (var i = 0, media; media = mediaSize.option[i]; i++) {
// No need to patch capabilities with localized names provided.
if (!media.custom_display_name_localized) {
media.custom_display_name =
media.custom_display_name ||
mediaDisplayNames[media.name] ||
media.name;
}
}
}
return capabilities;
};
DestinationStore.prototype = {
__proto__: cr.EventTarget.prototype,
/**
* @param {string=} opt_account Account to filter destinations by. When
* omitted, all destinations are returned.
* @return {!Array<!print_preview.Destination>} List of destinations
* accessible by the {@code account}.
*/
destinations: function(opt_account) {
if (opt_account) {
return this.destinations_.filter(function(destination) {
return !destination.account || destination.account == opt_account;
});
} else {
return this.destinations_.slice(0);
}
},
/**
* @return {print_preview.Destination} The currently selected destination or
* {@code null} if none is selected.
*/
get selectedDestination() {
return this.selectedDestination_;
},
/** @return {boolean} Whether destination selection is pending or not. */
get isAutoSelectDestinationInProgress() {
return this.selectedDestination_ == null &&
this.autoSelectTimeout_ != null;
},
/**
* @return {boolean} Whether a search for local destinations is in progress.
*/
get isLocalDestinationSearchInProgress() {
return this.isLocalDestinationSearchInProgress_ ||
this.isPrivetDestinationSearchInProgress_ ||
this.isExtensionDestinationSearchInProgress_;
},
/**
* @return {boolean} Whether a search for cloud destinations is in progress.
*/
get isCloudDestinationSearchInProgress() {
return !!this.cloudPrintInterface_ &&
this.cloudPrintInterface_.isCloudDestinationSearchInProgress;
},
/**
* Initializes the destination store. Sets the initially selected
* destination. If any inserted destinations match this ID, that destination
* will be automatically selected. This method must be called after the
* print_preview.AppState has been initialized.
* @param {boolean} isInAppKioskMode Whether the print preview is in App
* Kiosk mode.
* @param {?string} systemDefaultDestinationId ID of the system default
* destination.
* @param {?string} serializedDefaultDestinationSelectionRulesStr Serialized
* default destination selection rules.
*/
init: function(
isInAppKioskMode,
systemDefaultDestinationId,
serializedDefaultDestinationSelectionRulesStr) {
this.pdfPrinterEnabled_ = !isInAppKioskMode;
this.systemDefaultDestinationId_ = systemDefaultDestinationId;
this.createLocalPdfPrintDestination_();
if (!this.appState_.selectedDestinationId ||
!this.appState_.selectedDestinationOrigin) {
var destinationMatch = this.convertToDestinationMatch_(
serializedDefaultDestinationSelectionRulesStr);
if (destinationMatch) {
this.fetchMatchingDestination_(destinationMatch);
return;
}
}
if (!this.systemDefaultDestinationId_ &&
!(this.appState_.selectedDestinationId &&
this.appState_.selectedDestinationOrigin)) {
this.selectPdfDestination_();
return;
}
var origin = print_preview.Destination.Origin.LOCAL;
var id = this.systemDefaultDestinationId_;
var account = '';
var name = '';
var capabilities = null;
var extensionId = '';
var extensionName = '';
if (this.appState_.selectedDestinationId &&
this.appState_.selectedDestinationOrigin) {
origin = this.appState_.selectedDestinationOrigin;
id = this.appState_.selectedDestinationId;
account = this.appState_.selectedDestinationAccount || '';
name = this.appState_.selectedDestinationName || '';
capabilities = this.appState_.selectedDestinationCapabilities;
extensionId = this.appState_.selectedDestinationExtensionId || '';
extensionName = this.appState_.selectedDestinationExtensionName || '';
}
var candidate =
this.destinationMap_[this.getDestinationKey_(origin, id, account)];
if (candidate != null) {
this.selectDestination(candidate);
return;
}
if (this.fetchPreselectedDestination_(
origin,
id,
account,
name,
capabilities,
extensionId,
extensionName)) {
return;
}
this.selectPdfDestination_();
},
/**
* Attempts to fetch capabilities of the destination identified by the
* provided origin, id and account.
* @param {!print_preview.Destination.Origin} origin Destination origin.
* @param {string} id Destination id.
* @param {string} account User account destination is registered for.
* @param {string} name Destination display name.
* @param {?print_preview.Cdd} capabilities Destination capabilities.
* @param {string} extensionId Extension ID associated with this
* destination.
* @param {string} extensionName Extension name associated with this
* destination.
* @private
*/
fetchPreselectedDestination_: function(
origin, id, account, name, capabilities, extensionId, extensionName) {
this.autoSelectMatchingDestination_ =
this.createExactDestinationMatch_(origin, id);
if (origin == print_preview.Destination.Origin.LOCAL) {
this.nativeLayer_.startGetLocalDestinationCapabilities(id);
return true;
}
if (this.cloudPrintInterface_ &&
(origin == print_preview.Destination.Origin.COOKIES ||
origin == print_preview.Destination.Origin.DEVICE)) {
this.cloudPrintInterface_.printer(id, origin, account);
return true;
}
if (origin == print_preview.Destination.Origin.PRIVET) {
// TODO(noamsml): Resolve a specific printer instead of listing all
// privet printers in this case.
this.nativeLayer_.startGetPrivetDestinations();
// Create a fake selectedDestination_ that is not actually in the
// destination store. When the real destination is created, this
// destination will be overwritten.
this.selectedDestination_ = new print_preview.Destination(
id,
print_preview.Destination.Type.LOCAL,
print_preview.Destination.Origin.PRIVET,
name,
false /*isRecent*/,
print_preview.Destination.ConnectionStatus.ONLINE);
this.selectedDestination_.capabilities = capabilities;
cr.dispatchSimpleEvent(
this,
DestinationStore.EventType.CACHED_SELECTED_DESTINATION_INFO_READY);
return true;
}
if (origin == print_preview.Destination.Origin.EXTENSION) {
// TODO(tbarzic): Add support for requesting a single extension's
// printer list.
this.startLoadExtensionDestinations();
this.selectedDestination_ =
print_preview.ExtensionDestinationParser.parse({
extensionId: extensionId,
extensionName: extensionName,
id: id,
name: name
});
if (capabilities) {
this.selectedDestination_.capabilities = capabilities;
cr.dispatchSimpleEvent(
this,
DestinationStore.EventType
.CACHED_SELECTED_DESTINATION_INFO_READY);
}
return true;
}
return false;
},
/**
* Attempts to find a destination matching the provided rules.
* @param {!print_preview.DestinationMatch} destinationMatch Rules to match.
* @private
*/
fetchMatchingDestination_: function(destinationMatch) {
this.autoSelectMatchingDestination_ = destinationMatch;
if (destinationMatch.matchOrigin(
print_preview.Destination.Origin.LOCAL)) {
this.startLoadLocalDestinations();
}
if (destinationMatch.matchOrigin(
print_preview.Destination.Origin.PRIVET)) {
this.startLoadPrivetDestinations();
}
if (destinationMatch.matchOrigin(
print_preview.Destination.Origin.EXTENSION)) {
this.startLoadExtensionDestinations();
}
if (destinationMatch.matchOrigin(
print_preview.Destination.Origin.COOKIES) ||
destinationMatch.matchOrigin(
print_preview.Destination.Origin.DEVICE) ||
destinationMatch.matchOrigin(
print_preview.Destination.Origin.PROFILE)) {
this.startLoadCloudDestinations();
}
},
/**
* @param {?string} serializedDefaultDestinationSelectionRulesStr Serialized
* default destination selection rules.
* @return {!print_preview.DestinationMatch} Creates rules matching
* previously selected destination.
* @private
*/
convertToDestinationMatch_: function(
serializedDefaultDestinationSelectionRulesStr) {
var matchRules = null;
try {
if (serializedDefaultDestinationSelectionRulesStr) {
matchRules =
JSON.parse(serializedDefaultDestinationSelectionRulesStr);
}
} catch(e) {
console.error(
'Failed to parse defaultDestinationSelectionRules: ' + e);
}
if (!matchRules)
return;
var isLocal = !matchRules.kind || matchRules.kind == 'local';
var isCloud = !matchRules.kind || matchRules.kind == 'cloud';
if (!isLocal && !isCloud) {
console.error('Unsupported type: "' + matchRules.kind + '"');
return null;
}
var origins = [];
if (isLocal) {
origins.push(print_preview.Destination.Origin.LOCAL);
origins.push(print_preview.Destination.Origin.PRIVET);
origins.push(print_preview.Destination.Origin.EXTENSION);
}
if (isCloud) {
origins.push(print_preview.Destination.Origin.COOKIES);
origins.push(print_preview.Destination.Origin.DEVICE);
origins.push(print_preview.Destination.Origin.PROFILE);
}
var idRegExp = null;
try {
if (matchRules.idPattern) {
idRegExp = new RegExp(matchRules.idPattern || '.*');
}
} catch (e) {
console.error('Failed to parse regexp for "id": ' + e);
}
var displayNameRegExp = null;
try {
if (matchRules.namePattern) {
displayNameRegExp = new RegExp(matchRules.namePattern || '.*');
}
} catch (e) {
console.error('Failed to parse regexp for "name": ' + e);
}
return new DestinationMatch(
origins,
idRegExp,
displayNameRegExp,
true /*skipVirtualDestinations*/);
},
/**
* @return {print_preview.DestinationMatch} Creates rules matching
* previously selected destination.
* @private
*/
convertPreselectedToDestinationMatch_: function() {
if (this.appState_.selectedDestinationId &&
this.appState_.selectedDestinationOrigin) {
return this.createExactDestinationMatch_(
this.appState_.selectedDestinationOrigin,
this.appState_.selectedDestinationId);
}
if (this.systemDefaultDestinationId_) {
return this.createExactDestinationMatch_(
print_preview.Destination.Origin.LOCAL,
this.systemDefaultDestinationId_);
}
return null;
},
/**
* @param {!print_preview.Destination.Origin} origin Destination origin.
* @param {string} id Destination id.
* @return {!print_preview.DestinationMatch} Creates rules matching
* provided destination.
* @private
*/
createExactDestinationMatch_: function(origin, id) {
return new DestinationMatch(
[origin],
new RegExp('^' + id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '$'),
null /*displayNameRegExp*/,
false /*skipVirtualDestinations*/);
},
/**
* Sets the destination store's Google Cloud Print interface.
* @param {!cloudprint.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.SEARCH_FAILED,
this.onCloudPrintSearchDone_.bind(this));
this.tracker_.add(
this.cloudPrintInterface_,
cloudprint.CloudPrintInterface.EventType.PRINTER_DONE,
this.onCloudPrintPrinterDone_.bind(this));
this.tracker_.add(
this.cloudPrintInterface_,
cloudprint.CloudPrintInterface.EventType.PRINTER_FAILED,
this.onCloudPrintPrinterFailed_.bind(this));
this.tracker_.add(
this.cloudPrintInterface_,
cloudprint.CloudPrintInterface.EventType.PROCESS_INVITE_DONE,
this.onCloudPrintProcessInviteDone_.bind(this));
},
/**
* @return {boolean} Whether only default cloud destinations have been
* loaded.
*/
hasOnlyDefaultCloudDestinations: function() {
// TODO: Move the logic to print_preview.
return this.destinations_.every(function(dest) {
return dest.isLocal ||
dest.id == print_preview.Destination.GooglePromotedId.DOCS ||
dest.id == print_preview.Destination.GooglePromotedId.FEDEX;
});
},
/**
* @param {print_preview.Destination} destination Destination to select.
*/
selectDestination: function(destination) {
this.autoSelectMatchingDestination_ = null;
// When auto select expires, DESTINATION_SELECT event has to be dispatched
// anyway (see isAutoSelectDestinationInProgress() logic).
if (this.autoSelectTimeout_) {
clearTimeout(this.autoSelectTimeout_);
this.autoSelectTimeout_ = null;
} else if (destination == this.selectedDestination_) {
return;
}
if (destination == null) {
this.selectedDestination_ = null;
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SELECT);
return;
}
assert(!destination.isProvisional,
'Unable to select provisonal destinations');
// Update and persist selected destination.
this.selectedDestination_ = destination;
this.selectedDestination_.isRecent = true;
if (destination.id == print_preview.Destination.GooglePromotedId.FEDEX &&
!destination.isTosAccepted) {
assert(this.cloudPrintInterface_ != null,
'Selected FedEx destination, but GCP API is not available');
destination.isTosAccepted = true;
this.cloudPrintInterface_.updatePrinterTosAcceptance(destination, true);
}
this.appState_.persistSelectedDestination(this.selectedDestination_);
// Adjust metrics.
if (destination.cloudID &&
this.destinations_.some(function(otherDestination) {
return otherDestination.cloudID == destination.cloudID &&
otherDestination != destination;
})) {
this.metrics_.record(destination.isPrivet ?
print_preview.Metrics.DestinationSearchBucket.
PRIVET_DUPLICATE_SELECTED :
print_preview.Metrics.DestinationSearchBucket.
CLOUD_DUPLICATE_SELECTED);
}
// Notify about selected destination change.
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SELECT);
// Request destination capabilities, of not known yet.
if (destination.capabilities == null) {
if (destination.isPrivet) {
this.nativeLayer_.startGetPrivetDestinationCapabilities(
destination.id);
} else if (destination.isExtension) {
this.nativeLayer_.startGetExtensionDestinationCapabilities(
destination.id);
} else if (destination.isLocal) {
this.nativeLayer_.startGetLocalDestinationCapabilities(
destination.id);
} else {
assert(this.cloudPrintInterface_ != null,
'Cloud destination selected, but GCP is not enabled');
this.cloudPrintInterface_.printer(
destination.id, destination.origin, destination.account);
}
} else {
cr.dispatchSimpleEvent(
this,
DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
}
},
/**
* Attempts to resolve a provisional destination.
* @param {!print_preview.Destination} destinaion Provisional destination
* that should be resolved.
*/
resolveProvisionalDestination: function(destination) {
assert(
destination.provisionalType ==
print_preview.Destination.ProvisionalType.NEEDS_USB_PERMISSION,
'Provisional type cannot be resolved.');
this.nativeLayer_.grantExtensionPrinterAccess(destination.id);
},
/**
* Selects 'Save to PDF' destination (since it always exists).
* @private
*/
selectPdfDestination_: function() {
var saveToPdfKey = this.getDestinationKey_(
print_preview.Destination.Origin.LOCAL,
print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
'');
this.selectDestination(
this.destinationMap_[saveToPdfKey] || this.destinations_[0] || null);
},
/**
* Attempts to select system default destination with a fallback to
* 'Save to PDF' destination.
* @private
*/
selectDefaultDestination_: function() {
if (this.systemDefaultDestinationId_) {
if (this.autoSelectMatchingDestination_ &&
!this.autoSelectMatchingDestination_.matchIdAndOrigin(
this.systemDefaultDestinationId_,
print_preview.Destination.Origin.LOCAL)) {
if (this.fetchPreselectedDestination_(
print_preview.Destination.Origin.LOCAL,
this.systemDefaultDestinationId_,
'' /*account*/,
'' /*name*/,
null /*capabilities*/,
'' /*extensionId*/,
'' /*extensionName*/)) {
return;
}
}
}
this.selectPdfDestination_();
},
/** Initiates loading of local print destinations. */
startLoadLocalDestinations: function() {
if (!this.hasLoadedAllLocalDestinations_) {
this.hasLoadedAllLocalDestinations_ = true;
this.nativeLayer_.startGetLocalDestinations();
this.isLocalDestinationSearchInProgress_ = true;
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
}
},
/** Initiates loading of privet print destinations. */
startLoadPrivetDestinations: function() {
if (!this.hasLoadedAllPrivetDestinations_) {
if (this.privetDestinationSearchInProgress_)
clearTimeout(this.privetSearchTimeout_);
this.isPrivetDestinationSearchInProgress_ = true;
this.nativeLayer_.startGetPrivetDestinations();
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
this.privetSearchTimeout_ = setTimeout(
this.endPrivetPrinterSearch_.bind(this),
DestinationStore.PRIVET_SEARCH_DURATION_);
}
},
/** Initializes loading of extension managed print destinations. */
startLoadExtensionDestinations: function() {
if (this.hasLoadedAllExtensionDestinations_)
return;
if (this.isExtensionDestinationSearchInProgress_)
clearTimeout(this.extensionSearchTimeout_);
this.isExtensionDestinationSearchInProgress_ = true;
this.nativeLayer_.startGetExtensionDestinations();
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
this.extensionSearchTimeout_ = setTimeout(
this.endExtensionPrinterSearch_.bind(this),
DestinationStore.EXTENSION_SEARCH_DURATION_);
},
/**
* Initiates loading of cloud destinations.
* @param {print_preview.Destination.Origin=} opt_origin Search destinations
* for the specified origin only.
*/
startLoadCloudDestinations: function(opt_origin) {
if (this.cloudPrintInterface_ != null) {
var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || [];
if (origins.length == 0 ||
(opt_origin && origins.indexOf(opt_origin) < 0)) {
this.cloudPrintInterface_.search(
this.userInfo_.activeUser, opt_origin);
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_STARTED);
}
}
},
/** Requests load of COOKIE based cloud destinations. */
reloadUserCookieBasedDestinations: function() {
var origins = this.loadedCloudOrigins_[this.userInfo_.activeUser] || [];
if (origins.indexOf(print_preview.Destination.Origin.COOKIES) >= 0) {
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
} else {
this.startLoadCloudDestinations(
print_preview.Destination.Origin.COOKIES);
}
},
/** Initiates loading of all known destination types. */
startLoadAllDestinations: function() {
this.startLoadCloudDestinations();
this.startLoadLocalDestinations();
this.startLoadPrivetDestinations();
this.startLoadExtensionDestinations();
},
/**
* Wait for a privet device to be registered.
*/
waitForRegister: function(id) {
this.nativeLayer_.startGetPrivetDestinations();
this.waitForRegisterDestination_ = id;
},
/**
* Event handler for {@code
* print_preview.NativeLayer.EventType.PROVISIONAL_DESTINATION_RESOLVED}.
* Currently assumes the provisional destination is an extension
* destination.
* Called when a provisional destination resolvement attempt finishes.
* The provisional destination is removed from the store and replaced with
* a destination created from the resolved destination properties, if any
* are reported.
* Emits {@code DestinationStore.EventType.PROVISIONAL_DESTINATION_RESOLVED}
* event.
* @param {!Event} The event containing the provisional destination ID and
* resolved destination description. If the destination was not
* successfully resolved, the description will not be set.
* @private
*/
handleProvisionalDestinationResolved_: function(evt) {
var provisionalDestinationIndex = -1;
var provisionalDestination = null;
for (var i = 0; i < this.destinations_.length; ++i) {
if (evt.provisionalId == this.destinations_[i].id) {
provisionalDestinationIndex = i;
provisionalDestination = this.destinations_[i];
break;
}
}
if (!provisionalDestination)
return;
this.destinations_.splice(provisionalDestinationIndex, 1);
delete this.destinationMap_[this.getKey_(provisionalDestination)];
var destination = evt.destination ?
print_preview.ExtensionDestinationParser.parse(evt.destination) :
null;
if (destination)
this.insertIntoStore_(destination);
var event = new Event(
DestinationStore.EventType.PROVISIONAL_DESTINATION_RESOLVED);
event.provisionalId = evt.provisionalId;
event.destination = destination;
this.dispatchEvent(event);
},
/**
* Inserts {@code destination} to the data store and dispatches a
* DESTINATIONS_INSERTED event.
* @param {!print_preview.Destination} destination Print destination to
* insert.
* @private
*/
insertDestination_: function(destination) {
if (this.insertIntoStore_(destination)) {
this.destinationsInserted_(destination);
}
},
/**
* Inserts multiple {@code destinations} to the data store and dispatches
* single DESTINATIONS_INSERTED event.
* @param {!Array<print_preview.Destination>} destinations Print
* destinations to insert.
* @private
*/
insertDestinations_: function(destinations) {
var inserted = false;
destinations.forEach(function(destination) {
inserted = this.insertIntoStore_(destination) || inserted;
}, this);
if (inserted) {
this.destinationsInserted_();
}
},
/**
* Dispatches DESTINATIONS_INSERTED event. In auto select mode, tries to
* update selected destination to match
* {@code autoSelectMatchingDestination_}.
* @param {print_preview.Destination=} opt_destination The only destination
* that was changed or skipped if possibly more than one destination was
* changed. Used as a hint to limit destination search scope against
* {@code autoSelectMatchingDestination_).
*/
destinationsInserted_: function(opt_destination) {
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATIONS_INSERTED);
if (this.autoSelectMatchingDestination_) {
var destinationsToSearch =
opt_destination && [opt_destination] || this.destinations_;
destinationsToSearch.some(function(destination) {
if (this.autoSelectMatchingDestination_.match(destination)) {
this.selectDestination(destination);
return true;
}
}, this);
}
},
/**
* Updates an existing print destination with capabilities and display name
* information. If the destination doesn't already exist, it will be added.
* @param {!print_preview.Destination} destination Destination to update.
* @return {!print_preview.Destination} The existing destination that was
* updated or {@code null} if it was the new destination.
* @private
*/
updateDestination_: function(destination) {
assert(destination.constructor !== Array, 'Single printer expected');
var existingDestination = this.destinationMap_[this.getKey_(destination)];
if (existingDestination != null) {
existingDestination.capabilities = destination.capabilities;
} else {
this.insertDestination_(destination);
}
if (existingDestination == this.selectedDestination_ ||
destination == this.selectedDestination_) {
this.appState_.persistSelectedDestination(this.selectedDestination_);
cr.dispatchSimpleEvent(
this,
DestinationStore.EventType.SELECTED_DESTINATION_CAPABILITIES_READY);
}
return existingDestination;
},
/**
* Called when the search for Privet printers is done.
* @private
*/
endPrivetPrinterSearch_: function() {
this.nativeLayer_.stopGetPrivetDestinations();
this.isPrivetDestinationSearchInProgress_ = false;
this.hasLoadedAllPrivetDestinations_ = true;
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
},
/**
* Called when loading of extension managed printers is done.
* @private
*/
endExtensionPrinterSearch_: function() {
this.isExtensionDestinationSearchInProgress_ = false;
this.hasLoadedAllExtensionDestinations_ = true;
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
// Clear initially selected (cached) extension destination if it hasn't
// been found among reported extension destinations.
if (this.autoSelectMatchingDestination_ &&
this.autoSelectMatchingDestination_.matchOrigin(
print_preview.Destination.Origin.EXTENSION) &&
this.selectedDestination_ &&
this.selectedDestination_.isExtension) {
this.selectDefaultDestination_();
}
},
/**
* Inserts a destination into the store without dispatching any events.
* @return {boolean} Whether the inserted destination was not already in the
* store.
* @private
*/
insertIntoStore_: function(destination) {
var key = this.getKey_(destination);
var existingDestination = this.destinationMap_[key];
if (existingDestination == null) {
this.destinations_.push(destination);
this.destinationMap_[key] = destination;
return true;
} else if (existingDestination.connectionStatus ==
print_preview.Destination.ConnectionStatus.UNKNOWN &&
destination.connectionStatus !=
print_preview.Destination.ConnectionStatus.UNKNOWN) {
existingDestination.connectionStatus = destination.connectionStatus;
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.GET_CAPABILITIES_FAIL,
this.onGetCapabilitiesFail_.bind(this));
this.tracker_.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.DESTINATIONS_RELOAD,
this.onDestinationsReload_.bind(this));
this.tracker_.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.PRIVET_PRINTER_CHANGED,
this.onPrivetPrinterAdded_.bind(this));
this.tracker_.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.PRIVET_CAPABILITIES_SET,
this.onPrivetCapabilitiesSet_.bind(this));
this.tracker_.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.EXTENSION_PRINTERS_ADDED,
this.onExtensionPrintersAdded_.bind(this));
this.tracker_.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.EXTENSION_CAPABILITIES_SET,
this.onExtensionCapabilitiesSet_.bind(this));
this.tracker_.add(
this.nativeLayer_,
print_preview.NativeLayer.EventType.PROVISIONAL_DESTINATION_RESOLVED,
this.handleProvisionalDestinationResolved_.bind(this));
},
/**
* Creates a local PDF print destination.
* @return {!print_preview.Destination} Created print destination.
* @private
*/
createLocalPdfPrintDestination_: function() {
// TODO(alekseys): Create PDF printer in the native code and send its
// capabilities back with other local printers.
if (this.pdfPrinterEnabled_) {
this.insertDestination_(new print_preview.Destination(
print_preview.Destination.GooglePromotedId.SAVE_AS_PDF,
print_preview.Destination.Type.LOCAL,
print_preview.Destination.Origin.LOCAL,
loadTimeData.getString('printToPDF'),
false /*isRecent*/,
print_preview.Destination.ConnectionStatus.ONLINE));
}
},
/**
* Resets the state of the destination store to its initial state.
* @private
*/
reset_: function() {
this.destinations_ = [];
this.destinationMap_ = {};
this.selectDestination(null);
this.loadedCloudOrigins_ = {};
this.hasLoadedAllLocalDestinations_ = false;
this.hasLoadedAllPrivetDestinations_ = false;
this.hasLoadedAllExtensionDestinations_ = false;
clearTimeout(this.autoSelectTimeout_);
this.autoSelectTimeout_ = setTimeout(
this.selectDefaultDestination_.bind(this),
DestinationStore.AUTO_SELECT_TIMEOUT_);
},
/**
* Called when the local destinations have been got from the native layer.
* @param {Event} event Contains the local destinations.
* @private
*/
onLocalDestinationsSet_: function(event) {
var localDestinations = event.destinationInfos.map(function(destInfo) {
return print_preview.LocalDestinationParser.parse(destInfo);
});
this.insertDestinations_(localDestinations);
this.isLocalDestinationSearchInProgress_ = false;
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
},
/**
* Called when the native layer retrieves the capabilities for the selected
* local destination. Updates the destination with new capabilities if the
* destination already exists, otherwise it creates a new destination and
* then updates its capabilities.
* @param {Event} event Contains the capabilities of the local print
* destination.
* @private
*/
onLocalDestinationCapabilitiesSet_: function(event) {
var destinationId = event.settingsInfo['printerId'];
var key = this.getDestinationKey_(
print_preview.Destination.Origin.LOCAL,
destinationId,
'');
var destination = this.destinationMap_[key];
var capabilities = DestinationStore.localizeCapabilities_(
event.settingsInfo.capabilities);
// Special case for PDF printer (until local printers capabilities are
// reported in CDD format too).
if (destinationId ==
print_preview.Destination.GooglePromotedId.SAVE_AS_PDF) {
if (destination) {
destination.capabilities = capabilities;
}
} else {
if (destination) {
// In case there were multiple capabilities request for this local
// destination, just ignore the later ones.
if (destination.capabilities != null) {
return;
}
destination.capabilities = capabilities;
} else {
// TODO(rltoscano): This makes the assumption that the "deviceName" is
// the same as "printerName". We should include the "printerName" in
// the response. See http://crbug.com/132831.
destination = print_preview.LocalDestinationParser.parse(
{deviceName: destinationId, printerName: destinationId});
destination.capabilities = capabilities;
this.insertDestination_(destination);
}
}
if (this.selectedDestination_ &&
this.selectedDestination_.id == destinationId) {
cr.dispatchSimpleEvent(this,
DestinationStore.EventType.
SELECTED_DESTINATION_CAPABILITIES_READY);
}
},
/**
* Called when a request to get a local destination's print capabilities
* fails. If the destination is the initial destination, auto-select another
* destination instead.
* @param {Event} event Contains the destination ID that failed.
* @private
*/
onGetCapabilitiesFail_: function(event) {
console.error('Failed to get print capabilities for printer ' +
event.destinationId);
if (this.autoSelectMatchingDestination_ &&
this.autoSelectMatchingDestination_.matchIdAndOrigin(
event.destinationId, event.destinationOrigin)) {
this.selectDefaultDestination_();
}
},
/**
* Called when the /search call completes, either successfully or not.
* In case of success, stores fetched destinations.
* @param {Event} event Contains the request result.
* @private
*/
onCloudPrintSearchDone_: function(event) {
if (event.printers) {
this.insertDestinations_(event.printers);
}
if (event.searchDone) {
var origins = this.loadedCloudOrigins_[event.user] || [];
if (origins.indexOf(event.origin) < 0) {
this.loadedCloudOrigins_[event.user] = origins.concat([event.origin]);
}
}
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
},
/**
* Called when /printer call completes. Updates the specified destination's
* print capabilities.
* @param {Event} event Contains detailed information about the
* destination.
* @private
*/
onCloudPrintPrinterDone_: function(event) {
this.updateDestination_(event.printer);
},
/**
* Called when the Google Cloud Print interface fails to lookup a
* destination. Selects another destination if the failed destination was
* the initial destination.
* @param {Object} event Contains the ID of the destination that was failed
* to be looked up.
* @private
*/
onCloudPrintPrinterFailed_: function(event) {
if (this.autoSelectMatchingDestination_ &&
this.autoSelectMatchingDestination_.matchIdAndOrigin(
event.destinationId, event.destinationOrigin)) {
console.error(
'Failed to fetch last used printer caps: ' + event.destinationId);
this.selectDefaultDestination_();
}
},
/**
* Called when printer sharing invitation was processed successfully.
* @param {Event} event Contains detailed information about the invite and
* newly accepted destination (if known).
* @private
*/
onCloudPrintProcessInviteDone_: function(event) {
if (event.accept && event.printer) {
// Hint the destination list to promote this new destination.
event.printer.isRecent = true;
this.insertDestination_(event.printer);
}
},
/**
* Called when a Privet printer is added to the local network.
* @param {Object} event Contains information about the added printer.
* @private
*/
onPrivetPrinterAdded_: function(event) {
if (event.printer.serviceName == this.waitForRegisterDestination_ &&
!event.printer.isUnregistered) {
this.waitForRegisterDestination_ = null;
this.onDestinationsReload_();
} else {
this.insertDestinations_(
print_preview.PrivetDestinationParser.parse(event.printer));
}
},
/**
* Called when capabilities for a privet printer are set.
* @param {Object} event Contains the capabilities and printer ID.
* @private
*/
onPrivetCapabilitiesSet_: function(event) {
var destinations =
print_preview.PrivetDestinationParser.parse(event.printer);
destinations.forEach(function(dest) {
dest.capabilities = event.capabilities;
this.updateDestination_(dest);
}, this);
},
/**
* Called when an extension responds to a getExtensionDestinations
* request.
* @param {Object} event Contains information about list of printers
* reported by the extension.
* {@code done} parameter is set iff this is the final list of printers
* returned as part of getExtensionDestinations request.
* @private
*/
onExtensionPrintersAdded_: function(event) {
this.insertDestinations_(event.printers.map(function(printer) {
return print_preview.ExtensionDestinationParser.parse(printer);
}));
if (event.done && this.isExtensionDestinationSearchInProgress_) {
clearTimeout(this.extensionSearchTimeout_);
this.endExtensionPrinterSearch_();
}
},
/**
* Called when capabilities for an extension managed printer are set.
* @param {Object} event Contains the printer's capabilities and ID.
* @private
*/
onExtensionCapabilitiesSet_: function(event) {
var destinationKey = this.getDestinationKey_(
print_preview.Destination.Origin.EXTENSION,
event.printerId,
'' /* account */);
var destination = this.destinationMap_[destinationKey];
if (!destination)
return;
destination.capabilities = event.capabilities;
this.updateDestination_(destination);
},
/**
* Called from native layer after the user was requested to sign in, and did
* so successfully.
* @private
*/
onDestinationsReload_: function() {
this.reset_();
this.autoSelectMatchingDestination_ =
this.convertPreselectedToDestinationMatch_();
this.createLocalPdfPrintDestination_();
this.startLoadAllDestinations();
},
// TODO(vitalybuka): Remove three next functions replacing Destination.id
// and Destination.origin by complex ID.
/**
* Returns key to be used with {@code destinationMap_}.
* @param {!print_preview.Destination.Origin} origin Destination origin.
* @param {string} id Destination id.
* @param {string} account User account destination is registered for.
* @private
*/
getDestinationKey_: function(origin, id, account) {
return origin + '/' + id + '/' + account;
},
/**
* Returns key to be used with {@code destinationMap_}.
* @param {!print_preview.Destination} destination Destination.
* @private
*/
getKey_: function(destination) {
return this.getDestinationKey_(
destination.origin, destination.id, destination.account);
}
};
// Export
return {
DestinationStore: DestinationStore
};
});