Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[api-minor] Re-factor how Node.js packages/polyfills are loaded (issue 17245) #18051

Merged
merged 2 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions gulpfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -445,10 +445,8 @@ function checkChromePreferencesFile(chromePrefsPath, webPrefs) {

function tweakWebpackOutput(jsName) {
const replacer = [
" __webpack_exports__ = {};",
",__webpack_exports__={};",
" __webpack_exports__ = await __webpack_exports__;",
"\\(__webpack_exports__=await __webpack_exports__\\)",
" __webpack_exports__ = {};", // Normal builds.
",__webpack_exports__={};", // Minified builds.
];
const regex = new RegExp(`(${replacer.join("|")})`, "gm");

Expand All @@ -458,10 +456,6 @@ function tweakWebpackOutput(jsName) {
return ` __webpack_exports__ = globalThis.${jsName} = {};`;
case ",__webpack_exports__={};":
return `,__webpack_exports__=globalThis.${jsName}={};`;
case " __webpack_exports__ = await __webpack_exports__;":
return ` __webpack_exports__ = globalThis.${jsName} = await (globalThis.${jsName}Promise = __webpack_exports__);`;
case "(__webpack_exports__=await __webpack_exports__)":
return `(__webpack_exports__=globalThis.${jsName}=await (globalThis.${jsName}Promise=__webpack_exports__))`;
}
return match;
});
Expand Down
9 changes: 9 additions & 0 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
NodeCanvasFactory,
NodeCMapReaderFactory,
NodeFilterFactory,
NodePackages,
NodeStandardFontDataFactory,
} from "display-node_utils";
import { CanvasGraphics } from "./canvas.js";
Expand Down Expand Up @@ -2089,6 +2090,14 @@ class PDFWorker {
* @type {Promise<void>}
*/
get promise() {
if (
typeof PDFJSDev !== "undefined" &&
PDFJSDev.test("GENERIC") &&
isNodeJS
) {
// Ensure that all Node.js packages/polyfills have loaded.
return Promise.all([NodePackages.promise, this._readyCapability.promise]);
}
return this._readyCapability.promise;
}

Expand Down
24 changes: 9 additions & 15 deletions src/display/node_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,23 @@
* limitations under the License.
*/

import {
AbortException,
assert,
isNodeJS,
MissingPDFException,
} from "../shared/util.js";
import { AbortException, assert, MissingPDFException } from "../shared/util.js";
import {
extractFilenameFromHeader,
validateRangeRequestCapabilities,
} from "./network_utils.js";
import { NodePackages } from "./node_utils.js";

if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
throw new Error(
'Module "./node_stream.js" shall not be used with MOZCENTRAL builds.'
);
}

let fs, http, https, url;
if (isNodeJS) {
// Native packages.
fs = await __non_webpack_import__("fs");
http = await __non_webpack_import__("http");
https = await __non_webpack_import__("https");
url = await __non_webpack_import__("url");
}

const fileUriRegex = /^file:\/\/\/[a-zA-Z]:\//;

function parseUrl(sourceUrl) {
const url = NodePackages.get("url");
const parsedUrl = url.parse(sourceUrl);
if (parsedUrl.protocol === "file:" || parsedUrl.host) {
return parsedUrl;
Expand Down Expand Up @@ -347,11 +335,13 @@ class PDFNodeStreamFullReader extends BaseFullReader {

this._request = null;
if (this._url.protocol === "http:") {
const http = NodePackages.get("http");
this._request = http.request(
createRequestOptions(this._url, stream.httpHeaders),
handleResponse
);
} else {
const https = NodePackages.get("https");
this._request = https.request(
createRequestOptions(this._url, stream.httpHeaders),
handleResponse
Expand Down Expand Up @@ -394,11 +384,13 @@ class PDFNodeStreamRangeReader extends BaseRangeReader {

this._request = null;
if (this._url.protocol === "http:") {
const http = NodePackages.get("http");
this._request = http.request(
createRequestOptions(this._url, this._httpHeaders),
handleResponse
);
} else {
const https = NodePackages.get("https");
this._request = https.request(
createRequestOptions(this._url, this._httpHeaders),
handleResponse
Expand All @@ -423,6 +415,7 @@ class PDFNodeStreamFsFullReader extends BaseFullReader {
path = path.replace(/^\//, "");
}

const fs = NodePackages.get("fs");
fs.promises.lstat(path).then(
stat => {
// Setting right content length.
Expand Down Expand Up @@ -453,6 +446,7 @@ class PDFNodeStreamFsRangeReader extends BaseRangeReader {
path = path.replace(/^\//, "");
}

const fs = NodePackages.get("fs");
this._setReadableStream(fs.createReadStream(path, { start, end: end - 1 }));
}
}
Expand Down
118 changes: 77 additions & 41 deletions src/display/node_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,56 +27,90 @@ if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
);
}

let fs, canvas, path2d;
if (isNodeJS) {
// Native packages.
fs = await __non_webpack_import__("fs");
// Optional, third-party, packages.
try {
canvas = await __non_webpack_import__("canvas");
} catch {}
try {
path2d = await __non_webpack_import__("path2d");
} catch {}
}
// eslint-disable-next-line no-var
var packageCapability = Promise.withResolvers();
// eslint-disable-next-line no-var
var packageMap = null;

if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("SKIP_BABEL")) {
(function checkDOMMatrix() {
if (globalThis.DOMMatrix || !isNodeJS) {
return;
}
const DOMMatrix = canvas?.DOMMatrix;
const loadPackages = async () => {
// Native packages.
const fs = await __non_webpack_import__("fs"),
http = await __non_webpack_import__("http"),
https = await __non_webpack_import__("https"),
url = await __non_webpack_import__("url");

if (DOMMatrix) {
globalThis.DOMMatrix = DOMMatrix;
} else {
warn("Cannot polyfill `DOMMatrix`, rendering may be broken.");
// Optional, third-party, packages.
let canvas, path2d;
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("SKIP_BABEL")) {
Snuffleupagus marked this conversation as resolved.
Show resolved Hide resolved
try {
canvas = await __non_webpack_import__("canvas");
} catch {}
try {
path2d = await __non_webpack_import__("path2d");
} catch {}
}
})();

(function checkPath2D() {
if (globalThis.Path2D || !isNodeJS) {
return;
}
const CanvasRenderingContext2D = canvas?.CanvasRenderingContext2D;
const applyPath2DToCanvasRenderingContext =
path2d?.applyPath2DToCanvasRenderingContext;
const Path2D = path2d?.Path2D;

if (
CanvasRenderingContext2D &&
applyPath2DToCanvasRenderingContext &&
Path2D
) {
applyPath2DToCanvasRenderingContext(CanvasRenderingContext2D);
globalThis.Path2D = Path2D;
} else {
warn("Cannot polyfill `Path2D`, rendering may be broken.");
return new Map(Object.entries({ fs, http, https, url, canvas, path2d }));
};

loadPackages().then(
map => {
packageMap = map;
packageCapability.resolve();

if (typeof PDFJSDev === "undefined" || PDFJSDev.test("SKIP_BABEL")) {
return;
}
if (!globalThis.DOMMatrix) {
const DOMMatrix = map.get("canvas")?.DOMMatrix;

if (DOMMatrix) {
globalThis.DOMMatrix = DOMMatrix;
} else {
warn("Cannot polyfill `DOMMatrix`, rendering may be broken.");
}
}
if (!globalThis.Path2D) {
const CanvasRenderingContext2D =
map.get("canvas")?.CanvasRenderingContext2D;
const applyPath2DToCanvasRenderingContext =
map.get("path2d")?.applyPath2DToCanvasRenderingContext;
const Path2D = map.get("path2d")?.Path2D;

if (
CanvasRenderingContext2D &&
applyPath2DToCanvasRenderingContext &&
Path2D
) {
applyPath2DToCanvasRenderingContext(CanvasRenderingContext2D);
globalThis.Path2D = Path2D;
} else {
warn("Cannot polyfill `Path2D`, rendering may be broken.");
}
}
},
reason => {
warn(`loadPackages: ${reason}`);

packageMap = new Map();
timvandermeij marked this conversation as resolved.
Show resolved Hide resolved
packageCapability.resolve();
}
})();
);
}

class NodePackages {
static get promise() {
return packageCapability.promise;
}

static get(name) {
return packageMap?.get(name);
}
}

const fetchData = function (url) {
const fs = NodePackages.get("fs");
return fs.promises.readFile(url).then(data => new Uint8Array(data));
};

Expand All @@ -87,6 +121,7 @@ class NodeCanvasFactory extends BaseCanvasFactory {
* @ignore
*/
_createCanvas(width, height) {
const canvas = NodePackages.get("canvas");
return canvas.createCanvas(width, height);
}
}
Expand All @@ -113,5 +148,6 @@ export {
NodeCanvasFactory,
NodeCMapReaderFactory,
NodeFilterFactory,
NodePackages,
NodeStandardFontDataFactory,
};
2 changes: 2 additions & 0 deletions src/display/stubs.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
const NodeCanvasFactory = null;
const NodeCMapReaderFactory = null;
const NodeFilterFactory = null;
const NodePackages = null;
const NodeStandardFontDataFactory = null;
const PDFFetchStream = null;
const PDFNetworkStream = null;
Expand All @@ -25,6 +26,7 @@ export {
NodeCanvasFactory,
NodeCMapReaderFactory,
NodeFilterFactory,
NodePackages,
NodeStandardFontDataFactory,
PDFFetchStream,
PDFNetworkStream,
Expand Down
4 changes: 4 additions & 0 deletions test/unit/clitests_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
setVerbosityLevel,
VerbosityLevel,
} from "../../src/shared/util.js";
import { NodePackages } from "../../src/display/node_utils.js";

// Sets longer timeout, similar to `jasmine-boot.js`.
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
Expand All @@ -29,6 +30,9 @@ if (!isNodeJS) {
);
}

// Ensure that all Node.js packages/polyfills have loaded.
await NodePackages.promise;

// Reduce the amount of console "spam", by ignoring `info`/`warn` calls,
// when running the unit-tests in Node.js/Travis.
setVerbosityLevel(VerbosityLevel.ERRORS);
9 changes: 0 additions & 9 deletions web/pdfjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,6 @@
* limitations under the License.
*/

// Ensure that the viewer waits for the library to complete loading,
timvandermeij marked this conversation as resolved.
Show resolved Hide resolved
// to avoid breaking e.g. the standalone viewer components (see issue 17228).
if (
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) &&
!globalThis.pdfjsLib
) {
await globalThis.pdfjsLibPromise;
}

const {
AbortException,
AnnotationEditorLayer,
Expand Down