Revision control
Copy as Markdown
Other Tools
/* 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
/**
* PDFs opened from mailbox:// or imap:// attachments run in Thunderbird's
* the selection element's `backdrop-filter`, leaving the selection present
* but invisible.
*
* rendering so native selection highlighting is used instead. The
* mailbox test below will fail if the preference is re-enabled before bug
* 2070962 is fixed.
*/
const tabmail = document.getElementById("tabmail");
let testFolder;
add_setup(async function () {
const account = MailServices.accounts.createLocalMailAccount();
account.addIdentity(MailServices.accounts.createIdentity());
const rootFolder = account.incomingServer.rootFolder.QueryInterface(
Ci.nsIMsgLocalMailFolder
);
testFolder = rootFolder
.createLocalSubfolder("pdfsel")
.QueryInterface(Ci.nsIMsgLocalMailFolder);
testFolder.addMessage(
await IOUtils.readUTF8(getTestFilePath("files/pdfsel.eml"))
);
registerCleanupFunction(() => {
MailServices.accounts.removeAccount(account, false);
});
});
/**
* Open a URL in a content tab and wait for the pdf.js text layer to appear.
*
* @param {string} url
* @returns {object} The tab info.
*/
async function openInViewer(url) {
const tab = tabmail.openTab("contentTab", {
url,
background: false,
linkHandler: "single-page",
});
await TestUtils.waitForCondition(
() => tab.browser.currentURI?.spec != "about:blank",
"waiting for the tab to start loading"
);
await SpecialPowers.spawn(tab.browser, [], async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.querySelector(".textLayer span"),
"waiting for the pdf.js text layer"
);
});
return tab;
}
async function paint() {
await new Promise(resolve =>
window.requestAnimationFrame(() => window.requestAnimationFrame(resolve))
);
// There's no event for "the compositor has produced a frame", so settle.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(resolve => window.setTimeout(resolve, 300));
}
function setSelection(browser, enabled) {
return SpecialPowers.spawn(browser, [enabled], select => {
const selection = content.getSelection();
selection.removeAllRanges();
if (!select) {
return;
}
const span = Array.from(
content.document.querySelectorAll(".textLayer span")
).find(s => {
const rect = s.getBoundingClientRect();
return s.textContent.trim() && rect.width > 5 && rect.height > 5;
});
const range = content.document.createRange();
range.selectNodeContents(span);
selection.addRange(range);
});
}
async function frameToImageData(dataURI, region) {
const image = new Image();
image.src = dataURI;
await image.decode();
const canvas = new OffscreenCanvas(image.width, image.height);
const context = canvas.getContext("2d");
context.drawImage(image, 0, 0);
return context.getImageData(region.x, region.y, region.width, region.height);
}
/**
* Wait until the compositor stops producing frames, so that a later recording
* only contains the frames caused by the change under test.
*/
async function waitForIdleCompositor() {
await TestUtils.waitForCondition(async () => {
await window.windowUtils.startCompositionRecording();
await paint();
const collected = await window.windowUtils.stopCompositionRecording(false);
return (collected?.frames ?? []).length == 0;
}, "waiting for the compositor to go idle");
}
/**
* Settle on the opposite selection state, then record the composited output
* while switching to `selected`, and return the pixels of `region` from the
* last frame.
*
* @param {XULElement} browser
* @param {boolean} selected - Whether text should end up selected.
* @param {object} region - Device-pixel rect to read back.
* @returns {ImageData}
*/
async function regionWithSelection(browser, selected, region) {
await setSelection(browser, !selected);
await paint();
await waitForIdleCompositor();
await window.windowUtils.startCompositionRecording();
await setSelection(browser, selected);
await paint();
const collected = await window.windowUtils.stopCompositionRecording(false);
const frames = collected?.frames ?? [];
Assert.greater(
frames.length,
0,
`toggling the selection ${selected ? "on" : "off"} should compose a frame`
);
return frameToImageData(frames.at(-1).dataUri, region);
}
/**
* Whether selecting text changes what the compositor puts on screen where the
* selected text is.
*
* @param {XULElement} browser
* @returns {boolean}
*/
async function selectionIsPainted(browser) {
await waitForIdleCompositor();
const spanRect = await SpecialPowers.spawn(browser, [], () => {
const span = Array.from(
content.document.querySelectorAll(".textLayer span")
).find(s => {
const rect = s.getBoundingClientRect();
return s.textContent.trim() && rect.width > 5 && rect.height > 5;
});
return span.getBoundingClientRect().toJSON();
});
const browserRect = browser.getBoundingClientRect();
const scale = window.devicePixelRatio;
const region = {
x: Math.floor((browserRect.x + spanRect.x) * scale),
y: Math.floor((browserRect.y + spanRect.y) * scale),
width: Math.ceil(spanRect.width * scale),
height: Math.ceil(spanRect.height * scale),
};
const unselected = await regionWithSelection(browser, false, region);
const selected = await regionWithSelection(browser, true, region);
for (let i = 0; i < unselected.data.length; i++) {
if (unselected.data[i] != selected.data[i]) {
return true;
}
}
return false;
}
add_task(async function testFileURL() {
const url =
Services.io.newFileURI(
new FileUtils.File(getTestFilePath("files/pdfsel.pdf"))
).spec + "?type=application/pdf";
const tab = await openInViewer(url);
Assert.ok(
tab.browser.isRemoteBrowser,
"a file:// pdf should load in a content process"
);
Assert.ok(
await selectionIsPainted(tab.browser),
"selecting text in a file:// pdf should be visible on screen"
);
tabmail.closeTab(tab);
});
add_task(async function testMailboxURL() {
const about3Pane = tabmail.currentAbout3Pane;
about3Pane.displayFolder(testFolder);
about3Pane.threadTree.selectedIndex = 0;
await TestUtils.waitForCondition(
() =>
about3Pane.messageBrowser.contentWindow.currentAttachments?.length == 1,
"waiting for the attachment to be listed"
);
const attachment =
about3Pane.messageBrowser.contentWindow.currentAttachments[0];
let url = attachment.url;
if (!url.includes("type=application/pdf")) {
url += (url.includes("?") ? "&" : "?") + "type=application/pdf";
}
const tab = await openInViewer(url);
const selection = await SpecialPowers.spawn(tab.browser, [], () => {
const span = Array.from(
content.document.querySelectorAll(".textLayer span")
).find(s => s.textContent.trim());
const range = content.document.createRange();
range.selectNodeContents(span);
content.getSelection().addRange(range);
return content.getSelection().toString();
});
Assert.equal(
selection,
"Test text TRY TO SELECT test text",
"text in a mailbox:// pdf should be selectable"
);
Assert.ok(
await selectionIsPainted(tab.browser),
"selecting text in a mailbox:// pdf should be visible on screen"
);
tabmail.closeTab(tab);
});