Merge pull request #20096 from calixteman/bug1708041
Make the link annotations correctly announced by screen readers (bug 1708041)
This commit is contained in:
commit
daf2cbbfe9
@ -3833,6 +3833,10 @@ class LinkAnnotation extends Annotation {
|
||||
docAttachments: annotationGlobals.attachments,
|
||||
});
|
||||
}
|
||||
|
||||
get overlaysTextContent() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class PopupAnnotation extends Annotation {
|
||||
|
||||
@ -24,7 +24,7 @@ class SingleIntersector {
|
||||
|
||||
#maxY = -Infinity;
|
||||
|
||||
#quadPoints;
|
||||
#quadPoints = null;
|
||||
|
||||
#text = [];
|
||||
|
||||
@ -36,7 +36,13 @@ class SingleIntersector {
|
||||
|
||||
constructor(annotation) {
|
||||
this.#annotation = annotation;
|
||||
const quadPoints = (this.#quadPoints = annotation.data.quadPoints);
|
||||
const quadPoints = annotation.data.quadPoints;
|
||||
if (!quadPoints) {
|
||||
// If there are no quad points, we use the rectangle to determine the
|
||||
// bounds of the annotation.
|
||||
[this.#minX, this.#minY, this.#maxX, this.#maxY] = annotation.data.rect;
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0, ii = quadPoints.length; i < ii; i += 8) {
|
||||
this.#minX = Math.min(this.#minX, quadPoints[i]);
|
||||
@ -44,6 +50,9 @@ class SingleIntersector {
|
||||
this.#minY = Math.min(this.#minY, quadPoints[i + 5]);
|
||||
this.#maxY = Math.max(this.#maxY, quadPoints[i + 1]);
|
||||
}
|
||||
if (quadPoints.length > 8) {
|
||||
this.#quadPoints = quadPoints;
|
||||
}
|
||||
}
|
||||
|
||||
overlaps(other) {
|
||||
@ -73,7 +82,7 @@ class SingleIntersector {
|
||||
}
|
||||
|
||||
const quadPoints = this.#quadPoints;
|
||||
if (quadPoints.length === 8) {
|
||||
if (!quadPoints) {
|
||||
// We've only one quad, so if we intersect min/max bounds then we
|
||||
// intersect the quad.
|
||||
return true;
|
||||
@ -150,7 +159,7 @@ class Intersector {
|
||||
|
||||
constructor(annotations) {
|
||||
for (const annotation of annotations) {
|
||||
if (!annotation.data.quadPoints) {
|
||||
if (!annotation.data.quadPoints && !annotation.data.rect) {
|
||||
continue;
|
||||
}
|
||||
const intersector = new SingleIntersector(annotation);
|
||||
|
||||
@ -276,7 +276,10 @@ class AnnotationElement {
|
||||
|
||||
const container = document.createElement("section");
|
||||
container.setAttribute("data-annotation-id", data.id);
|
||||
if (!(this instanceof WidgetAnnotationElement)) {
|
||||
if (
|
||||
!(this instanceof WidgetAnnotationElement) &&
|
||||
!(this instanceof LinkAnnotationElement)
|
||||
) {
|
||||
container.tabIndex = 0;
|
||||
}
|
||||
const { style } = container;
|
||||
@ -797,16 +800,21 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
linkService.addLinkAttributes(link, data.url, data.newWindow);
|
||||
isBound = true;
|
||||
} else if (data.action) {
|
||||
this._bindNamedAction(link, data.action);
|
||||
this._bindNamedAction(link, data.action, data.overlaidText);
|
||||
isBound = true;
|
||||
} else if (data.attachment) {
|
||||
this.#bindAttachment(link, data.attachment, data.attachmentDest);
|
||||
this.#bindAttachment(
|
||||
link,
|
||||
data.attachment,
|
||||
data.overlaidText,
|
||||
data.attachmentDest
|
||||
);
|
||||
isBound = true;
|
||||
} else if (data.setOCGState) {
|
||||
this.#bindSetOCGState(link, data.setOCGState);
|
||||
this.#bindSetOCGState(link, data.setOCGState, data.overlaidText);
|
||||
isBound = true;
|
||||
} else if (data.dest) {
|
||||
this._bindLink(link, data.dest);
|
||||
this._bindLink(link, data.dest, data.overlaidText);
|
||||
isBound = true;
|
||||
} else {
|
||||
if (
|
||||
@ -848,9 +856,10 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
* @private
|
||||
* @param {Object} link
|
||||
* @param {Object} destination
|
||||
* @param {string} [overlaidText]
|
||||
* @memberof LinkAnnotationElement
|
||||
*/
|
||||
_bindLink(link, destination) {
|
||||
_bindLink(link, destination, overlaidText = "") {
|
||||
link.href = this.linkService.getDestinationHash(destination);
|
||||
link.onclick = () => {
|
||||
if (destination) {
|
||||
@ -861,6 +870,9 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
if (destination || destination === /* isTooltipOnly = */ "") {
|
||||
this.#setInternalLink();
|
||||
}
|
||||
if (overlaidText) {
|
||||
link.title = overlaidText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -869,14 +881,18 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
* @private
|
||||
* @param {Object} link
|
||||
* @param {Object} action
|
||||
* @param {string} [overlaidText]
|
||||
* @memberof LinkAnnotationElement
|
||||
*/
|
||||
_bindNamedAction(link, action) {
|
||||
_bindNamedAction(link, action, overlaidText = "") {
|
||||
link.href = this.linkService.getAnchorUrl("");
|
||||
link.onclick = () => {
|
||||
this.linkService.executeNamedAction(action);
|
||||
return false;
|
||||
};
|
||||
if (overlaidText) {
|
||||
link.title = overlaidText;
|
||||
}
|
||||
this.#setInternalLink();
|
||||
}
|
||||
|
||||
@ -884,12 +900,15 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
* Bind attachments to the link element.
|
||||
* @param {Object} link
|
||||
* @param {Object} attachment
|
||||
* @param {str} [dest]
|
||||
* @param {string} [overlaidText]
|
||||
* @param {string} [dest]
|
||||
*/
|
||||
#bindAttachment(link, attachment, dest = null) {
|
||||
#bindAttachment(link, attachment, overlaidText = "", dest = null) {
|
||||
link.href = this.linkService.getAnchorUrl("");
|
||||
if (attachment.description) {
|
||||
link.title = attachment.description;
|
||||
} else if (overlaidText) {
|
||||
link.title = overlaidText;
|
||||
}
|
||||
link.onclick = () => {
|
||||
this.downloadManager?.openOrDownloadData(
|
||||
@ -906,13 +925,17 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
* Bind SetOCGState actions to the link element.
|
||||
* @param {Object} link
|
||||
* @param {Object} action
|
||||
* @param {string} [overlaidText]
|
||||
*/
|
||||
#bindSetOCGState(link, action) {
|
||||
#bindSetOCGState(link, action, overlaidText = "") {
|
||||
link.href = this.linkService.getAnchorUrl("");
|
||||
link.onclick = () => {
|
||||
this.linkService.executeSetOCGState(action);
|
||||
return false;
|
||||
};
|
||||
if (overlaidText) {
|
||||
link.title = overlaidText;
|
||||
}
|
||||
this.#setInternalLink();
|
||||
}
|
||||
|
||||
@ -947,6 +970,9 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
if (data.overlaidText) {
|
||||
link.title = data.overlaidText;
|
||||
}
|
||||
|
||||
if (!link.onclick) {
|
||||
link.onclick = () => false;
|
||||
|
||||
@ -247,6 +247,51 @@ describe("Text widget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Link annotations with internal destinations", () => {
|
||||
describe("bug1708041.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"bug1708041.pdf",
|
||||
".page[data-page-number='1'] .annotationLayer"
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must click on a link and check if it navigates to the correct page", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const pageOneSelector = ".page[data-page-number='1']";
|
||||
const linkSelector = `${pageOneSelector} #pdfjs_internal_id_42R`;
|
||||
await page.waitForSelector(linkSelector);
|
||||
const linkTitle = await page.$eval(linkSelector, el => el.title);
|
||||
expect(linkTitle)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("Go to the last page");
|
||||
await page.click(linkSelector);
|
||||
const pageSixTextLayerSelector =
|
||||
".page[data-page-number='6'] .textLayer";
|
||||
await page.waitForSelector(pageSixTextLayerSelector, {
|
||||
visible: true,
|
||||
});
|
||||
await page.waitForFunction(
|
||||
sel => {
|
||||
const textLayer = document.querySelector(sel);
|
||||
return document.activeElement === textLayer;
|
||||
},
|
||||
{},
|
||||
pageSixTextLayerSelector
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Annotation and storage", () => {
|
||||
describe("issue14023.pdf", () => {
|
||||
let pages;
|
||||
|
||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -734,3 +734,4 @@
|
||||
!issue20062.pdf
|
||||
!issue20102.pdf
|
||||
!issue20065.pdf
|
||||
!bug1708041.pdf
|
||||
|
||||
BIN
test/pdfs/bug1708041.pdf
Normal file
BIN
test/pdfs/bug1708041.pdf
Normal file
Binary file not shown.
@ -192,6 +192,18 @@ class PDFLinkService {
|
||||
destArray: explicitDest,
|
||||
ignoreDestinationZoom: this._ignoreDestinationZoom,
|
||||
});
|
||||
|
||||
const ac = new AbortController();
|
||||
this.eventBus._on(
|
||||
"textlayerrendered",
|
||||
evt => {
|
||||
if (evt.pageNumber === pageNumber) {
|
||||
evt.source.textLayer.div.focus();
|
||||
ac.abort();
|
||||
}
|
||||
},
|
||||
{ signal: ac.signal }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user