diff --git a/src/core/document.js b/src/core/document.js
index bbe3fc836..4ac9c2788 100644
--- a/src/core/document.js
+++ b/src/core/document.js
@@ -787,7 +787,8 @@ class PDFDocument {
   get numPages() {
     let num = 0;
     if (this.xfaFactory) {
-      num = this.xfaFactory.numPages;
+      // num is a Promise.
+      num = this.xfaFactory.getNumPages();
     } else if (this.linearization) {
       num = this.linearization.numPages;
     } else {
diff --git a/src/core/xfa/factory.js b/src/core/xfa/factory.js
index 6547ab1d8..e2f49c243 100644
--- a/src/core/xfa/factory.js
+++ b/src/core/xfa/factory.js
@@ -19,6 +19,7 @@ import {
   $nodeName,
   $text,
   $toHTML,
+  $toPages,
 } from "./xfa_object.js";
 import { Binder } from "./bind.js";
 import { DataHandler } from "./data.js";
@@ -45,9 +46,32 @@ class XFAFactory {
     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 {
-      this.pages = this.form[$toHTML]();
+      this.pages = await this._createPagesHelper();
       this.dims = this.pages.children.map(c => {
         const { width, height } = c.attributes.style;
         return [0, 0, parseInt(width), parseInt(height)];
@@ -61,9 +85,9 @@ class XFAFactory {
     return this.dims[pageIndex];
   }
 
-  get numPages() {
+  async getNumPages() {
     if (!this.pages) {
-      this._createPages();
+      await this._createPages();
     }
     return this.dims.length;
   }
@@ -94,9 +118,9 @@ class XFAFactory {
     this.form[$globalData].fontFinder.add(fonts, reallyMissingFonts);
   }
 
-  getPages() {
+  async getPages() {
     if (!this.pages) {
-      this._createPages();
+      await this._createPages();
     }
     const pages = this.pages;
     this.pages = null;
diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js
index c616c54d6..cefb9b8af 100644
--- a/src/core/xfa/template.js
+++ b/src/core/xfa/template.js
@@ -55,6 +55,7 @@ import {
   $tabIndex,
   $text,
   $toHTML,
+  $toPages,
   $toStyle,
   $uid,
   ContentObject,
@@ -5395,7 +5396,12 @@ class Template extends XFAObject {
     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) {
       return HTMLResult.success({
         name: "div",
@@ -5641,6 +5647,7 @@ class Template extends XFAObject {
         }
       }
       pageArea = targetPageArea || pageArea[$getNextPage]();
+      yield null;
     }
   }
 }
diff --git a/src/core/xfa/xfa_object.js b/src/core/xfa/xfa_object.js
index 785950e8c..b1808fb0f 100644
--- a/src/core/xfa/xfa_object.js
+++ b/src/core/xfa/xfa_object.js
@@ -84,6 +84,7 @@ const $setSetAttributes = Symbol();
 const $setValue = Symbol();
 const $tabIndex = Symbol();
 const $text = Symbol();
+const $toPages = Symbol();
 const $toHTML = Symbol();
 const $toString = Symbol();
 const $toStyle = Symbol();
@@ -1137,6 +1138,7 @@ export {
   $tabIndex,
   $text,
   $toHTML,
+  $toPages,
   $toString,
   $toStyle,
   $uid,
diff --git a/test/unit/xfa_tohtml_spec.js b/test/unit/xfa_tohtml_spec.js
index da16e4bce..2d9c1bb77 100644
--- a/test/unit/xfa_tohtml_spec.js
+++ b/test/unit/xfa_tohtml_spec.js
@@ -39,7 +39,7 @@ describe("XFAFactory", function () {
   }
 
   describe("toHTML", function () {
-    it("should convert some basic properties to CSS", function () {
+    it("should convert some basic properties to CSS", async () => {
       const xml = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -86,9 +86,9 @@ describe("XFAFactory", function () {
       const factory = new XFAFactory({ "xdp:xdp": xml });
       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];
       expect(page1.attributes.style).toEqual({
         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) {
         pending("Image is not supported in Node.js.");
       }
@@ -174,15 +174,15 @@ describe("XFAFactory", function () {
       `;
       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");
 
       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 = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -208,9 +208,9 @@ describe("XFAFactory", function () {
       `;
       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 wrapper = page1.children[0];
       const draw = wrapper.children[0];
@@ -219,7 +219,7 @@ describe("XFAFactory", function () {
       expect(draw.attributes["aria-level"]).toEqual("2");
     });
 
-    it("should have aria table role", function () {
+    it("should have aria table role", async () => {
       const xml = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -263,9 +263,9 @@ describe("XFAFactory", function () {
       const factory = new XFAFactory({ "xdp:xdp": xml });
       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(
         pages,
         "xfaName",
@@ -303,7 +303,7 @@ describe("XFAFactory", function () {
       expect(cell.attributes.role).toEqual("cell");
     });
 
-    it("should have a maxLength property", function () {
+    it("should have a maxLength property", async () => {
       const xml = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -336,15 +336,15 @@ describe("XFAFactory", function () {
       `;
       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");
 
       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 = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -378,15 +378,15 @@ describe("XFAFactory", function () {
       `;
       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");
 
       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 = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -420,15 +420,15 @@ describe("XFAFactory", function () {
       `;
       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");
 
       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 = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -463,9 +463,9 @@ describe("XFAFactory", function () {
       `;
       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");
       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 = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -517,15 +517,15 @@ describe("XFAFactory", function () {
     `;
     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");
     expect(field1).not.toEqual(null);
     expect(field1.attributes.value).toEqual("123");
   });
 
-  it("should parse URLs correctly", function () {
+  it("should parse URLs correctly", async () => {
     function getXml(href) {
       return `
 <?xml version="1.0"?>
@@ -560,38 +560,38 @@ describe("XFAFactory", function () {
 
     // A valid, and complete, URL.
     factory = new XFAFactory({ "xdp:xdp": getXml("https://www.example.com/") });
-    expect(factory.numPages).toEqual(1);
-    pages = factory.getPages();
+    expect(await factory.getNumPages()).toEqual(1);
+    pages = await factory.getPages();
     a = searchHtmlNode(pages, "name", "a");
     expect(a.value).toEqual("https://www.example.com/");
     expect(a.attributes.href).toEqual("https://www.example.com/");
 
     // A valid, but incomplete, URL.
     factory = new XFAFactory({ "xdp:xdp": getXml("www.example.com/") });
-    expect(factory.numPages).toEqual(1);
-    pages = factory.getPages();
+    expect(await factory.getNumPages()).toEqual(1);
+    pages = await factory.getPages();
     a = searchHtmlNode(pages, "name", "a");
     expect(a.value).toEqual("www.example.com/");
     expect(a.attributes.href).toEqual("http://www.example.com/");
 
     // A valid email-address.
     factory = new XFAFactory({ "xdp:xdp": getXml("mailto:test@example.com") });
-    expect(factory.numPages).toEqual(1);
-    pages = factory.getPages();
+    expect(await factory.getNumPages()).toEqual(1);
+    pages = await factory.getPages();
     a = searchHtmlNode(pages, "name", "a");
     expect(a.value).toEqual("mailto:test@example.com");
     expect(a.attributes.href).toEqual("mailto:test@example.com");
 
     // Not a valid URL.
     factory = new XFAFactory({ "xdp:xdp": getXml("qwerty/") });
-    expect(factory.numPages).toEqual(1);
-    pages = factory.getPages();
+    expect(await factory.getNumPages()).toEqual(1);
+    pages = await factory.getPages();
     a = searchHtmlNode(pages, "name", "a");
     expect(a.value).toEqual("qwerty/");
     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 = `
 <?xml version="1.0"?>
 <xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
@@ -635,9 +635,9 @@ describe("XFAFactory", function () {
     `;
     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");
     expect(a.attributes.href).toEqual("https://github.com/mozilla/pdf.js");
     expect(a.attributes.newWindow).toEqual(true);