559 lines
14 KiB
JavaScript
559 lines
14 KiB
JavaScript
function waitForPdfJS(browser, url = null) {
|
|
// Runs tests after all "load" event handlers have fired off
|
|
const loadPromise = new Promise(resolve => {
|
|
let pageCounter = 0;
|
|
const removeEventListener1 = BrowserTestUtils.addContentEventListener(
|
|
browser,
|
|
"pagerender",
|
|
() => {
|
|
pageCounter += 1;
|
|
},
|
|
{ capture: false, wantUntrusted: true }
|
|
);
|
|
const removeEventListener2 = BrowserTestUtils.addContentEventListener(
|
|
browser,
|
|
"textlayerrendered",
|
|
() => {
|
|
pageCounter -= 1;
|
|
if (pageCounter === 0) {
|
|
removeEventListener1();
|
|
removeEventListener2();
|
|
resolve();
|
|
}
|
|
},
|
|
{ capture: false, wantUntrusted: true }
|
|
);
|
|
});
|
|
if (url) {
|
|
BrowserTestUtils.startLoadingURIString(browser, url);
|
|
}
|
|
return loadPromise;
|
|
}
|
|
|
|
async function waitForPdfJSClose(browser, closeTab = false) {
|
|
await SpecialPowers.spawn(browser, [], async () => {
|
|
const viewer = content.wrappedJSObject.PDFViewerApplication;
|
|
await viewer.testingClose();
|
|
});
|
|
if (closeTab) {
|
|
const tab = gBrowser.getTabForBrowser(browser);
|
|
if (tab) {
|
|
BrowserTestUtils.removeTab(tab);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function waitForPdfJSAnnotationLayer(browser, url) {
|
|
let loadPromise = BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"annotationlayerrendered",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
let pagePromise = waitForPdfJS(browser, url);
|
|
return Promise.all([pagePromise, loadPromise]);
|
|
}
|
|
|
|
async function waitForPdfJSAllLayers(browser, url, layers) {
|
|
let loadPromise = BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"textlayerrendered",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
let annotationPromise = BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"annotationlayerrendered",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
let annotationEditorPromise = BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"annotationeditorlayerrendered",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
let pagePromise = waitForPdfJS(browser, url);
|
|
|
|
await Promise.all([
|
|
pagePromise,
|
|
loadPromise,
|
|
annotationPromise,
|
|
annotationEditorPromise,
|
|
]);
|
|
|
|
await SpecialPowers.spawn(browser, [layers], async function (layers) {
|
|
const { ContentTaskUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/ContentTaskUtils.sys.mjs"
|
|
);
|
|
const { document } = content;
|
|
|
|
for (let i = 0; i < layers.length; i++) {
|
|
await ContentTaskUtils.waitForCondition(
|
|
() =>
|
|
layers[i].every(
|
|
name =>
|
|
!!document.querySelector(
|
|
`.page[data-page-number='${i + 1}'] .${name}`
|
|
)
|
|
),
|
|
`All the layers must be displayed on page ${i}`
|
|
);
|
|
}
|
|
});
|
|
|
|
await TestUtils.waitForTick();
|
|
}
|
|
|
|
async function waitForPdfJSCanvas(browser, url) {
|
|
let loadPromise = BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"pagerendered",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
const pagePromise = waitForPdfJS(browser, url);
|
|
return Promise.all([pagePromise, loadPromise]);
|
|
}
|
|
|
|
async function waitForPdfJSSandbox(browser) {
|
|
let loadPromise = BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"sandboxcreated",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
return loadPromise;
|
|
}
|
|
|
|
async function waitForSelector(
|
|
browser,
|
|
selector,
|
|
message,
|
|
checkVisibility = true
|
|
) {
|
|
return SpecialPowers.spawn(
|
|
browser,
|
|
[selector, message, checkVisibility],
|
|
async function (sel, msg, checkVis) {
|
|
const { ContentTaskUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/ContentTaskUtils.sys.mjs"
|
|
);
|
|
const { document } = content;
|
|
await ContentTaskUtils.waitForCondition(
|
|
() => !!document.querySelector(sel),
|
|
`${sel} must be displayed`
|
|
);
|
|
|
|
if (checkVis) {
|
|
await ContentTaskUtils.waitForCondition(
|
|
() => ContentTaskUtils.isVisible(document.querySelector(sel)),
|
|
msg
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
async function click(browser, selector) {
|
|
await waitForSelector(browser, selector);
|
|
await SpecialPowers.spawn(browser, [selector], async function (sel) {
|
|
const el = content.document.querySelector(sel);
|
|
await new Promise(r => {
|
|
el.addEventListener("click", r, { once: true });
|
|
el.click();
|
|
});
|
|
});
|
|
}
|
|
|
|
async function waitForTelemetry(browser) {
|
|
await BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"reporttelemetry",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
await TestUtils.waitForTick();
|
|
}
|
|
|
|
/**
|
|
* Enable an editor (Ink, FreeText, ...).
|
|
* @param {Object} browser
|
|
* @param {string} name
|
|
*/
|
|
async function enableEditor(browser, name, expectedPageRendered) {
|
|
info("Enable editor: " + name);
|
|
let loadPromise;
|
|
if (expectedPageRendered !== 0) {
|
|
loadPromise = new Promise(resolve => {
|
|
const removeEventListener = BrowserTestUtils.addContentEventListener(
|
|
browser,
|
|
"pagerendered",
|
|
() => {
|
|
expectedPageRendered -= 1;
|
|
if (expectedPageRendered === 0) {
|
|
removeEventListener();
|
|
resolve();
|
|
}
|
|
},
|
|
{ capture: false, wantUntrusted: true }
|
|
);
|
|
});
|
|
}
|
|
|
|
const layerPromise = waitForSelector(
|
|
browser,
|
|
`.annotationEditorLayer.${name.toLowerCase()}Editing`,
|
|
"Wait for the annotation editor layer",
|
|
true
|
|
);
|
|
const editingModePromise = BrowserTestUtils.waitForContentEvent(
|
|
browser,
|
|
"annotationeditormodechanged",
|
|
false,
|
|
null,
|
|
true
|
|
);
|
|
await clickOn(browser, `#editor${name}`);
|
|
await editingModePromise;
|
|
await layerPromise;
|
|
if (loadPromise) {
|
|
await loadPromise;
|
|
}
|
|
await TestUtils.waitForTick();
|
|
}
|
|
|
|
/**
|
|
* The text layer contains some spans with the text of the pdf.
|
|
* @param {Object} browser
|
|
* @param {string} text
|
|
* @returns {Object} the bbox of the span containing the text.
|
|
*/
|
|
async function getSpanBox(browser, text, pageNumber = 1) {
|
|
return SpecialPowers.spawn(
|
|
browser,
|
|
[text, pageNumber],
|
|
async function (text, number) {
|
|
const { ContentTaskUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/ContentTaskUtils.sys.mjs"
|
|
);
|
|
const { document } = content;
|
|
|
|
await ContentTaskUtils.waitForCondition(
|
|
() =>
|
|
!!document.querySelector(
|
|
`.page[data-page-number='${number}'] .textLayer .endOfContent`
|
|
),
|
|
"The text layer must be displayed"
|
|
);
|
|
|
|
let targetSpan = null;
|
|
for (const span of document.querySelectorAll(
|
|
`.page[data-page-number='${number}'] .textLayer span`
|
|
)) {
|
|
if (span.innerText.includes(text)) {
|
|
targetSpan = span;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert.ok(!!targetSpan, `document must have a span containing '${text}'`);
|
|
|
|
const { x, y, width, height } = targetSpan.getBoundingClientRect();
|
|
return { x, y, width, height };
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Count the number of elements corresponding to the given selector.
|
|
* @param {Object} browser
|
|
* @param {string} selector
|
|
* @returns
|
|
*/
|
|
async function countElements(browser, selector) {
|
|
return SpecialPowers.spawn(browser, [selector], async function (selector) {
|
|
return new Promise(resolve => {
|
|
content.setTimeout(() => {
|
|
resolve(content.document.querySelectorAll(selector).length);
|
|
}, 0);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Click at the given coordinates.
|
|
* @param {Object} browser
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @param {number} n
|
|
*/
|
|
async function clickAt(browser, x, y, n = 1) {
|
|
info(`Click at: (${x}, ${y}), ${n} times`);
|
|
await BrowserTestUtils.synthesizeMouseAtPoint(
|
|
x,
|
|
y,
|
|
{
|
|
type: "mousedown",
|
|
button: 0,
|
|
clickCount: n,
|
|
},
|
|
browser
|
|
);
|
|
await BrowserTestUtils.synthesizeMouseAtPoint(
|
|
x,
|
|
y,
|
|
{
|
|
type: "mouseup",
|
|
button: 0,
|
|
clickCount: n,
|
|
},
|
|
browser
|
|
);
|
|
await TestUtils.waitForTick();
|
|
}
|
|
|
|
/**
|
|
* Click on the element corresponding to the given selector.
|
|
* @param {Object} browser
|
|
* @param {string} selector
|
|
*/
|
|
async function clickOn(browser, selector) {
|
|
await waitForSelector(browser, selector);
|
|
const [x, y] = await SpecialPowers.spawn(
|
|
browser,
|
|
[selector],
|
|
async selector => {
|
|
const element = content.document.querySelector(selector);
|
|
Assert.ok(
|
|
!!element,
|
|
`Element "${selector}" must be available in order to be clicked`
|
|
);
|
|
const { x, y, width, height } = element.getBoundingClientRect();
|
|
return [x + width / 2, y + height / 2];
|
|
}
|
|
);
|
|
await clickAt(browser, x, y);
|
|
}
|
|
|
|
function focusEditorLayer(browser) {
|
|
return focus(browser, ".annotationEditorLayer");
|
|
}
|
|
|
|
/**
|
|
* Focus an element corresponding to the given selector.
|
|
* @param {Object} browser
|
|
* @param {string} selector
|
|
* @returns
|
|
*/
|
|
async function focus(browser, selector) {
|
|
info("Focus: " + selector);
|
|
return SpecialPowers.spawn(
|
|
browser,
|
|
[selector],
|
|
sel =>
|
|
new Promise(resolve => {
|
|
const el = content.document.querySelector(sel);
|
|
if (el === content.document.activeElement) {
|
|
resolve();
|
|
} else {
|
|
el.addEventListener("focus", () => resolve(), { once: true });
|
|
el.focus();
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hit a key.
|
|
*/
|
|
async function hitKey(browser, char) {
|
|
await SpecialPowers.spawn(browser, [char], async function (char) {
|
|
const { ContentTaskUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/ContentTaskUtils.sys.mjs"
|
|
);
|
|
const EventUtils = ContentTaskUtils.getEventUtils(content);
|
|
await EventUtils.synthesizeKey(char, {}, content);
|
|
});
|
|
await TestUtils.waitForTick();
|
|
}
|
|
|
|
/**
|
|
* Write some text using the keyboard.
|
|
* @param {Object} browser
|
|
* @param {string} text
|
|
*/
|
|
async function write(browser, text) {
|
|
info(`Write: ${text}`);
|
|
for (const char of text.split("")) {
|
|
hitKey(browser, char);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hit escape key.
|
|
*/
|
|
async function escape(browser) {
|
|
info("Escape");
|
|
await hitKey(browser, "KEY_Escape");
|
|
}
|
|
|
|
/**
|
|
* Add a FreeText annotation and write some text inside.
|
|
* @param {Object} browser
|
|
* @param {string} text
|
|
* @param {Object} box
|
|
*/
|
|
async function addFreeText(browser, text, box) {
|
|
info("Add FreeText: " + text);
|
|
const { x, y, width, height } = box;
|
|
const count = await countElements(browser, ".freeTextEditor");
|
|
await focusEditorLayer(browser);
|
|
await clickAt(browser, x + 0.1 * width, y + 0.5 * height);
|
|
await waitForEditors(browser, ".freeTextEditor", count + 1);
|
|
|
|
await write(browser, text);
|
|
await escape(browser);
|
|
}
|
|
|
|
async function waitForEditors(browser, selector, count) {
|
|
info(`Wait for ${count} editors`);
|
|
await BrowserTestUtils.waitForCondition(
|
|
async () => (await countElements(browser, selector)) === count
|
|
);
|
|
}
|
|
|
|
function changeMimeHandler(preferredAction, alwaysAskBeforeHandling) {
|
|
let handlerService = Cc[
|
|
"@mozilla.org/uriloader/handler-service;1"
|
|
].getService(Ci.nsIHandlerService);
|
|
let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
|
let handlerInfo = mimeService.getFromTypeAndExtension(
|
|
"application/pdf",
|
|
"pdf"
|
|
);
|
|
var oldAction = [
|
|
handlerInfo.preferredAction,
|
|
handlerInfo.alwaysAskBeforeHandling,
|
|
];
|
|
|
|
// Change and save mime handler settings
|
|
handlerInfo.alwaysAskBeforeHandling = alwaysAskBeforeHandling;
|
|
handlerInfo.preferredAction = preferredAction;
|
|
handlerService.store(handlerInfo);
|
|
|
|
// Refresh data
|
|
handlerInfo = mimeService.getFromTypeAndExtension("application/pdf", "pdf");
|
|
|
|
// Test: Mime handler was updated
|
|
is(
|
|
handlerInfo.alwaysAskBeforeHandling,
|
|
alwaysAskBeforeHandling,
|
|
"always-ask prompt change successful"
|
|
);
|
|
is(
|
|
handlerInfo.preferredAction,
|
|
preferredAction,
|
|
"mime handler change successful"
|
|
);
|
|
|
|
return oldAction;
|
|
}
|
|
|
|
function createTemporarySaveDirectory(id = "") {
|
|
var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
|
saveDir.append(`testsavedir${id}`);
|
|
if (!saveDir.exists()) {
|
|
saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
|
}
|
|
return saveDir;
|
|
}
|
|
|
|
async function cleanupDownloads(listId = Downloads.PUBLIC) {
|
|
info("cleaning up downloads");
|
|
let downloadList = await Downloads.getList(listId);
|
|
for (let download of await downloadList.getAll()) {
|
|
await download.finalize(true);
|
|
try {
|
|
if (Services.appinfo.OS === "WINNT") {
|
|
// We need to make the file writable to delete it on Windows.
|
|
await IOUtils.setPermissions(download.target.path, 0o600);
|
|
}
|
|
await IOUtils.remove(download.target.path);
|
|
} catch (error) {
|
|
info("The file " + download.target.path + " is not removed, " + error);
|
|
}
|
|
|
|
await downloadList.remove(download);
|
|
await download.finalize();
|
|
}
|
|
}
|
|
|
|
function makePDFJSHandler() {
|
|
let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
|
let handlerInfo = mimeService.getFromTypeAndExtension(
|
|
"application/pdf",
|
|
"pdf"
|
|
);
|
|
|
|
// Make sure pdf.js is the default handler.
|
|
is(
|
|
handlerInfo.alwaysAskBeforeHandling,
|
|
false,
|
|
"pdf handler defaults to always-ask is false"
|
|
);
|
|
is(
|
|
handlerInfo.preferredAction,
|
|
Ci.nsIHandlerInfo.handleInternally,
|
|
"pdf handler defaults to internal"
|
|
);
|
|
|
|
info("Pref action: " + handlerInfo.preferredAction);
|
|
}
|
|
|
|
async function testTelemetryEventExtra(record, expected, clear = true) {
|
|
await Services.fog.testFlushAllChildren();
|
|
const values = record.testGetValue();
|
|
Assert.equal(values.length, expected.length, "Should have the length");
|
|
|
|
for (let i = 0; i < expected.length; i++) {
|
|
const value = values[i];
|
|
Assert.deepEqual(
|
|
value?.extra ?? {},
|
|
expected[i],
|
|
`Should have the right value at index ${i}`
|
|
);
|
|
}
|
|
if (clear) {
|
|
Services.fog.testResetFOG();
|
|
}
|
|
}
|
|
|
|
function waitForPreviewVisible() {
|
|
return BrowserTestUtils.waitForCondition(function () {
|
|
let preview = document.querySelector(".printPreviewBrowser");
|
|
return preview && BrowserTestUtils.isVisible(preview);
|
|
});
|
|
}
|
|
|
|
function closePreview() {
|
|
EventUtils.synthesizeKey("KEY_Escape");
|
|
return BrowserTestUtils.waitForCondition(
|
|
() => !document.querySelector(".printPreviewBrowser")
|
|
);
|
|
}
|
|
|
|
function waitForTimeout(browser, n) {
|
|
return SpecialPowers.spawn(browser, [n], n => {
|
|
const { promise, resolve } = Promise.withResolvers();
|
|
content.setTimeout(resolve, n);
|
|
return promise;
|
|
});
|
|
}
|