Merge pull request #21687 from calixteman/fix/quad-regex-delete-word

Scan backwards to delete a word in a text field
This commit is contained in:
Tim van der Meij 2026-08-02 13:04:00 +02:00 committed by GitHub
commit d0779c411e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 78 additions and 5 deletions

View File

@ -1811,11 +1811,21 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
switch (event.inputType) {
// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
case "deleteWordBackward": {
const match = value
.substring(0, selectionStart)
.match(/\w*\W*$/);
if (match) {
selStart -= match[0].length;
// The previous unanchored regex could take quadratic time, so
// scan backwards over the trailing non-word characters and
// then the word.
const wordCharPattern = /\w/;
while (
selStart > 0 &&
!wordCharPattern.test(value[selStart - 1])
) {
selStart--;
}
while (
selStart > 0 &&
wordCharPattern.test(value[selStart - 1])
) {
selStart--;
}
break;
}

View File

@ -1204,6 +1204,69 @@ describe("Interaction", () => {
);
});
it("must efficiently delete a word from a large field", async () => {
const nonWordLength = 200000;
await Promise.all(
pages.map(async ([browserName, page]) => {
await waitForScripting(page);
const result = await page.$eval(
getSelector("27R"),
(element, length) => {
element.value = `${"!".repeat(length)}a`;
element.setSelectionRange(
element.value.length,
element.value.length
);
const eventBus = window.PDFViewerApplication.eventBus;
const eventBusPrototype = Object.getPrototypeOf(eventBus);
const originalDispatch = eventBusPrototype.dispatch;
let selection;
eventBusPrototype.dispatch = function (eventName, data) {
if (
this === eventBus &&
eventName === "dispatcheventinsandbox"
) {
const { selEnd, selStart } = data.detail;
selection = { selEnd, selStart };
return;
}
originalDispatch.call(this, eventName, data);
};
const event = new InputEvent("beforeinput", {
bubbles: true,
cancelable: true,
inputType: "deleteWordBackward",
});
try {
const startTime = performance.now();
element.dispatchEvent(event);
return {
duration: performance.now() - startTime,
selection,
};
} finally {
eventBusPrototype.dispatch = originalDispatch;
}
},
nonWordLength
);
expect(result.selection)
.withContext(`In ${browserName}`)
.toEqual({
selEnd: nonWordLength + 1,
selStart: nonWordLength,
});
expect(result.duration)
.withContext(`In ${browserName}`)
.toBeLessThan(1000);
})
);
});
it("must check that an infinite loop is not triggered", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {