Skip to content

Commit

Permalink
Only call the focus/blur callbacks when it's necessary (bug 1851517)
Browse files Browse the repository at this point in the history
Focus callback must be called only when the element has been blurred.
For example, blur callback (which implies some potential validation) is not called
because the newly focused element is an other tab, an alert dialog, ... so consequently
the focus callback mustn't be called when the element gets its focus back.
  • Loading branch information
calixteman authored and pull[bot] committed Feb 7, 2024
1 parent 507cfda commit 1618955
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 5 deletions.
51 changes: 46 additions & 5 deletions src/display/annotation_layer.js
Expand Up @@ -1013,7 +1013,7 @@ class WidgetAnnotationElement extends AnnotationElement {
return (isWin && event.ctrlKey) || (isMac && event.metaKey);
}

_setEventListener(element, baseName, eventName, valueGetter) {
_setEventListener(element, elementData, baseName, eventName, valueGetter) {
if (baseName.includes("mouse")) {
// Mouse events
element.addEventListener(baseName, event => {
Expand All @@ -1029,8 +1029,24 @@ class WidgetAnnotationElement extends AnnotationElement {
});
});
} else {
// Non mouse event
// Non-mouse events
element.addEventListener(baseName, event => {
if (baseName === "blur") {
if (!elementData.focused || !event.relatedTarget) {
return;
}
elementData.focused = false;
} else if (baseName === "focus") {
if (elementData.focused) {
return;
}
elementData.focused = true;
}

if (!valueGetter) {
return;
}

this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
Expand All @@ -1043,10 +1059,25 @@ class WidgetAnnotationElement extends AnnotationElement {
}
}

_setEventListeners(element, names, getter) {
_setEventListeners(element, elementData, names, getter) {
for (const [baseName, eventName] of names) {
if (eventName === "Action" || this.data.actions?.[eventName]) {
this._setEventListener(element, baseName, eventName, getter);
if (eventName === "Focus" || eventName === "Blur") {
elementData ||= { focused: false };
}
this._setEventListener(
element,
elementData,
baseName,
eventName,
getter
);
if (eventName === "Focus" && !this.data.actions?.Blur) {
// Ensure that elementData will have the correct value.
this._setEventListener(element, elementData, "blur", "Blur", null);
} else if (eventName === "Blur" && !this.data.actions?.Focus) {
this._setEventListener(element, elementData, "focus", "Focus", null);
}
}
}
}
Expand Down Expand Up @@ -1178,6 +1209,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
formattedValue: fieldFormattedValues,
lastCommittedValue: null,
commitKey: 1,
focused: false,
};

if (this.data.multiLine) {
Expand Down Expand Up @@ -1238,12 +1270,16 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {

if (this.enableScripting && this.hasJSActions) {
element.addEventListener("focus", event => {
if (elementData.focused) {
return;
}
const { target } = event;
if (elementData.userValue) {
target.value = elementData.userValue;
}
elementData.lastCommittedValue = target.value;
elementData.commitKey = 1;
elementData.focused = true;
});

element.addEventListener("updatefromsandbox", jsEvent => {
Expand Down Expand Up @@ -1349,9 +1385,10 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
const _blurListener = blurListener;
blurListener = null;
element.addEventListener("blur", event => {
if (!event.relatedTarget) {
if (!elementData.focused || !event.relatedTarget) {
return;
}
elementData.focused = false;
const { value } = event.target;
elementData.userValue = value;
if (elementData.lastCommittedValue !== value) {
Expand Down Expand Up @@ -1431,6 +1468,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {

this._setEventListeners(
element,
elementData,
[
["focus", "Focus"],
["blur", "Blur"],
Expand Down Expand Up @@ -1540,6 +1578,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {

this._setEventListeners(
element,
null,
[
["change", "Validate"],
["change", "Action"],
Expand Down Expand Up @@ -1630,6 +1669,7 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {

this._setEventListeners(
element,
null,
[
["change", "Validate"],
["change", "Action"],
Expand Down Expand Up @@ -1894,6 +1934,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {

this._setEventListeners(
selectElement,
null,
[
["focus", "Focus"],
["blur", "Blur"],
Expand Down
46 changes: 46 additions & 0 deletions test/integration/scripting_spec.js
Expand Up @@ -2154,4 +2154,50 @@ describe("Interaction", () => {
);
});
});

describe("Textfields and focus", () => {
let pages;
let otherPages;

beforeAll(async () => {
otherPages = await Promise.all(
global.integrationSessions.map(async session =>
session.browser.newPage()
)
);
pages = await loadAndWait("evaljs.pdf", getSelector("55R"));
});

afterAll(async () => {
await closePages(pages);
await Promise.all(otherPages.map(page => page.close()));
});

it("must check that focus/blur callbacks aren't called", async () => {
await Promise.all(
pages.map(async ([browserName, page], i) => {
await page.waitForFunction(
"window.PDFViewerApplication.scriptingReady === true"
);

await page.click(getSelector("55R"));
await page.type(getSelector("55R"), "Hello", { delay: 10 });
await page.click(getSelector("56R"));
await page.waitForTimeout(10);

await page.click(getSelector("55R"));
await page.type(getSelector("55R"), " World", { delay: 10 });
await page.waitForTimeout(10);

await otherPages[i].bringToFront();
await otherPages[i].waitForTimeout(100);
await page.bringToFront();
await page.waitForTimeout(100);

const text = await page.$eval(getSelector("55R"), el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("Hello World");
})
);
});
});
});

0 comments on commit 1618955

Please sign in to comment.