XFA - Get each page asynchronously in order to avoid blocking the event loop (#14014)
This commit is contained in:
parent
30bd5f0a39
commit
1681e25008
@ -787,7 +787,8 @@ class PDFDocument {
|
|||||||
get numPages() {
|
get numPages() {
|
||||||
let num = 0;
|
let num = 0;
|
||||||
if (this.xfaFactory) {
|
if (this.xfaFactory) {
|
||||||
num = this.xfaFactory.numPages;
|
// num is a Promise.
|
||||||
|
num = this.xfaFactory.getNumPages();
|
||||||
} else if (this.linearization) {
|
} else if (this.linearization) {
|
||||||
num = this.linearization.numPages;
|
num = this.linearization.numPages;
|
||||||
} else {
|
} else {
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
$nodeName,
|
$nodeName,
|
||||||
$text,
|
$text,
|
||||||
$toHTML,
|
$toHTML,
|
||||||
|
$toPages,
|
||||||
} from "./xfa_object.js";
|
} from "./xfa_object.js";
|
||||||
import { Binder } from "./bind.js";
|
import { Binder } from "./bind.js";
|
||||||
import { DataHandler } from "./data.js";
|
import { DataHandler } from "./data.js";
|
||||||
@ -45,9 +46,32 @@ class XFAFactory {
|
|||||||
return this.root && this.form;
|
return this.root && this.form;
|
||||||
}
|
}
|
||||||
|
|
||||||
_createPages() {
|
/**
|
||||||
|
* In order to avoid to block the event loop, the conversion
|
||||||
|
* into pages is made asynchronously.
|
||||||
|
*/
|
||||||
|
_createPagesHelper() {
|
||||||
|
const iterator = this.form[$toPages]();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const nextIteration = () => {
|
||||||
|
try {
|
||||||
|
const value = iterator.next();
|
||||||
|
if (value.done) {
|
||||||
|
resolve(value.value);
|
||||||
|
} else {
|
||||||
|
setTimeout(nextIteration, 0);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTimeout(nextIteration, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _createPages() {
|
||||||
try {
|
try {
|
||||||
this.pages = this.form[$toHTML]();
|
this.pages = await this._createPagesHelper();
|
||||||
this.dims = this.pages.children.map(c => {
|
this.dims = this.pages.children.map(c => {
|
||||||
const { width, height } = c.attributes.style;
|
const { width, height } = c.attributes.style;
|
||||||
return [0, 0, parseInt(width), parseInt(height)];
|
return [0, 0, parseInt(width), parseInt(height)];
|
||||||
@ -61,9 +85,9 @@ class XFAFactory {
|
|||||||
return this.dims[pageIndex];
|
return this.dims[pageIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
get numPages() {
|
async getNumPages() {
|
||||||
if (!this.pages) {
|
if (!this.pages) {
|
||||||
this._createPages();
|
await this._createPages();
|
||||||
}
|
}
|
||||||
return this.dims.length;
|
return this.dims.length;
|
||||||
}
|
}
|
||||||
@ -94,9 +118,9 @@ class XFAFactory {
|
|||||||
this.form[$globalData].fontFinder.add(fonts, reallyMissingFonts);
|
this.form[$globalData].fontFinder.add(fonts, reallyMissingFonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPages() {
|
async getPages() {
|
||||||
if (!this.pages) {
|
if (!this.pages) {
|
||||||
this._createPages();
|
await this._createPages();
|
||||||
}
|
}
|
||||||
const pages = this.pages;
|
const pages = this.pages;
|
||||||
this.pages = null;
|
this.pages = null;
|
||||||
|
@ -55,6 +55,7 @@ import {
|
|||||||
$tabIndex,
|
$tabIndex,
|
||||||
$text,
|
$text,
|
||||||
$toHTML,
|
$toHTML,
|
||||||
|
$toPages,
|
||||||
$toStyle,
|
$toStyle,
|
||||||
$uid,
|
$uid,
|
||||||
ContentObject,
|
ContentObject,
|
||||||
@ -5395,7 +5396,12 @@ class Template extends XFAObject {
|
|||||||
return searchNode(this, container, expr, true, true);
|
return searchNode(this, container, expr, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[$toHTML]() {
|
/**
|
||||||
|
* This function is a generator because the conversion into
|
||||||
|
* pages is done asynchronously and we want to save the state
|
||||||
|
* of the function where we were in the previous iteration.
|
||||||
|
*/
|
||||||
|
*[$toPages]() {
|
||||||
if (!this.subform.children.length) {
|
if (!this.subform.children.length) {
|
||||||
return HTMLResult.success({
|
return HTMLResult.success({
|
||||||
name: "div",
|
name: "div",
|
||||||
@ -5641,6 +5647,7 @@ class Template extends XFAObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pageArea = targetPageArea || pageArea[$getNextPage]();
|
pageArea = targetPageArea || pageArea[$getNextPage]();
|
||||||
|
yield null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,7 @@ const $setSetAttributes = Symbol();
|
|||||||
const $setValue = Symbol();
|
const $setValue = Symbol();
|
||||||
const $tabIndex = Symbol();
|
const $tabIndex = Symbol();
|
||||||
const $text = Symbol();
|
const $text = Symbol();
|
||||||
|
const $toPages = Symbol();
|
||||||
const $toHTML = Symbol();
|
const $toHTML = Symbol();
|
||||||
const $toString = Symbol();
|
const $toString = Symbol();
|
||||||
const $toStyle = Symbol();
|
const $toStyle = Symbol();
|
||||||
@ -1137,6 +1138,7 @@ export {
|
|||||||
$tabIndex,
|
$tabIndex,
|
||||||
$text,
|
$text,
|
||||||
$toHTML,
|
$toHTML,
|
||||||
|
$toPages,
|
||||||
$toString,
|
$toString,
|
||||||
$toStyle,
|
$toStyle,
|
||||||
$uid,
|
$uid,
|
||||||
|
@ -39,7 +39,7 @@ describe("XFAFactory", function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("toHTML", function () {
|
describe("toHTML", function () {
|
||||||
it("should convert some basic properties to CSS", function () {
|
it("should convert some basic properties to CSS", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -86,9 +86,9 @@ describe("XFAFactory", function () {
|
|||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
factory.setFonts([]);
|
factory.setFonts([]);
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(2);
|
expect(await factory.getNumPages()).toEqual(2);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const page1 = pages.children[0];
|
const page1 = pages.children[0];
|
||||||
expect(page1.attributes.style).toEqual({
|
expect(page1.attributes.style).toEqual({
|
||||||
height: "789px",
|
height: "789px",
|
||||||
@ -144,7 +144,7 @@ describe("XFAFactory", function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have an alt attribute from toolTip", function () {
|
it("should have an alt attribute from toolTip", async () => {
|
||||||
if (isNodeJS) {
|
if (isNodeJS) {
|
||||||
pending("Image is not supported in Node.js.");
|
pending("Image is not supported in Node.js.");
|
||||||
}
|
}
|
||||||
@ -174,15 +174,15 @@ describe("XFAFactory", function () {
|
|||||||
`;
|
`;
|
||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const field = searchHtmlNode(pages, "name", "img");
|
const field = searchHtmlNode(pages, "name", "img");
|
||||||
|
|
||||||
expect(field.attributes.alt).toEqual("alt text");
|
expect(field.attributes.alt).toEqual("alt text");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have a aria heading role and level", function () {
|
it("should have a aria heading role and level", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -208,9 +208,9 @@ describe("XFAFactory", function () {
|
|||||||
`;
|
`;
|
||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const page1 = pages.children[0];
|
const page1 = pages.children[0];
|
||||||
const wrapper = page1.children[0];
|
const wrapper = page1.children[0];
|
||||||
const draw = wrapper.children[0];
|
const draw = wrapper.children[0];
|
||||||
@ -219,7 +219,7 @@ describe("XFAFactory", function () {
|
|||||||
expect(draw.attributes["aria-level"]).toEqual("2");
|
expect(draw.attributes["aria-level"]).toEqual("2");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have aria table role", function () {
|
it("should have aria table role", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -263,9 +263,9 @@ describe("XFAFactory", function () {
|
|||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
factory.setFonts([]);
|
factory.setFonts([]);
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const table = searchHtmlNode(
|
const table = searchHtmlNode(
|
||||||
pages,
|
pages,
|
||||||
"xfaName",
|
"xfaName",
|
||||||
@ -303,7 +303,7 @@ describe("XFAFactory", function () {
|
|||||||
expect(cell.attributes.role).toEqual("cell");
|
expect(cell.attributes.role).toEqual("cell");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have a maxLength property", function () {
|
it("should have a maxLength property", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -336,15 +336,15 @@ describe("XFAFactory", function () {
|
|||||||
`;
|
`;
|
||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const field = searchHtmlNode(pages, "name", "input");
|
const field = searchHtmlNode(pages, "name", "input");
|
||||||
|
|
||||||
expect(field.attributes.maxLength).toEqual(123);
|
expect(field.attributes.maxLength).toEqual(123);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have an aria-label property from speak", function () {
|
it("should have an aria-label property from speak", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -378,15 +378,15 @@ describe("XFAFactory", function () {
|
|||||||
`;
|
`;
|
||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const field = searchHtmlNode(pages, "name", "input");
|
const field = searchHtmlNode(pages, "name", "input");
|
||||||
|
|
||||||
expect(field.attributes["aria-label"]).toEqual("Screen Reader");
|
expect(field.attributes["aria-label"]).toEqual("Screen Reader");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have an aria-label property from toolTip", function () {
|
it("should have an aria-label property from toolTip", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -420,15 +420,15 @@ describe("XFAFactory", function () {
|
|||||||
`;
|
`;
|
||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const field = searchHtmlNode(pages, "name", "input");
|
const field = searchHtmlNode(pages, "name", "input");
|
||||||
|
|
||||||
expect(field.attributes["aria-label"]).toEqual("Screen Reader");
|
expect(field.attributes["aria-label"]).toEqual("Screen Reader");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have an input or textarea", function () {
|
it("should have an input or textarea", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -463,9 +463,9 @@ describe("XFAFactory", function () {
|
|||||||
`;
|
`;
|
||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const field1 = searchHtmlNode(pages, "name", "input");
|
const field1 = searchHtmlNode(pages, "name", "input");
|
||||||
expect(field1).not.toEqual(null);
|
expect(field1).not.toEqual(null);
|
||||||
|
|
||||||
@ -474,7 +474,7 @@ describe("XFAFactory", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should have an input or textarea", function () {
|
it("should have an input or textarea", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -517,15 +517,15 @@ describe("XFAFactory", function () {
|
|||||||
`;
|
`;
|
||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
const field1 = searchHtmlNode(pages, "name", "input");
|
const field1 = searchHtmlNode(pages, "name", "input");
|
||||||
expect(field1).not.toEqual(null);
|
expect(field1).not.toEqual(null);
|
||||||
expect(field1.attributes.value).toEqual("123");
|
expect(field1.attributes.value).toEqual("123");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse URLs correctly", function () {
|
it("should parse URLs correctly", async () => {
|
||||||
function getXml(href) {
|
function getXml(href) {
|
||||||
return `
|
return `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
@ -560,38 +560,38 @@ describe("XFAFactory", function () {
|
|||||||
|
|
||||||
// A valid, and complete, URL.
|
// A valid, and complete, URL.
|
||||||
factory = new XFAFactory({ "xdp:xdp": getXml("https://www.example.com/") });
|
factory = new XFAFactory({ "xdp:xdp": getXml("https://www.example.com/") });
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
pages = factory.getPages();
|
pages = await factory.getPages();
|
||||||
a = searchHtmlNode(pages, "name", "a");
|
a = searchHtmlNode(pages, "name", "a");
|
||||||
expect(a.value).toEqual("https://www.example.com/");
|
expect(a.value).toEqual("https://www.example.com/");
|
||||||
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({ "xdp:xdp": getXml("www.example.com/") });
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
pages = factory.getPages();
|
pages = await factory.getPages();
|
||||||
a = searchHtmlNode(pages, "name", "a");
|
a = searchHtmlNode(pages, "name", "a");
|
||||||
expect(a.value).toEqual("www.example.com/");
|
expect(a.value).toEqual("www.example.com/");
|
||||||
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({ "xdp:xdp": getXml("mailto:test@example.com") });
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
pages = factory.getPages();
|
pages = await factory.getPages();
|
||||||
a = searchHtmlNode(pages, "name", "a");
|
a = searchHtmlNode(pages, "name", "a");
|
||||||
expect(a.value).toEqual("mailto:test@example.com");
|
expect(a.value).toEqual("mailto:test@example.com");
|
||||||
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({ "xdp:xdp": getXml("qwerty/") });
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
pages = factory.getPages();
|
pages = await factory.getPages();
|
||||||
a = searchHtmlNode(pages, "name", "a");
|
a = searchHtmlNode(pages, "name", "a");
|
||||||
expect(a.value).toEqual("qwerty/");
|
expect(a.value).toEqual("qwerty/");
|
||||||
expect(a.attributes.href).toEqual("");
|
expect(a.attributes.href).toEqual("");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should replace button with an URL by a link", function () {
|
it("should replace button with an URL by a link", async () => {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
@ -635,9 +635,9 @@ describe("XFAFactory", function () {
|
|||||||
`;
|
`;
|
||||||
const factory = new XFAFactory({ "xdp:xdp": xml });
|
const factory = new XFAFactory({ "xdp:xdp": xml });
|
||||||
|
|
||||||
expect(factory.numPages).toEqual(1);
|
expect(await factory.getNumPages()).toEqual(1);
|
||||||
|
|
||||||
const pages = factory.getPages();
|
const pages = await factory.getPages();
|
||||||
let a = searchHtmlNode(pages, "name", "a");
|
let a = searchHtmlNode(pages, "name", "a");
|
||||||
expect(a.attributes.href).toEqual("https://github.com/mozilla/pdf.js");
|
expect(a.attributes.href).toEqual("https://github.com/mozilla/pdf.js");
|
||||||
expect(a.attributes.newWindow).toEqual(true);
|
expect(a.attributes.newWindow).toEqual(true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user