aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/jsdom/lib/api.js
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
commit76cb9c2a39d477a64824a985ade40507e3bbade1 (patch)
tree41e997aa9c6f538d3a136af61dae9424db2005a9 /vanilla/node_modules/jsdom/lib/api.js
parent819a39a21ac992b1393244a4c283bbb125208c69 (diff)
downloadneko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.gz
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.bz2
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.zip
feat(vanilla): add testing infrastructure and tests (NK-wjnczv)
Diffstat (limited to 'vanilla/node_modules/jsdom/lib/api.js')
-rw-r--r--vanilla/node_modules/jsdom/lib/api.js373
1 files changed, 373 insertions, 0 deletions
diff --git a/vanilla/node_modules/jsdom/lib/api.js b/vanilla/node_modules/jsdom/lib/api.js
new file mode 100644
index 0000000..6040d4f
--- /dev/null
+++ b/vanilla/node_modules/jsdom/lib/api.js
@@ -0,0 +1,373 @@
+"use strict";
+const path = require("path");
+const { pathToFileURL } = require("url");
+const fs = require("fs").promises;
+const vm = require("vm");
+const toughCookie = require("tough-cookie");
+const sniffHTMLEncoding = require("html-encoding-sniffer");
+const whatwgURL = require("whatwg-url");
+const { legacyHookDecode } = require("@exodus/bytes/encoding.js");
+const { URL } = require("whatwg-url");
+const { MIMEType } = require("whatwg-mimetype");
+const { getGlobalDispatcher } = require("undici");
+const idlUtils = require("./jsdom/living/generated/utils.js");
+const VirtualConsole = require("./jsdom/virtual-console.js");
+const { createWindow } = require("./jsdom/browser/Window.js");
+const { parseIntoDocument } = require("./jsdom/browser/parser");
+const { fragmentSerialization } = require("./jsdom/living/domparsing/serialization.js");
+const createDecompressInterceptor = require("./jsdom/browser/resources/decompress-interceptor.js");
+const {
+ JSDOMDispatcher, DEFAULT_USER_AGENT, fetchCollected
+} = require("./jsdom/browser/resources/jsdom-dispatcher.js");
+const requestInterceptor = require("./jsdom/browser/resources/request-interceptor.js");
+
+class CookieJar extends toughCookie.CookieJar {
+ constructor(store, options) {
+ // jsdom cookie jars must be loose by default
+ super(store, { looseMode: true, ...options });
+ }
+}
+
+const window = Symbol("window");
+let sharedFragmentDocument = null;
+
+class JSDOM {
+ constructor(input = "", options = {}) {
+ const mimeType = new MIMEType(options.contentType === undefined ? "text/html" : options.contentType);
+ const { html, encoding } = normalizeHTML(input, mimeType);
+
+ options = transformOptions(options, encoding, mimeType);
+
+ this[window] = createWindow(options.windowOptions);
+
+ const documentImpl = idlUtils.implForWrapper(this[window]._document);
+
+ options.beforeParse(this[window]._globalProxy);
+
+ parseIntoDocument(html, documentImpl);
+
+ documentImpl.close();
+ }
+
+ get window() {
+ // It's important to grab the global proxy, instead of just the result of `createWindow(...)`, since otherwise
+ // things like `window.eval` don't exist.
+ return this[window]._globalProxy;
+ }
+
+ get virtualConsole() {
+ return this[window]._virtualConsole;
+ }
+
+ get cookieJar() {
+ // TODO NEWAPI move _cookieJar to window probably
+ return idlUtils.implForWrapper(this[window]._document)._cookieJar;
+ }
+
+ serialize() {
+ return fragmentSerialization(idlUtils.implForWrapper(this[window]._document), { requireWellFormed: false });
+ }
+
+ nodeLocation(node) {
+ if (!idlUtils.implForWrapper(this[window]._document)._parseOptions.sourceCodeLocationInfo) {
+ throw new Error("Location information was not saved for this jsdom. Use includeNodeLocations during creation.");
+ }
+
+ return idlUtils.implForWrapper(node).sourceCodeLocation;
+ }
+
+ getInternalVMContext() {
+ if (!vm.isContext(this[window])) {
+ throw new TypeError("This jsdom was not configured to allow script running. " +
+ "Use the runScripts option during creation.");
+ }
+
+ return this[window];
+ }
+
+ reconfigure(settings) {
+ if ("windowTop" in settings) {
+ this[window]._top = settings.windowTop;
+ }
+
+ if ("url" in settings) {
+ const document = idlUtils.implForWrapper(this[window]._document);
+
+ const url = whatwgURL.parseURL(settings.url);
+ if (url === null) {
+ throw new TypeError(`Could not parse "${settings.url}" as a URL`);
+ }
+
+ document._URL = url;
+ document._origin = whatwgURL.serializeURLOrigin(document._URL);
+ this[window]._sessionHistory.currentEntry.url = url;
+ document._clearBaseURLCache();
+ }
+ }
+
+ static fragment(string = "") {
+ if (!sharedFragmentDocument) {
+ sharedFragmentDocument = (new JSDOM()).window.document;
+ }
+
+ const template = sharedFragmentDocument.createElement("template");
+ template.innerHTML = string;
+ return template.content;
+ }
+
+ static async fromURL(url, options = {}) {
+ options = normalizeFromURLOptions(options);
+
+ // Build the dispatcher for the initial request
+ // For the initial fetch, we default to "usable" instead of no resource loading, since fromURL() implicitly requests
+ // fetching the initial resource. This does not impact further resource fetching, which uses options.resources.
+ const resourcesForInitialFetch = options.resources !== undefined ? options.resources : "usable";
+ const { effectiveDispatcher } = extractResourcesOptions(resourcesForInitialFetch, options.cookieJar);
+
+ const headers = { Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" };
+ if (options.referrer) {
+ headers.Referer = options.referrer;
+ }
+
+ const response = await fetchCollected(effectiveDispatcher, {
+ url,
+ headers
+ });
+
+ if (!response.ok) {
+ throw new Error(`Resource was not loaded. Status: ${response.status}`);
+ }
+
+ options = Object.assign(options, {
+ url: response.url,
+ contentType: response.headers["content-type"] || undefined,
+ referrer: options.referrer,
+ resources: options.resources
+ });
+
+ return new JSDOM(response.body, options);
+ }
+
+ static async fromFile(filename, options = {}) {
+ options = normalizeFromFileOptions(filename, options);
+ const nodeBuffer = await fs.readFile(filename);
+
+ return new JSDOM(nodeBuffer, options);
+ }
+}
+
+function normalizeFromURLOptions(options) {
+ // Checks on options that are invalid for `fromURL`
+ if (options.url !== undefined) {
+ throw new TypeError("Cannot supply a url option when using fromURL");
+ }
+ if (options.contentType !== undefined) {
+ throw new TypeError("Cannot supply a contentType option when using fromURL");
+ }
+
+ // Normalization of options which must be done before the rest of the fromURL code can use them, because they are
+ // given to request()
+ const normalized = { ...options };
+
+ if (options.referrer !== undefined) {
+ normalized.referrer = (new URL(options.referrer)).href;
+ }
+
+ if (options.cookieJar === undefined) {
+ normalized.cookieJar = new CookieJar();
+ }
+
+ return normalized;
+
+ // All other options don't need to be processed yet, and can be taken care of in the normal course of things when
+ // `fromURL` calls `new JSDOM(html, options)`.
+}
+
+function extractResourcesOptions(resources, cookieJar) {
+ // loadSubresources controls whether PerDocumentResourceLoader fetches scripts, stylesheets, etc.
+ // XHR always works regardless of this flag.
+ let userAgent, baseDispatcher, userInterceptors, loadSubresources;
+
+ if (resources === undefined) {
+ // resources: undefined means no automatic subresource fetching, but XHR still works
+ userAgent = DEFAULT_USER_AGENT;
+ baseDispatcher = getGlobalDispatcher();
+ userInterceptors = [];
+ loadSubresources = false;
+ } else if (resources === "usable") {
+ // resources: "usable" means use all defaults
+ userAgent = DEFAULT_USER_AGENT;
+ baseDispatcher = getGlobalDispatcher();
+ userInterceptors = [];
+ loadSubresources = true;
+ } else if (typeof resources === "object" && resources !== null) {
+ // resources: { userAgent?, dispatcher?, interceptors? }
+ userAgent = resources.userAgent !== undefined ? resources.userAgent : DEFAULT_USER_AGENT;
+ baseDispatcher = resources.dispatcher !== undefined ? resources.dispatcher : getGlobalDispatcher();
+ userInterceptors = resources.interceptors !== undefined ? resources.interceptors : [];
+ loadSubresources = true;
+ } else {
+ throw new TypeError(`resources must be undefined, "usable", or an object`);
+ }
+
+ // User interceptors come first (outermost), then decompress interceptor
+ const allUserInterceptors = [
+ ...userInterceptors,
+ createDecompressInterceptor()
+ ];
+
+ return {
+ userAgent,
+ effectiveDispatcher: new JSDOMDispatcher({
+ baseDispatcher,
+ cookieJar,
+ userAgent,
+ userInterceptors: allUserInterceptors
+ }),
+ loadSubresources
+ };
+}
+
+function normalizeFromFileOptions(filename, options) {
+ const normalized = { ...options };
+
+ if (normalized.contentType === undefined) {
+ const extname = path.extname(filename);
+ if (extname === ".xhtml" || extname === ".xht" || extname === ".xml") {
+ normalized.contentType = "application/xhtml+xml";
+ }
+ }
+
+ if (normalized.url === undefined) {
+ normalized.url = pathToFileURL(path.resolve(filename)).href;
+ }
+
+ return normalized;
+}
+
+function transformOptions(options, encoding, mimeType) {
+ const transformed = {
+ windowOptions: {
+ // Defaults
+ url: "about:blank",
+ referrer: "",
+ contentType: "text/html",
+ parsingMode: "html",
+ parseOptions: {
+ sourceCodeLocationInfo: false,
+ scriptingEnabled: false
+ },
+ runScripts: undefined,
+ encoding,
+ pretendToBeVisual: false,
+ storageQuota: 5000000,
+
+ // Defaults filled in later
+ dispatcher: undefined,
+ loadSubresources: undefined,
+ userAgent: undefined,
+ virtualConsole: undefined,
+ cookieJar: undefined
+ },
+
+ // Defaults
+ beforeParse() { }
+ };
+
+ // options.contentType was parsed into mimeType by the caller.
+ if (!mimeType.isHTML() && !mimeType.isXML()) {
+ throw new RangeError(`The given content type of "${options.contentType}" was not a HTML or XML content type`);
+ }
+
+ transformed.windowOptions.contentType = mimeType.essence;
+ transformed.windowOptions.parsingMode = mimeType.isHTML() ? "html" : "xml";
+
+ if (options.url !== undefined) {
+ transformed.windowOptions.url = (new URL(options.url)).href;
+ }
+
+ if (options.referrer !== undefined) {
+ transformed.windowOptions.referrer = (new URL(options.referrer)).href;
+ }
+
+ if (options.includeNodeLocations) {
+ if (transformed.windowOptions.parsingMode === "xml") {
+ throw new TypeError("Cannot set includeNodeLocations to true with an XML content type");
+ }
+
+ transformed.windowOptions.parseOptions = { sourceCodeLocationInfo: true };
+ }
+
+ transformed.windowOptions.cookieJar = options.cookieJar === undefined ?
+ new CookieJar() :
+ options.cookieJar;
+
+ transformed.windowOptions.virtualConsole = options.virtualConsole === undefined ?
+ (new VirtualConsole()).forwardTo(console) :
+ options.virtualConsole;
+
+ if (!(transformed.windowOptions.virtualConsole instanceof VirtualConsole)) {
+ throw new TypeError("virtualConsole must be an instance of VirtualConsole");
+ }
+
+ const { userAgent, effectiveDispatcher, loadSubresources } =
+ extractResourcesOptions(options.resources, transformed.windowOptions.cookieJar);
+ transformed.windowOptions.userAgent = userAgent;
+ transformed.windowOptions.dispatcher = effectiveDispatcher;
+ transformed.windowOptions.loadSubresources = loadSubresources;
+
+ if (options.runScripts !== undefined) {
+ transformed.windowOptions.runScripts = String(options.runScripts);
+ if (transformed.windowOptions.runScripts === "dangerously") {
+ transformed.windowOptions.parseOptions.scriptingEnabled = true;
+ } else if (transformed.windowOptions.runScripts !== "outside-only") {
+ throw new RangeError(`runScripts must be undefined, "dangerously", or "outside-only"`);
+ }
+ }
+
+ if (options.beforeParse !== undefined) {
+ transformed.beforeParse = options.beforeParse;
+ }
+
+ if (options.pretendToBeVisual !== undefined) {
+ transformed.windowOptions.pretendToBeVisual = Boolean(options.pretendToBeVisual);
+ }
+
+ if (options.storageQuota !== undefined) {
+ transformed.windowOptions.storageQuota = Number(options.storageQuota);
+ }
+
+ return transformed;
+}
+
+function normalizeHTML(html, mimeType) {
+ let encoding = "UTF-8";
+
+ if (html instanceof Uint8Array) {
+ // leave as-is
+ } else if (ArrayBuffer.isView(html)) {
+ html = new Uint8Array(html.buffer, html.byteOffset, html.byteLength);
+ } else if (html instanceof ArrayBuffer) {
+ html = new Uint8Array(html);
+ }
+
+ if (html instanceof Uint8Array) {
+ encoding = sniffHTMLEncoding(html, {
+ xml: mimeType.isXML(),
+ transportLayerEncodingLabel: mimeType.parameters.get("charset")
+ });
+ html = legacyHookDecode(html, encoding);
+ } else {
+ html = String(html);
+ }
+
+ return { html, encoding };
+}
+
+exports.JSDOM = JSDOM;
+
+exports.VirtualConsole = VirtualConsole;
+exports.CookieJar = CookieJar;
+exports.requestInterceptor = requestInterceptor;
+
+exports.toughCookie = toughCookie;