diff --git a/gulpfile.js b/gulpfile.js index 8e95a5257..056cf2293 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -33,7 +33,6 @@ var stream = require("stream"); var exec = require("child_process").exec; var spawn = require("child_process").spawn; var spawnSync = require("child_process").spawnSync; -var stripComments = require("gulp-strip-comments"); var streamqueue = require("streamqueue"); var merge = require("merge-stream"); var zip = require("gulp-zip"); @@ -65,6 +64,7 @@ var SRC_DIR = "src/"; var LIB_DIR = BUILD_DIR + "lib/"; var DIST_DIR = BUILD_DIR + "dist/"; var TYPES_DIR = BUILD_DIR + "types/"; +const TMP_DIR = BUILD_DIR + "tmp/"; var TYPESTEST_DIR = BUILD_DIR + "typestest/"; var COMMON_WEB_FILES = ["web/images/*.{png,svg,gif,cur}", "web/debugger.js"]; var MOZCENTRAL_DIFF_FILE = "mozcentral.diff"; @@ -106,7 +106,6 @@ const DEFINES = Object.freeze({ COMPONENTS: false, LIB: false, IMAGE_DECODERS: false, - NO_SOURCE_MAP: false, }); function transform(charEncoding, transformFunction) { @@ -171,8 +170,18 @@ function createStringSource(filename, content) { return source; } -function createWebpackConfig(defines, output) { - var versionInfo = getVersionJSON(); +function createWebpackConfig( + defines, + output, + { + disableVersionInfo = false, + disableSourceMaps = false, + disableLicenseHeader = false, + } = {} +) { + const versionInfo = !disableVersionInfo + ? getVersionJSON() + : { version: 0, commit: 0 }; var bundleDefines = builder.merge(defines, { BUNDLE_VERSION: versionInfo.version, BUNDLE_BUILD: versionInfo.commit, @@ -184,8 +193,9 @@ function createWebpackConfig(defines, output) { var enableSourceMaps = !bundleDefines.MOZCENTRAL && !bundleDefines.CHROME && + !bundleDefines.LIB && !bundleDefines.TESTING && - !bundleDefines.NO_SOURCE_MAP; + !disableSourceMaps; var skipBabel = bundleDefines.SKIP_BABEL; // `core-js` (see https://github.com/zloirock/core-js/issues/514), @@ -201,6 +211,13 @@ function createWebpackConfig(defines, output) { } const babelExcludeRegExp = new RegExp(`(${babelExcludes.join("|")})`); + const plugins = []; + if (!disableLicenseHeader) { + plugins.push( + new webpack2.BannerPlugin({ banner: licenseHeaderLibre, raw: true }) + ); + } + // Required to expose e.g., the `window` object. output.globalObject = "this"; @@ -210,9 +227,7 @@ function createWebpackConfig(defines, output) { performance: { hints: false, // Disable messages about larger file sizes. }, - plugins: [ - new webpack2.BannerPlugin({ banner: licenseHeaderLibre, raw: true }), - ], + plugins, resolve: { alias: { pdfjs: path.join(__dirname, "src"), @@ -329,16 +344,20 @@ function createMainBundle(defines) { .pipe(replaceJSRootName(mainAMDName, "pdfjsLib")); } -function createScriptingBundle(defines) { +function createScriptingBundle(defines, extraOptions = undefined) { var scriptingAMDName = "pdfjs-dist/build/pdf.scripting"; var scriptingOutputName = "pdf.scripting.js"; - var scriptingFileConfig = createWebpackConfig(defines, { - filename: scriptingOutputName, - library: scriptingAMDName, - libraryTarget: "umd", - umdNamedDefine: true, - }); + var scriptingFileConfig = createWebpackConfig( + defines, + { + filename: scriptingOutputName, + library: scriptingAMDName, + libraryTarget: "umd", + umdNamedDefine: true, + }, + extraOptions + ); return gulp .src("./src/pdf.scripting.js") .pipe(webpack2Stream(scriptingFileConfig)) @@ -346,51 +365,41 @@ function createScriptingBundle(defines) { .pipe(replaceJSRootName(scriptingAMDName, "pdfjsScripting")); } -function createSandboxBundle(defines, code) { - var sandboxAMDName = "pdfjs-dist/build/pdf.sandbox"; - var sandboxOutputName = "pdf.sandbox.js"; - var sandboxFileConfig = createWebpackConfig(defines, { - filename: sandboxOutputName, - library: sandboxAMDName, - libraryTarget: "umd", - umdNamedDefine: true, - }); - - // The code is the one from the bundle pdf.scripting.js - // so in order to have it in a string (which will be eval-ed - // in the sandbox) we must escape some chars. - // This way we've all the code (initialization+sandbox) in - // the same bundle. - code = code.replace(/["\\\n\t]/g, match => { - if (match === "\n") { - return "\\n"; - } - if (match === "\t") { - return "\\t"; - } - return `\\${match}`; - }); - return ( - gulp - .src("./src/scripting_api/quickjs-sandbox.js") - .pipe(webpack2Stream(sandboxFileConfig)) - .pipe(replaceWebpackRequire()) - .pipe(replaceJSRootName(sandboxAMDName, "pdfjsSandbox")) - // put the code in a string to be eval-ed in the sandbox - .pipe(replace("/* INITIALIZATION_CODE */", `${code}`)) - ); +function createTemporaryScriptingBundle(defines, extraOptions = undefined) { + return createScriptingBundle(defines, { + disableVersionInfo: !!(extraOptions && extraOptions.disableVersionInfo), + disableSourceMaps: true, + disableLicenseHeader: true, + }).pipe(gulp.dest(TMP_DIR)); } -function buildSandbox(defines, dir) { - const scriptingDefines = builder.merge(defines, { NO_SOURCE_MAP: true }); - return createScriptingBundle(scriptingDefines) - .pipe(stripComments()) - .pipe(gulp.dest(dir + "build")) - .on("data", file => { - const content = file.contents.toString(); - createSandboxBundle(defines, content).pipe(gulp.dest(dir + "build")); - fs.unlinkSync(dir + "build/pdf.scripting.js"); - }); +function createSandboxBundle(defines, extraOptions = undefined) { + var sandboxAMDName = "pdfjs-dist/build/pdf.sandbox"; + var sandboxOutputName = "pdf.sandbox.js"; + + const scriptingPath = TMP_DIR + "pdf.scripting.js"; + // Insert the source as a string to be `eval`-ed in the sandbox. + const sandboxDefines = builder.merge(defines, { + PDF_SCRIPTING_JS_SOURCE: fs.readFileSync(scriptingPath).toString(), + }); + fs.unlinkSync(scriptingPath); + + var sandboxFileConfig = createWebpackConfig( + sandboxDefines, + { + filename: sandboxOutputName, + library: sandboxAMDName, + libraryTarget: "umd", + umdNamedDefine: true, + }, + extraOptions + ); + + return gulp + .src("./src/pdf.sandbox.js") + .pipe(webpack2Stream(sandboxFileConfig)) + .pipe(replaceWebpackRequire()) + .pipe(replaceJSRootName(sandboxAMDName, "pdfjsSandbox")); } function createWorkerBundle(defines) { @@ -544,25 +553,6 @@ function makeRef(done, bot) { }); } -gulp.task("sandbox", function (done) { - const defines = builder.merge(DEFINES, { GENERIC: true }); - buildSandbox(defines, GENERIC_DIR); - done(); -}); - -gulp.task("watch-sandbox", function (done) { - const defines = builder.merge(DEFINES, { GENERIC: true }); - buildSandbox(defines, GENERIC_DIR); - const watcher = gulp.watch([ - "src/scripting_api/*.js", - "external/quickjs/*.js", - ]); - watcher.on("change", function () { - buildSandbox(defines, GENERIC_DIR); - }); - done(); -}); - gulp.task("default", function (done) { console.log("Available tasks:"); var tasks = Object.keys(gulp.registry().tasks()); @@ -798,6 +788,7 @@ function buildGeneric(defines, dir) { return merge([ createMainBundle(defines).pipe(gulp.dest(dir + "build")), createWorkerBundle(defines).pipe(gulp.dest(dir + "build")), + createSandboxBundle(defines).pipe(gulp.dest(dir + "build")), createWebBundle(defines).pipe(gulp.dest(dir + "web")), gulp.src(COMMON_WEB_FILES, { base: "web/" }).pipe(gulp.dest(dir + "web")), gulp.src("LICENSE").pipe(gulp.dest(dir)), @@ -836,14 +827,17 @@ gulp.task( "buildnumber", "default_preferences", "locale", + function scripting() { + var defines = builder.merge(DEFINES, { GENERIC: true }); + return createTemporaryScriptingBundle(defines); + }, function () { console.log(); console.log("### Creating generic viewer"); var defines = builder.merge(DEFINES, { GENERIC: true }); return buildGeneric(defines, GENERIC_DIR); - }, - "sandbox" + } ) ); @@ -855,6 +849,13 @@ gulp.task( "buildnumber", "default_preferences", "locale", + function scripting() { + var defines = builder.merge(DEFINES, { + GENERIC: true, + SKIP_BABEL: false, + }); + return createTemporaryScriptingBundle(defines); + }, function () { console.log(); console.log("### Creating generic (ES5) viewer"); @@ -864,13 +865,6 @@ gulp.task( }); return buildGeneric(defines, GENERIC_ES5_DIR); - }, - function () { - const defines = builder.merge(DEFINES, { - GENERIC: true, - SKIP_BABEL: false, - }); - return buildSandbox(defines, GENERIC_ES5_DIR); } ) ); @@ -964,6 +958,7 @@ function buildMinified(defines, dir) { return merge([ createMainBundle(defines).pipe(gulp.dest(dir + "build")), createWorkerBundle(defines).pipe(gulp.dest(dir + "build")), + createSandboxBundle(defines).pipe(gulp.dest(dir + "build")), createWebBundle(defines).pipe(gulp.dest(dir + "web")), createImageDecodersBundle( builder.merge(defines, { IMAGE_DECODERS: true }) @@ -1003,16 +998,15 @@ gulp.task( "buildnumber", "default_preferences", "locale", + function scripting() { + var defines = builder.merge(DEFINES, { MINIFIED: true, GENERIC: true }); + return createTemporaryScriptingBundle(defines); + }, function () { console.log(); console.log("### Creating minified viewer"); var defines = builder.merge(DEFINES, { MINIFIED: true, GENERIC: true }); - return buildSandbox(defines, MINIFIED_DIR); - }, - function () { - var defines = builder.merge(DEFINES, { MINIFIED: true, GENERIC: true }); - return buildMinified(defines, MINIFIED_DIR); } ) @@ -1024,19 +1018,17 @@ gulp.task( "buildnumber", "default_preferences", "locale", - function () { - console.log(); - console.log("### Creating minified (ES5) viewer"); + function scripting() { var defines = builder.merge(DEFINES, { MINIFIED: true, GENERIC: true, SKIP_BABEL: false, }); - - return buildSandbox(defines, MINIFIED_ES5_DIR); + return createTemporaryScriptingBundle(defines); }, - function () { + console.log(); + console.log("### Creating minified (ES5) viewer"); var defines = builder.merge(DEFINES, { MINIFIED: true, GENERIC: true, @@ -1238,80 +1230,85 @@ gulp.task("mozcentral", gulp.series("mozcentral-pre")); gulp.task( "chromium-pre", - gulp.series("buildnumber", "default_preferences", "locale", function () { - console.log(); - console.log("### Building Chromium extension"); - var defines = builder.merge(DEFINES, { CHROME: true, SKIP_BABEL: false }); + gulp.series( + "buildnumber", + "default_preferences", + "locale", + function scripting() { + var defines = builder.merge(DEFINES, { CHROME: true, SKIP_BABEL: false }); + return createTemporaryScriptingBundle(defines); + }, + function () { + console.log(); + console.log("### Building Chromium extension"); + var defines = builder.merge(DEFINES, { CHROME: true, SKIP_BABEL: false }); - var CHROME_BUILD_DIR = BUILD_DIR + "/chromium/", - CHROME_BUILD_CONTENT_DIR = CHROME_BUILD_DIR + "/content/"; + var CHROME_BUILD_DIR = BUILD_DIR + "/chromium/", + CHROME_BUILD_CONTENT_DIR = CHROME_BUILD_DIR + "/content/"; - // Clear out everything in the chrome extension build directory - rimraf.sync(CHROME_BUILD_DIR); + // Clear out everything in the chrome extension build directory + rimraf.sync(CHROME_BUILD_DIR); - var version = getVersionJSON().version; + var version = getVersionJSON().version; - return merge([ - createMainBundle(defines).pipe( - gulp.dest(CHROME_BUILD_CONTENT_DIR + "build") - ), - createWorkerBundle(defines).pipe( - gulp.dest(CHROME_BUILD_CONTENT_DIR + "build") - ), - createWebBundle(defines).pipe( - gulp.dest(CHROME_BUILD_CONTENT_DIR + "web") - ), - gulp - .src(COMMON_WEB_FILES, { base: "web/" }) - .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + "web")), + return merge([ + createMainBundle(defines).pipe( + gulp.dest(CHROME_BUILD_CONTENT_DIR + "build") + ), + createWorkerBundle(defines).pipe( + gulp.dest(CHROME_BUILD_CONTENT_DIR + "build") + ), + createSandboxBundle(defines).pipe( + gulp.dest(CHROME_BUILD_CONTENT_DIR + "build") + ), + createWebBundle(defines).pipe( + gulp.dest(CHROME_BUILD_CONTENT_DIR + "web") + ), + gulp + .src(COMMON_WEB_FILES, { base: "web/" }) + .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + "web")), - gulp - .src( - ["web/locale/*/viewer.properties", "web/locale/locale.properties"], - { base: "web/" } - ) - .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + "web")), - gulp - .src(["external/bcmaps/*.bcmap", "external/bcmaps/LICENSE"], { - base: "external/bcmaps", - }) - .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + "web/cmaps")), + gulp + .src( + ["web/locale/*/viewer.properties", "web/locale/locale.properties"], + { base: "web/" } + ) + .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + "web")), + gulp + .src(["external/bcmaps/*.bcmap", "external/bcmaps/LICENSE"], { + base: "external/bcmaps", + }) + .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + "web/cmaps")), - preprocessHTML("web/viewer.html", defines).pipe( - gulp.dest(CHROME_BUILD_CONTENT_DIR + "web") - ), - preprocessCSS("web/viewer.css", "chrome", defines, true) - .pipe( - postcss([autoprefixer({ overrideBrowserslist: ["chrome >= 49"] })]) - ) - .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + "web")), + preprocessHTML("web/viewer.html", defines).pipe( + gulp.dest(CHROME_BUILD_CONTENT_DIR + "web") + ), + preprocessCSS("web/viewer.css", "chrome", defines, true) + .pipe( + postcss([autoprefixer({ overrideBrowserslist: ["chrome >= 49"] })]) + ) + .pipe(gulp.dest(CHROME_BUILD_CONTENT_DIR + "web")), - gulp.src("LICENSE").pipe(gulp.dest(CHROME_BUILD_DIR)), - gulp - .src("extensions/chromium/manifest.json") - .pipe(replace(/\bPDFJSSCRIPT_VERSION\b/g, version)) - .pipe(gulp.dest(CHROME_BUILD_DIR)), - gulp - .src( - [ - "extensions/chromium/**/*.{html,js,css,png}", - "extensions/chromium/preferences_schema.json", - ], - { base: "extensions/chromium/" } - ) - .pipe(gulp.dest(CHROME_BUILD_DIR)), - ]); - }) + gulp.src("LICENSE").pipe(gulp.dest(CHROME_BUILD_DIR)), + gulp + .src("extensions/chromium/manifest.json") + .pipe(replace(/\bPDFJSSCRIPT_VERSION\b/g, version)) + .pipe(gulp.dest(CHROME_BUILD_DIR)), + gulp + .src( + [ + "extensions/chromium/**/*.{html,js,css,png}", + "extensions/chromium/preferences_schema.json", + ], + { base: "extensions/chromium/" } + ) + .pipe(gulp.dest(CHROME_BUILD_DIR)), + ]); + } + ) ); -gulp.task( - "chromium", - gulp.series("chromium-pre", function () { - var defines = builder.merge(DEFINES, { CHROME: true, SKIP_BABEL: false }); - var CHROME_BUILD_CONTENT_DIR = BUILD_DIR + "/chromium/content/"; - return buildSandbox(defines, CHROME_BUILD_CONTENT_DIR); - }) -); +gulp.task("chromium", gulp.series("chromium-pre")); gulp.task("jsdoc", function (done) { console.log(); @@ -1432,15 +1429,17 @@ gulp.task( gulp.series( "buildnumber", "default_preferences", - function () { + function scripting() { var defines = builder.merge(DEFINES, { GENERIC: true, LIB: true }); - - return buildLib(defines, "build/lib/"); + return createTemporaryScriptingBundle(defines); }, function () { var defines = builder.merge(DEFINES, { GENERIC: true, LIB: true }); - return buildSandbox(defines, "build/lib/"); + return merge([ + buildLib(defines, "build/lib/"), + createSandboxBundle(defines).pipe(gulp.dest("build/lib/")), + ]); } ) ); @@ -1450,14 +1449,13 @@ gulp.task( gulp.series( "buildnumber", "default_preferences", - function () { + function scripting() { var defines = builder.merge(DEFINES, { GENERIC: true, LIB: true, SKIP_BABEL: false, }); - - return buildLib(defines, "build/lib-es5/"); + return createTemporaryScriptingBundle(defines); }, function () { var defines = builder.merge(DEFINES, { @@ -1466,7 +1464,10 @@ gulp.task( SKIP_BABEL: false, }); - return buildSandbox(defines, "build/lib-es5/"); + return merge([ + buildLib(defines, "build/lib-es5/"), + createSandboxBundle(defines).pipe(gulp.dest("build/lib-es5/")), + ]); } ) ); @@ -1717,16 +1718,57 @@ gulp.task( }) ); -gulp.task("server", function () { - console.log(); - console.log("### Starting local server"); +gulp.task( + "dev-sandbox", + gulp.series( + function scripting() { + const defines = builder.merge(DEFINES, { GENERIC: true, TESTING: true }); + return createTemporaryScriptingBundle(defines, { + disableVersionInfo: true, + }); + }, + function () { + console.log(); + console.log("### Building development sandbox"); - var WebServer = require("./test/webserver.js").WebServer; - var server = new WebServer(); - server.port = 8888; - server.start(); + const defines = builder.merge(DEFINES, { GENERIC: true, TESTING: true }); + const sandboxDir = BUILD_DIR + "dev-sandbox/"; + + rimraf.sync(sandboxDir); + + return createSandboxBundle(defines, { + disableVersionInfo: true, + }).pipe(gulp.dest(sandboxDir)); + } + ) +); + +gulp.task("watch-dev-sandbox", function () { + gulp.watch( + [ + "src/pdf.{sandbox,scripting}.js", + "src/scripting_api/*.js", + "src/shared/scripting_utils.js", + "external/quickjs/*.js", + ], + { ignoreInitial: false }, + gulp.series("dev-sandbox") + ); }); +gulp.task( + "server", + gulp.parallel("watch-dev-sandbox", function () { + console.log(); + console.log("### Starting local server"); + + var WebServer = require("./test/webserver.js").WebServer; + var server = new WebServer(); + server.port = 8888; + server.start(); + }) +); + gulp.task("clean", function (done) { console.log(); console.log("### Cleaning up project builds"); diff --git a/package-lock.json b/package-lock.json index c3f80c08a..973b04ba7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2178,15 +2178,6 @@ "ansi-wrap": "^0.1.0" } }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, "ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -2196,15 +2187,6 @@ "ansi-wrap": "0.1.0" } }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -4028,15 +4010,6 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, - "decomment": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/decomment/-/decomment-0.9.3.tgz", - "integrity": "sha512-5skH5BfUL3n09RDmMVaHS1QGCiZRnl2nArUwmsE9JRY93Ueh3tihYl5wIrDdAuXnoFhxVis/DmRWREO2c6DG3w==", - "dev": true, - "requires": { - "esprima": "4.0.1" - } - }, "decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", @@ -7073,79 +7046,6 @@ "replacestream": "^4.0.0" } }, - "gulp-strip-comments": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/gulp-strip-comments/-/gulp-strip-comments-2.5.2.tgz", - "integrity": "sha512-lb1bW7rsPWDD8f4ZPSguDvmCdjKmjr5HR4yZb9ros3sLl5AfW7oUj8KzY9/VRisT7dG8dL7hVHzNpQEVxfwZGQ==", - "dev": true, - "requires": { - "decomment": "^0.9.0", - "plugin-error": "^0.1.2", - "through2": "^2.0.3" - }, - "dependencies": { - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - } - }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, "gulp-zip": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-5.0.2.tgz", diff --git a/package.json b/package.json index 0a58bbd5a..195f8f33b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "gulp-postcss": "^9.0.0", "gulp-rename": "^2.0.0", "gulp-replace": "^1.0.0", - "gulp-strip-comments": "^2.5.2", "gulp-zip": "^5.0.2", "jasmine": "^3.6.3", "jsdoc": "^3.6.6", diff --git a/src/scripting_api/quickjs-sandbox.js b/src/pdf.sandbox.js similarity index 82% rename from src/scripting_api/quickjs-sandbox.js rename to src/pdf.sandbox.js index cbf6e4e87..9dec2bc5f 100644 --- a/src/scripting_api/quickjs-sandbox.js +++ b/src/pdf.sandbox.js @@ -13,17 +13,24 @@ * limitations under the License. */ -import ModuleLoader from "../../external/quickjs/quickjs-eval.js"; +import ModuleLoader from "../external/quickjs/quickjs-eval.js"; + +/* eslint-disable-next-line no-unused-vars */ +const pdfjsVersion = PDFJSDev.eval("BUNDLE_VERSION"); +/* eslint-disable-next-line no-unused-vars */ +const pdfjsBuild = PDFJSDev.eval("BUNDLE_BUILD"); + +const TESTING = + typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || TESTING"); class Sandbox { - constructor(module, testMode) { + constructor(module) { this._evalInSandbox = module.cwrap("evalInSandbox", null, [ "string", "int", ]); this._dispatchEventName = null; this._module = module; - this._testMode = testMode; this._alertOnError = 1; } @@ -43,14 +50,14 @@ class Sandbox { "module = Object.create(null);", // Next line is replaced by code from initialization.js // when we create the bundle for the sandbox. - "/* INITIALIZATION_CODE */", + PDFJSDev.eval("PDF_SCRIPTING_JS_SOURCE"), `data = ${sandboxData};`, `module.exports.initSandbox({ data, extra: {${extraStr}}, out: this});`, "delete exports;", "delete module;", "delete data;", ]; - if (!this._testMode) { + if (!TESTING) { code = code.concat(extra.map(name => `delete ${name};`)); code.push("delete debugMe;"); } @@ -81,7 +88,7 @@ class Sandbox { } evalForTesting(code, key) { - if (this._testMode) { + if (TESTING) { this._evalInSandbox( `try { send({ id: "${key}", result: ${code} }); @@ -94,13 +101,9 @@ class Sandbox { } } -function QuickJSSandbox(testMode = false) { - testMode = - testMode && - (typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || TESTING")); +function QuickJSSandbox() { return ModuleLoader().then(module => { - return new Sandbox(module, testMode); + return new Sandbox(module); }); } diff --git a/test/unit/scripting_spec.js b/test/unit/scripting_spec.js index e4d6b1077..c8062b097 100644 --- a/test/unit/scripting_spec.js +++ b/test/unit/scripting_spec.js @@ -15,6 +15,8 @@ import { loadScript } from "../../src/display/display_utils.js"; +const sandboxBundleSrc = "../../build/generic/build/pdf.sandbox.js"; + describe("Scripting", function () { let sandbox, send_queue, test_id, ref; @@ -44,11 +46,9 @@ describe("Scripting", function () { send_queue.set(event.detail.id, event.detail); } }; - const promise = loadScript("../../build/generic/build/pdf.sandbox.js").then( - () => { - return window.pdfjsSandbox.QuickJSSandbox(true); - } - ); + const promise = loadScript(sandboxBundleSrc).then(() => { + return window.pdfjsSandbox.QuickJSSandbox(); + }); sandbox = { createSandbox(data) { promise.then(sbx => sbx.create(data)); diff --git a/web/app.js b/web/app.js index 620c9c239..ff7aa9fb8 100644 --- a/web/app.js +++ b/web/app.js @@ -248,12 +248,16 @@ const PDFViewerApplication = { url: "", baseUrl: "", externalServices: DefaultExternalServices, - _boundEvents: {}, - contentDispositionFilename: null, + _boundEvents: Object.create(null), + documentInfo: null, + metadata: null, + _contentDispositionFilename: null, + _contentLength: null, triggerDelayedFallback: null, _saveInProgress: false, _wheelUnusedTicks: 0, _idleCallbacks: new Set(), + _scriptingInstance: null, // Called once when the document is loaded. async initialize(appConfig) { @@ -789,7 +793,10 @@ const PDFViewerApplication = { this.downloadComplete = false; this.url = ""; this.baseUrl = ""; - this.contentDispositionFilename = null; + this.documentInfo = null; + this.metadata = null; + this._contentDispositionFilename = null; + this._contentLength = null; this.triggerDelayedFallback = null; this._saveInProgress = false; for (const callback of this._idleCallbacks) { @@ -797,6 +804,18 @@ const PDFViewerApplication = { } this._idleCallbacks.clear(); + if (this._scriptingInstance) { + const { scripting, events } = this._scriptingInstance; + try { + scripting.destroySandbox(); + } catch (ex) {} + + for (const [name, listener] of events) { + window.removeEventListener(name, listener); + } + this._scriptingInstance = null; + } + this.pdfSidebar.reset(); this.pdfOutlineViewer.reset(); this.pdfAttachmentViewer.reset(); @@ -942,7 +961,7 @@ const PDFViewerApplication = { // Use this.url instead of this.baseUrl to perform filename detection based // on the reference fragment as ultimate fallback if needed. const filename = - this.contentDispositionFilename || getPDFFileNameFromURL(this.url); + this._contentDispositionFilename || getPDFFileNameFromURL(this.url); const downloadManager = this.downloadManager; downloadManager.onerror = err => { // This error won't really be helpful because it's likely the @@ -975,7 +994,7 @@ const PDFViewerApplication = { // Use this.url instead of this.baseUrl to perform filename detection based // on the reference fragment as ultimate fallback if needed. const filename = - this.contentDispositionFilename || getPDFFileNameFromURL(this.url); + this._contentDispositionFilename || getPDFFileNameFromURL(this.url); const downloadManager = this.downloadManager; downloadManager.onerror = err => { // This error won't really be helpful because it's likely the @@ -1403,54 +1422,71 @@ const PDFViewerApplication = { * @private */ async _initializeJavaScript(pdfDocument) { - const objects = await pdfDocument.getFieldObjects(); - - if (pdfDocument !== this.pdfDocument) { - return; // The document was closed while the JavaScript data resolved. - } - if (!objects || !AppOptions.get("enableScripting")) { + if (!AppOptions.get("enableScripting")) { return; } - const calculationOrder = await pdfDocument.getCalculationOrderIds(); - const scripting = this.externalServices.scripting; - const { - info, - metadata, - contentDispositionFilename, - } = await pdfDocument.getMetadata(); + const [objects, calculationOrder] = await Promise.all([ + pdfDocument.getFieldObjects(), + pdfDocument.getCalculationOrderIds(), + ]); - window.addEventListener("updateFromSandbox", event => { - const detail = event.detail; - const id = detail.id; + if (!objects || pdfDocument !== this.pdfDocument) { + // No FieldObjects were found in the document, + // or the document was closed while the data resolved. + return; + } + const { scripting } = this.externalServices; + // Store a reference to the current scripting-instance, to allow destruction + // of the sandbox and removal of the event listeners at document closing. + this._scriptingInstance = { scripting, events: new Map() }; + + if (!this.documentInfo) { + // It should be *extremely* rare for metadata to not have been resolved + // when this code runs, but ensure that we handle that case here. + await new Promise(resolve => { + const metadataLoaded = () => { + this.eventBus._off("metadataloaded", metadataLoaded); + resolve(); + }; + this.eventBus._on("metadataloaded", metadataLoaded); + }); + if (pdfDocument !== this.pdfDocument) { + return; // The document was closed while the metadata resolved. + } + } + + const updateFromSandbox = event => { + const { detail } = event; + const { id, command, value } = detail; if (!id) { - switch (detail.command) { + switch (command) { case "alert": // eslint-disable-next-line no-alert - window.alert(detail.value); + window.alert(value); break; case "clear": console.clear(); break; case "error": - console.error(detail.value); + console.error(value); break; case "layout": - this.pdfViewer.spreadMode = apiPageLayoutToSpreadMode(detail.value); + this.pdfViewer.spreadMode = apiPageLayoutToSpreadMode(value); return; case "page-num": - this.pdfViewer.currentPageNumber = detail.value + 1; + this.pdfViewer.currentPageNumber = value + 1; return; case "print": this.triggerPrinting(); return; case "println": - console.log(detail.value); + console.log(value); break; case "zoom": - if (typeof detail.value === "string") { - this.pdfViewer.currentScaleValue = detail.value; + if (typeof value === "string") { + this.pdfViewer.currentScaleValue = value; } else { - this.pdfViewer.currentScale = detail.value; + this.pdfViewer.currentScale = value; } return; } @@ -1461,22 +1497,44 @@ const PDFViewerApplication = { if (element) { element.dispatchEvent(new CustomEvent("updateFromSandbox", { detail })); } else { - const value = detail.value; if (value !== undefined && value !== null) { - // the element hasn't been rendered yet so use annotation storage - pdfDocument.annotationStorage.setValue(id, detail.value); + // The element hasn't been rendered yet, use the AnnotationStorage. + pdfDocument.annotationStorage.setValue(id, value); } } - }); + }; + window.addEventListener("updateFromSandbox", updateFromSandbox); + // Ensure that the event listener can be removed at document closing. + this._scriptingInstance.events.set("updateFromSandbox", updateFromSandbox); - window.addEventListener("dispatchEventInSandbox", function (event) { + const dispatchEventInSandbox = event => { scripting.dispatchEventInSandbox(event.detail); - }); + }; + window.addEventListener("dispatchEventInSandbox", dispatchEventInSandbox); + // Ensure that the event listener can be removed at document closing. + this._scriptingInstance.events.set( + "dispatchEventInSandbox", + dispatchEventInSandbox + ); const dispatchEventName = generateRandomStringForSandbox(objects); - const { length } = await pdfDocument.getDownloadInfo(); + + if (!this._contentLength) { + // Always waiting for the entire PDF document to be loaded will, most + // likely, delay sandbox-creation too much in the general case for all + // PDF documents which are not provided as binary data to the API. + // Hence we'll simply have to trust that the `contentLength` (as provided + // by the server), when it exists, is accurate enough here. + const { length } = await pdfDocument.getDownloadInfo(); + + if (pdfDocument !== this.pdfDocument) { + return; // The document was closed while the download info resolved. + } + this._contentLength = length; + } const filename = - contentDispositionFilename || getPDFFileNameFromURL(this.url); + this._contentDispositionFilename || getPDFFileNameFromURL(this.url); + scripting.createSandbox({ objects, dispatchEventName, @@ -1486,11 +1544,11 @@ const PDFViewerApplication = { language: navigator.language, }, docInfo: { - ...info, + ...this.documentInfo, baseURL: this.baseUrl, - filesize: length, + filesize: this._contentLength, filename, - metadata, + metadata: this.metadata, numPages: pdfDocument.numPages, URL: this.url, }, @@ -1568,6 +1626,7 @@ const PDFViewerApplication = { info, metadata, contentDispositionFilename, + contentLength, } = await pdfDocument.getMetadata(); if (pdfDocument !== this.pdfDocument) { @@ -1575,7 +1634,8 @@ const PDFViewerApplication = { } this.documentInfo = info; this.metadata = metadata; - this.contentDispositionFilename = contentDispositionFilename; + this._contentDispositionFilename = contentDispositionFilename; + this._contentLength = contentLength; // Provides some basic debug information console.log( @@ -1652,6 +1712,8 @@ const PDFViewerApplication = { generator: generatorId, formType, }); + + this.eventBus.dispatch("metadataloaded", { source: this }); }, /** diff --git a/web/app_options.js b/web/app_options.js index d13f2026d..caf2455ca 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -223,14 +223,6 @@ const defaultOptions = { value: false, kind: OptionKind.API, }, - scriptingSrc: { - /** @type {string} */ - value: - typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION") - ? "../build/generic/build/pdf.sandbox.js" - : "../build/pdf.sandbox.js", - kind: OptionKind.VIEWER, - }, verbosity: { /** @type {number} */ value: 1, @@ -265,6 +257,14 @@ if ( value: typeof navigator !== "undefined" ? navigator.language : "en-US", kind: OptionKind.VIEWER, }; + defaultOptions.sandboxBundleSrc = { + /** @type {string} */ + value: + typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION") + ? "../build/dev-sandbox/pdf.sandbox.js" + : "../build/pdf.sandbox.js", + kind: OptionKind.VIEWER, + }; } const userOptions = Object.create(null); diff --git a/web/devcom.js b/web/devcom.js deleted file mode 100644 index 3df13f604..000000000 --- a/web/devcom.js +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright 2017 Mozilla Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DefaultExternalServices, PDFViewerApplication } from "./app.js"; -import { loadScript, shadow } from "pdfjs-lib"; - -const DevCom = {}; - -class DevExternalServices extends DefaultExternalServices { - static get scripting() { - const promise = loadScript("../build/pdf.sandbox.js").then(() => { - return window.pdfjsSandbox.QuickJSSandbox(); - }); - const sandbox = { - createSandbox(data) { - promise.then(sbx => sbx.create(data)); - }, - dispatchEventInSandbox(event) { - promise.then(sbx => sbx.dispatchEvent(event)); - }, - destroySandbox() { - promise.then(sbx => sbx.nukeSandbox()); - }, - }; - - return shadow(this, "scripting", sandbox); - } -} -PDFViewerApplication.externalServices = DevExternalServices; - -export { DevCom }; diff --git a/web/genericcom.js b/web/genericcom.js index f1f7dba38..2b83f0dfe 100644 --- a/web/genericcom.js +++ b/web/genericcom.js @@ -14,11 +14,11 @@ */ import { DefaultExternalServices, PDFViewerApplication } from "./app.js"; -import { loadScript, shadow } from "pdfjs-lib"; import { AppOptions } from "./app_options.js"; import { BasePreferences } from "./preferences.js"; import { DownloadManager } from "./download_manager.js"; import { GenericL10n } from "./genericl10n.js"; +import { loadScript } from "pdfjs-lib"; if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) { throw new Error( @@ -39,6 +39,29 @@ class GenericPreferences extends BasePreferences { } } +class GenericScripting { + constructor() { + this._ready = loadScript(AppOptions.get("sandboxBundleSrc")).then(() => { + return window.pdfjsSandbox.QuickJSSandbox(); + }); + } + + async createSandbox(data) { + const sandbox = await this._ready; + sandbox.create(data); + } + + async dispatchEventInSandbox(event) { + const sandbox = await this._ready; + sandbox.dispatchEvent(event); + } + + async destroySandbox() { + const sandbox = await this._ready; + sandbox.nukeSandbox(); + } +} + class GenericExternalServices extends DefaultExternalServices { static createDownloadManager(options) { return new DownloadManager(); @@ -53,22 +76,7 @@ class GenericExternalServices extends DefaultExternalServices { } static get scripting() { - const promise = loadScript(AppOptions.get("scriptingSrc")).then(() => { - return window.pdfjsSandbox.QuickJSSandbox(); - }); - const sandbox = { - createSandbox(data) { - promise.then(sbx => sbx.create(data)); - }, - dispatchEventInSandbox(event) { - promise.then(sbx => sbx.dispatchEvent(event)); - }, - destroySandbox() { - promise.then(sbx => sbx.nukeSandbox()); - }, - }; - - return shadow(this, "scripting", sandbox); + return new GenericScripting(); } } PDFViewerApplication.externalServices = GenericExternalServices; diff --git a/web/viewer.js b/web/viewer.js index 1bc2b35a8..d447c962a 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -56,9 +56,6 @@ if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC")) { if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("CHROME")) { require("./chromecom.js"); } -if (typeof PDFJSDev === "undefined") { - import("./devcom.js"); -} if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("CHROME || GENERIC")) { require("./pdf_print_service.js"); }