Bug 1670122: add check box in print UI to print selection only. r=mstriemer,fluent-reviewers,flod

Uses a new printing actor to determine if there was a selection within
the browsing context.

We now create two browsers, the primary browser and a selected browser,
and will use the appropriate browser depending on the settings value
for printSelectionOnly.

Differential Revision: https://phabricator.services.mozilla.com/D94467
This commit is contained in:
Emma Malysz 2020-12-17 23:14:43 +00:00
Родитель cc5d428399
Коммит ae87050b91
13 изменённых файлов: 375 добавлений и 78 удалений

Просмотреть файл

@ -1408,10 +1408,16 @@ toolbarpaletteitem > toolbaritem {
}
.printPreviewBrowser {
display: none;
opacity: 1;
transition: opacity 60ms;
}
.previewStack[previewtype="primary"] > .printPreviewBrowser[previewtype="primary"],
.previewStack[previewtype="selection"] > .printPreviewBrowser[previewtype="selection"] {
display: block;
}
.previewStack[rendering=true] > .printPreviewBrowser {
opacity: 0;
transition: opacity 1ms 250ms;

Просмотреть файл

@ -271,7 +271,7 @@
</menulist>
<html:template id="printPreviewStackTemplate">
<stack class="previewStack" rendering="true" flex="1">
<stack class="previewStack" rendering="true" flex="1" previewtype="primary">
<vbox class="previewRendering" flex="1">
<h1 class="print-pending-label" data-l10n-id="printui-loading"></h1>
</vbox>

Просмотреть файл

@ -0,0 +1,28 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["PrintingSelectionChild"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
class PrintingSelectionChild extends JSWindowActorChild {
receiveMessage(message) {
switch (message.name) {
case "PrintingSelection:HasSelection":
return this.hasSelection(this.document.ownerGlobal);
}
return undefined;
}
hasSelection(global) {
const { content } = global;
let focusedWindow = Services.focus.focusedWindow || content;
let selection = focusedWindow.getSelection();
return selection.type == "Range";
}
}

Просмотреть файл

@ -53,6 +53,7 @@ FINAL_TARGET_FILES.actors += [
"PopupBlockingParent.jsm",
"PrintingChild.jsm",
"PrintingParent.jsm",
"PrintingSelectionChild.jsm",
"PurgeSessionHistoryChild.jsm",
"RemotePageChild.jsm",
"SelectChild.jsm",

Просмотреть файл

@ -200,6 +200,10 @@
<input is="setting-checkbox" type="checkbox" id="backgrounds-enabled" data-setting-name="printBackgrounds">
<label for="backgrounds-enabled" data-l10n-id="printui-backgrounds-checkbox"></label>
</div>
<div id="print-selection-container" class="row cols-2" hidden>
<input is="setting-checkbox" type="checkbox" id="print-selection-enabled" data-setting-name="printSelectionOnly">
<label for="print-selection-enabled" data-l10n-id="printui-selection-checkbox"></label>
</div>
</section>
</details>

Просмотреть файл

@ -115,6 +115,8 @@ var PrintEventHandler = {
allPaperSizes: {},
previewIsEmpty: false,
_delayedChanges: {},
_hasRenderedSelectionPreview: false,
_hasRenderedPrimaryPreview: false,
_userChangedSettings: {},
settingFlags: {
margins: Ci.nsIPrintSettings.kInitSaveMargins,
@ -144,7 +146,11 @@ var PrintEventHandler = {
// These settings do not have an associated pref value or flag, but
// changing them requires us to update the print preview.
_nonFlaggedUpdatePreviewSettings: new Set(["pageRanges", "numPagesPerSheet"]),
_nonFlaggedUpdatePreviewSettings: new Set([
"pageRanges",
"numPagesPerSheet",
"printSelectionOnly",
]),
_noPreviewUpdateSettings: new Set(["numCopies", "printDuplex"]),
async init() {
@ -154,10 +160,10 @@ var PrintEventHandler = {
// is initiated and the print preview clone must be a snapshot from the
// time that the print was started.
let sourceBrowsingContext = this.getSourceBrowsingContext();
this.previewBrowser = PrintUtils.createPreviewBrowser(
sourceBrowsingContext,
ourBrowser
);
({
previewBrowser: this.previewBrowser,
selectionPreviewBrowser: this.selectionPreviewBrowser,
} = PrintUtils.createPreviewBrowsers(sourceBrowsingContext, ourBrowser));
// Get the temporary browser that will previously have been created for the
// platform code to generate the static clone printing doc into if this
@ -171,6 +177,10 @@ var PrintEventHandler = {
this.previewBrowser.swapDocShells(existingBrowser);
existingBrowser.remove();
}
this.hasSelection =
args.getProperty("hasSelection") && this.selectionPreviewBrowser;
document.querySelector("#print-selection-container").hidden = !this
.hasSelection;
let sourcePrincipal =
sourceBrowsingContext.currentWindowGlobal.documentPrincipal;
@ -181,6 +191,14 @@ var PrintEventHandler = {
this.originalSourceCurrentURI =
sourceBrowsingContext.currentWindowContext.documentURI.spec;
this.sourceWindowId =
sourceBrowsingContext.top.embedderElement.browsingContext.currentWindowGlobal.outerWindowId;
this.selectionWindowId =
sourceBrowsingContext.currentWindowGlobal.outerWindowId;
// We don't need the sourceBrowsingContext anymore, get rid of it.
sourceBrowsingContext = undefined;
this.printProgressIndicator = document.getElementById("print-progress");
this.printForm = document.getElementById("print");
if (sourceIsPdf) {
@ -297,10 +315,7 @@ var PrintEventHandler = {
let settingsToChange = await this.refreshSettings(selectedPrinter.value);
await this.updateSettings(settingsToChange, true);
// Kick off the initial print preview with the source browsing context.
let initialPreviewDone = this._updatePrintPreview(sourceBrowsingContext);
// We don't need the sourceBrowsingContext anymore, get rid of it.
sourceBrowsingContext = undefined;
let initialPreviewDone = this._updatePrintPreview();
// Use a DeferredTask for updating the preview. This will ensure that we
// only have one update running at a time.
@ -319,6 +334,9 @@ var PrintEventHandler = {
);
await document.l10n.translateElements([this.previewBrowser]);
if (this.selectionPreviewBrowser) {
await document.l10n.translateElements([this.selectionPreviewBrowser]);
}
document.body.removeAttribute("loading");
@ -722,17 +740,16 @@ var PrintEventHandler = {
},
/**
* Create a print preview for the provided source browsingContext, or refresh
* the preview with new settings when omitted.
*
* @param sourceBrowsingContext {BrowsingContext} [optional]
* The source BrowsingContext (the one associated with a tab or
* subdocument) that should be previewed.
* Creates a print preview or refreshes the preview with new settings when omitted.
*
* @return {Promise} Resolves when the preview has been updated.
*/
async _updatePrintPreview(sourceBrowsingContext) {
let { previewBrowser, settings } = this;
async _updatePrintPreview() {
let { settings } = this;
let { printSelectionOnly } = this.viewSettings;
if (!this.selectionPreviewBrowser) {
printSelectionOnly = false;
}
// We never want the progress dialog to show
settings.showPrintProgress = false;
@ -740,10 +757,25 @@ var PrintEventHandler = {
this._showRenderingIndicator();
let sourceWinId;
if (sourceBrowsingContext) {
sourceWinId = sourceBrowsingContext.currentWindowGlobal.outerWindowId;
// If it's the first time loading this type of browser, get the stored window id.
if (printSelectionOnly && !this._hasRenderedSelectionPreview) {
sourceWinId = this.selectionWindowId;
this._hasRenderedSelectionPreview = true;
} else if (!printSelectionOnly && !this._hasRenderedPrimaryPreview) {
sourceWinId = this.sourceWindowId;
this._hasRenderedPrimaryPreview = true;
}
this.previewBrowser.parentElement.setAttribute(
"previewtype",
printSelectionOnly ? "selection" : "primary"
);
let previewBrowser = printSelectionOnly
? this.selectionPreviewBrowser
: this.previewBrowser;
const isFirstCall = !this.printInitiationTime;
if (isFirstCall) {
let params = new URLSearchParams(location.search);
@ -757,13 +789,12 @@ var PrintEventHandler = {
.add(elapsed);
}
let totalPageCount, sheetCount, hasSelection, isEmpty;
let totalPageCount, sheetCount, isEmpty;
try {
// This resolves with a PrintPreviewSuccessInfo dictionary.
({
totalPageCount,
sheetCount,
hasSelection,
isEmpty,
} = await previewBrowser.frameLoader.printPreview(settings, sourceWinId));
} catch (e) {
@ -780,14 +811,14 @@ var PrintEventHandler = {
}
// Update the settings print options on whether there is a selection.
settings.isPrintSelectionRBEnabled = hasSelection;
settings.isPrintSelectionRBEnabled = this.hasSelection;
document.dispatchEvent(
new CustomEvent("page-count", {
detail: { sheetCount, totalPages: totalPageCount },
})
);
this.previewBrowser.setAttribute("sheet-count", sheetCount);
previewBrowser.setAttribute("sheet-count", sheetCount);
this._hideRenderingIndicator();
@ -1684,7 +1715,12 @@ class PrintUIForm extends PrintUIControlMixin(HTMLFormElement) {
}
removeNonPdfSettings() {
let selectors = ["#margins", "#headers-footers", "#backgrounds"];
let selectors = [
"#margins",
"#headers-footers",
"#backgrounds",
"#print-selection-container",
];
for (let selector of selectors) {
this.querySelector(selector).remove();
}

Просмотреть файл

@ -31,8 +31,17 @@ customElements.define(
}
get previewBrowser() {
// Assuming we're a sibling of our preview browser.
return this.parentNode.querySelector(".printPreviewBrowser");
if (!this._previewBrowser) {
// Assuming we're a sibling of our preview browser.
this._previewBrowser = this.parentNode.querySelector(
".printPreviewBrowser"
);
}
return this._previewBrowser;
}
set previewBrowser(aBrowser) {
this._previewBrowser = aBrowser;
}
connectedCallback() {
@ -76,6 +85,25 @@ customElements.define(
this.mutationObserver.observe(this.previewBrowser, {
attributes: ["current-page", "sheet-count"],
});
this.currentPreviewBrowserObserver = new MutationObserver(changes => {
for (let change of changes) {
if (change.attributeName == "previewtype") {
let previewType = change.target.getAttribute("previewtype");
this.previewBrowser = change.target.querySelector(
`browser[previewtype="${previewType}"]`
);
this.mutationObserver.disconnect();
this.mutationObserver.observe(this.previewBrowser, {
attributes: ["current-page", "sheet-count"],
});
}
}
});
this.currentPreviewBrowserObserver.observe(this.parentNode, {
attributes: ["previewtype"],
});
// Initial render with some default values
// We'll be updated with real values when available
this.update(this.constructor.defaultProperties);
@ -86,6 +114,8 @@ customElements.define(
this.shadowRoot.textContent = "";
this.mutationObserver?.disconnect();
delete this.mutationObserver;
this.currentPreviewBrowserObserver?.disconnect();
delete this.currentPreviewBrowserObserver;
}
handleEvent(event) {

Просмотреть файл

@ -143,38 +143,55 @@ var PrintUtils = {
}
},
createPreviewBrowser(aBrowsingContext, aDialogBrowser) {
let browser = gBrowser.createBrowser({
remoteType: aBrowsingContext.currentRemoteType,
userContextId: aBrowsingContext.originAttributes.userContextId,
initialBrowsingContextGroupId: aBrowsingContext.group.id,
skipLoad: true,
});
browser.addEventListener("DOMWindowClose", function(e) {
// Ignore close events from printing, see the code creating browsers in
// printUtils.js and nsDocumentViewer::OnDonePrinting.
//
// When we print with the new print UI we don't bother creating a new
// <browser> element, so the close event gets dispatched to us.
//
// Ignoring it is harmless (and doesn't cause correctness issues, because
// the preview document can't run script anyways).
e.preventDefault();
e.stopPropagation();
});
browser.addEventListener("contextmenu", function(e) {
e.preventDefault();
});
browser.classList.add("printPreviewBrowser");
browser.setAttribute("flex", "1");
browser.setAttribute("printpreview", "true");
document.l10n.setAttributes(browser, "printui-preview-label");
createPreviewBrowsers(aBrowsingContext, aDialogBrowser) {
let _createPreviewBrowser = previewType => {
// When we're not previewing the selection we want to make
// sure that the top-level browser is being printed.
let browsingContext =
previewType == "selection"
? aBrowsingContext
: aBrowsingContext.top.embedderElement.browsingContext;
let browser = gBrowser.createBrowser({
remoteType: browsingContext.currentRemoteType,
userContextId: browsingContext.originAttributes.userContextId,
initialBrowsingContextGroupId: browsingContext.group.id,
skipLoad: true,
});
browser.addEventListener("DOMWindowClose", function(e) {
// Ignore close events from printing, see the code creating browsers in
// printUtils.js and nsDocumentViewer::OnDonePrinting.
//
// When we print with the new print UI we don't bother creating a new
// <browser> element, so the close event gets dispatched to us.
//
// Ignoring it is harmless (and doesn't cause correctness issues, because
// the preview document can't run script anyways).
e.preventDefault();
e.stopPropagation();
});
browser.addEventListener("contextmenu", function(e) {
e.preventDefault();
});
browser.classList.add("printPreviewBrowser");
browser.setAttribute("flex", "1");
browser.setAttribute("printpreview", "true");
browser.setAttribute("previewtype", previewType);
document.l10n.setAttributes(browser, "printui-preview-label");
return browser;
};
let previewStack = document.importNode(
document.getElementById("printPreviewStackTemplate").content,
true
).firstElementChild;
previewStack.append(browser);
let previewBrowser = _createPreviewBrowser("primary");
previewStack.append(previewBrowser);
let selectionPreviewBrowser;
if (aBrowsingContext.currentRemoteType) {
selectionPreviewBrowser = _createPreviewBrowser("selection");
previewStack.append(selectionPreviewBrowser);
}
// show the toolbar after we go into print preview mode so
// that we can initialize the toolbar with total num pages
@ -183,7 +200,7 @@ var PrintUtils = {
previewStack.append(previewPagination);
aDialogBrowser.parentElement.prepend(previewStack);
return browser;
return { previewBrowser, selectionPreviewBrowser };
},
/**
@ -209,6 +226,17 @@ var PrintUtils = {
aPrintInitiationTime,
aPrintSelectionOnly
) {
let hasSelection = aPrintSelectionOnly;
if (!aPrintSelectionOnly) {
let sourceActor = aBrowsingContext.currentWindowGlobal.getActor(
"PrintingSelection"
);
hasSelection = await sourceActor.sendQuery(
"PrintingSelection:HasSelection",
{}
);
}
let sourceBrowser = aBrowsingContext.top.embedderElement;
let previewBrowser = this.getPreviewBrowser(sourceBrowser);
if (previewBrowser) {
@ -227,6 +255,7 @@ var PrintUtils = {
let args = PromptUtils.objectToPropBag({
previewBrowser: aExistingPreviewBrowser,
printSelectionOnly: !!aPrintSelectionOnly,
hasSelection,
});
let dialogBox = gBrowser.getTabDialogBox(sourceBrowser);
return dialogBox.open(
@ -297,8 +326,11 @@ var PrintUtils = {
!PRINT_ALWAYS_SILENT &&
(!aOpenWindowInfo || aOpenWindowInfo.isForWindowDotPrint)
) {
let browsingContext = Services.focus.focusedContentBrowsingContext
? Services.focus.focusedContentBrowsingContext
: aBrowsingContext;
this._openTabModalPrint(
aBrowsingContext,
browsingContext,
browser,
printInitiationTime,
aPrintSelectionOnly
@ -423,8 +455,11 @@ var PrintUtils = {
}
if (PRINT_TAB_MODAL) {
let browsingContext = Services.focus.focusedContentBrowsingContext
? Services.focus.focusedContentBrowsingContext
: gBrowser.selectedBrowser.browsingContext;
return this._openTabModalPrint(
gBrowser.selectedBrowser.browsingContext,
browsingContext,
/* aExistingPreviewBrowser = */ undefined,
Date.now()
);

Просмотреть файл

@ -209,18 +209,13 @@ add_task(async function testPrintOnNewWindowDoesntClose() {
set: [["print.tab_modal.enabled", true]],
});
let win = await BrowserTestUtils.openNewBrowserWindow();
let browser = win.gBrowser.selectedBrowser;
BrowserTestUtils.loadURI(browser, PrintHelper.defaultTestPageUrl);
await BrowserTestUtils.browserLoaded(
browser,
true,
PrintHelper.defaultTestPageUrl
);
let helper = new PrintHelper(browser);
await helper.startPrint();
let file = helper.mockFilePicker("print_new_window_close.pdf");
await helper.assertPrintToFile(file, () => {
EventUtils.sendKey("return", helper.win);
await PrintHelper.withTestPage(async helper => {
await helper.startPrint();
let file = helper.mockFilePicker("print_new_window_close.pdf");
await helper.assertPrintToFile(file, () => {
EventUtils.sendKey("return", helper.win);
});
});
ok(!win.closed, "Shouldn't be closed");
await BrowserTestUtils.closeWindow(win);

Просмотреть файл

@ -270,7 +270,7 @@ add_task(async function testMultiplePreviewNavigation() {
await helper2.startPrint();
let [previewBrowser1, previewBrowser2] = document.querySelectorAll(
".printPreviewBrowser"
".printPreviewBrowser[previewtype='primary']"
);
ok(previewBrowser1 && previewBrowser2, "There are 2 preview browsers");
@ -317,3 +317,65 @@ add_task(async function testMultiplePreviewNavigation() {
gBrowser.removeTab(tab2);
}, "longerArticle.html");
});
add_task(async function testPreviewNavigationSelection() {
await PrintHelper.withTestPage(async helper => {
await SpecialPowers.spawn(helper.sourceBrowser, [], async function() {
let element = content.document.querySelector("#page-2");
content.window.getSelection().selectAllChildren(element);
});
await helper.startPrint();
let paginationElem = document.querySelector(".printPreviewNavigation");
let paginationSheetIndicator = paginationElem.shadowRoot.querySelector(
"#sheetIndicator"
);
// Wait for the first _updatePrintPreview before interacting with the preview
await waitForPageStatusUpdate(
paginationSheetIndicator,
{ sheetNum: 1, sheetCount: 3 },
"Paginator indicates the correct number of sheets"
);
// click a navigation button
// and verify the indicator is updated correctly
EventUtils.synthesizeMouseAtCenter(
paginationElem.shadowRoot.querySelector("#navigateNext"),
{}
);
await waitForPageStatusUpdate(
paginationSheetIndicator,
{ sheetNum: 2, sheetCount: 3 },
"Indicator updates on navigation"
);
await helper.openMoreSettings();
let printSelect = helper.get("print-selection-container");
await helper.waitForPreview(() => helper.click(printSelect));
// Wait for the first _updatePrintPreview before interacting with the preview
await waitForPageStatusUpdate(
paginationSheetIndicator,
{ sheetNum: 1, sheetCount: 2 },
"Paginator indicates the correct number of sheets"
);
// click a navigation button
// and verify the indicator is updated correctly
EventUtils.synthesizeMouseAtCenter(
paginationElem.shadowRoot.querySelector("#navigateNext"),
{}
);
await waitForPageStatusUpdate(
paginationSheetIndicator,
{ sheetNum: 2, sheetCount: 2 },
"Indicator updates on navigation"
);
// move focus before closing the dialog
helper.get("cancel-button").focus();
await helper.awaitAnimationFrame();
await helper.closeDialog();
}, "longerArticle.html");
});

Просмотреть файл

@ -9,6 +9,12 @@ const sources = [
`<html><iframe id="f" src="https://example.com/document-builder.sjs?html=${frameSource}"></iframe></html>`,
];
async function getPreviewText(previewBrowser) {
return SpecialPowers.spawn(previewBrowser, [], function() {
return content.document.body.textContent;
});
}
add_task(async function print_selection() {
// Testing the native print dialog is much harder.
await SpecialPowers.pushPrefEnv({
@ -44,18 +50,104 @@ add_task(async function print_selection() {
() => !!document.querySelector(".printPreviewBrowser")
);
let previewBrowser = document.querySelector(".printPreviewBrowser");
async function getPreviewText() {
return SpecialPowers.spawn(previewBrowser, [], function() {
return content.document.body.textContent;
});
}
let previewBrowser = document.querySelector(
".printPreviewBrowser[previewtype='selection']"
);
let previewText = () => getPreviewText(previewBrowser);
// The preview process is async, wait for it to not be empty.
await BrowserTestUtils.waitForCondition(getPreviewText);
let textContent = await getPreviewText();
let textContent = await TestUtils.waitForCondition(previewText);
is(textContent, "other text", "Correct content loaded");
let printSelect = document
.querySelector(".printSettingsBrowser")
.contentDocument.querySelector("#print-selection-enabled");
ok(!printSelect.hidden, "Print selection checkbox is shown");
ok(printSelect.checked, "Print selection checkbox is checked");
// Closing the tab also closes the preview dialog and such.
}
);
}
});
add_task(async function no_print_selection() {
// Ensures the print selection checkbox is hidden if nothing is selected
await PrintHelper.withTestPage(async helper => {
await helper.startPrint();
await helper.openMoreSettings();
let printSelect = helper.get("print-selection-container");
ok(printSelect.hidden, "Print selection checkbox is hidden");
await helper.closeDialog();
});
});
add_task(async function print_selection_switch() {
await PrintHelper.withTestPage(async helper => {
await SpecialPowers.spawn(helper.sourceBrowser, [], async function() {
let element = content.document.querySelector("h1");
content.window.getSelection().selectAllChildren(element);
});
await helper.startPrint();
await helper.openMoreSettings();
let printSelect = helper.get("print-selection-container");
ok(!printSelect.checked, "Print selection checkbox is not checked");
let selectionBrowser = document.querySelector(
".printPreviewBrowser[previewtype='selection']"
);
let primaryBrowser = document.querySelector(
".printPreviewBrowser[previewtype='primary']"
);
let selectedText = "Article title";
let fullText = await getPreviewText(primaryBrowser);
function getCurrentBrowser(previewType) {
let browser =
previewType == "selection" ? selectionBrowser : primaryBrowser;
is(
browser.parentElement.getAttribute("previewtype"),
previewType,
"Expected browser is showing"
);
return browser;
}
helper.assertSettingsMatch({
printSelectionOnly: false,
});
is(
selectionBrowser.parentElement.getAttribute("previewtype"),
"primary",
"Print selection browser is not shown"
);
await helper.assertSettingsChanged(
{ printSelectionOnly: false },
{ printSelectionOnly: true },
async () => {
await helper.waitForPreview(() => helper.click(printSelect));
let text = await getPreviewText(getCurrentBrowser("selection"));
is(text, selectedText, "Correct content loaded");
}
);
await helper.assertSettingsChanged(
{ printSelectionOnly: true },
{ printSelectionOnly: false },
async () => {
await helper.waitForPreview(() => helper.click(printSelect));
let previewType = selectionBrowser.parentElement.getAttribute(
"previewtype"
);
is(previewType, "primary", "Print selection browser is not shown");
let text = await getPreviewText(getCurrentBrowser(previewType));
is(text, fullText, "Correct content loaded");
}
);
await helper.closeDialog();
});
});

Просмотреть файл

@ -53,6 +53,7 @@ printui-duplex-checkbox = Print on both sides
printui-options = Options
printui-headers-footers-checkbox = Print headers and footers
printui-backgrounds-checkbox = Print backgrounds
printui-selection-checkbox = Print selection only
printui-color-mode-label = Color mode
printui-color-mode-color = Color

Просмотреть файл

@ -366,6 +366,13 @@ let JSWINDOWACTORS = {
},
},
PrintingSelection: {
child: {
moduleURI: "resource://gre/actors/PrintingSelectionChild.jsm",
},
allFrames: true,
},
PurgeSessionHistory: {
child: {
moduleURI: "resource://gre/actors/PurgeSessionHistoryChild.jsm",