Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/display/editor/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -1480,8 +1480,12 @@ class AnnotationEditorUIManager {
if (!boxes) {
return;
}
const parent = textLayer.parentElement;
if (!parent) {
return;
}
this.#floatingToolbar ||= new FloatingToolbar(this);
this.#floatingToolbar.show(textLayer, boxes, this.direction === "ltr");
this.#floatingToolbar.show(parent, boxes, this.direction === "ltr");
}

/**
Expand Down
52 changes: 2 additions & 50 deletions test/integration/freetext_editor_spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,17 @@ import {
awaitPromise,
clearEditors,
closePages,
commit,
copy,
copyToClipboard,
countSerialized,
countStorageEntries,
createFreeTextEditor,
createPromise,
dragAndDrop,
firstPageOnTop,
getAnnotationSelector,
getEditors,
getEditorSelector,
getFirstSerialized,
getNextEditorId,
getRect,
getSerialized,
isCanvasMonochrome,
Expand Down Expand Up @@ -67,55 +66,8 @@ const selectAll = selectEditors.bind(null, "freeText");

const clearAll = clearEditors.bind(null, "freeText");

const commit = async page => {
await page.keyboard.press("Escape");
await page.waitForSelector(".freeTextEditor.selectedEditor .overlay.enabled");
};

const switchToFreeText = switchToEditor.bind(null, "FreeText");

const cancelFocusIn = async (page, selector) => {
page.evaluate(sel => {
const el = document.querySelector(sel);
el.addEventListener(
"focusin",
evt => {
evt.preventDefault();
evt.stopPropagation();
},
{ capture: true, once: true }
);
}, selector);
};

const createFreeTextEditor = async ({
page,
x,
y,
data = null,
noFocusIn = false,
}) => {
const editorSelector = getEditorSelector(await getNextEditorId(page));
const serializedCount = await countSerialized(page);
const storageEntriesCount = await countStorageEntries(page);

await page.mouse.click(x, y);
await page.waitForSelector(editorSelector, { visible: true });
if (data) {
await page.type(`${editorSelector} .internal`, data);
}
if (noFocusIn) {
await cancelFocusIn(page, editorSelector);
}
await commit(page);

await waitForSelectedEditor(page, editorSelector);
await waitForStorageEntries(page, storageEntriesCount + 1);
await waitForSerialized(page, serializedCount + 1);

return editorSelector;
};

describe("FreeText Editor", () => {
describe("FreeText", () => {
let pages;
Expand Down
130 changes: 130 additions & 0 deletions test/integration/highlight_editor_spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import {
awaitPromise,
closePages,
createFreeTextEditor,
getAnnotationSelector,
getEditorSelector,
getFirstSerialized,
Expand Down Expand Up @@ -53,6 +54,8 @@ const selectAll = selectEditors.bind(null, "highlight");

const switchToHighlight = switchToEditor.bind(null, "Highlight");

const switchToFreeText = switchToEditor.bind(null, "FreeText");

describe("Highlight Editor", () => {
describe("Editor must be removed without exception", () => {
let pages;
Expand Down Expand Up @@ -2931,4 +2934,131 @@ describe("Highlight Editor", () => {
});
});
});

describe("editToolbar is rendering over annotations", () => {
let pages;

beforeEach(async () => {
pages = await loadAndWait(
"toolbar-overlap-with-annotations.pdf",
".annotationEditorLayer",
120
);
});

afterEach(async () => {
await closePages(pages);
});

it("must check that the edit toolbar is rendered above link annotations", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.waitForSelector(
`.page[data-page-number = "1"] .textLayer .endOfContent`
);

const linkSelector = `a[href="https://github.com/mozilla/pdf.js"]`;
const linkRect = await getRect(page, linkSelector);

const topElementId = await page.evaluate(
(px, py) => document.elementFromPoint(px, py)?.id || null,
linkRect.x + linkRect.width / 2,
linkRect.y + linkRect.height / 2
);
expect(topElementId)
.withContext(`In ${browserName}`)
.toEqual("pdfjs_internal_id_14R");

// Select some text to show the floating toolbar.
const firstRect = await getSpanRectFromText(page, 1, "A:");
await page.mouse.click(firstRect.x, firstRect.y, {
count: 2,
delay: 100,
});

const highlightButtonSelector = `.page[data-page-number = "1"] .editToolbar button.highlightButton`;
await page.waitForSelector(highlightButtonSelector, {
visible: true,
});

const topElementClass = await page.evaluate(
(px, py) => document.elementFromPoint(px, py)?.className || null,
linkRect.x,
linkRect.y
);
expect(topElementClass)
.withContext(`In ${browserName}`)
.toContain("highlightButton");
})
);
});

it("must check that the edit toolbar is rendered above text annotations", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await switchToFreeText(page);

const myText = await getSpanRectFromText(page, 1, "My text");
// "CHECK" is roughly 50px wide at the default font size and
// "CHECK" roughly aligned with the end of "Languages".
const editorSelector = await createFreeTextEditor({
page,
x: myText.x + myText.width / 2,
y: myText.y + myText.height + 20,
data: "CHECK",
});

const freeTextRect = await getRect(page, editorSelector);
const freeTextTopElement = await page.evaluate(
(px, py) =>
document.elementFromPoint(px, py)?.getAttribute("data-l10n-id") ||
null,
freeTextRect.x + freeTextRect.width / 2,
freeTextRect.y + freeTextRect.height / 2
);
expect(freeTextTopElement)
.withContext(`In ${browserName}`)
.toEqual("pdfjs-free-text2");

// Close the text editor.
await switchToFreeText(page, /* disable */ true);

// Double click on "myText" to show the floating toolbar.
await page.mouse.click(
myText.x + myText.width / 2,
myText.y + myText.height / 2,
{ count: 2, delay: 100 }
);

const toolbarSelector = `.page[data-page-number = "1"] .editToolbar:has(button.highlightButton)`;
const highlightButtonSelector = `${toolbarSelector} button.highlightButton`;
await page.waitForSelector(highlightButtonSelector, {
visible: true,
});

let topElement = await page.evaluate(
(px, py) => document.elementFromPoint(px, py)?.className || null,
freeTextRect.x + freeTextRect.width / 2,
freeTextRect.y + freeTextRect.height / 2
);
expect(topElement)
.withContext(`In ${browserName}`)
.toContain("highlightButton");

// Re-open the text editor without dismissing the floating toolbar,
// so both are present on the page at the same time.
await switchToFreeText(page);

topElement = await page.evaluate(
(px, py) => document.elementFromPoint(px, py)?.className || null,
freeTextRect.x + freeTextRect.width / 2,
freeTextRect.y + freeTextRect.height / 2
);
expect(topElement)
.withContext(`In ${browserName}`)
.toContain("highlightButton");
})
);
});
});
});
49 changes: 49 additions & 0 deletions test/integration/test_utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,53 @@ async function waitForUnselectedEditor(page, selector) {
return page.waitForSelector(`${selector}:not(.selectedEditor)`);
}

async function commit(page) {
await page.keyboard.press("Escape");
await page.waitForSelector(".freeTextEditor.selectedEditor .overlay.enabled");
}

async function cancelFocusIn(page, selector) {
page.evaluate(sel => {
const el = document.querySelector(sel);
el.addEventListener(
"focusin",
evt => {
evt.preventDefault();
evt.stopPropagation();
},
{ capture: true, once: true }
);
}, selector);
}

async function createFreeTextEditor({
page,
x,
y,
data = null,
noFocusIn = false,
}) {
const editorSelector = getEditorSelector(await getNextEditorId(page));
const serializedCount = await countSerialized(page);
const storageEntriesCount = await countStorageEntries(page);

await page.mouse.click(x, y);
await page.waitForSelector(editorSelector, { visible: true });
if (data) {
await page.type(`${editorSelector} .internal`, data);
}
if (noFocusIn) {
await cancelFocusIn(page, editorSelector);
}
await commit(page);

await waitForSelectedEditor(page, editorSelector);
await waitForStorageEntries(page, storageEntriesCount + 1);
await waitForSerialized(page, serializedCount + 1);

return editorSelector;
}

async function mockClipboard(pages) {
return Promise.all(
pages.map(async ([_, page]) => {
Expand Down Expand Up @@ -1189,10 +1236,12 @@ export {
clearInput,
closePages,
closeSinglePage,
commit,
copy,
copyToClipboard,
countSerialized,
countStorageEntries,
createFreeTextEditor,
createPromise,
createPromiseWithArgs,
dragAndDrop,
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@
!issue11473.pdf
!bug1001080.pdf
!issue15716.pdf
!toolbar-overlap-with-annotations.pdf
!bug1671312_reduced.pdf
!bug1671312_ArialNarrow.pdf
!issue17848.pdf
Expand Down
Binary file added test/pdfs/toolbar-overlap-with-annotations.pdf
Binary file not shown.
7 changes: 7 additions & 0 deletions test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,13 @@
"rounds": 1,
"type": "eq"
},
{
"id": "toolbar-overlap-with-annotations",
"file": "pdfs/toolbar-overlap-with-annotations.pdf",
"md5": "33f76794ba75dfcf0938267c25b8df14",
"rounds": 1,
"type": "eq"
},
{
"id": "unix01-partial",
"file": "pdfs/unix01.pdf",
Expand Down
5 changes: 4 additions & 1 deletion web/annotation_editor_layer_builder.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
font-size: calc(100px * var(--total-scale-factor));
transform-origin: 0 0;
cursor: auto;
isolation: isolate;

.selectedEditor {
z-index: 100000 !important;
Expand Down Expand Up @@ -241,7 +242,7 @@
.highlightEditor,
.signatureEditor
),
.textLayer {
.page {
.editToolbar {
--editor-toolbar-delete-image: url(images/editor-toolbar-delete.svg);
--editor-toolbar-bg-color: light-dark(#f0f0f4, #2b2a33);
Expand Down Expand Up @@ -329,6 +330,8 @@
position: absolute;
inset-inline-end: 0;
inset-block-start: calc(100% + var(--editor-toolbar-vert-offset));
z-index: 2;
isolation: isolate;

border-radius: 6px;
background-color: var(--editor-toolbar-bg-color);
Expand Down
2 changes: 2 additions & 0 deletions web/annotation_layer_builder.css
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
left: 0;
pointer-events: none;
transform-origin: 0 0;
z-index: 1;
isolation: isolate;

&[data-main-rotation="90"] .norotate {
transform: rotate(270deg) translateX(-100%);
Expand Down
Loading