Start using Iterator.prototype.join in the code-base

This is an upcoming JavaScript feature, which helps avoid creating intermediate Arrays in some cases; see https://github.com/tc39/proposal-iterator-join

Firefox implemented this in [bug 2004803](https://bugzilla.mozilla.org/show_bug.cgi?id=2004803), and it was enabled by default in [bug 2047995](https://bugzilla.mozilla.org/show_bug.cgi?id=2047995).

This patch changes two Objects to Maps, in the XFA-parsing respectively the find-implementation, to make use of the new feature.
This commit is contained in:
Jonas Jenwald 2026-07-16 19:00:34 +02:00
parent 16290afa3b
commit 9a7335d26b
5 changed files with 48 additions and 37 deletions

View File

@ -1280,13 +1280,13 @@ class PDFDocument {
if (!streams) { if (!streams) {
return null; return null;
} }
const data = Object.create(null); const data = new Map();
for (const [key, stream] of streams) { for (const [key, stream] of streams) {
if (!stream) { if (!stream) {
continue; continue;
} }
try { try {
data[key] = stringToUTF8String(stream.getString()); data.set(key, stringToUTF8String(stream.getString()));
} catch { } catch {
warn("XFA - Invalid utf-8 string."); warn("XFA - Invalid utf-8 string.");
return null; return null;

View File

@ -132,10 +132,7 @@ class XFAFactory {
} }
static _createDocument(data) { static _createDocument(data) {
if (!data["/xdp:xdp"]) { return !data.get("/xdp:xdp") ? data.get("xdp:xdp") : data.values().join("");
return data["xdp:xdp"];
}
return Object.values(data).join("");
} }
static getRichTextAsHtml(rc) { static getRichTextAsHtml(rc) {

View File

@ -1159,6 +1159,14 @@ if (
}; };
} }
// TODO: Remove this once `Iterator.prototype.join` is generally available.
if (typeof Iterator.prototype.join !== "function") {
// eslint-disable-next-line no-extend-native
Iterator.prototype.join = function (separator) {
return [...this].join(separator);
};
}
export { export {
_isValidExplicitDest, _isValidExplicitDest,
AbortException, AbortException,

View File

@ -82,7 +82,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
factory.setFonts([]); factory.setFonts([]);
expect(await factory.getNumPages()).toEqual(2); expect(await factory.getNumPages()).toEqual(2);
@ -168,7 +168,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -202,7 +202,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -256,7 +256,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
factory.setFonts([]); factory.setFonts([]);
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -330,7 +330,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -372,7 +372,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -414,7 +414,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -457,7 +457,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -511,7 +511,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -555,7 +555,9 @@ describe("XFAFactory", function () {
let factory, pages, a; let factory, pages, a;
// A valid, and complete, URL. // A valid, and complete, URL.
factory = new XFAFactory({ "xdp:xdp": getXml("https://www.example.com/") }); factory = new XFAFactory(
new Map([["xdp:xdp", getXml("https://www.example.com/")]])
);
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
pages = await factory.getPages(); pages = await factory.getPages();
a = searchHtmlNode(pages, "name", "a"); a = searchHtmlNode(pages, "name", "a");
@ -563,7 +565,9 @@ describe("XFAFactory", function () {
expect(a.attributes.href).toEqual("https://www.example.com/"); expect(a.attributes.href).toEqual("https://www.example.com/");
// A valid, but incomplete, URL. // A valid, but incomplete, URL.
factory = new XFAFactory({ "xdp:xdp": getXml("www.example.com/") }); factory = new XFAFactory(
new Map([["xdp:xdp", getXml("www.example.com/")]])
);
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
pages = await factory.getPages(); pages = await factory.getPages();
a = searchHtmlNode(pages, "name", "a"); a = searchHtmlNode(pages, "name", "a");
@ -571,7 +575,9 @@ describe("XFAFactory", function () {
expect(a.attributes.href).toEqual("http://www.example.com/"); expect(a.attributes.href).toEqual("http://www.example.com/");
// A valid email-address. // A valid email-address.
factory = new XFAFactory({ "xdp:xdp": getXml("mailto:test@example.com") }); factory = new XFAFactory(
new Map([["xdp:xdp", getXml("mailto:test@example.com")]])
);
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
pages = await factory.getPages(); pages = await factory.getPages();
a = searchHtmlNode(pages, "name", "a"); a = searchHtmlNode(pages, "name", "a");
@ -579,7 +585,7 @@ describe("XFAFactory", function () {
expect(a.attributes.href).toEqual("mailto:test@example.com"); expect(a.attributes.href).toEqual("mailto:test@example.com");
// Not a valid URL. // Not a valid URL.
factory = new XFAFactory({ "xdp:xdp": getXml("qwerty/") }); factory = new XFAFactory(new Map([["xdp:xdp", getXml("qwerty/")]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
pages = await factory.getPages(); pages = await factory.getPages();
a = searchHtmlNode(pages, "name", "a"); a = searchHtmlNode(pages, "name", "a");
@ -629,7 +635,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);
@ -675,7 +681,7 @@ describe("XFAFactory", function () {
</xfa:datasets> </xfa:datasets>
</xdp:xdp> </xdp:xdp>
`; `;
const factory = new XFAFactory({ "xdp:xdp": xml }); const factory = new XFAFactory(new Map([["xdp:xdp", xml]]));
expect(await factory.getNumPages()).toEqual(1); expect(await factory.getNumPages()).toEqual(1);

View File

@ -28,20 +28,20 @@ const FindState = {
PENDING: 3, PENDING: 3,
}; };
const CHARACTERS_TO_NORMALIZE = { const CHARACTERS_TO_NORMALIZE = new Map([
"\u2010": "-", // Hyphen ["\u2010", "-"], // Hyphen
"\u2018": "'", // Left single quotation mark ["\u2018", "'"], // Left single quotation mark
"\u2019": "'", // Right single quotation mark ["\u2019", "'"], // Right single quotation mark
"\u201A": "'", // Single low-9 quotation mark ["\u201A", "'"], // Single low-9 quotation mark
"\u201B": "'", // Single high-reversed-9 quotation mark ["\u201B", "'"], // Single high-reversed-9 quotation mark
"\u201C": '"', // Left double quotation mark ["\u201C", '"'], // Left double quotation mark
"\u201D": '"', // Right double quotation mark ["\u201D", '"'], // Right double quotation mark
"\u201E": '"', // Double low-9 quotation mark ["\u201E", '"'], // Double low-9 quotation mark
"\u201F": '"', // Double high-reversed-9 quotation mark ["\u201F", '"'], // Double high-reversed-9 quotation mark
"\u00BC": "1/4", // Vulgar fraction one quarter ["\u00BC", "1/4"], // Vulgar fraction one quarter
"\u00BD": "1/2", // Vulgar fraction one half ["\u00BD", "1/2"], // Vulgar fraction one half
"\u00BE": "3/4", // Vulgar fraction three quarters ["\u00BE", "3/4"], // Vulgar fraction three quarters
}; ]);
// These diacritics aren't considered as combining diacritics // These diacritics aren't considered as combining diacritics
// when searching in a document: // when searching in a document:
@ -121,7 +121,7 @@ function normalize(text, options = {}) {
normalizationRegex = withSyllablesRegExp; normalizationRegex = withSyllablesRegExp;
} else { } else {
// Compile the regular expression for text normalization once. // Compile the regular expression for text normalization once.
const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(""); const replace = CHARACTERS_TO_NORMALIZE.keys().join("");
const toNormalizeWithNFKC = getNormalizeWithNFKC(); const toNormalizeWithNFKC = getNormalizeWithNFKC();
// 3040-309F: Hiragana // 3040-309F: Hiragana
@ -206,7 +206,7 @@ function normalize(text, options = {}) {
i -= shiftOrigin; i -= shiftOrigin;
if (p1) { if (p1) {
// Maybe fractions or quotations mark... // Maybe fractions or quotations mark...
const replacement = CHARACTERS_TO_NORMALIZE[p1]; const replacement = CHARACTERS_TO_NORMALIZE.get(p1);
const jj = replacement.length; const jj = replacement.length;
for (let j = 1; j < jj; j++) { for (let j = 1; j < jj; j++) {
positions.push(i - shift + j, shift - j); positions.push(i - shift + j, shift - j);