diff options
Diffstat (limited to 'chromium/chrome/browser/resources/print_preview')
31 files changed, 1153 insertions, 217 deletions
diff --git a/chromium/chrome/browser/resources/print_preview/BUILD.gn b/chromium/chrome/browser/resources/print_preview/BUILD.gn index 63df1dd2a18..abe2ff3da74 100644 --- a/chromium/chrome/browser/resources/print_preview/BUILD.gn +++ b/chromium/chrome/browser/resources/print_preview/BUILD.gn @@ -44,7 +44,7 @@ if (optimize_webui) { ] deps = [ - "../pdf/elements:web_components", + "../pdf:web_components", "ui:web_components", ] defines = chrome_grit_defines @@ -74,10 +74,23 @@ js_type_check("print_preview_module_resources") { ":dark_mode_behavior", ":metrics", ":native_layer", + ":print_preview", ":print_preview_utils", ] } +js_library("print_preview") { + deps = [ + ":cloud_print_interface", + ":native_layer", + "data:destination", + "data:destination_store", + "data:measurement_system", + "ui:app", + "ui:settings_select", + ] +} + js_library("print_preview_utils") { deps = [ ":dark_mode_behavior", @@ -118,6 +131,7 @@ js_library("native_layer") { "data:destination_match", "data:destination_policies", "data:measurement_system", + "data:printer_status_cros", "//ui/webui/resources/js:assert.m", "//ui/webui/resources/js:cr.m", ] diff --git a/chromium/chrome/browser/resources/print_preview/data/BUILD.gn b/chromium/chrome/browser/resources/print_preview/data/BUILD.gn index 030f5a2088f..61a94abfa0a 100644 --- a/chromium/chrome/browser/resources/print_preview/data/BUILD.gn +++ b/chromium/chrome/browser/resources/print_preview/data/BUILD.gn @@ -22,6 +22,7 @@ js_type_check("closure_compile_module") { ":measurement_system", ":model", ":printable_area", + ":printer_status_cros", ":scaling", ":size", ":state", @@ -165,3 +166,6 @@ js_library("user_manager") { "//ui/webui/resources/js:web_ui_listener_behavior.m", ] } + +js_library("printer_status_cros") { +} diff --git a/chromium/chrome/browser/resources/print_preview/data/destination.js b/chromium/chrome/browser/resources/print_preview/data/destination.js index 3c6155bd266..a6938175df1 100644 --- a/chromium/chrome/browser/resources/print_preview/data/destination.js +++ b/chromium/chrome/browser/resources/print_preview/data/destination.js @@ -10,6 +10,7 @@ import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; // <if expr="chromeos"> import {ColorModeRestriction, DestinationPolicies, DuplexModeRestriction, PinModeRestriction} from './destination_policies.js'; +import {PrinterStatusReason} from '../data/printer_status_cros.js'; // </if> /** @@ -139,7 +140,7 @@ export let VendorCapability; * only on Chrome OS. * * @typedef {{ - * vendor_capability: !Array<!VendorCapability>, + * vendor_capability: (Array<!VendorCapability>|undefined), * collate: ({default: (boolean|undefined)}|undefined), * color: ({ * option: !Array<{ @@ -162,7 +163,8 @@ export let VendorCapability; * type: (string|undefined), * vendor_id: (string|undefined), * custom_display_name: (string|undefined), - * is_default: (boolean|undefined) + * is_default: (boolean|undefined), + * name: (string|undefined), * }> * }|undefined), * dpi: ({ @@ -412,6 +414,12 @@ export class Destination { * @private {string} */ this.eulaUrl_ = ''; + + /** + * Stores the printer status reason for a local Chrome OS printer. + * @private {!PrinterStatusReason} + */ + this.printerStatusReason_ = PrinterStatusReason.UNKNOWN_REASON; // </if> assert( @@ -597,6 +605,22 @@ export class Destination { set eulaUrl(eulaUrl) { this.eulaUrl_ = eulaUrl; } + + /** + * @return {!PrinterStatusReason} The printer status reason for a local + * Chrome OS printer. + */ + get printerStatusReason() { + return this.printerStatusReason_; + } + + /** + * @param {!PrinterStatusReason} printerStatusReason The printer status reason + * to be set. + */ + set printerStatusReason(printerStatusReason) { + this.printerStatusReason_ = printerStatusReason; + } // </if> /** diff --git a/chromium/chrome/browser/resources/print_preview/data/destination_store.js b/chromium/chrome/browser/resources/print_preview/data/destination_store.js index f8b40cdd974..2844928fe10 100644 --- a/chromium/chrome/browser/resources/print_preview/data/destination_store.js +++ b/chromium/chrome/browser/resources/print_preview/data/destination_store.js @@ -10,7 +10,7 @@ import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; import {CloudPrintInterface, CloudPrintInterfaceEventType, CloudPrintInterfacePrinterFailedDetail, CloudPrintInterfaceProcessInviteDetail, CloudPrintInterfaceSearchDoneDetail} from '../cloud_print_interface.js'; import {Metrics, MetricsContext} from '../metrics.js'; -import {CapabilitiesResponse, LocalDestinationInfo, NativeLayer, PrinterSetupResponse, PrivetPrinterDescription, ProvisionalDestinationInfo} from '../native_layer.js'; +import {CapabilitiesResponse, LocalDestinationInfo, NativeLayer, NativeLayerImpl, PrinterSetupResponse, PrivetPrinterDescription, ProvisionalDestinationInfo} from '../native_layer.js'; import {Cdd, CloudOrigins, createDestinationKey, createRecentDestinationKey, Destination, DestinationConnectionStatus, DestinationOrigin, DestinationProvisionalType, DestinationType, RecentDestination} from './destination.js'; import {DestinationMatch, originToType, PrinterType} from './destination_match.js'; @@ -229,7 +229,7 @@ export class DestinationStore extends EventTarget { * Used to fetch local print destinations. * @private {!NativeLayer} */ - this.nativeLayer_ = NativeLayer.getInstance(); + this.nativeLayer_ = NativeLayerImpl.getInstance(); /** * Whether PDF printer is enabled. It's disabled, for example, in App diff --git a/chromium/chrome/browser/resources/print_preview/data/model.js b/chromium/chrome/browser/resources/print_preview/data/model.js index 8eea52a6e5f..b0a3948e24e 100644 --- a/chromium/chrome/browser/resources/print_preview/data/model.js +++ b/chromium/chrome/browser/resources/print_preview/data/model.js @@ -10,7 +10,7 @@ import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.m import {BackgroundGraphicsModeRestriction, Policies} from '../native_layer.js'; -import {Cdd, CddCapabilities, Destination, DestinationOrigin, DestinationType, RecentDestination} from './destination.js'; +import {Cdd, CddCapabilities, Destination, DestinationOrigin, DestinationType, RecentDestination, VendorCapability} from './destination.js'; import {getPrinterTypeForDestination} from './destination_match.js'; // <if expr="chromeos"> import {ColorModeRestriction, DuplexModeRestriction, PinModeRestriction} from './destination_policies.js'; @@ -44,6 +44,7 @@ export let Setting; * collate: !Setting, * layout: !Setting, * color: !Setting, + * customMargins: !Setting, * mediaSize: !Setting, * margins: !Setting, * dpi: !Setting, @@ -950,7 +951,8 @@ Polymer({ if (this.settings.vendorItems.available) { const vendorSettings = {}; - for (const item of caps.vendor_capability) { + for (const item of /** @type {!Array<!VendorCapability>} */ ( + caps.vendor_capability)) { let defaultValue = null; if (item.type === 'SELECT' && item.select_cap && item.select_cap.option) { diff --git a/chromium/chrome/browser/resources/print_preview/data/printer_status_cros.js b/chromium/chrome/browser/resources/print_preview/data/printer_status_cros.js new file mode 100644 index 00000000000..0cc0c513121 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/data/printer_status_cros.js @@ -0,0 +1,56 @@ +// Copyright 2020 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. + +/** + * These values must be kept in sync with the Reason enum in + * /chromeos/printing/cups_printer_status.h + * @enum {number} + */ +export const PrinterStatusReason = { + CONNECTING_TO_DEVICE: 0, + DEVICE_ERROR: 1, + DOOR_OPEN: 2, + LOW_ON_INK: 3, + LOW_ON_PAPER: 4, + NO_ERROR: 5, + OUT_OF_INK: 6, + OUT_OF_PAPER: 7, + OUTPUT_ALMOST_FULL: 8, + OUTPUT_FULL: 9, + PAPER_JAM: 10, + PAUSED: 11, + PRINTER_QUEUE_FULL: 12, + PRINTER_UNREACHABLE: 13, + STOPPED: 14, + TRAY_MISSING: 15, + UNKNOWN_REASON: 16, +}; + +/** + * These values must be kept in sync with the Severity enum in + * /chromeos/printing/cups_printer_status.h + * @enum {number} + */ +export const PrinterStatusSeverity = { + UNKOWN_SEVERITY: 0, + REPORT: 1, + WARNING: 2, + ERROR: 3, +}; + +/** + * A container for the results of a printer status query. A printer status query + * can return multiple error reasons. |timestamp| is set at the time of status + * creation. + * + * @typedef {{ + * printerId: string, + * statusReasons: !Array<{ + * reason: PrinterStatusReason, + * severity: PrinterStatusSeverity, + * }>, + * timestamp: number, + * }} + */ +export let PrinterStatus; diff --git a/chromium/chrome/browser/resources/print_preview/metrics.js b/chromium/chrome/browser/resources/print_preview/metrics.js index 34325bc2883..dc889e6ad99 100644 --- a/chromium/chrome/browser/resources/print_preview/metrics.js +++ b/chromium/chrome/browser/resources/print_preview/metrics.js @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {NativeLayer} from './native_layer.js'; +import {NativeLayer, NativeLayerImpl} from './native_layer.js'; /** * Object used to measure usage statistics. @@ -89,7 +89,7 @@ export class MetricsContext { this.maxBucket_ = maxBucket; /** @private {!NativeLayer} */ - this.nativeLayer_ = NativeLayer.getInstance(); + this.nativeLayer_ = NativeLayerImpl.getInstance(); } /** diff --git a/chromium/chrome/browser/resources/print_preview/native_layer.js b/chromium/chrome/browser/resources/print_preview/native_layer.js index ff0c3d18686..a7f13fc56bb 100644 --- a/chromium/chrome/browser/resources/print_preview/native_layer.js +++ b/chromium/chrome/browser/resources/print_preview/native_layer.js @@ -3,12 +3,13 @@ // found in the LICENSE file. import {assert} from 'chrome://resources/js/assert.m.js'; -import {sendWithPromise} from 'chrome://resources/js/cr.m.js'; +import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js'; import {Cdd, Destination} from './data/destination.js'; import {PrinterType} from './data/destination_match.js'; // <if expr="chromeos"> import {DestinationPolicies} from './data/destination_policies.js'; +import {PrinterStatus} from './data/printer_status_cros.js'; // </if> import {MeasurementSystemUnitType} from './data/measurement_system.js'; @@ -118,7 +119,7 @@ export let PrivetPrinterDescription; * printer:(PrivetPrinterDescription | * LocalDestinationInfo | * undefined), - * capabilities: !Cdd, + * capabilities: ?Cdd, * }} */ export let CapabilitiesResponse; @@ -146,44 +147,22 @@ export let ProvisionalDestinationInfo; /** * An interface to the native Chromium printing system layer. + * @interface */ export class NativeLayer { - /** - * Creates a new NativeLayer if the current instance is not set. - * @return {!NativeLayer} The singleton instance. - */ - static getInstance() { - if (currentInstance === null) { - currentInstance = new NativeLayer(); - } - return assert(currentInstance); - } - - /** - * @param {!NativeLayer} instance The NativeLayer instance - * to set for print preview construction. - */ - static setInstance(instance) { - currentInstance = instance; - } - // <if expr="chromeos"> /** * Requests access token for cloud print requests for DEVICE origin. * @return {!Promise<string>} */ - getAccessToken() { - return sendWithPromise('getAccessToken'); - } + getAccessToken() {} // </if> /** * Gets the initial settings to initialize the print preview with. * @return {!Promise<!NativeInitialSettings>} */ - getInitialSettings() { - return sendWithPromise('getInitialSettings'); - } + getInitialSettings() {} /** * Requests the system's print destinations. The promise will be resolved @@ -193,9 +172,7 @@ export class NativeLayer { * request. * @return {!Promise} */ - getPrinters(type) { - return sendWithPromise('getPrinters', type); - } + getPrinters(type) {} /** * Requests the destination's printing capabilities. Returns a promise that @@ -204,13 +181,7 @@ export class NativeLayer { * @param {!PrinterType} type The destination's printer type. * @return {!Promise<!CapabilitiesResponse>} */ - getPrinterCapabilities(destinationId, type) { - return sendWithPromise( - 'getPrinterCapabilities', destinationId, - destinationId === Destination.GooglePromotedId.SAVE_AS_PDF ? - PrinterType.PDF_PRINTER : - type); - } + getPrinterCapabilities(destinationId, type) {} // <if expr="chromeos"> /** @@ -220,9 +191,7 @@ export class NativeLayer { * @param {!string} destinationId ID of the destination. * @return {!Promise<string>} */ - getEulaUrl(destinationId) { - return sendWithPromise('getEulaUrl', destinationId); - } + getEulaUrl(destinationId) {} /** * Requests Chrome to resolve provisional extension destination by granting @@ -230,19 +199,14 @@ export class NativeLayer { * @param {string} provisionalDestinationId * @return {!Promise<!ProvisionalDestinationInfo>} */ - grantExtensionPrinterAccess(provisionalDestinationId) { - return sendWithPromise( - 'grantExtensionPrinterAccess', provisionalDestinationId); - } + grantExtensionPrinterAccess(provisionalDestinationId) {} /** * Requests that Chrome perform printer setup for the given printer. * @param {string} printerId * @return {!Promise<!PrinterSetupResponse>} */ - setupPrinter(printerId) { - return sendWithPromise('setupPrinter', printerId); - } + setupPrinter(printerId) {} // </if> /** @@ -256,17 +220,13 @@ export class NativeLayer { * @return {!Promise<number>} Promise that resolves with the unique ID of * the preview UI when the preview has been generated. */ - getPreview(printTicket) { - return sendWithPromise('getPreview', printTicket); - } + getPreview(printTicket) {} /** * Opens the chrome://settings printing page. For Chrome OS, open the - * printing settings in the Settings App. + * printing settings in the Settings App. */ - openSettingsPrintPage() { - chrome.send('openPrinterSettings'); - } + openSettingsPrintPage() {} /** * Requests that the document be printed. @@ -275,28 +235,20 @@ export class NativeLayer { * @return {!Promise} Promise that will resolve when the print request is * finished or rejected. */ - print(printTicket) { - return sendWithPromise('print', printTicket); - } + print(printTicket) {} /** Requests that the current pending print request be cancelled. */ - cancelPendingPrintRequest() { - chrome.send('cancelPendingPrintRequest'); - } + cancelPendingPrintRequest() {} /** * Sends the app state to be saved in the sticky settings. * @param {string} appStateStr JSON string of the app state to persist. */ - saveAppState(appStateStr) { - chrome.send('saveAppState', [appStateStr]); - } + saveAppState(appStateStr) {} // <if expr="not chromeos and not is_win"> /** Shows the system's native printing dialog. */ - showSystemDialog() { - chrome.send('showSystemDialog'); - } + showSystemDialog() {} // </if> /** @@ -306,17 +258,10 @@ export class NativeLayer { * @param {boolean} isCancel whether this was called due to the user * closing the dialog without printing. */ - dialogClose(isCancel) { - if (isCancel) { - chrome.send('closePrintPreviewDialog'); - } - chrome.send('dialogClose'); - } + dialogClose(isCancel) {} /** Hide the print preview dialog and allow the native layer to close it. */ - hidePreview() { - chrome.send('hidePreview'); - } + hidePreview() {} /** * Opens the Google Cloud Print sign-in tab. If the user signs in @@ -324,26 +269,16 @@ export class NativeLayer { * @param {boolean} addAccount Whether to open an 'add a new account' or * default sign in page. */ - signIn(addAccount) { - chrome.send('signIn', [addAccount]); - } - - /** - * Sends a message to the test, letting it know that an - * option has been set to a particular value and that the change has - * finished modifying the preview area. - */ - uiLoadedForTest() { - chrome.send('UILoadedForTest'); - } + signIn(addAccount) {} + // <if expr="chromeos"> /** - * Notifies the test that the option it tried to change - * had not been changed successfully. + * Sends a request to the printer with id |printerId| for its current status. + * @param {string} printerId + * @return {!Promise<!PrinterStatus>} */ - uiFailedLoadingForTest() { - chrome.send('UIFailedLoadingForTest'); - } + requestPrinterStatusUpdate(printerId) {} + // </if> /** * Notifies the metrics handler to record a histogram value. @@ -351,11 +286,117 @@ export class NativeLayer { * @param {number} bucket The bucket to record * @param {number} maxBucket The maximum bucket value in the histogram. */ + recordInHistogram(histogram, bucket, maxBucket) {} +} + +/** @implements {NativeLayer} */ +export class NativeLayerImpl { + // <if expr="chromeos"> + /** @override */ + getAccessToken() { + return sendWithPromise('getAccessToken'); + } + // </if> + + /** @override */ + getInitialSettings() { + return sendWithPromise('getInitialSettings'); + } + + /** @override */ + getPrinters(type) { + return sendWithPromise('getPrinters', type); + } + + /** @override */ + getPrinterCapabilities(destinationId, type) { + return sendWithPromise( + 'getPrinterCapabilities', destinationId, + destinationId === Destination.GooglePromotedId.SAVE_AS_PDF ? + PrinterType.PDF_PRINTER : + type); + } + + // <if expr="chromeos"> + /** @override */ + getEulaUrl(destinationId) { + return sendWithPromise('getEulaUrl', destinationId); + } + + /** @override */ + grantExtensionPrinterAccess(provisionalDestinationId) { + return sendWithPromise( + 'grantExtensionPrinterAccess', provisionalDestinationId); + } + + /** @override */ + setupPrinter(printerId) { + return sendWithPromise('setupPrinter', printerId); + } + // </if> + + /** @override */ + getPreview(printTicket) { + return sendWithPromise('getPreview', printTicket); + } + + /** @override */ + openSettingsPrintPage() { + chrome.send('openPrinterSettings'); + } + + /** @override */ + print(printTicket) { + return sendWithPromise('print', printTicket); + } + + /** @override */ + cancelPendingPrintRequest() { + chrome.send('cancelPendingPrintRequest'); + } + + /** @override */ + saveAppState(appStateStr) { + chrome.send('saveAppState', [appStateStr]); + } + + // <if expr="not chromeos and not is_win"> + /** @override */ + showSystemDialog() { + chrome.send('showSystemDialog'); + } + // </if> + + /** @override */ + dialogClose(isCancel) { + if (isCancel) { + chrome.send('closePrintPreviewDialog'); + } + chrome.send('dialogClose'); + } + + /** @override */ + hidePreview() { + chrome.send('hidePreview'); + } + + /** @override */ + signIn(addAccount) { + chrome.send('signIn', [addAccount]); + } + + // <if expr="chromeos"> + /** @override */ + requestPrinterStatusUpdate(printerId) { + return sendWithPromise('requestPrinterStatus', printerId); + } + // </if> + + /** @override */ recordInHistogram(histogram, bucket, maxBucket) { chrome.send( 'metricsHandler:recordInHistogram', [histogram, bucket, maxBucket]); } } -/** @private {?NativeLayer} */ -let currentInstance = null; +addSingletonGetter(NativeLayerImpl); diff --git a/chromium/chrome/browser/resources/print_preview/print_preview.html b/chromium/chrome/browser/resources/print_preview/print_preview.html index 4aa26d79dbd..6a792ccc483 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview.html +++ b/chromium/chrome/browser/resources/print_preview/print_preview.html @@ -1,6 +1,5 @@ <!doctype html> -<html dir="$i18n{textdirection}" lang="$i18n{language}" $i18n{a11yenhanced} - class="loading"> +<html dir="$i18n{textdirection}" lang="$i18n{language}" class="loading"> <head> <title>$i18n{title}</title> <meta charset="utf-8"> diff --git a/chromium/chrome/browser/resources/print_preview/print_preview.js b/chromium/chrome/browser/resources/print_preview/print_preview.js index 21658c88e7f..10d3ac4aae4 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview.js +++ b/chromium/chrome/browser/resources/print_preview/print_preview.js @@ -4,25 +4,30 @@ import './ui/app.js'; +export {PluralStringProxyImpl as PrintPreviewPluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js'; export {CloudPrintInterface, CloudPrintInterfaceEventType} from './cloud_print_interface.js'; export {CloudPrintInterfaceImpl} from './cloud_print_interface_impl.js'; -export {ColorMode, createDestinationKey, Destination, DestinationCertificateStatus, DestinationConnectionStatus, DestinationOrigin, DestinationType, makeRecentDestination} from './data/destination.js'; +export {ColorMode, createDestinationKey, Destination, DestinationCertificateStatus, DestinationConnectionStatus, DestinationOrigin, DestinationType, makeRecentDestination, RecentDestination} from './data/destination.js'; export {PrinterType} from './data/destination_match.js'; // <if expr="chromeos"> export {ColorModeRestriction, DuplexModeRestriction, PinModeRestriction} from './data/destination_policies.js'; +export {PrinterStatus, PrinterStatusReason, PrinterStatusSeverity} from './data/printer_status_cros.js'; +export {PrinterState} from './ui/printer_status_icon_cros.js'; // </if> export {DestinationErrorType, DestinationStore} from './data/destination_store.js'; +export {PageLayoutInfo} from './data/document_info.js'; export {InvitationStore} from './data/invitation_store.js'; -export {CustomMarginsOrientation, Margins, MarginsType} from './data/margins.js'; +export {CustomMarginsOrientation, Margins, MarginsSetting, MarginsType} from './data/margins.js'; export {MeasurementSystem, MeasurementSystemUnitType} from './data/measurement_system.js'; export {DuplexMode, DuplexType, getInstance, whenReady} from './data/model.js'; export {ScalingType} from './data/scaling.js'; export {Size} from './data/size.js'; export {Error, State} from './data/state.js'; -export {BackgroundGraphicsModeRestriction, NativeLayer} from './native_layer.js'; +export {BackgroundGraphicsModeRestriction, CapabilitiesResponse, LocalDestinationInfo, NativeInitialSettings, NativeLayer, NativeLayerImpl, PrinterSetupResponse, ProvisionalDestinationInfo} from './native_layer.js'; export {getSelectDropdownBackground} from './print_preview_utils.js'; export {DEFAULT_MAX_COPIES} from './ui/copies_settings.js'; export {DestinationState} from './ui/destination_settings.js'; export {PluginProxy} from './ui/plugin_proxy.js'; export {PreviewAreaState} from './ui/preview_area.js'; export {SelectBehavior} from './ui/select_behavior.js'; +export {SelectOption} from './ui/settings_select.js'; diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_pdf_resources.grd b/chromium/chrome/browser/resources/print_preview/print_preview_pdf_resources.grd new file mode 100644 index 00000000000..d8483272925 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/print_preview_pdf_resources.grd @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grit latest_public_release="0" current_release="1" output_all_resource_defines="false"> + <outputs> + <output filename="grit/print_preview_pdf_resources.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="grit/print_preview_pdf_resources_map.cc" + type="resource_file_map_source" /> + <output filename="grit/print_preview_pdf_resources_map.h" + type="resource_map_header" /> + <output filename="print_preview_pdf_resources.pak" type="data_package" /> + </outputs> + <release seq="1"> + <includes> + <include name="IDR_PRINT_PREVIEW_PDF_INDEX_PP_HTML" + file="../pdf/index_pp.html" + type="BINDATA" /> + <include name="IDR_PRINT_PREVIEW_PDF_PDF_VIEWER_PP_JS" + file="${root_gen_dir}/chrome/browser/resources/pdf/pdf_viewer_pp.js" + use_base_dir="false" + type="BINDATA" /> + <include name="IDR_PRINT_PREVIEW_PDF_VIEWER_PAGE_INDICATOR_JS" + file="${root_gen_dir}/chrome/browser/resources/pdf/elements/viewer-page-indicator.js" + use_base_dir="false" + type="BINDATA" /> + </includes> + </release> +</grit> diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_resources.grd b/chromium/chrome/browser/resources/print_preview/print_preview_resources.grd index 8f7cd3fe0b5..9bbd50d630f 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview_resources.grd +++ b/chromium/chrome/browser/resources/print_preview/print_preview_resources.grd @@ -49,6 +49,12 @@ <include name="IDR_PRINT_PREVIEW_UI_DESTINATION_SELECT_CROS_JS" file="${root_gen_dir}/chrome/browser/resources/print_preview/ui/destination_select_cros.js" use_base_dir="false" compress="false" type="BINDATA"/> + <include name="IDR_PRINT_PREVIEW_UI_PRINT_PREVIEW_DESTINATION_DROPDOWN_CROS_JS" + file="${root_gen_dir}/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.js" + use_base_dir="false" compress="false" type="BINDATA"/> + <include name="IDR_PRINT_PREVIEW_UI_PRINT_PREVIEW_PRINTER_STATUS_ICON_CROS_JS" + file="${root_gen_dir}/chrome/browser/resources/print_preview/ui/printer_status_icon_cros.js" + use_base_dir="false" compress="false" type="BINDATA"/> </if> <include name="IDR_PRINT_PREVIEW_UI_DESTINATION_SELECT_CSS_JS" file="${root_gen_dir}/chrome/browser/resources/print_preview/ui/destination_select_css.js" @@ -141,16 +147,6 @@ <include name="IDR_PRINT_PREVIEW_UI_ICONS_JS" file="${root_gen_dir}/chrome/browser/resources/print_preview/ui/icons.js" use_base_dir="false" compress="false" type="BINDATA"/> - <include name="IDR_PRINT_PREVIEW_PDF_INDEX_PP_HTML" - file="../pdf/index_pp.html" - type="BINDATA" /> - <include name="IDR_PRINT_PREVIEW_PDF_MAIN_PP_JS" - file="../pdf/main_pp.js" - type="BINDATA" /> - <include name="IDR_PRINT_PREVIEW_PDF_VIEWER_PAGE_INDICATOR_JS" - file="${root_gen_dir}/chrome/browser/resources/pdf/elements/viewer-page-indicator.js" - use_base_dir="false" - type="BINDATA" /> </includes> <structures> <structure name="IDR_PRINT_PREVIEW_PRINT_PREVIEW_HTML" @@ -196,6 +192,10 @@ file="data/destination_policies.js" compress="false" type="chrome_html" /> + <structure name="IDR_PRINT_PREVIEW_DATA_PRINTER_STATUS_CROS_JS" + file="data/printer_status_cros.js" + compress="false" + type="chrome_html" /> </if> <structure name="IDR_PRINT_PREVIEW_DATA_DESTINATION_STORE_JS" file="data/destination_store.js" diff --git a/chromium/chrome/browser/resources/print_preview/print_preview_resources_vulcanized.grd b/chromium/chrome/browser/resources/print_preview/print_preview_resources_vulcanized.grd index 0eecabc3c5d..1ac21ce8b10 100644 --- a/chromium/chrome/browser/resources/print_preview/print_preview_resources_vulcanized.grd +++ b/chromium/chrome/browser/resources/print_preview/print_preview_resources_vulcanized.grd @@ -15,23 +15,11 @@ <include name="IDR_PRINT_PREVIEW_PRINT_PREVIEW_HTML" file="print_preview.html" type="chrome_html" - preprocess="true" - compress="gzip" /> + preprocess="true" /> <include name="IDR_PRINT_PREVIEW_PRINT_PREVIEW_ROLLUP_JS" file="${root_gen_dir}\chrome\browser\resources\print_preview\print_preview.rollup.js" use_base_dir="false" preprocess="true" - compress="gzip" - type="BINDATA" /> - <include name="IDR_PRINT_PREVIEW_PDF_INDEX_PP_HTML" - file="../pdf/index_pp.html" - type="BINDATA" /> - <include name="IDR_PRINT_PREVIEW_PDF_MAIN_PP_JS" - file="../pdf/main_pp.js" - type="BINDATA" /> - <include name="IDR_PRINT_PREVIEW_PDF_VIEWER_PAGE_INDICATOR_JS" - file="${root_gen_dir}/chrome/browser/resources/pdf/elements/viewer-page-indicator.js" - use_base_dir="false" type="BINDATA" /> </includes> </release> diff --git a/chromium/chrome/browser/resources/print_preview/ui/BUILD.gn b/chromium/chrome/browser/resources/print_preview/ui/BUILD.gn index 860caec75e9..d95c0bb6565 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/BUILD.gn +++ b/chromium/chrome/browser/resources/print_preview/ui/BUILD.gn @@ -53,8 +53,10 @@ js_type_check("closure_compile_module") { if (is_chromeos) { deps += [ + ":destination_dropdown_cros", ":destination_select_cros", ":pin_settings", + ":printer_status_icon_cros", ] } else { deps += [ ":destination_select" ] @@ -117,6 +119,7 @@ js_library("button_strip") { "../data:destination", "../data:state", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + "//ui/webui/resources/js:plural_string_proxy", ] if (is_chromeos) { @@ -131,6 +134,7 @@ js_library("header") { "../data:state", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", "//ui/webui/resources/js:load_time_data.m", + "//ui/webui/resources/js:plural_string_proxy", ] } @@ -160,9 +164,11 @@ js_library("destination_settings") { if (is_chromeos) { js_library("destination_select_cros") { deps = [ + ":destination_dropdown_cros", ":select_behavior", "..:print_preview_utils", "../data:destination", + "../data:printer_status_cros", "//third_party/polymer/v3_0/components-chromium/iron-iconset-svg:iron-iconset-svg", "//third_party/polymer/v3_0/components-chromium/iron-meta:iron-meta", "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", @@ -178,6 +184,22 @@ if (is_chromeos) { "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", ] } + + js_library("destination_dropdown_cros") { + deps = [ + ":printer_status_icon_cros", + "..:print_preview_utils", + "//third_party/polymer/v3_0/components-chromium/iron-dropdown:iron-dropdown", + "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + "//ui/webui/resources/cr_elements/cr_input:cr_input.m", + ] + } + + js_library("printer_status_icon_cros") { + deps = [ + "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled", + ] + } } else { js_library("destination_select") { deps = [ @@ -544,8 +566,10 @@ html_to_js("web_components") { ] if (is_chromeos) { js_files += [ + "destination_dropdown_cros.js", "destination_select_cros.js", "pin_settings.js", + "printer_status_icon_cros.js", ] } else { js_files += [ "destination_select.js" ] diff --git a/chromium/chrome/browser/resources/print_preview/ui/app.js b/chromium/chrome/browser/resources/print_preview/ui/app.js index 575afacd7aa..5c7b6601686 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/app.js +++ b/chromium/chrome/browser/resources/print_preview/ui/app.js @@ -26,7 +26,7 @@ import {DuplexMode, whenReady} from '../data/model.js'; import {PrintableArea} from '../data/printable_area.js'; import {Size} from '../data/size.js'; import {Error, State} from '../data/state.js'; -import {NativeInitialSettings, NativeLayer} from '../native_layer.js'; +import {NativeInitialSettings, NativeLayer, NativeLayerImpl} from '../native_layer.js'; import {DestinationState} from './destination_settings.js'; import {PreviewAreaState} from './preview_area.js'; @@ -168,7 +168,7 @@ Polymer({ /** @override */ attached() { document.documentElement.classList.remove('loading'); - this.nativeLayer_ = NativeLayer.getInstance(); + this.nativeLayer_ = NativeLayerImpl.getInstance(); this.addWebUIListener('print-failed', this.onPrintFailed_.bind(this)); this.addWebUIListener( 'print-preset-options', this.onPrintPresetOptions_.bind(this)); diff --git a/chromium/chrome/browser/resources/print_preview/ui/button_strip.js b/chromium/chrome/browser/resources/print_preview/ui/button_strip.js index 28ea8d6232d..4624470278a 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/button_strip.js +++ b/chromium/chrome/browser/resources/print_preview/ui/button_strip.js @@ -8,6 +8,7 @@ import 'chrome://resources/cr_elements/shared_vars_css.m.js'; import '../strings.m.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js'; import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js'; import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; @@ -50,7 +51,6 @@ Polymer({ /** @private */ errorMessage_: { type: String, - computed: 'computeErrorMessage_(destination.id, maxSheets, sheetCount)', observer: 'errorMessageChanged_', }, // </if> @@ -59,6 +59,9 @@ Polymer({ observers: [ 'updatePrintButtonLabel_(destination.id)', 'updatePrintButtonEnabled_(state, destination.id, maxSheets, sheetCount)', + // <if expr="chromeos"> + 'updateErrorMessage_(state, destination.id, maxSheets, sheetCount)', + // </if> ], /** @private {!State} */ @@ -140,19 +143,17 @@ Polymer({ return this.sheetCount > 0 && this.printButtonDisabled_(); }, - /** - * @return {string} Localized message to show as an error. - * @private - */ - computeErrorMessage_() { + /** @private */ + updateErrorMessage_() { if (!this.showSheetsError_()) { - return ''; + this.errorMessage_ = ''; + return; } - - const singularOrPlural = this.maxSheets > 1 ? 'Plural' : 'Singular'; - const label = loadTimeData.getString(`sheetsLimitLabel${singularOrPlural}`); - return loadTimeData.getStringF( - 'sheetsLimitErrorMessage', this.maxSheets.toLocaleString(), label); + PluralStringProxyImpl.getInstance() + .getPluralString('sheetsLimitErrorMessage', this.maxSheets) + .then(label => { + this.errorMessage_ = label; + }); }, /** diff --git a/chromium/chrome/browser/resources/print_preview/ui/destination_dialog.js b/chromium/chrome/browser/resources/print_preview/ui/destination_dialog.js index e0532e60acf..c3b9f5bbd2c 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/destination_dialog.js +++ b/chromium/chrome/browser/resources/print_preview/ui/destination_dialog.js @@ -33,7 +33,7 @@ import {DestinationStore} from '../data/destination_store.js'; import {Invitation} from '../data/invitation.js'; import {InvitationStore} from '../data/invitation_store.js'; import {Metrics, MetricsContext} from '../metrics.js'; -import {NativeLayer} from '../native_layer.js'; +import {NativeLayerImpl} from '../native_layer.js'; Polymer({ is: 'print-preview-destination-dialog', @@ -129,12 +129,7 @@ Polymer({ this.$$('.promo-text').innerHTML = this.i18nAdvanced('cloudPrintPromotion', { substitutions: ['<a is="action-link" class="sign-in">', '</a>'], - attrs: { - 'is': (node, v) => v === 'action-link', - 'class': (node, v) => v === 'sign-in', - 'tabindex': (node, v) => v === '0', - 'role': (node, v) => v === 'link', - }, + attrs: ['is', 'class', 'tabindex', 'role'], }); }, @@ -340,7 +335,7 @@ Polymer({ /** @private */ onSignInClick_() { this.metrics_.record(Metrics.DestinationSearchBucket.SIGNIN_TRIGGERED); - NativeLayer.getInstance().signIn(false); + NativeLayerImpl.getInstance().signIn(false); }, /** @private */ @@ -426,7 +421,7 @@ Polymer({ this.metrics_.record(Metrics.DestinationSearchBucket.ACCOUNT_CHANGED); } else { select.value = this.activeUser; - NativeLayer.getInstance().signIn(true); + NativeLayerImpl.getInstance().signIn(true); this.metrics_.record( Metrics.DestinationSearchBucket.ADD_ACCOUNT_SELECTED); } @@ -463,6 +458,6 @@ Polymer({ /** @private */ onOpenSettingsPrintPage_() { this.metrics_.record(Metrics.DestinationSearchBucket.MANAGE_BUTTON_CLICKED); - NativeLayer.getInstance().openSettingsPrintPage(); + NativeLayerImpl.getInstance().openSettingsPrintPage(); }, }); diff --git a/chromium/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.html b/chromium/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.html new file mode 100644 index 00000000000..c20bcbae4a6 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.html @@ -0,0 +1,150 @@ +<style include="cr-shared-style cr-hidden-style md-select"> + :host([opened_]) cr-input { + --cr-input-border-radius: 4px 4px 0 0; + } + + iron-dropdown, + cr-input { + width: var(--md-select-width); + } + + cr-input::part(input) { + opacity: 1; + padding-inline-end: 32px; + padding-inline-start: 8px; + text-overflow: clip; + } + + iron-dropdown { + border: 0.5px solid rgba(0, 0, 0, 0.5); + max-height: 270px; + } + + iron-dropdown [slot='dropdown-content'] { + background-color: white; + box-shadow: 0 2px 6px var(--google-grey-600); + min-width: var(--md-select-width); + padding: 8px 0; + } + + #input-overlay { + border-radius: 4px; + height: 100%; + left: 0; + overflow: hidden; + pointer-events: none; + position: absolute; + top: 0; + width: 100%; + } + + #dropdown-icon { + --iron-icon-height: 20px; + --iron-icon-width: 20px; + margin-top: -10px; + padding-inline-end: 6px; + position: absolute; + right: 0; + top: 50%; + } + + :host-context([dir='rtl']) #dropdown-icon { + left: 0; + right: unset; + } + + cr-input:focus-within #dropdown-icon { + --iron-icon-fill-color: var(--google-blue-600); + } + + #input-box { + height: 100%; + left: 0; + pointer-events: none; + top: 0; + width: 100%; + } + + #dropdown-box { + pointer-events: initial; + width: 100%; + } + + .list-item { + background: none; + border: none; + box-sizing: border-box; + font: inherit; + min-height: 32px; + padding: 0 8px; + text-align: start; + width: 100%; + } + + .list-item:focus { + outline: none; + } + + .list-item[selected_] { + background-color: var(--google-blue-refresh-100); + } + + .dot { + background-color: #bbb; + border-radius: 50%; + display: inline-block; + height: 10px; + width: 10px; + } + + #pre-input-box, + .printer-display-name { + padding-inline-start: 8px; + } +</style> +<cr-input id="dropdownInput" on-keydown="onKeyDown_" + value="[[value.displayName]]" disabled="[[disabled]]" readonly> + <div id="pre-input-overlay" slot="inline-prefix"> + <div id="pre-input-box"> + <iron-icon icon="[[destinationIcon]]"></iron-icon> + </div> + </div> + <div id="input-overlay" slot="suffix"> + <div id="input-box"> + <iron-icon id="dropdown-icon" icon="cr:arrow-drop-down"></iron-icon> + </div> + <div id="dropdown-box"> + <iron-dropdown horizontal-align="left" vertical-align="top" + vertical-offset="0" no-cancel-on-outside-click + no-cancel-on-esc-key> + <div slot="dropdown-content"> + <template is="dom-repeat" items="[[itemList]]"> + <button id="[[item.key]]" class="list-item" on-click="onSelect_" + tabindex="-1" value="[[item.key]]"> + <printer-status-icon-cros background="white" + state$="[[computePrinterState_(item.printerStatusReason)]]"> + </printer-status-icon-cros> + <span class="printer-display-name">[[item.displayName]]</span> + </button> + </template> + <button class="list-item" on-click="onSelect_" tabindex="-1" + value="[[pdfDestinationKey]]" hidden$="[[pdfPrinterDisabled]]"> + $i18n{printToPDF} + </button> + <button class="list-item" on-click="onSelect_" tabindex="-1" + value="[[driveDestinationKey]]" hidden$="[[!driveDestinationKey]]"> + $i18n{printToGoogleDrive} + </button> + <button class="list-item" on-click="onSelect_" tabindex="-1" + value="noDestinations" hidden$="[[!noDestinations]]"> + $i18n{noDestinationsMessage} + </button> + <button class="list-item" on-click="onSelect_" tabindex="-1" + value="seeMore" aria-label$="[[i18n(seeMoreDestinationsLabel)]]"> + $i18n{seeMore} + </button> + </div> + </iron-dropdown> + </div> + </div> +</cr-input> diff --git a/chromium/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.js b/chromium/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.js new file mode 100644 index 00000000000..3d95796c331 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.js @@ -0,0 +1,273 @@ +// Copyright 2020 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. + +import 'chrome://resources/cr_elements/cr_input/cr_input.m.js'; +import 'chrome://resources/cr_elements/hidden_style_css.m.js'; +import 'chrome://resources/cr_elements/md_select_css.m.js'; +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +// TODO(gavinwill): Remove iron-dropdown dependency https://crbug.com/1082587. +import 'chrome://resources/polymer/v3_0/iron-dropdown/iron-dropdown.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; + +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +import {Destination} from '../data/destination.js'; +import {PrinterStatusReason} from '../data/printer_status_cros.js'; + +import {PrinterState} from './printer_status_icon_cros.js'; + +Polymer({ + is: 'print-preview-destination-dropdown-cros', + + _template: html`{__html_template__}`, + + properties: { + /** @type {!Destination} */ + value: Object, + + /** @type {!Array<!Destination>} */ + itemList: { + type: Array, + observer: 'enqueueDropdownRefit_', + }, + + /** @type {boolean} */ + disabled: { + type: Boolean, + value: false, + }, + + driveDestinationKey: String, + + noDestinations: Boolean, + + pdfPrinterDisabled: Boolean, + + pdfDestinationKey: String, + + destinationIcon: String, + }, + + listeners: { + 'mousemove': 'onMouseMove_', + }, + + /** @override */ + attached() { + this.pointerDownListener_ = event => this.onPointerDown_(event); + document.addEventListener('pointerdown', this.pointerDownListener_); + }, + + /** @override */ + detached() { + document.removeEventListener('pointerdown', this.pointerDownListener_); + }, + + /** + * Enqueues a task to refit the iron-dropdown if it is open. + * @private + */ + enqueueDropdownRefit_() { + const dropdown = this.$$('iron-dropdown'); + if (!this.dropdownRefitPending_ && dropdown.opened) { + this.dropdownRefitPending_ = true; + setTimeout(() => { + dropdown.refit(); + this.dropdownRefitPending_ = false; + }, 0); + } + }, + + /** @private */ + openDropdown_() { + if (this.disabled) { + return; + } + + this.$$('iron-dropdown').open(); + this.opened_ = true; + }, + + /** @private */ + closeDropdown_() { + this.$$('iron-dropdown').close(); + this.opened_ = false; + + const selectedItem = this.findSelectedItem_(); + if (selectedItem) { + selectedItem.removeAttribute('selected_'); + } + }, + + /** + * @param {!Event} event + * @private + */ + onMouseMove_(event) { + const item = event.composedPath().find( + elm => elm.classList && elm.classList.contains('list-item')); + if (!item) { + return; + } + + // Select the item the mouse is hovering over. If the user uses the + // keyboard, the selection will shift. But once the user moves the mouse, + // selection should be updated based on the location of the mouse cursor. + const selectedItem = this.findSelectedItem_(); + if (item === selectedItem) { + return; + } + + if (selectedItem) { + selectedItem.removeAttribute('selected_'); + } + item.setAttribute('selected_', ''); + }, + + /** + * @param {!Event} event + * @private + */ + onPointerDown_(event) { + const paths = event.composedPath(); + const dropdown = + /** @type {!IronDropdownElement} */ (this.$$('iron-dropdown')); + const dropdownInput = + /** @type {!CrInputElement} */ (this.$$('#dropdownInput')); + + // Exit if path includes |dropdown| because event will be handled by + // onSelect_. + if (paths.includes(dropdown)) { + return; + } + + if (!paths.includes(dropdownInput) || dropdown.opened) { + this.closeDropdown_(); + return; + } + + this.openDropdown_(); + }, + + /** @private */ + onSelect_() { + const selectedItem = this.findSelectedItem_(); + this.closeDropdown_(); + this.fire('dropdown-value-selected', selectedItem); + }, + + /** + * @param {!Event} event + * @private + */ + onKeyDown_(event) { + event.stopPropagation(); + const dropdown = this.$$('iron-dropdown'); + switch (event.code) { + case 'Tab': + this.closeDropdown_(); + break; + case 'ArrowUp': + case 'ArrowDown': { + const items = dropdown.getElementsByClassName('list-item'); + if (items.length === 0) { + break; + } + this.updateSelected_(event.code === 'ArrowDown'); + break; + } + case 'Enter': { + if (dropdown.opened) { + this.onSelect_(); + break; + } + this.openDropdown_(); + break; + } + case 'Escape': { + if (dropdown.opened) { + this.closeDropdown_(); + event.preventDefault(); + } + break; + } + } + }, + + /** + * Updates the currently selected element based on keyboard up/down movement. + * @param {boolean} moveDown + * @private + */ + updateSelected_(moveDown) { + const items = this.getButtonListFromDropdown_(); + const numItems = items.length; + if (numItems === 0) { + return; + } + + let nextIndex = 0; + const currentIndex = this.findSelectedItemIndex_(); + if (currentIndex === -1) { + nextIndex = moveDown ? 0 : numItems - 1; + } else { + const delta = moveDown ? 1 : -1; + nextIndex = (numItems + currentIndex + delta) % numItems; + items[currentIndex].removeAttribute('selected_'); + } + items[nextIndex].setAttribute('selected_', ''); + // The newly selected item might not be visible because the dropdown needs + // to be scrolled. So scroll the dropdown if necessary. + items[nextIndex].scrollIntoViewIfNeeded(); + }, + + /** + * Finds the currently selected dropdown item. + * @return {Element|undefined} Currently selected dropdown item, or undefined + * if no item is selected. + * @private + */ + findSelectedItem_() { + const items = this.getButtonListFromDropdown_(); + return items.find(item => item.hasAttribute('selected_')); + }, + + /** + * Finds the index of currently selected dropdown item. + * @return {number} Index of the currently selected dropdown item, or -1 if + * no item is selected. + * @private + */ + findSelectedItemIndex_() { + const items = this.getButtonListFromDropdown_(); + return items.findIndex(item => item.hasAttribute('selected_')); + }, + + /** + * Returns list of all the visible items in the dropdown. + * @return {!Array<!Element>} + * @private + */ + getButtonListFromDropdown_() { + const dropdown = this.$$('iron-dropdown'); + return Array.from(dropdown.getElementsByClassName('list-item')) + .filter(item => !item.hidden); + }, + + /** + * @param {?PrinterStatusReason} printerStatusReason + * @return {number} + * @private + */ + computePrinterState_(printerStatusReason) { + if (!printerStatusReason || + printerStatusReason === PrinterStatusReason.UNKNOWN_REASON) { + return PrinterState.UNKNOWN; + } + if (printerStatusReason === PrinterStatusReason.NO_ERROR) { + return PrinterState.GOOD; + } + return PrinterState.ERROR; + }, +}); diff --git a/chromium/chrome/browser/resources/print_preview/ui/destination_select_cros.html b/chromium/chrome/browser/resources/print_preview/ui/destination_select_cros.html index 2d8d0b8d28f..9ef6b0878c1 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/destination_select_cros.html +++ b/chromium/chrome/browser/resources/print_preview/ui/destination_select_cros.html @@ -7,8 +7,8 @@ <div class="throbber"></div> </div> <template is="dom-if" if="[[!printerStatusFlagEnabled_]]"> - <select class="md-select" aria-label$="[[i18n(destinationLabel)]]" - hidden$="[[!loaded]]" + <select id="dropdown" class="md-select" + aria-label$="[[i18n(destinationLabel)]]" hidden$="[[!loaded]]" style="background-image:[[backgroundImages_]];" disabled$="[[disabled]]" value="{{selectedValue::change}}"> @@ -33,7 +33,16 @@ </select> </template> <template is="dom-if" if="[[printerStatusFlagEnabled_]]"> - <div>print-preview-destination-select-cros</div> + <print-preview-destination-dropdown-cros id="dropdown" + value="[[destination]]" hidden$="[[!loaded]]" + item-list="[[recentDestinationList]]" + pdf-destination-key="[[pdfDestinationKey_]]" + drive-destination-key="[[driveDestinationKey]]" + no-destinations="[[noDestinations]]" + pdf-printer-disabled="[[pdfPrinterDisabled]]" + destination-icon="[[destinationIcon_]]" disabled="[[disabled]]" + on-dropdown-value-selected="onDropdownValueSelected_"> + </print-preview-destination-dropdown-cros> </template> </div> </print-preview-settings-section> diff --git a/chromium/chrome/browser/resources/print_preview/ui/destination_select_cros.js b/chromium/chrome/browser/resources/print_preview/ui/destination_select_cros.js index f76ac017631..9eab3a242b4 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/destination_select_cros.js +++ b/chromium/chrome/browser/resources/print_preview/ui/destination_select_cros.js @@ -7,22 +7,46 @@ import 'chrome://resources/cr_elements/shared_vars_css.m.js'; import 'chrome://resources/cr_elements/md_select_css.m.js'; import 'chrome://resources/js/util.m.js'; import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js'; +import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; import 'chrome://resources/polymer/v3_0/iron-meta/iron-meta.js'; +import './destination_dropdown_cros.js'; import './destination_select_css.js'; import './icons.js'; import './print_preview_shared_css.js'; import './throbber_css.js'; import '../strings.m.js'; +import {assert} from 'chrome://resources/js/assert.m.js'; import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; import {Base, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; -import {Destination, DestinationOrigin, PDF_DESTINATION_KEY, RecentDestination} from '../data/destination.js'; +import {CloudOrigins, Destination, DestinationOrigin, PDF_DESTINATION_KEY, RecentDestination} from '../data/destination.js'; +import {PrinterStatus, PrinterStatusReason, PrinterStatusSeverity} from '../data/printer_status_cros.js'; +import {NativeLayer, NativeLayerImpl} from '../native_layer.js'; import {getSelectDropdownBackground} from '../print_preview_utils.js'; import {SelectBehavior} from './select_behavior.js'; +/** @const {!Map<!PrinterStatusReason, string>} */ +const ERROR_STRING_KEY_MAP = new Map([ + [PrinterStatusReason.CONNECTING_TO_DEVICE, 'printerStatusConnectingToDevice'], + [PrinterStatusReason.DEVICE_ERROR, 'printerStatusDeviceError'], + [PrinterStatusReason.DOOR_OPEN, 'printerStatusDoorOpen'], + [PrinterStatusReason.LOW_ON_INK, 'printerStatusLowOnInk'], + [PrinterStatusReason.LOW_ON_PAPER, 'printerStatusLowOnPaper'], + [PrinterStatusReason.OUT_OF_INK, 'printerStatusOutOfInk'], + [PrinterStatusReason.OUT_OF_PAPER, 'printerStatusOutOfPaper'], + [PrinterStatusReason.OUTPUT_ALMOST_FULL, 'printerStatusOutputAlmostFull'], + [PrinterStatusReason.OUTPUT_FULL, 'printerStatusOutputFull'], + [PrinterStatusReason.PAPER_JAM, 'printerStatusPaperJam'], + [PrinterStatusReason.PAUSED, 'printerStatusPaused'], + [PrinterStatusReason.PRINTER_QUEUE_FULL, 'printerStatusPrinterQueueFull'], + [PrinterStatusReason.PRINTER_UNREACHABLE, 'printerStatusPrinterUnreachable'], + [PrinterStatusReason.STOPPED, 'printerStatusStopped'], + [PrinterStatusReason.TRAY_MISSING, 'printerStatusTrayMissing'], +]); + Polymer({ is: 'print-preview-destination-select-cros', @@ -36,7 +60,11 @@ Polymer({ dark: Boolean, /** @type {!Destination} */ - destination: Object, + destination: { + type: Object, + observer: 'updateStatusText_', + }, + disabled: Boolean, @@ -49,7 +77,10 @@ Polymer({ pdfPrinterDisabled: Boolean, /** @type {!Array<!Destination>} */ - recentDestinationList: Array, + recentDestinationList: { + type: Array, + observer: 'onRecentDestinationListChanged_', + }, /** @private {string} */ pdfDestinationKey_: { @@ -57,17 +88,20 @@ Polymer({ value: PDF_DESTINATION_KEY, }, + /** @private */ + statusText_: String, + /** @private {string} */ - statusText_: { + backgroundImages_: { type: String, - computed: 'computeStatusText_(destination)', + computed: + 'computeBackgroundImages_(destinationIcon_, dark, noDestinations)', }, /** @private {string} */ - backgroundImages_: { + destinationIcon_: { type: String, - computed: - 'computeBackgroundImages_(selectedValue, destination, noDestinations, dark)', + computed: 'computeDestinationIcon_(selectedValue, destination)', }, /** @private */ @@ -78,14 +112,32 @@ Polymer({ }, readOnly: true, }, + + /** + * The key for this map is a destination.id and the value is a + * destination.key. This map is needed to track which destinations have had + * statuses requested while also giving quick look up of destination id to + * the corresponding destination key. + * @private {!Map<string, string>} + */ + statusRequestedMap_: Map, }, /** @private {!IronMetaElement} */ meta_: /** @type {!IronMetaElement} */ ( Base.create('iron-meta', {type: 'iconset'})), + /** @override */ + attached() { + if (!this.printerStatusFlagEnabled_) { + return; + } + + this.statusRequestedMap_ = new Map(); + }, + focus() { - this.$$('.md-select').focus(); + this.$$('#dropdown').focus(); }, /** Sets the select to the current value of |destination|. */ @@ -100,7 +152,7 @@ Polymer({ * @return {string} The iconset and icon for the current selection. * @private */ - getDestinationIcon_() { + computeDestinationIcon_() { if (!this.selectedValue) { return ''; } @@ -140,8 +192,7 @@ Polymer({ * @private */ computeBackgroundImages_() { - const icon = this.getDestinationIcon_(); - if (!icon) { + if (!this.destinationIcon_) { return ''; } @@ -149,7 +200,7 @@ Polymer({ if (this.noDestinations) { iconSetAndIcon = ['cr', 'error']; } - iconSetAndIcon = iconSetAndIcon || icon.split(':'); + iconSetAndIcon = iconSetAndIcon || this.destinationIcon_.split(':'); const iconset = /** @type {!IronIconsetSvgElement} */ ( this.meta_.byKey(iconSetAndIcon[0])); return getSelectDropdownBackground(iconset, iconSetAndIcon[1], this); @@ -160,17 +211,179 @@ Polymer({ }, /** - * @return {string} The connection status text to display. + * @param {!Event} e * @private */ - computeStatusText_() { + onDropdownValueSelected_(e) { + assert(this.printerStatusFlagEnabled_); + + const selectedItem = e.detail; + if (!selectedItem || selectedItem.value === this.destination.key) { + return; + } + + this.fire('selected-option-change', selectedItem.value); + }, + + /** + * Send a printer status request for any new destination in the dropdown. + * @private + */ + onRecentDestinationListChanged_() { + if (!this.printerStatusFlagEnabled_) { + return; + } + + for (const destination of this.recentDestinationList) { + if (destination.origin !== DestinationOrigin.CROS || + this.statusRequestedMap_.has(destination.id)) { + continue; + } + + NativeLayerImpl.getInstance() + .requestPrinterStatusUpdate(destination.id) + .then(status => this.onPrinterStatusReceived_(status)); + this.statusRequestedMap_.set(destination.id, destination.key); + } + }, + + /** + * Check if the printer in |printerStatus| is currently in the dropdown. + * Update its status icon if it's present. + * @param {!PrinterStatus} printerStatus + * @private + */ + onPrinterStatusReceived_(printerStatus) { + assert(this.printerStatusFlagEnabled_); + if (!printerStatus.printerId) { + return; + } + + const destinationKey = + this.statusRequestedMap_.get(printerStatus.printerId); + if (!destinationKey) { + return; + } + + const indexFound = this.recentDestinationList.findIndex(destination => { + return destination.id === printerStatus.printerId && + destination.origin === DestinationOrigin.CROS; + }); + if (indexFound === -1) { + return; + } + + const statusReason = this.getStatusReasonFromPrinterStatus_(printerStatus); + if (!statusReason) { + return; + } + + + this.recentDestinationList[indexFound].printerStatusReason = statusReason; + // Set the new printer status reason then use notifyPath to trigger the + // dropdown printer status icons to recalculate their badge color. + this.notifyPath(`recentDestinationList.${indexFound}.printerStatusReason`); + + // Update the status text if this printer status is for the + // currently selected printer. + if (this.destination && this.destination.key === destinationKey) { + this.updateStatusText_(); + } + }, + + /** + * A |printerStatus| can have multiple status reasons so this function's + * responsibility is to determine which status reason is most relevant to + * surface to the user. Any status reason with a severity of WARNING or ERROR + * will get highest precedence since this usually means the printer is in a + * bad state. NO_ERROR status reason is the next highest precedence so the + * printer can be shown as available whenever possible. + * @param {!PrinterStatus} printerStatus + * @return {!PrinterStatusReason} Status reason extracted from + * |printerStatus|. + * @private + */ + getStatusReasonFromPrinterStatus_(printerStatus) { + assert(this.printerStatusFlagEnabled_); + + if (!printerStatus.printerId) { + return PrinterStatusReason.UNKNOWN_REASON; + } + + let seenNoErrorReason = false; + for (const statusReason of printerStatus.statusReasons) { + const reason = statusReason.reason; + const severity = statusReason.severity; + + if (reason !== PrinterStatusReason.UNKNOWN_REASON && + (severity === PrinterStatusSeverity.WARNING || + severity === PrinterStatusSeverity.ERROR)) { + return reason; + } + + if (reason === PrinterStatusReason.NO_ERROR) { + seenNoErrorReason = true; + } + } + return seenNoErrorReason ? PrinterStatusReason.NO_ERROR : + PrinterStatusReason.UNKNOWN_REASON; + }, + + /** + * Check the current destination for an error status then set |statusText_| + * appropriately. If no error status exists, unset |statusText_|. + * @private + */ + updateStatusText_: function() { // |destination| can be either undefined, or null here. if (!this.destination) { - return ''; + this.statusText_ = ''; + return; + } + + // Cloudprint destinations contain their own status text. + if (CloudOrigins.some(origin => origin === this.destination.origin)) { + this.statusText_ = this.destination.shouldShowInvalidCertificateError ? + this.i18n('noLongerSupportedFragment') : + this.destination.connectionStatusText; + return; } - return this.destination.shouldShowInvalidCertificateError ? - this.i18n('noLongerSupportedFragment') : - this.destination.connectionStatusText; + // Only when the flag is enabled do we need to fetch a local printer status + // error string. + if (!this.printerStatusFlagEnabled_) { + this.statusText_ = ''; + return; + } + + const printerStatusReason = this.destination.printerStatusReason; + if (!printerStatusReason || + printerStatusReason === PrinterStatusReason.NO_ERROR || + printerStatusReason === PrinterStatusReason.UNKNOWN_REASON) { + this.statusText_ = ''; + return; + } + + this.statusText_ = this.getErrorString_(printerStatusReason); + }, + + /** + * @param {!PrinterStatusReason} printerStatusReason + * @return {!string} + * @private + */ + getErrorString_: function(printerStatusReason) { + const errorTextKey = ERROR_STRING_KEY_MAP.get(printerStatusReason); + return errorTextKey ? + this.i18n(errorTextKey, this.destination.displayName) : + ''; + }, + + /** + * @return {!boolean} + * @private + */ + shouldShowStatus_: function() { + return !!this.statusText_; }, }); diff --git a/chromium/chrome/browser/resources/print_preview/ui/destination_settings.js b/chromium/chrome/browser/resources/print_preview/ui/destination_settings.js index f7259dbcfd0..4dce6653ae7 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/destination_settings.js +++ b/chromium/chrome/browser/resources/print_preview/ui/destination_settings.js @@ -66,7 +66,7 @@ Polymer({ value: null, }, - /** @private {!DestinationState} */ + /** @type {!DestinationState} */ destinationState: { type: Number, notify: true, @@ -527,6 +527,11 @@ Polymer({ }); }, + /** @return {!DestinationStore} */ + getDestinationStoreForTest() { + return assert(this.destinationStore_); + }, + // <if expr="chromeos"> /** * @param {!CustomEvent<string>} e Event containing the current destination's diff --git a/chromium/chrome/browser/resources/print_preview/ui/header.js b/chromium/chrome/browser/resources/print_preview/ui/header.js index 3848934ecb3..e441b03788d 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/header.js +++ b/chromium/chrome/browser/resources/print_preview/ui/header.js @@ -9,6 +9,7 @@ import './print_preview_vars_css.js'; import '../strings.m.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; +import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js'; import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {Destination} from '../data/destination.js'; @@ -40,12 +41,13 @@ Polymer({ sheetCount: Number, /** @private {?string} */ - summary_: { - type: String, - computed: 'computeSummary_(sheetCount, state, destination.id)', - }, + summary_: String, }, + observers: [ + 'updateSummary_(sheetCount, state, destination.id)', + ], + /** * @return {boolean} * @private @@ -56,21 +58,22 @@ Polymer({ this.destination.id === Destination.GooglePromotedId.DOCS); }, - /** - * @return {?string} - * @private - */ - computeSummary_() { + /** @private */ + updateSummary_() { switch (this.state) { case (State.PRINTING): - return loadTimeData.getString( + this.summary_ = loadTimeData.getString( this.isPdfOrDrive_() ? 'saving' : 'printing'); + break; case (State.READY): - return this.getSheetsSummary_(); + this.updateSheetsSummary_(); + break; case (State.FATAL_ERROR): - return this.getErrorMessage_(); + this.summary_ = this.getErrorMessage_(); + break; default: - return null; + this.summary_ = null; + break; } }, @@ -89,21 +92,19 @@ Polymer({ } }, - /** - * @return {string} - * @private - */ - getSheetsSummary_() { + /** @private */ + updateSheetsSummary_() { if (this.sheetCount === 0) { - return ''; + this.summary_ = ''; + return; } - const pageOrSheets = this.isPdfOrDrive_() ? 'Page' : 'Sheets'; - const singularOrPlural = this.sheetCount > 1 ? 'Plural' : 'Singular'; - const label = loadTimeData.getString( - `printPreview${pageOrSheets}Label${singularOrPlural}`); - return loadTimeData.getStringF( - 'printPreviewSummaryFormatShort', this.sheetCount.toLocaleString(), - label); + const pageOrSheet = this.isPdfOrDrive_() ? 'Page' : 'Sheet'; + PluralStringProxyImpl.getInstance() + .getPluralString( + `printPreview${pageOrSheet}SummaryLabel`, this.sheetCount) + .then(label => { + this.summary_ = label; + }); }, }); diff --git a/chromium/chrome/browser/resources/print_preview/ui/link_container.html b/chromium/chrome/browser/resources/print_preview/ui/link_container.html index 6ada90e22fa..9c483864f6c 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/link_container.html +++ b/chromium/chrome/browser/resources/print_preview/ui/link_container.html @@ -42,7 +42,7 @@ } .link:not([actionable]) .label { - @apply --print-preview-disabled-label; + opacity: var(--cr-disabled-opacity); } </style> <div class="link" id="systemDialogLink" diff --git a/chromium/chrome/browser/resources/print_preview/ui/more_settings.html b/chromium/chrome/browser/resources/print_preview/ui/more_settings.html index d63ce664e66..13c6d2c7678 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/more_settings.html +++ b/chromium/chrome/browser/resources/print_preview/ui/more_settings.html @@ -33,7 +33,7 @@ } :host([disabled]) #label { - @apply --print-preview-disabled-label; + opacity: var(--cr-disabled-opacity); } </style> <div on-click="toggleExpandButton_" actionable> diff --git a/chromium/chrome/browser/resources/print_preview/ui/preview_area.js b/chromium/chrome/browser/resources/print_preview/ui/preview_area.js index e75d9bffbf7..feb0ebf3849 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/preview_area.js +++ b/chromium/chrome/browser/resources/print_preview/ui/preview_area.js @@ -25,7 +25,7 @@ import {PrintableArea} from '../data/printable_area.js'; import {ScalingType} from '../data/scaling.js'; import {Size} from '../data/size.js'; import {Error, State} from '../data/state.js'; -import {NativeLayer} from '../native_layer.js'; +import {NativeLayer, NativeLayerImpl} from '../native_layer.js'; import {areRangesEqual} from '../print_preview_utils.js'; import {MARGIN_KEY_MAP} from './margin_control_container.js'; @@ -140,7 +140,7 @@ Polymer({ /** @override */ attached() { - this.nativeLayer_ = NativeLayer.getInstance(); + this.nativeLayer_ = NativeLayerImpl.getInstance(); this.addWebUIListener( 'page-preview-ready', this.onPagePreviewReady_.bind(this)); diff --git a/chromium/chrome/browser/resources/print_preview/ui/print_preview_search_box.html b/chromium/chrome/browser/resources/print_preview/ui/print_preview_search_box.html index 816b95996fe..6ecd94cd733 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/print_preview_search_box.html +++ b/chromium/chrome/browser/resources/print_preview/ui/print_preview_search_box.html @@ -50,7 +50,7 @@ on-search="onSearchTermSearch" on-input="onSearchTermInput" aria-label$="[[label]]" placeholder="[[label]]" autofocus="[[autofocus]]" spellcheck="false"> - <div slot="prefix" id="icon" class="cr-icon icon-search" alt=""></div> + <div slot="inline-prefix" id="icon" class="cr-icon icon-search" alt=""></div> <cr-icon-button id="clearSearch" class="icon-cancel" hidden$="[[!hasSearchText]]" slot="suffix" on-click="onClearClick_" title="[[clearLabel]]"> diff --git a/chromium/chrome/browser/resources/print_preview/ui/print_preview_vars_css.html b/chromium/chrome/browser/resources/print_preview/ui/print_preview_vars_css.html index d476823e7ec..2208774d52e 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/print_preview_vars_css.html +++ b/chromium/chrome/browser/resources/print_preview/ui/print_preview_vars_css.html @@ -8,10 +8,6 @@ --print-preview-settings-border: 1px solid var(--google-grey-200); --print-preview-dialog-margin: 34px; - --print-preview-disabled-label: { - color: var(--paper-grey-600); - opacity: .65; - } --cr-form-field-label-height: initial; --cr-form-field-label-line-height: .75rem; --destination-item-height: 32px; diff --git a/chromium/chrome/browser/resources/print_preview/ui/printer_status_icon_cros.html b/chromium/chrome/browser/resources/print_preview/ui/printer_status_icon_cros.html new file mode 100644 index 00000000000..450956a6557 --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/ui/printer_status_icon_cros.html @@ -0,0 +1,67 @@ +<style include="cr-shared-style"> + div { + display: inline; + position: relative; + } + + .badge { + border-radius: 50%; + display: inline-block; + position: absolute; + --status-badge-radius: 8px; + --background-badge-radius: 12px; + --background-badge-left: 12px; + --background-badge-top: 6px; + } + + :host-context([dir='rtl']) .badge { + --background-badge-left: -4px; + } + + #status-badge { + height: var(--status-badge-radius); + left: calc(var(--background-badge-left) + (var(--background-badge-radius) - var(--status-badge-radius))/2); + top: calc(var(--background-badge-top) + (var(--background-badge-radius) - var(--status-badge-radius))/2); + width: var(--status-badge-radius); + } + + :host-context([state='0']) #status-badge { + background-color: var(--google-green-700); + } + + :host-context([state='1']) #status-badge { + background-color: var(--google-red-600); + } + + :host-context([state='2']) #status-badge { + background-color: var(--google-grey-500); + } + + :host-context([dir='rtl']) #status-badge { + right: calc(var(--background-badge-left) + (var(--background-badge-radius) - var(--status-badge-radius))/2); + } + + #background-badge { + height: var(--background-badge-radius); + left: var(--background-badge-left); + top: var(--background-badge-top); + width: var(--background-badge-radius); + } + + :host-context([background='grey']) #background-badge { + background-color: var(--google-grey-refresh-100); + } + + :host-context([background='white']) #background-badge { + background-color: white; + } + + :host-context([dir='rtl']) #background-badge { + right: var(--background-badge-left); + } +</style> +<div> + <iron-icon icon="print-preview:print"></iron-icon> + <span id="background-badge" class="badge"></span> + <span id="status-badge" class="badge"></span> +</div> diff --git a/chromium/chrome/browser/resources/print_preview/ui/printer_status_icon_cros.js b/chromium/chrome/browser/resources/print_preview/ui/printer_status_icon_cros.js new file mode 100644 index 00000000000..3ae2dc8a7de --- /dev/null +++ b/chromium/chrome/browser/resources/print_preview/ui/printer_status_icon_cros.js @@ -0,0 +1,40 @@ +// Copyright 2020 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. + +import 'chrome://resources/cr_elements/shared_vars_css.m.js'; +import './icons.js'; + +import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; + +/** + * Enumeration giving a local Chrome OS printer 3 different state possibilities + * depending on its current status. + * @enum {number} + */ +export const PrinterState = { + GOOD: 0, + ERROR: 1, + UNKNOWN: 2, +}; + +Polymer({ + is: 'printer-status-icon-cros', + + properties: { + /** Determines color of the background badge. */ + background: String, + + /** + * State of the associated printer. Determines color of the status badge. + * @type {!PrinterState} + */ + state: { + type: Number, + reflectToAttribute: true, + } + }, + + _template: html`{__html_template__}`, + +}); diff --git a/chromium/chrome/browser/resources/print_preview/ui/select_behavior.js b/chromium/chrome/browser/resources/print_preview/ui/select_behavior.js index 4e21bb4d903..fbc6df96b58 100644 --- a/chromium/chrome/browser/resources/print_preview/ui/select_behavior.js +++ b/chromium/chrome/browser/resources/print_preview/ui/select_behavior.js @@ -31,10 +31,11 @@ export const SelectBehavior = { } this.debounce('select-change', () => { - this.onProcessSelectChange(this.selectedValue); - - // For testing only - this.fire('process-select-change'); + if (this.isConnected) { + this.onProcessSelectChange(this.selectedValue); + // For testing only + this.fire('process-select-change'); + } }, 100); }, |