// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type {CapabilitiesResponse, Cdd, ColorOption, DpiOption, DuplexOption, ExtensionDestinationInfo, LocalDestinationInfo, MediaSizeCapability, MediaSizeOption, MediaTypeOption, NativeInitialSettings, PageOrientationOption} from 'chrome://print/print_preview.js';
import {DEFAULT_MAX_COPIES, Destination, DestinationOrigin, DestinationStore, GooglePromotedDestinationId, MeasurementSystemUnitType, VendorCapabilityValueType} from 'chrome://print/print_preview.js';
import type {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals} from 'chrome://webui-test/chai_assert.js';
import {eventToPromise} from 'chrome://webui-test/test_util.js';
export function getDefaultInitialSettings(isPdf: boolean = false):
NativeInitialSettings {
return {
isInKioskAutoPrintMode: false,
isInAppKioskMode: false,
pdfPrinterDisabled: false,
thousandsDelimiter: ',',
decimalDelimiter: '.',
previewModifiable: !isPdf,
documentTitle: 'title',
documentHasSelection: true,
shouldPrintSelectionOnly: false,
previewIsFromArc: false,
printerName: 'FooDevice',
serializedAppStateStr: null,
serializedDefaultDestinationSelectionRulesStr: null,
destinationsManaged: false,
uiLocale: 'en-us',
unitType: MeasurementSystemUnitType.IMPERIAL,
isDriveMounted: true,
};
}
export function getCddTemplate(
printerId: string, printerName?: string): CapabilitiesResponse {
const template: CapabilitiesResponse = {
printer: {
deviceName: printerId,
printerName: printerName || '',
},
capabilities: {
version: '1.0',
printer: {
collate: {default: true},
copies: {default: 1, max: DEFAULT_MAX_COPIES},
color: {
option: [
{type: 'STANDARD_COLOR', is_default: true},
{type: 'STANDARD_MONOCHROME'},
] as ColorOption[],
},
dpi: {
option: [
{horizontal_dpi: 200, vertical_dpi: 200, is_default: true},
{horizontal_dpi: 100, vertical_dpi: 100},
] as DpiOption[],
},
duplex: {
option: [
{type: 'NO_DUPLEX', is_default: true},
{type: 'LONG_EDGE'},
{type: 'SHORT_EDGE'},
] as DuplexOption[],
},
page_orientation: {
option: [
{type: 'PORTRAIT', is_default: true},
{type: 'LANDSCAPE'},
{type: 'AUTO'},
] as PageOrientationOption[],
},
media_size: {
option: [
{
name: 'NA_LETTER',
width_microns: 215900,
height_microns: 279400,
is_default: true,
custom_display_name: 'Letter',
},
{
name: 'CUSTOM',
width_microns: 215900,
height_microns: 215900,
custom_display_name: 'CUSTOM_SQUARE',
has_borderless_variant: true,
},
{
name: 'LEGAL',
width_microns: 215900,
height_microns: 355600,
custom_display_name: 'Legal',
imageable_area_left_microns: 5000,
imageable_area_bottom_microns: 5000,
imageable_area_right_microns: 5000,
imageable_area_top_microns: 5000,
has_borderless_variant: false,
},
{
name: '4x6',
width_microns: 101600,
height_microns: 152400,
custom_display_name: '4 x 6 in',
imageable_area_left_microns: 0,
imageable_area_bottom_microns: 0,
imageable_area_right_microns: 101600,
imageable_area_top_microns: 152400,
},
] as MediaSizeOption[],
},
media_type: {
option: [
{
vendor_id: 'stationery',
custom_display_name: 'Plain',
is_default: true,
},
{
vendor_id: 'photographic',
custom_display_name: 'Photo',
},
] as MediaTypeOption[],
},
},
},
};
// <if expr="is_chromeos">
template.capabilities!.printer.pin = {supported: true};
// </if>
return template;
}
/**
* Gets a CDD template and adds some dummy vendor capabilities. For select
* capabilities, the values of these options are arbitrary. These values are
* provided and read by the destination, so there are no fixed options like
* there are for margins or color.
* @param printerName Defaults to an empty string.
*/
export function getCddTemplateWithAdvancedSettings(
numSettings: number, printerId: string,
printerName?: string): CapabilitiesResponse {
const template = getCddTemplate(printerId, printerName);
if (numSettings < 1) {
return template;
}
template.capabilities!.printer.vendor_capability = [{
display_name: 'Print Area',
id: 'printArea',
type: 'SELECT',
select_cap: {
option: [
{display_name: 'A4', value: 4, is_default: true},
{display_name: 'A6', value: 6},
{display_name: 'A7', value: 7},
],
},
}];
if (numSettings < 2) {
return template;
}
// Add new capability.
template.capabilities!.printer.vendor_capability!.push({
display_name: 'Paper Type',
id: 'paperType',
type: 'SELECT',
select_cap: {
option: [
{display_name: 'Standard', value: 0, is_default: true},
{display_name: 'Recycled', value: 1},
{display_name: 'Special', value: 2},
],
},
});
if (numSettings < 3) {
return template;
}
template.capabilities!.printer.vendor_capability!.push({
display_name: 'Watermark',
id: 'watermark',
type: 'TYPED_VALUE',
typed_value_cap: {
default: '',
},
});
if (numSettings < 4) {
return template;
}
template.capabilities!.printer.vendor_capability.push({
display_name: 'Staple',
id: 'finishings/4',
type: 'TYPED_VALUE',
typed_value_cap: {
default: '',
value_type: VendorCapabilityValueType.BOOLEAN,
},
});
return template;
}
/**
* @return The capabilities of the Save as PDF destination.
*/
export function getPdfPrinter(): {capabilities: Cdd} {
return {
capabilities: {
version: '1.0',
printer: {
page_orientation: {
option: [
{type: 'AUTO', is_default: true},
{type: 'PORTRAIT'},
{type: 'LANDSCAPE'},
] as PageOrientationOption[],
},
color: {option: [{type: 'STANDARD_COLOR', is_default: true}]},
media_size: {
option: [{
name: 'NA_LETTER',
width_microns: 0,
height_microns: 0,
is_default: true,
}],
},
},
},
};
}
/**
* Get the default media size for |device|.
* @return The width and height of the default media.
*/
export function getDefaultMediaSize(device: CapabilitiesResponse):
MediaSizeOption {
const size = device.capabilities!.printer.media_size!.option!.find(
opt => !!opt.is_default);
return {
width_microns: size!.width_microns,
height_microns: size!.height_microns,
};
}
/**
* Get the default page orientation for |device|.
* @return The default orientation.
*/
export function getDefaultOrientation(device: CapabilitiesResponse): string {
const options = device.capabilities!.printer.page_orientation!.option;
const orientation = options!.find(opt => !!opt.is_default)!.type;
assert(orientation);
return orientation;
}
interface ExtensionPrinters {
destinations: Destination[];
infoLists: ExtensionDestinationInfo[][];
}
export function getExtensionDestinations(): ExtensionPrinters {
const destinations: Destination[] = [];
const infoLists: ExtensionDestinationInfo[][] = [];
infoLists.push([]);
infoLists.push([]);
[{
id: 'IDA',
name: 'PrinterA',
extensionId: 'ext1',
extensionName: 'ExtensionOne',
},
{
id: 'IDB',
name: 'PrinterB',
extensionId: 'ext1',
extensionName: 'ExtensionOne',
},
{
id: 'IDC',
name: 'PrinterC',
extensionId: 'ext2',
extensionName: 'ExtensionTwo',
},
].forEach(info => {
const destination =
new Destination(info.id, DestinationOrigin.EXTENSION, info.name, {
extensionId: info.extensionId,
extensionName: info.extensionName,
});
if (info.extensionId === 'ext1') {
infoLists[0]!.push(info);
} else {
infoLists[1]!.push(info);
}
destinations.push(destination);
});
return {destinations, infoLists};
}
/**
* Creates 5 local destinations, adds them to |localDestinations|.
*/
export function getDestinations(localDestinations: LocalDestinationInfo[]):
Destination[] {
const destinations: Destination[] = [];
// <if expr="not is_chromeos">
const origin = DestinationOrigin.LOCAL;
// </if>
// <if expr="is_chromeos">
const origin = DestinationOrigin.CROS;
// </if>
// Five destinations. FooDevice is the system default.
[{deviceName: 'ID1', printerName: 'One'},
{deviceName: 'ID2', printerName: 'Two'},
{deviceName: 'ID3', printerName: 'Three'},
{deviceName: 'ID4', printerName: 'Four'},
{deviceName: 'FooDevice', printerName: 'FooName'}]
.forEach(info => {
const destination =
new Destination(info.deviceName, origin, info.printerName);
localDestinations.push(info);
destinations.push(destination);
});
return destinations;
}
/**
* Returns a media size capability with custom and localized names.
*/
export function getMediaSizeCapabilityWithCustomNames(): MediaSizeCapability {
const customLocalizedMediaName = 'Vendor defined localized media name';
const customMediaName = 'Vendor defined media name';
return {
option: [
{
name: 'CUSTOM',
width_microns: 15900,
height_microns: 79400,
is_default: true,
custom_display_name_localized:
[{locale: navigator.language, value: customLocalizedMediaName}],
},
{
name: 'CUSTOM',
width_microns: 15900,
height_microns: 79400,
custom_display_name: customMediaName,
},
] as MediaSizeOption[],
};
}
/**
* @param input The value to set for the input element.
* @param parentElement The element that receives the input-change event.
* @return Promise that resolves when the input-change event has fired.
*/
export async function triggerInputEvent(
inputElement: HTMLInputElement|CrInputElement, input: string,
parentElement: HTMLElement): Promise<void> {
inputElement.value = input;
if (inputElement.tagName === 'CR-INPUT') {
await (inputElement as CrInputElement).updateComplete;
}
inputElement.dispatchEvent(
new CustomEvent('input', {composed: true, bubbles: true}));
return await eventToPromise('input-change', parentElement);
}
const TestListenerElementBase = WebUiListenerMixin(PolymerElement);
class TestListenerElement extends TestListenerElementBase {
static get is() {
return 'test-listener-element';
}
}
declare global {
interface HTMLElementTagNameMap {
'test-listener-element': TestListenerElement;
}
}
export function setupTestListenerElement(): void {
customElements.define(TestListenerElement.is, TestListenerElement);
}
export function createDestinationStore(): DestinationStore {
const testListenerElement = document.createElement('test-listener-element');
document.body.appendChild(testListenerElement);
return new DestinationStore(
testListenerElement.addWebUiListener.bind(testListenerElement));
}
// <if expr="is_chromeos">
/**
* @return The Google Drive destination.
*/
export function getGoogleDriveDestination(): Destination {
return new Destination(
'Save to Drive CrOS', DestinationOrigin.LOCAL, 'Save to Google Drive');
}
// </if>
/** @return The Save as PDF destination. */
export function getSaveAsPdfDestination(): Destination {
return new Destination(
GooglePromotedDestinationId.SAVE_AS_PDF, DestinationOrigin.LOCAL,
loadTimeData.getString('printToPDF'));
}
/**
* @param section The settings section that contains the select to toggle.
* @param option The option to select.
* @return Promise that resolves when the option has been selected and the
* process-select-change event has fired.
*/
export function selectOption(
section: HTMLElement, option: string): Promise<void> {
const select = section.shadowRoot!.querySelector('select')!;
select.value = option;
select.dispatchEvent(new CustomEvent('change'));
return eventToPromise('process-select-change', section);
}
// Fake MediaQueryList used in mocking response of |window.matchMedia|.
export class FakeMediaQueryList extends EventTarget implements MediaQueryList {
private listener_: ((e: MediaQueryListEvent) => any)|null = null;
private matches_: boolean = false;
private media_: string;
constructor(media: string) {
super();
this.media_ = media;
}
addListener(listener: (e: MediaQueryListEvent) => any) {
this.listener_ = listener;
}
removeListener(listener: (e: MediaQueryListEvent) => any) {
assertEquals(listener, this.listener_);
this.listener_ = null;
}
onchange() {
if (this.listener_) {
this.listener_(new MediaQueryListEvent(
'change', {media: this.media_, matches: this.matches_}));
}
}
get media(): string {
return this.media_;
}
get matches(): boolean {
return this.matches_;
}
set matches(matches: boolean) {
if (this.matches_ !== matches) {
this.matches_ = matches;
this.onchange();
}
}
}