556 lines
15 KiB
JavaScript
556 lines
15 KiB
JavaScript
const PRINT_DOCUMENT_URI = "chrome://global/content/print.html";
|
|
const DEFAULT_PRINTER_NAME = "Mozilla Save to PDF";
|
|
const { MockFilePicker } = SpecialPowers;
|
|
|
|
let pickerMocked = false;
|
|
|
|
class PrintHelper {
|
|
static async withTestPage(testFn, pagePathname, useHTTPS = false) {
|
|
let pageUrl = "";
|
|
if (pagePathname) {
|
|
pageUrl = useHTTPS
|
|
? this.getTestPageUrlHTTPS(pagePathname)
|
|
: this.getTestPageUrl(pagePathname);
|
|
} else {
|
|
pageUrl = useHTTPS
|
|
? this.defaultTestPageUrlHTTPS
|
|
: this.defaultTestPageUrl;
|
|
}
|
|
info("withTestPage: " + pageUrl);
|
|
let isPdf = pageUrl.endsWith(".pdf");
|
|
|
|
let taskReturn = await BrowserTestUtils.withNewTab(
|
|
isPdf ? "about:blank" : pageUrl,
|
|
async function (browser) {
|
|
if (isPdf) {
|
|
let loaded = BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"documentloaded",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
BrowserTestUtils.startLoadingURIString(browser, pageUrl);
|
|
await loaded;
|
|
}
|
|
await testFn(new PrintHelper(browser));
|
|
}
|
|
);
|
|
|
|
await SpecialPowers.popPrefEnv();
|
|
if (isPdf) {
|
|
await SpecialPowers.popPrefEnv();
|
|
}
|
|
|
|
// Reset all of the other printing prefs to their default.
|
|
this.resetPrintPrefs();
|
|
return taskReturn;
|
|
}
|
|
|
|
static async withTestPageHTTPS(testFn, pagePathname) {
|
|
return this.withTestPage(testFn, pagePathname, /* useHttps */ true);
|
|
}
|
|
|
|
static resetPrintPrefs() {
|
|
for (let name of Services.prefs.getChildList("print.")) {
|
|
Services.prefs.clearUserPref(name);
|
|
}
|
|
Services.prefs.clearUserPref("print_printer");
|
|
Services.prefs.clearUserPref("print.more-settings.open");
|
|
}
|
|
|
|
static getTestPageUrl(pathName) {
|
|
if (pathName.startsWith("http://") || pathName.startsWith("file://")) {
|
|
return pathName;
|
|
}
|
|
const testPath = getRootDirectory(gTestPath).replace(
|
|
"chrome://mochitests/content",
|
|
"http://example.com"
|
|
);
|
|
return testPath + pathName;
|
|
}
|
|
|
|
static getTestPageUrlHTTPS(pathName) {
|
|
if (pathName.startsWith("https://") || pathName.startsWith("file://")) {
|
|
return pathName;
|
|
}
|
|
const testPath = getRootDirectory(gTestPath).replace(
|
|
"chrome://mochitests/content",
|
|
"https://example.com"
|
|
);
|
|
return testPath + pathName;
|
|
}
|
|
|
|
static get defaultTestPageUrl() {
|
|
return this.getTestPageUrl("simplifyArticleSample.html");
|
|
}
|
|
|
|
static get defaultTestPageUrlHTTPS() {
|
|
return this.getTestPageUrlHTTPS("simplifyArticleSample.html");
|
|
}
|
|
|
|
static createMockPaper(paperProperties = {}) {
|
|
return Object.assign(
|
|
{
|
|
id: "regular",
|
|
name: "Regular Size",
|
|
width: 612,
|
|
height: 792,
|
|
unwriteableMargin: Promise.resolve(
|
|
paperProperties.unwriteableMargin || {
|
|
top: 0.1,
|
|
bottom: 0.1,
|
|
left: 0.1,
|
|
right: 0.1,
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIPaperMargin]),
|
|
}
|
|
),
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIPaper]),
|
|
},
|
|
paperProperties
|
|
);
|
|
}
|
|
|
|
constructor(sourceBrowser) {
|
|
this.sourceBrowser = sourceBrowser;
|
|
}
|
|
|
|
async startPrint(condition = {}) {
|
|
this.sourceBrowser.ownerGlobal.document
|
|
.getElementById("cmd_print")
|
|
.doCommand();
|
|
return this.waitForDialog(condition);
|
|
}
|
|
|
|
async waitForDialog(condition = {}) {
|
|
let dialog = await TestUtils.waitForCondition(
|
|
() => this.dialog,
|
|
"Wait for dialog"
|
|
);
|
|
await dialog._dialogReady;
|
|
|
|
if (Object.keys(condition).length === 0) {
|
|
await this.win._initialized;
|
|
// Wait a frame so the rendering spinner is hidden.
|
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
} else if (condition.waitFor == "loadComplete") {
|
|
await BrowserTestUtils.waitForAttributeRemoval("loading", this.doc.body);
|
|
}
|
|
}
|
|
|
|
beforeInit(initFn) {
|
|
// Run a function when the print.html document is created,
|
|
// but before its init is called from the domcontentloaded handler
|
|
TestUtils.topicObserved("document-element-inserted", doc => {
|
|
return (
|
|
doc.nodePrincipal.isSystemPrincipal &&
|
|
doc.contentType == "text/html" &&
|
|
doc.URL.startsWith("chrome://global/content/print.html")
|
|
);
|
|
}).then(([doc]) => {
|
|
doc.addEventListener("DOMContentLoaded", () => {
|
|
initFn(doc.ownerGlobal);
|
|
});
|
|
});
|
|
}
|
|
|
|
async withClosingFn(closeFn) {
|
|
let { dialog } = this;
|
|
await closeFn();
|
|
if (this.dialog) {
|
|
await TestUtils.waitForCondition(
|
|
() => !this.dialog,
|
|
"Wait for dialog to close"
|
|
);
|
|
}
|
|
await dialog._closingPromise;
|
|
}
|
|
|
|
resetSettings() {
|
|
this.win.PrintEventHandler.settings =
|
|
this.win.PrintEventHandler.defaultSettings;
|
|
this.win.PrintEventHandler.saveSettingsToPrefs(
|
|
this.win.PrintEventHandler.kInitSaveAll
|
|
);
|
|
}
|
|
|
|
async closeDialog() {
|
|
this.resetSettings();
|
|
await this.withClosingFn(() => this.dialog.close());
|
|
}
|
|
|
|
assertDialogClosed() {
|
|
is(this._dialogs.length, 0, "There are no print dialogs");
|
|
}
|
|
|
|
assertDialogOpen() {
|
|
is(this._dialogs.length, 1, "There is one print dialog");
|
|
ok(BrowserTestUtils.isVisible(this.dialog._box), "The dialog is visible");
|
|
}
|
|
|
|
assertDialogHidden() {
|
|
is(this._dialogs.length, 1, "There is one print dialog");
|
|
ok(BrowserTestUtils.isHidden(this.dialog._box), "The dialog is hidden");
|
|
Assert.greater(
|
|
this.dialog._box.getBoundingClientRect().width,
|
|
0,
|
|
"The dialog should still have boxes"
|
|
);
|
|
}
|
|
|
|
async assertPrintToFile(file, testFn) {
|
|
ok(!file.exists(), "File does not exist before printing");
|
|
await this.withClosingFn(testFn);
|
|
await TestUtils.waitForCondition(
|
|
() => file.exists() && file.fileSize > 0,
|
|
"Wait for target file to get created",
|
|
50
|
|
);
|
|
ok(file.exists(), "Created target file");
|
|
|
|
await TestUtils.waitForCondition(
|
|
() => file.fileSize > 0,
|
|
"Wait for the print progress to run",
|
|
50
|
|
);
|
|
|
|
Assert.greater(file.fileSize, 0, "Target file not empty");
|
|
}
|
|
|
|
setupMockPrint() {
|
|
if (this.resolveShowSystemDialog) {
|
|
throw new Error("Print already mocked");
|
|
}
|
|
|
|
// Create some Promises that we can resolve from the test.
|
|
let showSystemDialogPromise = new Promise(resolve => {
|
|
this.resolveShowSystemDialog = result => {
|
|
if (result !== undefined) {
|
|
resolve(result);
|
|
} else {
|
|
resolve(true);
|
|
}
|
|
};
|
|
});
|
|
let printPromise = new Promise((resolve, reject) => {
|
|
this.resolvePrint = resolve;
|
|
this.rejectPrint = reject;
|
|
});
|
|
|
|
// Mock PrintEventHandler with our Promises.
|
|
this.win.PrintEventHandler._showPrintDialog = (window, haveSelection) => {
|
|
this.systemDialogOpenedWithSelection = haveSelection;
|
|
return showSystemDialogPromise;
|
|
};
|
|
this.win.PrintEventHandler._doPrint = (bc, settings) => {
|
|
this._printedSettings = settings;
|
|
return printPromise;
|
|
};
|
|
}
|
|
|
|
addMockPrinter(opts = {}) {
|
|
if (typeof opts == "string") {
|
|
opts = { name: opts };
|
|
}
|
|
let {
|
|
name = "Mock Printer",
|
|
paperList,
|
|
printerInfoPromise = Promise.resolve(),
|
|
paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches,
|
|
paperId,
|
|
} = opts;
|
|
let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
|
|
Ci.nsIPrintSettingsService
|
|
);
|
|
// Use the fallbackPaperList as the default for mock printers
|
|
if (!paperList) {
|
|
info("addMockPrinter, using the fallbackPaperList");
|
|
paperList = Cc["@mozilla.org/gfx/printerlist;1"].createInstance(
|
|
Ci.nsIPrinterList
|
|
).fallbackPaperList;
|
|
}
|
|
|
|
let defaultSettings = PSSVC.createNewPrintSettings();
|
|
defaultSettings.printerName = name;
|
|
defaultSettings.toFileName = "";
|
|
defaultSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatNative;
|
|
defaultSettings.outputDestination =
|
|
Ci.nsIPrintSettings.kOutputDestinationPrinter;
|
|
defaultSettings.paperSizeUnit = paperSizeUnit;
|
|
if (paperId) {
|
|
defaultSettings.paperId = paperId;
|
|
}
|
|
|
|
if (
|
|
defaultSettings.paperId &&
|
|
Array.from(paperList).find(p => p.id == defaultSettings.paperId)
|
|
) {
|
|
info(
|
|
`addMockPrinter, using paperId: ${defaultSettings.paperId} from the paperList`
|
|
);
|
|
} else if (paperList.length) {
|
|
defaultSettings.paperId = paperList[0].id;
|
|
info(
|
|
`addMockPrinter, corrected default paperId setting value: ${defaultSettings.paperId}`
|
|
);
|
|
}
|
|
|
|
let printer = {
|
|
name,
|
|
supportsColor: Promise.resolve(true),
|
|
supportsMonochrome: Promise.resolve(true),
|
|
printerInfo: printerInfoPromise.then(() => ({
|
|
paperList,
|
|
defaultSettings,
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrinterInfo]),
|
|
})),
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrinter]),
|
|
};
|
|
|
|
if (!this._mockPrinters) {
|
|
this._mockPrinters = [printer];
|
|
this.beforeInit(win => (win._mockPrinters = this._mockPrinters));
|
|
} else {
|
|
this._mockPrinters.push(printer);
|
|
}
|
|
return printer;
|
|
}
|
|
|
|
get _tabDialogBox() {
|
|
return this.sourceBrowser.ownerGlobal.gBrowser.getTabDialogBox(
|
|
this.sourceBrowser
|
|
);
|
|
}
|
|
|
|
get _tabDialogBoxManager() {
|
|
return this._tabDialogBox.getTabDialogManager();
|
|
}
|
|
|
|
get _dialogs() {
|
|
return this._tabDialogBox.getTabDialogManager()._dialogs;
|
|
}
|
|
|
|
get dialog() {
|
|
return this._dialogs.find(dlg =>
|
|
dlg._box.querySelector(".printSettingsBrowser")
|
|
);
|
|
}
|
|
|
|
get paginationElem() {
|
|
return this.dialog._box.querySelector(".printPreviewNavigation");
|
|
}
|
|
|
|
get paginationSheetIndicator() {
|
|
return this.paginationElem.shadowRoot.querySelector("#sheetIndicator");
|
|
}
|
|
|
|
get currentPrintPreviewBrowser() {
|
|
return this.win.PrintEventHandler.printPreviewEl.lastPreviewBrowser;
|
|
}
|
|
|
|
get _printBrowser() {
|
|
return this.dialog._frame;
|
|
}
|
|
|
|
get doc() {
|
|
return this._printBrowser.contentDocument;
|
|
}
|
|
|
|
get win() {
|
|
return this._printBrowser.contentWindow;
|
|
}
|
|
|
|
get(id) {
|
|
return this.doc.getElementById(id);
|
|
}
|
|
|
|
get sheetCount() {
|
|
return this.doc.l10n.getAttributes(this.get("sheet-count")).args.sheetCount;
|
|
}
|
|
|
|
get sourceURI() {
|
|
return this.win.PrintEventHandler.activeCurrentURI;
|
|
}
|
|
|
|
async waitForReaderModeReady() {
|
|
if (gBrowser.selectedBrowser.isArticle) {
|
|
return;
|
|
}
|
|
await new Promise(resolve => {
|
|
let onReaderModeChange = {
|
|
receiveMessage(message) {
|
|
if (
|
|
message.data &&
|
|
message.data.isArticle !== undefined &&
|
|
gBrowser.selectedBrowser.isArticle
|
|
) {
|
|
AboutReaderParent.removeMessageListener(
|
|
"Reader:UpdateReaderButton",
|
|
onReaderModeChange
|
|
);
|
|
resolve();
|
|
}
|
|
},
|
|
};
|
|
AboutReaderParent.addMessageListener(
|
|
"Reader:UpdateReaderButton",
|
|
onReaderModeChange
|
|
);
|
|
});
|
|
}
|
|
|
|
async waitForPreview(changeFn) {
|
|
changeFn();
|
|
await BrowserTestUtils.waitForEvent(this.doc, "preview-updated");
|
|
}
|
|
|
|
async waitForSettingsEvent(changeFn) {
|
|
let changed = BrowserTestUtils.waitForEvent(this.doc, "print-settings");
|
|
await changeFn?.();
|
|
await BrowserTestUtils.waitForCondition(
|
|
() => !this.win.PrintEventHandler._delayedSettingsChangeTask.isArmed,
|
|
"Wait for all delayed tasks to execute"
|
|
);
|
|
await changed;
|
|
}
|
|
|
|
click(el, { scroll = true } = {}) {
|
|
if (scroll) {
|
|
el.scrollIntoView();
|
|
}
|
|
ok(BrowserTestUtils.isVisible(el), "Element must be visible to click");
|
|
EventUtils.synthesizeMouseAtCenter(el, {}, this.win);
|
|
}
|
|
|
|
text(el, text) {
|
|
this.click(el);
|
|
el.value = "";
|
|
EventUtils.sendString(text, this.win);
|
|
}
|
|
|
|
async openMoreSettings(options) {
|
|
let details = this.get("more-settings");
|
|
if (!details.open) {
|
|
this.click(details.firstElementChild, options);
|
|
}
|
|
await this.awaitAnimationFrame();
|
|
}
|
|
|
|
dispatchSettingsChange(settings) {
|
|
this.doc.dispatchEvent(
|
|
new CustomEvent("update-print-settings", {
|
|
detail: settings,
|
|
})
|
|
);
|
|
}
|
|
|
|
get settings() {
|
|
return this.win.PrintEventHandler.settings;
|
|
}
|
|
|
|
get viewSettings() {
|
|
return this.win.PrintEventHandler.viewSettings;
|
|
}
|
|
|
|
_assertMatches(a, b, msg) {
|
|
if (Array.isArray(a)) {
|
|
is(a.length, b.length, msg);
|
|
for (let i = 0; i < a.length; ++i) {
|
|
this._assertMatches(a[i], b[i], msg);
|
|
}
|
|
return;
|
|
}
|
|
is(a, b, msg);
|
|
}
|
|
|
|
assertSettingsMatch(expected) {
|
|
let { settings } = this;
|
|
for (let [setting, value] of Object.entries(expected)) {
|
|
this._assertMatches(settings[setting], value, `${setting} matches`);
|
|
}
|
|
}
|
|
|
|
assertPrintedWithSettings(expected) {
|
|
ok(this._printedSettings, "Printed settings have been recorded");
|
|
for (let [setting, value] of Object.entries(expected)) {
|
|
this._assertMatches(
|
|
this._printedSettings[setting],
|
|
value,
|
|
`${setting} matches printed setting`
|
|
);
|
|
}
|
|
}
|
|
|
|
get _lastPrintPreviewSettings() {
|
|
return this.win.PrintEventHandler._lastPrintPreviewSettings;
|
|
}
|
|
|
|
assertPreviewedWithSettings(expected) {
|
|
let settings = this._lastPrintPreviewSettings;
|
|
ok(settings, "Last preview settings are available");
|
|
for (let [setting, value] of Object.entries(expected)) {
|
|
this._assertMatches(
|
|
settings[setting],
|
|
value,
|
|
`${setting} matches previewed setting`
|
|
);
|
|
}
|
|
}
|
|
|
|
async assertSettingsChanged(from, to, changeFn) {
|
|
is(
|
|
Object.keys(from).length,
|
|
Object.keys(to).length,
|
|
"Got the same number of settings to check"
|
|
);
|
|
ok(
|
|
Object.keys(from).every(s => s in to),
|
|
"Checking the same setting names"
|
|
);
|
|
this.assertSettingsMatch(from);
|
|
await changeFn();
|
|
this.assertSettingsMatch(to);
|
|
}
|
|
|
|
async assertSettingsNotChanged(settings, changeFn) {
|
|
await this.assertSettingsChanged(settings, settings, changeFn);
|
|
}
|
|
|
|
awaitAnimationFrame() {
|
|
return new Promise(resolve => this.win.requestAnimationFrame(resolve));
|
|
}
|
|
|
|
mockFilePickerCancel() {
|
|
if (!pickerMocked) {
|
|
pickerMocked = true;
|
|
MockFilePicker.init(window.browsingContext);
|
|
registerCleanupFunction(() => MockFilePicker.cleanup());
|
|
}
|
|
MockFilePicker.returnValue = MockFilePicker.returnCancel;
|
|
}
|
|
|
|
mockFilePicker(filename) {
|
|
if (!pickerMocked) {
|
|
pickerMocked = true;
|
|
MockFilePicker.init(window.browsingContext);
|
|
registerCleanupFunction(() => MockFilePicker.cleanup());
|
|
}
|
|
MockFilePicker.returnValue = MockFilePicker.returnOK;
|
|
let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
|
file.append(filename);
|
|
registerCleanupFunction(() => {
|
|
if (file.exists()) {
|
|
file.remove(false);
|
|
}
|
|
});
|
|
MockFilePicker.setFiles([file]);
|
|
return file;
|
|
}
|
|
}
|
|
|
|
function waitForPreviewVisible() {
|
|
return BrowserTestUtils.waitForCondition(function () {
|
|
let preview = document.querySelector(".printPreviewBrowser");
|
|
return preview && BrowserTestUtils.isVisible(preview);
|
|
});
|
|
}
|