blob: bd6a143ae4d7a817c667763b287f2fc40f25d525 [file] [log] [blame]
// Copyright 2016 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.
/**
* @fileoverview 'settings-cups-add-printer-dialog' includes multiple dialogs to
* set up a new CUPS printer.
* Subdialogs include:
* - 'add-printer-discovery-dialog' is a dialog showing discovered printers on
* the network that are available for setup.
* - 'add-printer-manually-dialog' is a dialog in which user can manually enter
* the information to set up a new printer.
* - 'add-printer-configuring-dialog' is the configuring-in-progress dialog.
* - 'add-printer-manufacturer-model-dialog' is a dialog in which the user can
* manually select the manufacture and model of the new printer.
*/
/**
* Different dialogs in add printer flow.
* @enum {string}
*/
const AddPrinterDialogs = {
DISCOVERY: 'add-printer-discovery-dialog',
MANUALLY: 'add-printer-manually-dialog',
CONFIGURING: 'add-printer-configuring-dialog',
MANUFACTURER: 'add-printer-manufacturer-model-dialog',
};
/**
* The maximum height of the discovered printers list when the searching spinner
* is not showing.
* @type {number}
*/
const kPrinterListFullHeight = 350;
/**
* Return a reset CupsPrinterInfo object.
* @return {!CupsPrinterInfo}
*/
function getEmptyPrinter_() {
return {
ppdManufacturer: '',
ppdModel: '',
printerAddress: '',
printerAutoconf: false,
printerDescription: '',
printerId: '',
printerManufacturer: '',
printerModel: '',
printerMakeAndModel: '',
printerName: '',
printerPPDPath: '',
printerProtocol: 'ipp',
printerQueue: '',
printerStatus: '',
};
}
Polymer({
is: 'add-printer-discovery-dialog',
behaviors: [WebUIListenerBehavior],
properties: {
/** @type {!Array<!CupsPrinterInfo>|undefined} */
discoveredPrinters: {
type: Array,
},
/** @type {!CupsPrinterInfo} */
selectedPrinter: {
type: Object,
notify: true,
},
discovering_: {
type: Boolean,
value: true,
},
},
/** @override */
ready: function() {
settings.CupsPrintersBrowserProxyImpl.getInstance()
.startDiscoveringPrinters();
this.addWebUIListener(
'on-printer-discovered', this.onPrinterDiscovered_.bind(this));
this.addWebUIListener(
'on-printer-discovery-done', this.onPrinterDiscoveryDone_.bind(this));
},
close: function() {
this.$$('add-printer-dialog').close();
},
/**
* @param {!Array<!CupsPrinterInfo>} printers
* @private
*/
onPrinterDiscovered_: function(printers) {
this.discovering_ = true;
this.discoveredPrinters = printers;
},
/** @private */
onPrinterDiscoveryDone_: function() {
this.discovering_ = false;
this.$$('add-printer-list').style.maxHeight = kPrinterListFullHeight + 'px';
this.$.noPrinterMessage.hidden = !!this.discoveredPrinters.length;
if (!this.discoveredPrinters.length) {
this.selectedPrinter = getEmptyPrinter_();
this.fire('no-detected-printer');
}
},
/** @private */
stopDiscoveringPrinters_: function() {
settings.CupsPrintersBrowserProxyImpl.getInstance()
.stopDiscoveringPrinters();
this.discovering_ = false;
},
/** @private */
switchToManualAddDialog_: function() {
this.stopDiscoveringPrinters_();
// We're abandoning discovery in favor of manual specification, so
// drop the selection if one exists.
this.selectedPrinter = getEmptyPrinter_();
this.close();
this.fire('open-manually-add-printer-dialog');
},
/** @private */
onCancelTap_: function() {
this.stopDiscoveringPrinters_();
this.close();
},
/** @private */
switchToConfiguringDialog_: function() {
this.stopDiscoveringPrinters_();
this.close();
this.fire('open-configuring-printer-dialog');
},
/**
* @param {?CupsPrinterInfo} selectedPrinter
* @return {boolean} Whether the add printer button is enabled.
* @private
*/
canAddPrinter_: function(selectedPrinter) {
return !!selectedPrinter && !!selectedPrinter.printerName;
},
});
Polymer({
is: 'add-printer-manually-dialog',
properties: {
/** @type {!CupsPrinterInfo} */
newPrinter: {type: Object, notify: true, value: getEmptyPrinter_},
},
/** @private */
switchToDiscoveryDialog_: function() {
this.newPrinter = getEmptyPrinter_();
this.$$('add-printer-dialog').close();
this.fire('open-discovery-printers-dialog');
},
/** @private */
onCancelTap_: function() {
this.$$('add-printer-dialog').close();
},
/** @private */
addPressed_: function() {
// Set the default printer queue to be "ipp/print".
if (!this.newPrinter.printerQueue) {
this.set('newPrinter.printerQueue', 'ipp/print');
}
this.$$('add-printer-dialog').close();
this.fire('open-configuring-printer-dialog');
},
/** @private */
onAddressChanged_: function() {
// TODO(xdai): Check if the printer address exists and then show the
// corresponding message after the API is ready.
// The format of address is: ip-address-or-hostname:port-number.
},
/**
* @param {!Event} event
* @private
*/
onProtocolChange_: function(event) {
this.set('newPrinter.printerProtocol', event.target.value);
},
/**
* This function uses regular expressions to determine whether the provided
* printer address is valid. Address can be either an ipv4/6 address or a
* hostname followed by an optional port.
* NOTE: The regular expression for hostnames will allow hostnames that are
* over 255 characters.
* @param {String} name
* @param {String} address
* @return {boolean} Whether the add printer button is enabled.
* @private
*/
canAddPrinter_: function(name, address) {
if (!name || !address)
return false;
const hostnamePrefix = '([a-z\\d]|[a-z\\d][a-z\\d\\-]{0,61}[a-z\\d])';
// Matches an arbitrary number of 'prefix patterns' which are separated by a
// dot.
const hostnameSuffix = `(\\.${hostnamePrefix})*`;
// Matches an optional port at the end of the address.
const portNumber = '(:\\d+)?';
const ipv6Full = '(([a-f\\d]){1,4}(:(:)?([a-f\\d]){1,4}){1,7})';
// Special cases for addresses using a shorthand notation.
const ipv6Prefix = '(::([a-f\\d]){1,4})';
const ipv6Suffix = '(([a-f\\d]){1,4}::)';
const ipv6Combined = `(${ipv6Full}|${ipv6Prefix}|${ipv6Suffix})`;
const ipv6WithPort = `(\\[${ipv6Combined}\\]${portNumber})`;
// Matches valid hostnames and ipv4 addresses.
const hostnameRegex =
new RegExp(`^${hostnamePrefix}${hostnameSuffix}${portNumber}$`, 'i');
// Matches valid ipv6 addresses.
const ipv6AddressRegex =
new RegExp(`^(${ipv6Combined}|${ipv6WithPort})$`, 'i');
const invalidIpv6Regex = new RegExp('.*::.*::.*');
return hostnameRegex.test(address) ||
(ipv6AddressRegex.test(address) && !invalidIpv6Regex.test(address));
},
});
Polymer({
is: 'add-printer-manufacturer-model-dialog',
behaviors: [
SetManufacturerModelBehavior,
],
properties: {
setupFailed: {
type: Boolean,
value: false,
},
},
close: function() {
this.$$('add-printer-dialog').close();
},
/** @private */
onCancelTap_: function() {
this.close();
settings.CupsPrintersBrowserProxyImpl.getInstance().cancelPrinterSetUp(
this.activePrinter);
},
/** @private */
switchToConfiguringDialog_: function() {
this.close();
this.fire('open-configuring-printer-dialog');
},
/**
* @param {string} ppdManufacturer
* @param {string} ppdModel
* @param {string} printerPPDPath
* @return {boolean} Whether we have enough information to set up the printer
* @private
*/
canAddPrinter_: function(ppdManufacturer, ppdModel, printerPPDPath) {
return !!((ppdManufacturer && ppdModel) || printerPPDPath);
},
});
Polymer({
is: 'add-printer-configuring-dialog',
properties: {
printerName: String,
dialogTitle: String,
},
/** @override */
attached: function() {
this.$.configuringMessage.textContent =
loadTimeData.getStringF('printerConfiguringMessage', this.printerName);
},
/** @private */
onCancelConfiguringTap_: function() {
this.close();
this.fire('configuring-dialog-closed');
},
close: function() {
this.$$('add-printer-dialog').close();
},
});
Polymer({
is: 'settings-cups-add-printer-dialog',
behaviors: [WebUIListenerBehavior],
properties: {
/** @type {!CupsPrinterInfo} */
newPrinter: {
type: Object,
},
/** @type {boolean} whether the new printer setup is failed. */
setupFailed: {
type: Boolean,
value: false,
},
configuringDialogTitle: String,
/** @private {string} */
previousDialog_: String,
/** @private {string} */
currentDialog_: String,
/** @private {boolean} */
showDiscoveryDialog_: {
type: Boolean,
value: false,
},
/** @private {boolean} */
showManuallyAddDialog_: {
type: Boolean,
value: false,
},
/** @private {boolean} */
showConfiguringDialog_: {
type: Boolean,
value: false,
},
/** @private {boolean} */
showManufacturerDialog_: {
type: Boolean,
value: false,
},
},
listeners: {
'configuring-dialog-closed': 'configuringDialogClosed_',
'open-manually-add-printer-dialog': 'openManuallyAddPrinterDialog_',
'open-configuring-printer-dialog': 'openConfiguringPrinterDialog_',
'open-discovery-printers-dialog': 'openDiscoveryPrintersDialog_',
'open-manufacturer-model-dialog': 'openManufacturerModelDialog_',
'no-detected-printer': 'onNoDetectedPrinter_',
},
/** @override */
ready: function() {
this.addWebUIListener('on-add-cups-printer', this.onAddPrinter_.bind(this));
this.addWebUIListener(
'on-manually-add-discovered-printer',
this.onManuallyAddDiscoveredPrinter_.bind(this));
},
/** Opens the Add printer discovery dialog. */
open: function() {
this.resetData_();
this.switchDialog_('', AddPrinterDialogs.DISCOVERY, 'showDiscoveryDialog_');
},
/**
* Reset all the printer data in the Add printer flow.
* @private
*/
resetData_: function() {
if (this.newPrinter)
this.newPrinter = getEmptyPrinter_();
this.setupFailed = false;
},
/** @private */
openManuallyAddPrinterDialog_: function() {
this.switchDialog_(
this.currentDialog_, AddPrinterDialogs.MANUALLY,
'showManuallyAddDialog_');
},
/** @private */
openDiscoveryPrintersDialog_: function() {
this.switchDialog_(
this.currentDialog_, AddPrinterDialogs.DISCOVERY,
'showDiscoveryDialog_');
},
/** @private */
addPrinter_: function() {
settings.CupsPrintersBrowserProxyImpl.getInstance().addCupsPrinter(
this.newPrinter);
},
/** @private */
switchToManufacturerDialog_: function() {
this.$$('add-printer-configuring-dialog').close();
this.fire('open-manufacturer-model-dialog');
},
/**
* Handler for getPrinterInfo success.
* @param {!PrinterMakeModel} info
* @private
* */
onPrinterFound_: function(info) {
this.newPrinter.printerAutoconf = info.autoconf;
this.newPrinter.printerManufacturer = info.manufacturer;
this.newPrinter.printerModel = info.model;
this.newPrinter.printerMakeAndModel = info.makeAndModel;
// Add the printer if it's configurable. Otherwise, forward to the
// manufacturer dialog.
if (this.newPrinter.printerAutoconf) {
this.addPrinter_();
} else {
this.switchToManufacturerDialog_();
}
},
/**
* Handler for getPrinterInfo failure.
* @param {*} rejected
* @private
*/
infoFailed_: function(rejected) {
this.switchToManufacturerDialog_();
},
/** @private */
openConfiguringPrinterDialog_: function() {
this.switchDialog_(
this.currentDialog_, AddPrinterDialogs.CONFIGURING,
'showConfiguringDialog_');
if (this.previousDialog_ == AddPrinterDialogs.DISCOVERY) {
this.configuringDialogTitle =
loadTimeData.getString('addPrintersNearbyTitle');
settings.CupsPrintersBrowserProxyImpl.getInstance().addDiscoveredPrinter(
this.newPrinter.printerId);
} else if (this.previousDialog_ == AddPrinterDialogs.MANUFACTURER) {
this.configuringDialogTitle =
loadTimeData.getString('selectManufacturerAndModelTitle');
this.addPrinter_();
} else if (this.previousDialog_ == AddPrinterDialogs.MANUALLY) {
this.configuringDialogTitle =
loadTimeData.getString('addPrintersManuallyTitle');
if (this.newPrinter.printerProtocol == 'ipp' ||
this.newPrinter.printerProtocol == 'ipps') {
settings.CupsPrintersBrowserProxyImpl.getInstance()
.getPrinterInfo(this.newPrinter)
.then(this.onPrinterFound_.bind(this), this.infoFailed_.bind(this));
} else {
// Defer the switch until all the elements are drawn.
this.async(this.switchToManufacturerDialog_.bind(this));
}
}
},
/** @private */
openManufacturerModelDialog_: function() {
this.switchDialog_(
this.currentDialog_, AddPrinterDialogs.MANUFACTURER,
'showManufacturerDialog_');
},
/** @private */
configuringDialogClosed_: function() {
// If the configuring dialog is closed, we want to return whence we came.
//
// TODO(justincarlson) - This shouldn't need to be a conditional;
// clean up the way we switch dialogs so we don't have to supply
// redundant information and can just return to the previous
// dialog.
if (this.previousDialog_ == AddPrinterDialogs.DISCOVERY) {
this.switchDialog_(
this.currentDialog_, this.previousDialog_, 'showDiscoveryDialog_');
} else if (this.previousDialog_ == AddPrinterDialogs.MANUALLY) {
this.switchDialog_(
this.currentDialog_, this.previousDialog_, 'showManuallyAddDialog_');
} else if (this.previousDialog_ == AddPrinterDialogs.MANUFACTURER) {
this.switchDialog_(
this.currentDialog_, this.previousDialog_, 'showManufacturerDialog_');
}
},
/** @private */
onNoDetectedPrinter_: function() {
// If there is no detected printer, automatically open manually-add-printer
// dialog only when the user opens the discovery-dialog through the
// "ADD PRINTER" button.
if (!this.previousDialog_) {
this.$$('add-printer-discovery-dialog').close();
this.newPrinter = getEmptyPrinter_();
this.openManuallyAddPrinterDialog_();
}
},
/**
* Switch dialog from |fromDialog| to |toDialog|.
* @param {string} fromDialog
* @param {string} toDialog
* @param {string} domIfBooleanName The name of the boolean variable
* corresponding to the |toDialog|.
* @private
*/
switchDialog_: function(fromDialog, toDialog, domIfBooleanName) {
this.previousDialog_ = fromDialog;
this.currentDialog_ = toDialog;
this.set(domIfBooleanName, true);
this.async(function() {
const dialog = this.$$(toDialog);
dialog.addEventListener('close', () => {
this.set(domIfBooleanName, false);
});
});
},
/**
* Use the given printer as the starting point for a user-driven
* add of a printer. This is called if we can't automatically configure
* the printer, and need more information from the user.
*
* @param {!CupsPrinterInfo} printer
* @private
*/
onManuallyAddDiscoveredPrinter_: function(printer) {
this.newPrinter = printer;
this.switchToManufacturerDialog_();
},
/**
* @param {boolean} success
* @param {string} printerName
* @private
*/
onAddPrinter_: function(success, printerName) {
// 'on-add-cups-printer' event might be triggered by editing an existing
// printer, in which case there is no configuring dialog.
if (!this.$$('add-printer-configuring-dialog'))
return;
this.$$('add-printer-configuring-dialog').close();
if (success)
return;
if (this.previousDialog_ == AddPrinterDialogs.MANUFACTURER) {
this.setupFailed = true;
}
},
});