Bug 1660665, hide subdialog if user chooses to print using system dialog. r=jwatt,Gijs,mstriemer
When the user chooses to print using the system dialog, we should hide the print UI. We are choosing to hide the dialog stack instead of closing the dialog because the print preview browser still needs to be available if the user tries to print. We close the window if the user cancels the system dialog or once we receive the promise from PrintUtils.printWindow. Differential Revision: https://phabricator.services.mozilla.com/D88096
This commit is contained in:
@@ -9045,6 +9045,10 @@ class TabDialogBox {
|
|||||||
}
|
}
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getManager() {
|
||||||
|
return this._dialogManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([
|
TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||||
|
|||||||
@@ -105,3 +105,52 @@ add_task(async function test_tabdialogbox_close_on_content_nav() {
|
|||||||
ok(true, "Dialog should close for cross origin navigation by the content.");
|
ok(true, "Dialog should close for cross origin navigation by the content.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides a dialog stack and tests that behavior doesn't change. Ensures
|
||||||
|
* navigation triggered by web content still closes all dialogs.
|
||||||
|
*/
|
||||||
|
add_task(async function test_tabdialogbox_hide() {
|
||||||
|
await BrowserTestUtils.withNewTab("https://example.com", async function(
|
||||||
|
browser
|
||||||
|
) {
|
||||||
|
// Open a dialog and wait for it to be ready
|
||||||
|
let dialogBox = gBrowser.getTabDialogBox(browser);
|
||||||
|
let dialogBoxManager = dialogBox.getManager();
|
||||||
|
let closedPromises = [
|
||||||
|
dialogBox.open(TEST_DIALOG_PATH),
|
||||||
|
dialogBox.open(TEST_DIALOG_PATH),
|
||||||
|
];
|
||||||
|
|
||||||
|
let dialogs = dialogBox._dialogManager._dialogs;
|
||||||
|
|
||||||
|
is(
|
||||||
|
dialogBox._dialogManager._dialogs.length,
|
||||||
|
2,
|
||||||
|
"Dialog manager has two dialogs."
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Waiting for dialogs to open.");
|
||||||
|
await Promise.all(dialogs.map(dialog => dialog._dialogReady));
|
||||||
|
|
||||||
|
is(dialogBoxManager._dialogStack.hidden, false, "Dialog stack is showing");
|
||||||
|
|
||||||
|
dialogBoxManager.hideDialog(browser);
|
||||||
|
|
||||||
|
is(
|
||||||
|
dialogBoxManager._dialogs.length,
|
||||||
|
2,
|
||||||
|
"Dialog manager still has two dialogs."
|
||||||
|
);
|
||||||
|
|
||||||
|
is(dialogBoxManager._dialogStack.hidden, true, "Dialog stack is hidden");
|
||||||
|
|
||||||
|
// Navigate to a different page
|
||||||
|
BrowserTestUtils.loadURI(browser, "https://example.org");
|
||||||
|
|
||||||
|
info("Waiting for dialogs to close.");
|
||||||
|
await closedPromises;
|
||||||
|
|
||||||
|
ok(true, "All open dialogs should still close on navigation");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ const TEST_DIALOG_PATH = TEST_ROOT_CHROME + "subdialog.xhtml";
|
|||||||
/**
|
/**
|
||||||
* Tests that tab dialogs are focused when switching tabs.
|
* Tests that tab dialogs are focused when switching tabs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
add_task(async function test_tabdialogbox_tab_switch_focus() {
|
add_task(async function test_tabdialogbox_tab_switch_focus() {
|
||||||
// Open 3 tabs
|
// Open 3 tabs
|
||||||
let tabPromises = [];
|
let tabPromises = [];
|
||||||
@@ -74,3 +73,56 @@ add_task(async function test_tabdialogbox_tab_switch_focus() {
|
|||||||
BrowserTestUtils.removeTab(tab);
|
BrowserTestUtils.removeTab(tab);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that other dialogs are still visible if one dialog is hidden.
|
||||||
|
*/
|
||||||
|
add_task(async function test_tabdialogbox_tab_switch_hidden() {
|
||||||
|
// Open 2 tabs
|
||||||
|
let tabPromises = [];
|
||||||
|
for (let i = 0; i < 2; i += 1) {
|
||||||
|
tabPromises.push(
|
||||||
|
BrowserTestUtils.openNewForegroundTab(
|
||||||
|
gBrowser,
|
||||||
|
"http://example.com",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for tabs to be ready
|
||||||
|
let tabs = await Promise.all(tabPromises);
|
||||||
|
|
||||||
|
// Open subdialog in tabs
|
||||||
|
let dialogs = [];
|
||||||
|
let dialogBox, dialogBoxManager, browser;
|
||||||
|
for (let i = 0; i < 2; i += 1) {
|
||||||
|
dialogBox = gBrowser.getTabDialogBox(tabs[i].linkedBrowser);
|
||||||
|
browser = tabs[i].linkedBrowser;
|
||||||
|
dialogBox.open(TEST_DIALOG_PATH);
|
||||||
|
dialogBoxManager = dialogBox.getManager();
|
||||||
|
dialogs.push(dialogBoxManager._topDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for dialogs to be ready
|
||||||
|
await Promise.all([dialogs[0]._dialogReady, dialogs[1]._dialogReady]);
|
||||||
|
|
||||||
|
// Hide the top dialog
|
||||||
|
dialogBoxManager.hideDialog(browser);
|
||||||
|
|
||||||
|
is(dialogBoxManager._dialogStack.hidden, true, "Dialog stack is hidden");
|
||||||
|
|
||||||
|
// Switch to first tab
|
||||||
|
await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
|
||||||
|
|
||||||
|
// Check the dialog stack is showing in first tab
|
||||||
|
dialogBoxManager = gBrowser
|
||||||
|
.getTabDialogBox(tabs[0].linkedBrowser)
|
||||||
|
.getManager();
|
||||||
|
is(dialogBoxManager._dialogStack.hidden, false, "Dialog stack is showing");
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
BrowserTestUtils.removeTab(tab);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -198,11 +198,22 @@ var PrintEventHandler = {
|
|||||||
// This file in only used if pref print.always_print_silent is false, so
|
// This file in only used if pref print.always_print_silent is false, so
|
||||||
// no need to check that here.
|
// no need to check that here.
|
||||||
|
|
||||||
|
if (document.body.getAttribute("rendering")) {
|
||||||
// Disable elements of form while waiting to initialize
|
// Disable elements of form while waiting to initialize
|
||||||
for (let element of document.querySelector("#print").elements) {
|
for (let element of document.querySelector("#print").elements) {
|
||||||
element.disabled = true;
|
element.disabled = true;
|
||||||
}
|
}
|
||||||
await window._initialized;
|
await window._initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the dialog box before opening system dialog
|
||||||
|
// We cannot close the window yet because the browsing context for the
|
||||||
|
// print preview browser is needed to print the page.
|
||||||
|
let sourceBrowser = this.getSourceBrowsingContext().top.embedderElement;
|
||||||
|
let dialogBoxManager = gBrowser
|
||||||
|
.getTabDialogBox(sourceBrowser)
|
||||||
|
.getManager();
|
||||||
|
dialogBoxManager.hideDialog(sourceBrowser);
|
||||||
|
|
||||||
// Use our settings to prepopulate the system dialog.
|
// Use our settings to prepopulate the system dialog.
|
||||||
// The system print dialog won't recognize our internal save-to-pdf
|
// The system print dialog won't recognize our internal save-to-pdf
|
||||||
@@ -225,7 +236,7 @@ var PrintEventHandler = {
|
|||||||
"printing.dialog_opened_via_preview_tm",
|
"printing.dialog_opened_via_preview_tm",
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
PRINTPROMPTSVC.showPrintDialog(window, settings);
|
await this._showPrintDialog(PRINTPROMPTSVC, window, settings);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.result == Cr.NS_ERROR_ABORT) {
|
if (e.result == Cr.NS_ERROR_ABORT) {
|
||||||
Services.telemetry.scalarAdd(
|
Services.telemetry.scalarAdd(
|
||||||
@@ -237,7 +248,7 @@ var PrintEventHandler = {
|
|||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
this.print(settings);
|
await this.print(settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.refreshSettings(selectedPrinter.value);
|
await this.refreshSettings(selectedPrinter.value);
|
||||||
@@ -414,10 +425,7 @@ var PrintEventHandler = {
|
|||||||
try {
|
try {
|
||||||
this.settings.showPrintProgress = true;
|
this.settings.showPrintProgress = true;
|
||||||
let bc = this.previewBrowser.browsingContext;
|
let bc = this.previewBrowser.browsingContext;
|
||||||
await bc.top.embedderElement.print(
|
await this._doPrint(bc, settings);
|
||||||
bc.currentWindowGlobal.outerWindowId,
|
|
||||||
settings
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Cu.reportError(e);
|
Cu.reportError(e);
|
||||||
}
|
}
|
||||||
@@ -706,6 +714,26 @@ var PrintEventHandler = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the window. This method has been abstracted into a helper for
|
||||||
|
* testing purposes.
|
||||||
|
*/
|
||||||
|
_doPrint(aBrowsingContext, aSettings) {
|
||||||
|
return aBrowsingContext.top.embedderElement.print(
|
||||||
|
aBrowsingContext.currentWindowGlobal.outerWindowId,
|
||||||
|
aSettings
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the system dialog. This method has been abstracted into a helper for
|
||||||
|
* testing purposes. The showPrintDialog() call blocks until the dialog is
|
||||||
|
* closed, so we mark it as async to allow us to reject from the test.
|
||||||
|
*/
|
||||||
|
async _showPrintDialog(aPrintingPromptService, aWindow, aSettings) {
|
||||||
|
return aPrintingPromptService.showPrintDialog(aWindow, aSettings);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var PrintSettingsViewProxy = {
|
var PrintSettingsViewProxy = {
|
||||||
|
|||||||
@@ -34,3 +34,5 @@ support-files =
|
|||||||
|
|
||||||
[browser_preview_switch_print_selected.js]
|
[browser_preview_switch_print_selected.js]
|
||||||
skip-if = os == "mac" || (verify && !debug && (os == 'linux'))
|
skip-if = os == "mac" || (verify && !debug && (os == 'linux'))
|
||||||
|
|
||||||
|
[browser_system_dialog_subdialog_hidden.js]
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ function assertExpectedPrintPage(helper) {
|
|||||||
|
|
||||||
add_task(async function testModalPrintDialog() {
|
add_task(async function testModalPrintDialog() {
|
||||||
await PrintHelper.withTestPage(async helper => {
|
await PrintHelper.withTestPage(async helper => {
|
||||||
helper.assertDialogHidden();
|
helper.assertDialogClosed();
|
||||||
|
|
||||||
await helper.startPrint();
|
await helper.startPrint();
|
||||||
|
|
||||||
helper.assertDialogVisible();
|
helper.assertDialogOpen();
|
||||||
|
|
||||||
// Check that we're printing the right page.
|
// Check that we're printing the right page.
|
||||||
assertExpectedPrintPage(helper);
|
assertExpectedPrintPage(helper);
|
||||||
@@ -26,26 +26,26 @@ add_task(async function testModalPrintDialog() {
|
|||||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, helper.win);
|
EventUtils.synthesizeKey("VK_ESCAPE", {}, helper.win);
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.assertDialogHidden();
|
helper.assertDialogClosed();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function testPrintMultiple() {
|
add_task(async function testPrintMultiple() {
|
||||||
await PrintHelper.withTestPage(async helper => {
|
await PrintHelper.withTestPage(async helper => {
|
||||||
helper.assertDialogHidden();
|
helper.assertDialogClosed();
|
||||||
|
|
||||||
// First print as usual.
|
// First print as usual.
|
||||||
await helper.startPrint();
|
await helper.startPrint();
|
||||||
helper.assertDialogVisible();
|
helper.assertDialogOpen();
|
||||||
assertExpectedPrintPage(helper);
|
assertExpectedPrintPage(helper);
|
||||||
|
|
||||||
// Trigger the command a few more times, verify the overlay still exists.
|
// Trigger the command a few more times, verify the overlay still exists.
|
||||||
await helper.startPrint();
|
await helper.startPrint();
|
||||||
helper.assertDialogVisible();
|
helper.assertDialogOpen();
|
||||||
await helper.startPrint();
|
await helper.startPrint();
|
||||||
helper.assertDialogVisible();
|
helper.assertDialogOpen();
|
||||||
await helper.startPrint();
|
await helper.startPrint();
|
||||||
helper.assertDialogVisible();
|
helper.assertDialogOpen();
|
||||||
|
|
||||||
// Verify it's still the correct page.
|
// Verify it's still the correct page.
|
||||||
assertExpectedPrintPage(helper);
|
assertExpectedPrintPage(helper);
|
||||||
@@ -57,9 +57,9 @@ add_task(async function testPrintMultiple() {
|
|||||||
|
|
||||||
add_task(async function testCancelButton() {
|
add_task(async function testCancelButton() {
|
||||||
await PrintHelper.withTestPage(async helper => {
|
await PrintHelper.withTestPage(async helper => {
|
||||||
helper.assertDialogHidden();
|
helper.assertDialogClosed();
|
||||||
await helper.startPrint();
|
await helper.startPrint();
|
||||||
helper.assertDialogVisible();
|
helper.assertDialogOpen();
|
||||||
|
|
||||||
let cancelButton = helper.doc.querySelector("button[name=cancel]");
|
let cancelButton = helper.doc.querySelector("button[name=cancel]");
|
||||||
ok(cancelButton, "Got the cancel button");
|
ok(cancelButton, "Got the cancel button");
|
||||||
@@ -67,15 +67,15 @@ add_task(async function testCancelButton() {
|
|||||||
EventUtils.synthesizeMouseAtCenter(cancelButton, {}, helper.win)
|
EventUtils.synthesizeMouseAtCenter(cancelButton, {}, helper.win)
|
||||||
);
|
);
|
||||||
|
|
||||||
helper.assertDialogHidden();
|
helper.assertDialogClosed();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function testTabOrder() {
|
add_task(async function testTabOrder() {
|
||||||
await PrintHelper.withTestPage(async helper => {
|
await PrintHelper.withTestPage(async helper => {
|
||||||
helper.assertDialogHidden();
|
helper.assertDialogClosed();
|
||||||
await helper.startPrint();
|
await helper.startPrint();
|
||||||
helper.assertDialogVisible();
|
helper.assertDialogOpen();
|
||||||
|
|
||||||
const printerPicker = helper.doc.getElementById("printer-picker");
|
const printerPicker = helper.doc.getElementById("printer-picker");
|
||||||
is(
|
is(
|
||||||
@@ -127,6 +127,6 @@ add_task(async function testTabOrder() {
|
|||||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||||
});
|
});
|
||||||
|
|
||||||
helper.assertDialogHidden();
|
helper.assertDialogClosed();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
|
||||||
|
add_task(async function testModalPrintDialog() {
|
||||||
|
await PrintHelper.withTestPage(async helper => {
|
||||||
|
await helper.startPrint();
|
||||||
|
|
||||||
|
helper.assertDialogOpen();
|
||||||
|
|
||||||
|
await helper.setupMockPrint();
|
||||||
|
|
||||||
|
helper.doc.querySelector("#open-dialog-link").click();
|
||||||
|
|
||||||
|
helper.assertDialogHidden();
|
||||||
|
|
||||||
|
await helper.withClosingFn(() => {
|
||||||
|
helper.resolveShowSystemDialog();
|
||||||
|
helper.resolvePrint();
|
||||||
|
});
|
||||||
|
|
||||||
|
helper.assertDialogClosed();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testModalPrintDialogCancelled() {
|
||||||
|
await PrintHelper.withTestPage(async helper => {
|
||||||
|
await helper.startPrint();
|
||||||
|
|
||||||
|
helper.assertDialogOpen();
|
||||||
|
|
||||||
|
await helper.setupMockPrint();
|
||||||
|
|
||||||
|
helper.doc.querySelector("#open-dialog-link").click();
|
||||||
|
|
||||||
|
helper.assertDialogHidden();
|
||||||
|
|
||||||
|
await helper.withClosingFn(() => {
|
||||||
|
helper.rejectShowSystemDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
helper.assertDialogClosed();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -48,6 +48,12 @@ class PrintHelper {
|
|||||||
async withClosingFn(closeFn) {
|
async withClosingFn(closeFn) {
|
||||||
let { dialog } = this;
|
let { dialog } = this;
|
||||||
await closeFn();
|
await closeFn();
|
||||||
|
if (this.dialog) {
|
||||||
|
await TestUtils.waitForCondition(
|
||||||
|
() => !this.dialog,
|
||||||
|
"Wait for dialog to close"
|
||||||
|
);
|
||||||
|
}
|
||||||
await dialog._closingPromise;
|
await dialog._closingPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,13 +61,40 @@ class PrintHelper {
|
|||||||
await this.withClosingFn(() => this.dialog.close());
|
await this.withClosingFn(() => this.dialog.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
assertDialogHidden() {
|
assertDialogClosed() {
|
||||||
is(this._dialogs.length, 0, "There are no print dialogs");
|
is(this._dialogs.length, 0, "There are no print dialogs");
|
||||||
}
|
}
|
||||||
|
|
||||||
assertDialogVisible() {
|
assertDialogOpen() {
|
||||||
is(this._dialogs.length, 1, "There is one print dialog");
|
is(this._dialogs.length, 1, "There is one print dialog");
|
||||||
BrowserTestUtils.is_visible(this.dialog._box, "The dialog is visible");
|
ok(BrowserTestUtils.is_visible(this.dialog._box), "The dialog is visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
assertDialogHidden() {
|
||||||
|
is(this._dialogs.length, 1, "There is one print dialog");
|
||||||
|
ok(BrowserTestUtils.is_hidden(this.dialog._box), "The dialog is hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
async setupMockPrint() {
|
||||||
|
if (this.resolveShowSystemDialog) {
|
||||||
|
throw new Error("Print already mocked");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create some Promises that we can resolve/reject from the test.
|
||||||
|
let showSystemDialogPromise = new Promise((resolve, reject) => {
|
||||||
|
this.resolveShowSystemDialog = resolve;
|
||||||
|
this.rejectShowSystemDialog = () => {
|
||||||
|
reject(Components.Exception("", Cr.NS_ERROR_ABORT));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
let printPromise = new Promise((resolve, reject) => {
|
||||||
|
this.resolvePrint = resolve;
|
||||||
|
this.rejectPrint = reject;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock PrintEventHandler with our Promises.
|
||||||
|
this.win.PrintEventHandler._showPrintDialog = () => showSystemDialogPromise;
|
||||||
|
this.win.PrintEventHandler._doPrint = () => printPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
get _tabDialogBox() {
|
get _tabDialogBox() {
|
||||||
@@ -70,6 +103,10 @@ class PrintHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _tabDialogBoxManager() {
|
||||||
|
return this._tabDialogBox.getManager();
|
||||||
|
}
|
||||||
|
|
||||||
get _dialogs() {
|
get _dialogs() {
|
||||||
return this._tabDialogBox._dialogManager._dialogs;
|
return this._tabDialogBox._dialogManager._dialogs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -921,6 +921,15 @@ class SubDialogManager {
|
|||||||
this._topDialog.close();
|
this._topDialog.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the dialog stack for a specific browser.
|
||||||
|
* @param aBrowser - The browser associated with the tab dialog.
|
||||||
|
*/
|
||||||
|
hideDialog(aBrowser) {
|
||||||
|
aBrowser.removeAttribute("tabDialogShowing");
|
||||||
|
this._dialogStack.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abort open dialogs.
|
* Abort open dialogs.
|
||||||
* @param {function} [filterFn] - Function which should return true for
|
* @param {function} [filterFn] - Function which should return true for
|
||||||
@@ -992,6 +1001,7 @@ class SubDialogManager {
|
|||||||
this._topDialog._prevActiveElement?.focus();
|
this._topDialog._prevActiveElement?.focus();
|
||||||
this._topDialog._overlay.setAttribute("topmost", true);
|
this._topDialog._overlay.setAttribute("topmost", true);
|
||||||
this._topDialog._addDialogEventListeners(false);
|
this._topDialog._addDialogEventListeners(false);
|
||||||
|
this._dialogStack.hidden = false;
|
||||||
} else {
|
} else {
|
||||||
// We have closed the last dialog, do cleanup.
|
// We have closed the last dialog, do cleanup.
|
||||||
this._topLevelPrevActiveElement.focus();
|
this._topLevelPrevActiveElement.focus();
|
||||||
|
|||||||
Reference in New Issue
Block a user