diff --git a/examples/components/simpleviewer.mjs b/examples/components/simpleviewer.mjs index 88044e733..e9e155e64 100644 --- a/examples/components/simpleviewer.mjs +++ b/examples/components/simpleviewer.mjs @@ -40,6 +40,8 @@ const SANDBOX_BUNDLE_SRC = new URL( window.location ); +const WASM_URL = "../../node_modules/pdfjs-dist/build/wasm/"; + const container = document.getElementById("viewerContainer"); const eventBus = new pdfjsViewer.EventBus(); @@ -59,6 +61,7 @@ const pdfFindController = new pdfjsViewer.PDFFindController({ const pdfScriptingManager = new pdfjsViewer.PDFScriptingManager({ eventBus, sandboxBundleSrc: SANDBOX_BUNDLE_SRC, + wasmUrl: WASM_URL, }); const pdfViewer = new pdfjsViewer.PDFViewer({ diff --git a/examples/components/singlepageviewer.mjs b/examples/components/singlepageviewer.mjs index cf2520701..e806d6f10 100644 --- a/examples/components/singlepageviewer.mjs +++ b/examples/components/singlepageviewer.mjs @@ -40,6 +40,8 @@ const SANDBOX_BUNDLE_SRC = new URL( window.location ); +const WASM_URL = "../../node_modules/pdfjs-dist/build/wasm/"; + const container = document.getElementById("viewerContainer"); const eventBus = new pdfjsViewer.EventBus(); @@ -59,6 +61,7 @@ const pdfFindController = new pdfjsViewer.PDFFindController({ const pdfScriptingManager = new pdfjsViewer.PDFScriptingManager({ eventBus, sandboxBundleSrc: SANDBOX_BUNDLE_SRC, + wasmUrl: WASM_URL, }); const pdfSinglePageViewer = new pdfjsViewer.PDFSinglePageViewer({ diff --git a/external/quickjs/quickjs-eval.js b/external/quickjs/quickjs-eval.js index 459fb5cf3..ab9ae8b5b 100644 Binary files a/external/quickjs/quickjs-eval.js and b/external/quickjs/quickjs-eval.js differ diff --git a/external/quickjs/quickjs-eval.wasm b/external/quickjs/quickjs-eval.wasm new file mode 100644 index 000000000..b5fe551aa Binary files /dev/null and b/external/quickjs/quickjs-eval.wasm differ diff --git a/gulpfile.mjs b/gulpfile.mjs index 31786872a..941b83ecf 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -621,8 +621,8 @@ function createStandardFontBundle() { ); } -function createWasmBundle() { - return ordered([ +function createWasmBundle({ includeQuickJS = true } = {}) { + const sources = [ gulp.src( [ "external/openjpeg/*.wasm", @@ -649,7 +649,22 @@ function createWasmBundle() { encoding: false, } ), - ]); + ]; + if (includeQuickJS) { + sources.push( + gulp.src( + [ + "external/quickjs/quickjs-eval.js", + "external/quickjs/quickjs-eval.wasm", + ], + { + base: "external/quickjs", + encoding: false, + } + ) + ); + } + return ordered(sources); } function checkFile(filePath) { @@ -1506,7 +1521,9 @@ gulp.task( createStandardFontBundle().pipe( gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/standard_fonts") ), - createWasmBundle().pipe(gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/wasm")), + createWasmBundle({ includeQuickJS: false }).pipe( + gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/wasm") + ), preprocessHTML("web/viewer.html", defines).pipe( gulp.dest(MOZCENTRAL_CONTENT_DIR + "web") @@ -2401,7 +2418,12 @@ gulp.task( }, function watchWasm() { gulp.watch( - ["external/openjpeg/*", "external/qcms/*", "external/jbig2/*"], + [ + "external/openjpeg/*", + "external/qcms/*", + "external/jbig2/*", + "external/quickjs/*", + ], { ignoreInitial: false }, gulp.series("dev-wasm") ); @@ -2412,7 +2434,6 @@ gulp.task( "src/pdf.{sandbox,sandbox.external,scripting}.js", "src/scripting_api/*.js", "src/shared/scripting_utils.js", - "external/quickjs/*.js", ], { ignoreInitial: false }, gulp.series("dev-sandbox") diff --git a/src/pdf.sandbox.js b/src/pdf.sandbox.js index d50e82d09..601214b29 100644 --- a/src/pdf.sandbox.js +++ b/src/pdf.sandbox.js @@ -13,7 +13,6 @@ * limitations under the License. */ -import ModuleLoader from "../external/quickjs/quickjs-eval.js"; import { SandboxSupportBase } from "./pdf.sandbox.external.js"; class SandboxSupport extends SandboxSupportBase { @@ -136,8 +135,12 @@ class Sandbox { } } -function QuickJSSandbox() { - return ModuleLoader().then(module => new Sandbox(window, module)); +async function QuickJSSandbox(wasmUrl = "../web/wasm/") { + const { default: ModuleLoader } = await __raw_import__( + `${wasmUrl}quickjs-eval.js` + ); + const module = await ModuleLoader(); + return new Sandbox(window, module); } globalThis.pdfjsSandbox = { diff --git a/test/components/simple-viewer.js b/test/components/simple-viewer.js index a8db16b0a..51f6f4ea4 100644 --- a/test/components/simple-viewer.js +++ b/test/components/simple-viewer.js @@ -18,7 +18,7 @@ import { EventBus } from "../../web/event_utils.js"; import { GenericL10n } from "../../web/genericl10n.js"; import { PDFFindController } from "../../web/pdf_find_controller.js"; import { PDFLinkService } from "../../web/pdf_link_service.js"; -import { PDFScriptingManager } from "../../web/pdf_scripting_manager.js"; +import { PDFScriptingManager } from "../../web/pdf_scripting_manager.component.js"; import { PDFViewer } from "../../web/pdf_viewer.js"; // The workerSrc property shall be specified. @@ -42,11 +42,16 @@ const SEARCH_FOR = ""; // try "Mozilla"; const SANDBOX_BUNDLE_SRC = new URL( typeof PDFJSDev === "undefined" - ? "../../src/pdf.sandbox.js" + ? "../../build/generic/build/pdf.sandbox.mjs" : "../../build/pdf.sandbox.mjs", window.location ); +const WASM_URL = + typeof PDFJSDev === "undefined" + ? "../../build/generic/web/wasm/" + : "../../build/wasm/"; + const fileUrl = new URLSearchParams(location.search).get("file") ?? DEFAULT_URL; const container = document.getElementById("viewerContainer"); @@ -68,6 +73,7 @@ const pdfFindController = new PDFFindController({ const pdfScriptingManager = new PDFScriptingManager({ eventBus, sandboxBundleSrc: SANDBOX_BUNDLE_SRC, + wasmUrl: WASM_URL, }); const pdfViewer = new PDFViewer({ @@ -80,6 +86,7 @@ const pdfViewer = new PDFViewer({ }); pdfLinkService.setViewer(pdfViewer); pdfScriptingManager.setViewer(pdfViewer); +window.pdfScriptingManager = pdfScriptingManager; eventBus.on("pagesinit", function () { // We can use pdfViewer now, e.g. let's change default scale. diff --git a/test/integration/simple_viewer_spec.mjs b/test/integration/simple_viewer_spec.mjs index 34dc6e293..21e8e6303 100644 --- a/test/integration/simple_viewer_spec.mjs +++ b/test/integration/simple_viewer_spec.mjs @@ -15,6 +15,14 @@ // Integration tests for the simple viewer (test/components/). +function getSelector(id) { + return `[data-element-id="${id}"]`; +} + +function getAnnotationSelector(id) { + return `[data-annotation-id="${id}"]`; +} + describe("Simple viewer", () => { describe("TextLayerBuilder without abortSignal", () => { let pages; @@ -55,4 +63,45 @@ describe("Simple viewer", () => { ); }); }); + + describe("Scripting support with evaljs.pdf", () => { + let pages; + + beforeEach(async () => { + const origin = new URL(global.integrationBaseUrl).origin; + pages = await Promise.all( + global.integrationSessions.map(async session => { + const page = await session.browser.newPage(); + await page.goto( + `${origin}/test/components/simple-viewer.html` + + `?file=/test/pdfs/evaljs.pdf` + ); + await page.bringToFront(); + await page.waitForSelector(getSelector("55R")); + await page.waitForFunction( + "window.pdfScriptingManager?.ready === true" + ); + return [session.name, page]; + }) + ); + }); + + afterEach(async () => { + await Promise.all(pages.map(([, page]) => page.close())); + }); + + it("must evaluate JavaScript entered in the input field", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.type(getSelector("55R"), "1 + 1"); + await page.click(getAnnotationSelector("57R")); + await page.waitForFunction( + `document.querySelector('${getSelector("56R")}').value !== ""` + ); + const text = await page.$eval(getSelector("56R"), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("2"); + }) + ); + }); + }); }); diff --git a/web/chromecom.js b/web/chromecom.js index 96231d5de..f62e4c02c 100644 --- a/web/chromecom.js +++ b/web/chromecom.js @@ -439,7 +439,10 @@ class ExternalServices extends BaseExternalServices { } createScripting() { - return new GenericScripting(AppOptions.get("sandboxBundleSrc")); + return new GenericScripting( + AppOptions.get("sandboxBundleSrc"), + AppOptions.get("wasmUrl") + ); } createSignatureStorage(eventBus, signal) { diff --git a/web/generic_scripting.js b/web/generic_scripting.js index cf40f541e..27f805b8e 100644 --- a/web/generic_scripting.js +++ b/web/generic_scripting.js @@ -34,7 +34,7 @@ async function docProperties(pdfDocument) { } class GenericScripting { - constructor(sandboxBundleSrc) { + constructor(sandboxBundleSrc, wasmUrl) { this._ready = new Promise((resolve, reject) => { const sandbox = typeof PDFJSDev === "undefined" @@ -42,7 +42,9 @@ class GenericScripting { : __raw_import__(sandboxBundleSrc); sandbox .then(pdfjsSandbox => { - resolve(pdfjsSandbox.QuickJSSandbox()); + resolve( + pdfjsSandbox.QuickJSSandbox(new URL(wasmUrl, location.href).href) + ); }) .catch(reject); }); diff --git a/web/genericcom.js b/web/genericcom.js index a2d74ba09..77909b487 100644 --- a/web/genericcom.js +++ b/web/genericcom.js @@ -44,7 +44,10 @@ class ExternalServices extends BaseExternalServices { } createScripting() { - return new GenericScripting(AppOptions.get("sandboxBundleSrc")); + return new GenericScripting( + AppOptions.get("sandboxBundleSrc"), + AppOptions.get("wasmUrl") + ); } createSignatureStorage(eventBus, signal) { diff --git a/web/pdf_scripting_manager.component.js b/web/pdf_scripting_manager.component.js index 6e1730186..34219207b 100644 --- a/web/pdf_scripting_manager.component.js +++ b/web/pdf_scripting_manager.component.js @@ -30,7 +30,8 @@ class PDFScriptingManagerComponents extends PDFScriptingManager { } options.externalServices ||= { - createScripting: () => new GenericScripting(options.sandboxBundleSrc), + createScripting: () => + new GenericScripting(options.sandboxBundleSrc, options.wasmUrl), }; options.docProperties ||= pdfDocument => docProperties(pdfDocument); super(options);