Commit ae42d116 authored by rbpotter's avatar rbpotter Committed by Commit Bot

Print Preview: Show an error when there are no printers

In kiosk app mode with no printers configured the preview spins forever
because in kiosk mode we cannot fall back to Save as PDF. This
currently only occurs on Chrome OS, as other platforms do not have app
kiosk mode. Handle this case gracefully by notifying the user that
there are no printers available.

Also update the change button to the old behavior of disabled during
load, since there should now be no case where the preview and
destinations settings show spinners indefinitely.

See the linked bug comment 9 for screenshot.

Bug: 903680
Change-Id: I2c3e9a5b41520176cd8fe170886d97e317df05d3
Reviewed-on: https://chromium-review.googlesource.com/c/1344836
Commit-Queue: Rebekah Potter <rbpotter@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#611528}
parent 72e42f65
...@@ -1170,6 +1170,7 @@ cr.define('print_preview', function() { ...@@ -1170,6 +1170,7 @@ cr.define('print_preview', function() {
this, DestinationStore.EventType.DESTINATION_SEARCH_DONE); this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
if (type === print_preview.PrinterType.EXTENSION_PRINTER) if (type === print_preview.PrinterType.EXTENSION_PRINTER)
this.endExtensionPrinterSearch_(); this.endExtensionPrinterSearch_();
this.sendNoPrinterEventIfNeeded_();
} }
/** /**
...@@ -1270,6 +1271,23 @@ cr.define('print_preview', function() { ...@@ -1270,6 +1271,23 @@ cr.define('print_preview', function() {
} }
cr.dispatchSimpleEvent( cr.dispatchSimpleEvent(
this, DestinationStore.EventType.DESTINATION_SEARCH_DONE); this, DestinationStore.EventType.DESTINATION_SEARCH_DONE);
this.sendNoPrinterEventIfNeeded_();
}
/**
* Checks if the search is done and no printers are found. If so, fires a
* DestinationStore.EventType.NO_DESTINATIONS_FOUND event.
* @private
*/
sendNoPrinterEventIfNeeded_() {
if (this.isPrintDestinationSearchInProgress ||
!this.selectFirstDestination_) {
return;
}
this.selectFirstDestination_ = false;
cr.dispatchSimpleEvent(
this, DestinationStore.EventType.NO_DESTINATIONS_FOUND);
} }
/** /**
...@@ -1397,6 +1415,8 @@ cr.define('print_preview', function() { ...@@ -1397,6 +1415,8 @@ cr.define('print_preview', function() {
'print_preview.DestinationStore.PROVISIONAL_DESTINATION_RESOLVED', 'print_preview.DestinationStore.PROVISIONAL_DESTINATION_RESOLVED',
CACHED_SELECTED_DESTINATION_INFO_READY: CACHED_SELECTED_DESTINATION_INFO_READY:
'print_preview.DestinationStore.CACHED_SELECTED_DESTINATION_INFO_READY', 'print_preview.DestinationStore.CACHED_SELECTED_DESTINATION_INFO_READY',
NO_DESTINATIONS_FOUND:
'print_preview.DestinationStore.NO_DESTINATIONS_FOUND',
SELECTED_DESTINATION_CAPABILITIES_READY: 'print_preview.DestinationStore' + SELECTED_DESTINATION_CAPABILITIES_READY: 'print_preview.DestinationStore' +
'.SELECTED_DESTINATION_CAPABILITIES_READY', '.SELECTED_DESTINATION_CAPABILITIES_READY',
SELECTED_DESTINATION_INVALID: SELECTED_DESTINATION_INVALID:
......
...@@ -194,6 +194,12 @@ Polymer({ ...@@ -194,6 +194,12 @@ Polymer({
this.destinationStore_, this.destinationStore_,
print_preview.DestinationStore.EventType.DESTINATION_SELECT, print_preview.DestinationStore.EventType.DESTINATION_SELECT,
this.onDestinationSelect_.bind(this)); this.onDestinationSelect_.bind(this));
// <if expr="chromeos">
this.tracker_.add(
this.destinationStore_,
print_preview.DestinationStore.EventType.NO_DESTINATIONS_FOUND,
this.onNoDestinationsFound_.bind(this));
// </if>
this.tracker_.add( this.tracker_.add(
this.destinationStore_, this.destinationStore_,
print_preview.DestinationStore.EventType print_preview.DestinationStore.EventType
...@@ -656,6 +662,15 @@ Polymer({ ...@@ -656,6 +662,15 @@ Polymer({
return this.settingsExpandedByUser_ || !this.shouldShowMoreSettings_; return this.settingsExpandedByUser_ || !this.shouldShowMoreSettings_;
}, },
// <if expr="chromeos">
/** @private */
onNoDestinationsFound_: function() {
this.$.state.transitTo(print_preview_new.State.INVALID_PRINTER);
this.$.previewArea.setNoDestinationsFound();
this.$.destinationSettings.noDestinationsFound = true;
},
// </if>
/** @private */ /** @private */
close_: function() { close_: function() {
this.$.state.transitTo(print_preview_new.State.CLOSING); this.$.state.transitTo(print_preview_new.State.CLOSING);
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
<link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html"> <link rel="import" href="chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.html">
<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
<link rel="import" href="chrome://resources/cr_elements/icons.html">
<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html"> <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
<link rel="import" href="chrome://resources/html/event_tracker.html"> <link rel="import" href="chrome://resources/html/event_tracker.html">
...@@ -34,13 +35,15 @@ ...@@ -34,13 +35,15 @@
} }
.destination-settings-box, .destination-settings-box,
.no-destinations-display,
.throbber-container { .throbber-container {
align-items: center; align-items: center;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
} }
.destination-settings-box iron-icon { .destination-settings-box iron-icon,
.no-destinations-display iron-icon {
fill: var(--google-grey-600); fill: var(--google-grey-600);
flex: 0; flex: 0;
margin-inline-end: 8px; margin-inline-end: 8px;
...@@ -63,7 +66,8 @@ ...@@ -63,7 +66,8 @@
} }
.destination-info-wrapper > div, .destination-info-wrapper > div,
.destination-throbber-name { .destination-throbber-name,
.no-destinations-message {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
...@@ -77,10 +81,18 @@ ...@@ -77,10 +81,18 @@
<print-preview-settings-section> <print-preview-settings-section>
<span slot="title">$i18n{destinationLabel}</span> <span slot="title">$i18n{destinationLabel}</span>
<div slot="controls"> <div slot="controls">
<div class="throbber-container" hidden="[[!loadingDestination_]]"> <div class="throbber-container"
hidden="[[!shouldShowSpinner_(
loadingDestination_, noDestinationsFound)]]">
<div class="throbber"></div> <div class="throbber"></div>
<div class="destination-throbber-name"></div> <div class="destination-throbber-name"></div>
</div> </div>
<div class="no-destinations-display" hidden="[[!noDestinationsFound]]">
<iron-icon icon="cr:error"></iron-icon>
<div class="no-destinations-message">
$i18n{noDestinationsMessage}
</div>
</div>
<div class="destination-settings-box" hidden="[[loadingDestination_]]"> <div class="destination-settings-box" hidden="[[loadingDestination_]]">
<iron-icon icon$="[[destination.icon]]" hidden="[[!destination]]"> <iron-icon icon$="[[destination.icon]]" hidden="[[!destination]]">
</iron-icon> </iron-icon>
...@@ -100,7 +112,7 @@ ...@@ -100,7 +112,7 @@
<div slot="controls"> <div slot="controls">
<paper-button <paper-button
disabled$="[[shouldDisableButton_(destinationStore, disabled, disabled$="[[shouldDisableButton_(destinationStore, disabled,
state)]]" state, noDestinationsFound)]]"
on-click="onChangeButtonClick_"> on-click="onChangeButtonClick_">
$i18n{changeDestination} $i18n{changeDestination}
</paper-button> </paper-button>
......
...@@ -31,6 +31,11 @@ Polymer({ ...@@ -31,6 +31,11 @@ Polymer({
/** @type {!print_preview_new.State} */ /** @type {!print_preview_new.State} */
state: Number, state: Number,
noDestinationsFound: {
type: Boolean,
value: false,
},
/** @private {boolean} */ /** @private {boolean} */
showCloudPrintPromo_: { showCloudPrintPromo_: {
type: Boolean, type: Boolean,
...@@ -58,8 +63,8 @@ Polymer({ ...@@ -58,8 +63,8 @@ Polymer({
* @private * @private
*/ */
shouldDisableButton_: function() { shouldDisableButton_: function() {
return !this.destinationStore || return !this.destinationStore || this.noDestinationsFound ||
(this.disabled && this.state != print_preview_new.State.NOT_READY && (this.disabled &&
this.state != print_preview_new.State.INVALID_PRINTER); this.state != print_preview_new.State.INVALID_PRINTER);
}, },
...@@ -68,6 +73,14 @@ Polymer({ ...@@ -68,6 +73,14 @@ Polymer({
this.loadingDestination_ = !this.destination || !this.destination.id; this.loadingDestination_ = !this.destination || !this.destination.id;
}, },
/**
* @return {boolean} Whether to show the spinner.
* @private
*/
shouldShowSpinner_: function() {
return this.loadingDestination_ && !this.noDestinationsFound;
},
/** /**
* @return {string} The connection status text to display. * @return {string} The connection status text to display.
* @private * @private
......
...@@ -22,6 +22,9 @@ print_preview_new.PreviewAreaState = { ...@@ -22,6 +22,9 @@ print_preview_new.PreviewAreaState = {
INVALID_SETTINGS: 'invalid-settings', INVALID_SETTINGS: 'invalid-settings',
PREVIEW_FAILED: 'preview-failed', PREVIEW_FAILED: 'preview-failed',
UNSUPPORTED_CLOUD_PRINTER: 'unsupported-cloud-printer', UNSUPPORTED_CLOUD_PRINTER: 'unsupported-cloud-printer',
// <if expr="chromeos">
NO_DESTINATIONS_FOUND: 'no-destinations-found',
// </if>
}; };
Polymer({ Polymer({
...@@ -260,15 +263,15 @@ Polymer({ ...@@ -260,15 +263,15 @@ Polymer({
currentMessage_: function() { currentMessage_: function() {
switch (this.previewState) { switch (this.previewState) {
case print_preview_new.PreviewAreaState.NO_PLUGIN: case print_preview_new.PreviewAreaState.NO_PLUGIN:
return this.i18nAdvanced('noPlugin'); return this.i18n('noPlugin');
case print_preview_new.PreviewAreaState.LOADING: case print_preview_new.PreviewAreaState.LOADING:
return this.i18nAdvanced('loading'); return this.i18n('loading');
case print_preview_new.PreviewAreaState.DISPLAY_PREVIEW: case print_preview_new.PreviewAreaState.DISPLAY_PREVIEW:
return ''; return '';
// <if expr="is_macosx"> // <if expr="is_macosx">
case print_preview_new.PreviewAreaState.OPEN_IN_PREVIEW_LOADING: case print_preview_new.PreviewAreaState.OPEN_IN_PREVIEW_LOADING:
case print_preview_new.PreviewAreaState.OPEN_IN_PREVIEW_LOADED: case print_preview_new.PreviewAreaState.OPEN_IN_PREVIEW_LOADED:
return this.i18nAdvanced('openingPDFInPreview'); return this.i18n('openingPDFInPreview');
// </if> // </if>
case print_preview_new.PreviewAreaState.INVALID_SETTINGS: case print_preview_new.PreviewAreaState.INVALID_SETTINGS:
return this.i18nAdvanced('invalidPrinterSettings', { return this.i18nAdvanced('invalidPrinterSettings', {
...@@ -276,12 +279,16 @@ Polymer({ ...@@ -276,12 +279,16 @@ Polymer({
tags: ['BR'], tags: ['BR'],
}); });
case print_preview_new.PreviewAreaState.PREVIEW_FAILED: case print_preview_new.PreviewAreaState.PREVIEW_FAILED:
return this.i18nAdvanced('previewFailed'); return this.i18n('previewFailed');
case print_preview_new.PreviewAreaState.UNSUPPORTED_CLOUD_PRINTER: case print_preview_new.PreviewAreaState.UNSUPPORTED_CLOUD_PRINTER:
return this.i18nAdvanced('unsupportedCloudPrinter', { return this.i18nAdvanced('unsupportedCloudPrinter', {
substitutions: [], substitutions: [],
tags: ['BR'], tags: ['BR'],
}); });
// <if expr="chromeos">
case print_preview_new.PreviewAreaState.NO_DESTINATIONS_FOUND:
return this.i18n('noDestinationsMessage');
// </if>
default: default:
return ''; return '';
} }
...@@ -317,6 +324,13 @@ Polymer({ ...@@ -317,6 +324,13 @@ Polymer({
} }
}, },
// <if expr="chromeos">
setNoDestinationsFound: function() {
this.previewState =
print_preview_new.PreviewAreaState.NO_DESTINATIONS_FOUND;
},
// </if>
// <if expr="is_macosx"> // <if expr="is_macosx">
/** Set the preview state to display the "opening in preview" message. */ /** Set the preview state to display the "opening in preview" message. */
setOpeningPdfInPreview: function() { setOpeningPdfInPreview: function() {
......
...@@ -12,6 +12,7 @@ cr.define('destination_select_test', function() { ...@@ -12,6 +12,7 @@ cr.define('destination_select_test', function() {
DefaultDestinationSelectionRules: 'default destination selection rules', DefaultDestinationSelectionRules: 'default destination selection rules',
SystemDefaultPrinterPolicy: 'system default printer policy', SystemDefaultPrinterPolicy: 'system default printer policy',
KioskModeSelectsFirstPrinter: 'kiosk mode selects first printer', KioskModeSelectsFirstPrinter: 'kiosk mode selects first printer',
NoPrintersShowsError: 'no printers shows error',
}; };
const suiteName = 'DestinationSelectTests'; const suiteName = 'DestinationSelectTests';
...@@ -42,10 +43,13 @@ cr.define('destination_select_test', function() { ...@@ -42,10 +43,13 @@ cr.define('destination_select_test', function() {
/* /*
* Sets the initial settings to the stored value and creates the page. * Sets the initial settings to the stored value and creates the page.
* @return {!Promise} Promise that resolves when initial settings and * @param {boolean=} opt_expectPrinterFailure Whether printer fetch is
* printer capabilities have been returned. * expected to fail
* @return {!Promise} Promise that resolves when initial settings and,
* if printer failure is not expected, printer capabilities have
* been returned.
*/ */
function setInitialSettings() { function setInitialSettings(opt_expectPrinterFailure) {
nativeLayer.setInitialSettings(initialSettings); nativeLayer.setInitialSettings(initialSettings);
nativeLayer.setLocalDestinations(localDestinations); nativeLayer.setLocalDestinations(localDestinations);
print_preview.NativeLayer.setInstance(nativeLayer); print_preview.NativeLayer.setInstance(nativeLayer);
...@@ -53,10 +57,10 @@ cr.define('destination_select_test', function() { ...@@ -53,10 +57,10 @@ cr.define('destination_select_test', function() {
page = document.createElement('print-preview-app'); page = document.createElement('print-preview-app');
document.body.appendChild(page); document.body.appendChild(page);
return Promise.all([ const promises = [nativeLayer.whenCalled('getInitialSettings')];
nativeLayer.whenCalled('getInitialSettings'), if (!opt_expectPrinterFailure)
nativeLayer.whenCalled('getPrinterCapabilities') promises.push(nativeLayer.whenCalled('getPrinterCapabilities'));
]); return Promise.all(promises);
} }
/** /**
...@@ -247,6 +251,38 @@ cr.define('destination_select_test', function() { ...@@ -247,6 +251,38 @@ cr.define('destination_select_test', function() {
assertPrinterDisplay(destinations[0].displayName); assertPrinterDisplay(destinations[0].displayName);
}); });
}); });
/**
* Tests that if there is no system default destination, the default
* selection rules and recent destinations are empty, the preview
* is in app kiosk mode (so no PDF printer), and there are no
* destinations found, the no destinations found error is displayed.
*/
test(assert(TestNames.NoPrintersShowsError), function() {
initialSettings.serializedDefaultDestinationSelectionRulesStr = '';
initialSettings.serializedAppStateStr = '';
initialSettings.isInAppKioskMode = true;
initialSettings.printerName = '';
localDestinations = [];
return Promise
.all([
setInitialSettings(true),
test_util.eventToPromise(
print_preview.DestinationStore.EventType.NO_DESTINATIONS_FOUND,
page.destinationStore_),
])
.then(function() {
assertEquals(undefined, page.destination_);
const destinationSettings =
page.$$('print-preview-destination-settings');
assertTrue(destinationSettings.$$('.throbber-container').hidden);
assertTrue(
destinationSettings.$$('.destination-settings-box').hidden);
assertFalse(
destinationSettings.$$('.no-destinations-display').hidden);
});
});
}); });
return { return {
......
...@@ -20,9 +20,10 @@ cr.define('destination_settings_test', function() { ...@@ -20,9 +20,10 @@ cr.define('destination_settings_test', function() {
print_preview.NativeLayer.setInstance(nativeLayer); print_preview.NativeLayer.setInstance(nativeLayer);
destinationSettings = destinationSettings =
document.createElement('print-preview-destination-settings'); document.createElement('print-preview-destination-settings');
destinationSettings.disabled = false;
destinationSettings.destinationStore = null; destinationSettings.destinationStore = null;
destinationSettings.state = print_preview_new.State.NOT_READY; destinationSettings.state = print_preview_new.State.NOT_READY;
// Disabled is true when state is NOT_READY.
destinationSettings.disabled = true;
document.body.appendChild(destinationSettings); document.body.appendChild(destinationSettings);
}); });
...@@ -33,8 +34,8 @@ cr.define('destination_settings_test', function() { ...@@ -33,8 +34,8 @@ cr.define('destination_settings_test', function() {
// Initial state: No destination store, button should be disabled. // Initial state: No destination store, button should be disabled.
assertTrue(button.disabled); assertTrue(button.disabled);
// Set up the destination store, but no destination yet. Button is now // Set up the destination store, but no destination yet. Button is
// enabled. // disabled.
const userInfo = new print_preview.UserInfo(); const userInfo = new print_preview.UserInfo();
const destinationStore = new print_preview.DestinationStore( const destinationStore = new print_preview.DestinationStore(
userInfo, new WebUIListenerTracker()); userInfo, new WebUIListenerTracker());
...@@ -44,7 +45,7 @@ cr.define('destination_settings_test', function() { ...@@ -44,7 +45,7 @@ cr.define('destination_settings_test', function() {
[] /* recentDestinations */); [] /* recentDestinations */);
destinationSettings.destinationStore = destinationStore; destinationSettings.destinationStore = destinationStore;
destinationSettings.state = print_preview_new.State.NOT_READY; destinationSettings.state = print_preview_new.State.NOT_READY;
assertFalse(button.disabled); assertTrue(button.disabled);
// Simulate loading a destination and setting state to ready. The button // Simulate loading a destination and setting state to ready. The button
// is still enabled. // is still enabled.
...@@ -53,6 +54,7 @@ cr.define('destination_settings_test', function() { ...@@ -53,6 +54,7 @@ cr.define('destination_settings_test', function() {
print_preview.DestinationOrigin.LOCAL, 'FooName', true /* isRecent */, print_preview.DestinationOrigin.LOCAL, 'FooName', true /* isRecent */,
print_preview.DestinationConnectionStatus.ONLINE); print_preview.DestinationConnectionStatus.ONLINE);
destinationSettings.state = print_preview_new.State.READY; destinationSettings.state = print_preview_new.State.READY;
destinationSettings.disabled = false;
assertFalse(button.disabled); assertFalse(button.disabled);
// Simulate setting a setting to an invalid value. Button is disabled due // Simulate setting a setting to an invalid value. Button is disabled due
...@@ -68,6 +70,13 @@ cr.define('destination_settings_test', function() { ...@@ -68,6 +70,13 @@ cr.define('destination_settings_test', function() {
destinationSettings.state = print_preview_new.State.INVALID_PRINTER; destinationSettings.state = print_preview_new.State.INVALID_PRINTER;
destinationSettings.disabled = true; destinationSettings.disabled = true;
assertFalse(button.disabled); assertFalse(button.disabled);
// Simulate the user having no printers.
destinationSettings.destination = null;
destinationSettings.state = print_preview_new.State.INVALID_PRINTER;
destinationSettings.disabled = true;
destinationSettings.noDestinationsFound = true;
assertTrue(button.disabled);
}); });
}); });
......
...@@ -87,7 +87,8 @@ cr.define('print_preview', function() { ...@@ -87,7 +87,8 @@ cr.define('print_preview', function() {
/** @override */ /** @override */
getPrinters(type) { getPrinters(type) {
this.methodCalled('getPrinters', type); this.methodCalled('getPrinters', type);
if (type == print_preview.PrinterType.LOCAL_PRINTER) { if (type == print_preview.PrinterType.LOCAL_PRINTER &&
this.localDestinationInfos_.length > 0) {
cr.webUIListenerCallback( cr.webUIListenerCallback(
'printers-added', type, this.localDestinationInfos_); 'printers-added', type, this.localDestinationInfos_);
} else if ( } else if (
......
...@@ -618,6 +618,7 @@ PrintPreviewDestinationSelectTest = class extends NewPrintPreviewTest { ...@@ -618,6 +618,7 @@ PrintPreviewDestinationSelectTest = class extends NewPrintPreviewTest {
/** @override */ /** @override */
get extraLibraries() { get extraLibraries() {
return super.extraLibraries.concat([ return super.extraLibraries.concat([
'../settings/test_util.js',
'../test_browser_proxy.js', '../test_browser_proxy.js',
'native_layer_stub.js', 'native_layer_stub.js',
'print_preview_test_utils.js', 'print_preview_test_utils.js',
...@@ -675,6 +676,12 @@ TEST_F( ...@@ -675,6 +676,12 @@ TEST_F(
destination_select_test.TestNames.KioskModeSelectsFirstPrinter); destination_select_test.TestNames.KioskModeSelectsFirstPrinter);
}); });
GEN('#if defined(OS_CHROMEOS)');
TEST_F('PrintPreviewDestinationSelectTest', 'NoPrintersShowsError', function() {
this.runMochaTest(destination_select_test.TestNames.NoPrintersShowsError);
});
GEN('#endif');
PrintPreviewDestinationDialogTest = class extends NewPrintPreviewTest { PrintPreviewDestinationDialogTest = class extends NewPrintPreviewTest {
/** @override */ /** @override */
get browsePreload() { get browsePreload() {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment