Source code

Revision control

Copy as Markdown

Other Tools

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://")) {
return pathName;
}
const testPath = getRootDirectory(gTestPath).replace(
);
return testPath + pathName;
}
static getTestPageUrlHTTPS(pathName) {
if (pathName.startsWith("https://")) {
return pathName;
}
const testPath = getRootDirectory(gTestPath).replace(
);
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);
});
}