diff options
Diffstat (limited to 'alarm/node_modules/jsdom/lib/jsdom/browser')
13 files changed, 3038 insertions, 0 deletions
diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/Window.js b/alarm/node_modules/jsdom/lib/jsdom/browser/Window.js new file mode 100644 index 0000000..9b2d75f --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/Window.js @@ -0,0 +1,933 @@ +"use strict"; +const vm = require("vm"); +const webIDLConversions = require("webidl-conversions"); +const { CSSStyleDeclaration } = require("cssstyle"); +const { Performance: RawPerformance } = require("w3c-hr-time"); +const notImplemented = require("./not-implemented"); +const { installInterfaces } = require("../living/interfaces"); +const { define, mixin } = require("../utils"); +const Element = require("../living/generated/Element"); +const EventTarget = require("../living/generated/EventTarget"); +const EventHandlerNonNull = require("../living/generated/EventHandlerNonNull"); +const OnBeforeUnloadEventHandlerNonNull = require("../living/generated/OnBeforeUnloadEventHandlerNonNull"); +const OnErrorEventHandlerNonNull = require("../living/generated/OnErrorEventHandlerNonNull"); +const PageTransitionEvent = require("../living/generated/PageTransitionEvent"); +const namedPropertiesWindow = require("../living/named-properties-window"); +const postMessage = require("../living/post-message"); +const DOMException = require("domexception/webidl2js-wrapper"); +const { btoa, atob } = require("abab"); +const idlUtils = require("../living/generated/utils"); +const WebSocketImpl = require("../living/websockets/WebSocket-impl").implementation; +const BarProp = require("../living/generated/BarProp"); +const documents = require("../living/documents.js"); +const External = require("../living/generated/External"); +const Navigator = require("../living/generated/Navigator"); +const Performance = require("../living/generated/Performance"); +const Screen = require("../living/generated/Screen"); +const Storage = require("../living/generated/Storage"); +const Selection = require("../living/generated/Selection"); +const reportException = require("../living/helpers/runtime-script-errors"); +const { getCurrentEventHandlerValue } = require("../living/helpers/create-event-accessor.js"); +const { fireAnEvent } = require("../living/helpers/events"); +const SessionHistory = require("../living/window/SessionHistory"); +const { forEachMatchingSheetRuleOfElement, getResolvedValue, propertiesWithResolvedValueImplemented, + SHADOW_DOM_PSEUDO_REGEXP } = require("../living/helpers/style-rules.js"); +const CustomElementRegistry = require("../living/generated/CustomElementRegistry"); +const jsGlobals = require("./js-globals.json"); + +const GlobalEventHandlersImpl = require("../living/nodes/GlobalEventHandlers-impl").implementation; +const WindowEventHandlersImpl = require("../living/nodes/WindowEventHandlers-impl").implementation; + +const events = new Set([ + // GlobalEventHandlers + "abort", "autocomplete", + "autocompleteerror", "blur", + "cancel", "canplay", "canplaythrough", + "change", "click", + "close", "contextmenu", + "cuechange", "dblclick", + "drag", "dragend", + "dragenter", + "dragleave", "dragover", + "dragstart", "drop", + "durationchange", "emptied", + "ended", "focus", + "input", "invalid", + "keydown", "keypress", + "keyup", "load", "loadeddata", + "loadedmetadata", "loadstart", + "mousedown", "mouseenter", + "mouseleave", "mousemove", + "mouseout", "mouseover", + "mouseup", "wheel", + "pause", "play", + "playing", "progress", + "ratechange", "reset", + "resize", "scroll", + "securitypolicyviolation", + "seeked", "seeking", + "select", "sort", "stalled", + "submit", "suspend", + "timeupdate", "toggle", + "volumechange", "waiting", + + // WindowEventHandlers + "afterprint", + "beforeprint", + "hashchange", + "languagechange", + "message", + "messageerror", + "offline", + "online", + "pagehide", + "pageshow", + "popstate", + "rejectionhandled", + "storage", + "unhandledrejection", + "unload" + + // "error" and "beforeunload" are added separately +]); + +exports.createWindow = function (options) { + return new Window(options); +}; + +const jsGlobalEntriesToInstall = Object.entries(jsGlobals).filter(([name]) => name in global); + +// TODO remove when we drop Node v10 support. +const anyNodeVersionQueueMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : process.nextTick; + +// https://html.spec.whatwg.org/#the-window-object +function setupWindow(windowInstance, { runScripts }) { + if (runScripts === "outside-only" || runScripts === "dangerously") { + contextifyWindow(windowInstance); + + // Without this, these globals will only appear to scripts running inside the context using vm.runScript; they will + // not appear to scripts running from the outside, including to JSDOM implementation code. + for (const [globalName, globalPropDesc] of jsGlobalEntriesToInstall) { + const propDesc = { ...globalPropDesc, value: vm.runInContext(globalName, windowInstance) }; + Object.defineProperty(windowInstance, globalName, propDesc); + } + } else { + // Without contextifying the window, none of the globals will exist. So, let's at least alias them from the Node.js + // context. See https://github.com/jsdom/jsdom/issues/2727 for more background and discussion. + for (const [globalName, globalPropDesc] of jsGlobalEntriesToInstall) { + const propDesc = { ...globalPropDesc, value: global[globalName] }; + Object.defineProperty(windowInstance, globalName, propDesc); + } + } + + installInterfaces(windowInstance, ["Window"]); + + const EventTargetConstructor = windowInstance.EventTarget; + + // eslint-disable-next-line func-name-matching, func-style, no-shadow + const windowConstructor = function Window() { + throw new TypeError("Illegal constructor"); + }; + Object.setPrototypeOf(windowConstructor, EventTargetConstructor); + + Object.defineProperty(windowInstance, "Window", { + configurable: true, + writable: true, + value: windowConstructor + }); + + const windowPrototype = Object.create(EventTargetConstructor.prototype); + Object.defineProperties(windowPrototype, { + constructor: { + value: windowConstructor, + writable: true, + configurable: true + }, + [Symbol.toStringTag]: { + value: "Window", + configurable: true + } + }); + + windowConstructor.prototype = windowPrototype; + Object.setPrototypeOf(windowInstance, windowPrototype); + + EventTarget.setup(windowInstance, windowInstance); + mixin(windowInstance, WindowEventHandlersImpl.prototype); + mixin(windowInstance, GlobalEventHandlersImpl.prototype); + windowInstance._initGlobalEvents(); + + Object.defineProperty(windowInstance, "onbeforeunload", { + configurable: true, + enumerable: true, + get() { + return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(this, "beforeunload")); + }, + set(V) { + if (!idlUtils.isObject(V)) { + V = null; + } else { + V = OnBeforeUnloadEventHandlerNonNull.convert(V, { + context: "Failed to set the 'onbeforeunload' property on 'Window': The provided value" + }); + } + this._setEventHandlerFor("beforeunload", V); + } + }); + + Object.defineProperty(windowInstance, "onerror", { + configurable: true, + enumerable: true, + get() { + return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(this, "error")); + }, + set(V) { + if (!idlUtils.isObject(V)) { + V = null; + } else { + V = OnErrorEventHandlerNonNull.convert(V, { + context: "Failed to set the 'onerror' property on 'Window': The provided value" + }); + } + this._setEventHandlerFor("error", V); + } + }); + + for (const event of events) { + Object.defineProperty(windowInstance, `on${event}`, { + configurable: true, + enumerable: true, + get() { + return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(this, event)); + }, + set(V) { + if (!idlUtils.isObject(V)) { + V = null; + } else { + V = EventHandlerNonNull.convert(V, { + context: `Failed to set the 'on${event}' property on 'Window': The provided value` + }); + } + this._setEventHandlerFor(event, V); + } + }); + } + + windowInstance._globalObject = windowInstance; +} + +// NOTE: per https://heycam.github.io/webidl/#Global, all properties on the Window object must be own-properties. +// That is why we assign everything inside of the constructor, instead of using a shared prototype. +// You can verify this in e.g. Firefox or Internet Explorer, which do a good job with Web IDL compliance. +function Window(options) { + setupWindow(this, { runScripts: options.runScripts }); + + const rawPerformance = new RawPerformance(); + const windowInitialized = rawPerformance.now(); + + const window = this; + + // ### PRIVATE DATA PROPERTIES + + this._resourceLoader = options.resourceLoader; + + // vm initialization is deferred until script processing is activated + this._globalProxy = this; + Object.defineProperty(idlUtils.implForWrapper(this), idlUtils.wrapperSymbol, { get: () => this._globalProxy }); + + // List options explicitly to be clear which are passed through + this._document = documents.createWrapper(window, { + parsingMode: options.parsingMode, + contentType: options.contentType, + encoding: options.encoding, + cookieJar: options.cookieJar, + url: options.url, + lastModified: options.lastModified, + referrer: options.referrer, + parseOptions: options.parseOptions, + defaultView: this._globalProxy, + global: this, + parentOrigin: options.parentOrigin + }, { alwaysUseDocumentClass: true }); + + if (vm.isContext(window)) { + const documentImpl = idlUtils.implForWrapper(window._document); + documentImpl._defaultView = window._globalProxy = vm.runInContext("this", window); + } + + const documentOrigin = idlUtils.implForWrapper(this._document)._origin; + this._origin = documentOrigin; + + // https://html.spec.whatwg.org/#session-history + this._sessionHistory = new SessionHistory({ + document: idlUtils.implForWrapper(this._document), + url: idlUtils.implForWrapper(this._document)._URL, + stateObject: null + }, this); + + this._virtualConsole = options.virtualConsole; + + this._runScripts = options.runScripts; + + // Set up the window as if it's a top level window. + // If it's not, then references will be corrected by frame/iframe code. + this._parent = this._top = this._globalProxy; + this._frameElement = null; + + // This implements window.frames.length, since window.frames returns a + // self reference to the window object. This value is incremented in the + // HTMLFrameElement implementation. + this._length = 0; + + // https://dom.spec.whatwg.org/#window-current-event + this._currentEvent = undefined; + + this._pretendToBeVisual = options.pretendToBeVisual; + this._storageQuota = options.storageQuota; + + // Some properties (such as localStorage and sessionStorage) share data + // between windows in the same origin. This object is intended + // to contain such data. + if (options.commonForOrigin && options.commonForOrigin[documentOrigin]) { + this._commonForOrigin = options.commonForOrigin; + } else { + this._commonForOrigin = { + [documentOrigin]: { + localStorageArea: new Map(), + sessionStorageArea: new Map(), + windowsInSameOrigin: [this] + } + }; + } + + this._currentOriginData = this._commonForOrigin[documentOrigin]; + + // ### WEB STORAGE + + this._localStorage = Storage.create(window, [], { + associatedWindow: this, + storageArea: this._currentOriginData.localStorageArea, + type: "localStorage", + url: this._document.documentURI, + storageQuota: this._storageQuota + }); + this._sessionStorage = Storage.create(window, [], { + associatedWindow: this, + storageArea: this._currentOriginData.sessionStorageArea, + type: "sessionStorage", + url: this._document.documentURI, + storageQuota: this._storageQuota + }); + + // ### SELECTION + + // https://w3c.github.io/selection-api/#dfn-selection + this._selection = Selection.createImpl(window); + + // https://w3c.github.io/selection-api/#dom-window + this.getSelection = function () { + return window._selection; + }; + + // ### GETTERS + + const locationbar = BarProp.create(window); + const menubar = BarProp.create(window); + const personalbar = BarProp.create(window); + const scrollbars = BarProp.create(window); + const statusbar = BarProp.create(window); + const toolbar = BarProp.create(window); + const external = External.create(window); + const navigator = Navigator.create(window, [], { userAgent: this._resourceLoader._userAgent }); + const performance = Performance.create(window, [], { rawPerformance }); + const screen = Screen.create(window); + const customElementRegistry = CustomElementRegistry.create(window); + + define(this, { + get length() { + return window._length; + }, + get window() { + return window._globalProxy; + }, + get frameElement() { + return idlUtils.wrapperForImpl(window._frameElement); + }, + get frames() { + return window._globalProxy; + }, + get self() { + return window._globalProxy; + }, + get parent() { + return window._parent; + }, + get top() { + return window._top; + }, + get document() { + return window._document; + }, + get external() { + return external; + }, + get location() { + return idlUtils.wrapperForImpl(idlUtils.implForWrapper(window._document)._location); + }, + get history() { + return idlUtils.wrapperForImpl(idlUtils.implForWrapper(window._document)._history); + }, + get navigator() { + return navigator; + }, + get locationbar() { + return locationbar; + }, + get menubar() { + return menubar; + }, + get personalbar() { + return personalbar; + }, + get scrollbars() { + return scrollbars; + }, + get statusbar() { + return statusbar; + }, + get toolbar() { + return toolbar; + }, + get performance() { + return performance; + }, + get screen() { + return screen; + }, + get origin() { + return window._origin; + }, + // The origin IDL attribute is defined with [Replaceable]. + set origin(value) { + Object.defineProperty(this, "origin", { + value, + writable: true, + enumerable: true, + configurable: true + }); + }, + get localStorage() { + if (idlUtils.implForWrapper(this._document)._origin === "null") { + throw DOMException.create(window, [ + "localStorage is not available for opaque origins", + "SecurityError" + ]); + } + + return this._localStorage; + }, + get sessionStorage() { + if (idlUtils.implForWrapper(this._document)._origin === "null") { + throw DOMException.create(window, [ + "sessionStorage is not available for opaque origins", + "SecurityError" + ]); + } + + return this._sessionStorage; + }, + get customElements() { + return customElementRegistry; + }, + get event() { + return window._currentEvent ? idlUtils.wrapperForImpl(window._currentEvent) : undefined; + }, + set event(value) { + Object.defineProperty(window, "event", { configurable: true, enumerable: true, writable: true, value }); + } + }); + + namedPropertiesWindow.initializeWindow(this, this._globalProxy); + + // ### METHODS + + // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers + + // In the spec the list of active timers is a set of IDs. We make it a map of IDs to Node.js timer objects, so that + // we can call Node.js-side clearTimeout() when clearing, and thus allow process shutdown faster. + const listOfActiveTimers = new Map(); + let latestTimerId = 0; + + this.setTimeout = function (handler, timeout = 0, ...args) { + if (typeof handler !== "function") { + handler = webIDLConversions.DOMString(handler); + } + timeout = webIDLConversions.long(timeout); + + return timerInitializationSteps(handler, timeout, args, { methodContext: window, repeat: false }); + }; + this.setInterval = function (handler, timeout = 0, ...args) { + if (typeof handler !== "function") { + handler = webIDLConversions.DOMString(handler); + } + timeout = webIDLConversions.long(timeout); + + return timerInitializationSteps(handler, timeout, args, { methodContext: window, repeat: true }); + }; + + this.clearTimeout = function (handle = 0) { + handle = webIDLConversions.long(handle); + + const nodejsTimer = listOfActiveTimers.get(handle); + if (nodejsTimer) { + clearTimeout(nodejsTimer); + listOfActiveTimers.delete(handle); + } + }; + this.clearInterval = function (handle = 0) { + handle = webIDLConversions.long(handle); + + const nodejsTimer = listOfActiveTimers.get(handle); + if (nodejsTimer) { + // We use setTimeout() in timerInitializationSteps even for this.setInterval(). + clearTimeout(nodejsTimer); + listOfActiveTimers.delete(handle); + } + }; + + function timerInitializationSteps(handler, timeout, args, { methodContext, repeat, previousHandle }) { + // This appears to be unspecced, but matches browser behavior for close()ed windows. + if (!methodContext._document) { + return 0; + } + + // TODO: implement timer nesting level behavior. + + const methodContextProxy = methodContext._globalProxy; + const handle = previousHandle !== undefined ? previousHandle : ++latestTimerId; + + function task() { + if (!listOfActiveTimers.has(handle)) { + return; + } + + try { + if (typeof handler === "function") { + handler.apply(methodContextProxy, args); + } else if (window._runScripts === "dangerously") { + vm.runInContext(handler, window, { filename: window.location.href, displayErrors: false }); + } + } catch (e) { + reportException(window, e, window.location.href); + } + + if (listOfActiveTimers.has(handle)) { + if (repeat) { + timerInitializationSteps(handler, timeout, args, { methodContext, repeat: true, previousHandle: handle }); + } else { + listOfActiveTimers.delete(handle); + } + } + } + + if (timeout < 0) { + timeout = 0; + } + + const nodejsTimer = setTimeout(task, timeout); + listOfActiveTimers.set(handle, nodejsTimer); + + return handle; + } + + // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#microtask-queuing + + this.queueMicrotask = function (callback) { + callback = webIDLConversions.Function(callback); + + anyNodeVersionQueueMicrotask(() => { + try { + callback(); + } catch (e) { + reportException(window, e, window.location.href); + } + }); + }; + + // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animation-frames + + let animationFrameCallbackId = 0; + const mapOfAnimationFrameCallbacks = new Map(); + let animationFrameNodejsInterval = null; + + // Unlike the spec, where an animation frame happens every 60 Hz regardless, we optimize so that if there are no + // requestAnimationFrame() calls outstanding, we don't fire the timer. This helps us track that. + let numberOfOngoingAnimationFrameCallbacks = 0; + + if (this._pretendToBeVisual) { + this.requestAnimationFrame = function (callback) { + callback = webIDLConversions.Function(callback); + + const handle = ++animationFrameCallbackId; + mapOfAnimationFrameCallbacks.set(handle, callback); + + ++numberOfOngoingAnimationFrameCallbacks; + if (numberOfOngoingAnimationFrameCallbacks === 1) { + animationFrameNodejsInterval = setInterval(() => { + runAnimationFrameCallbacks(rawPerformance.now() - windowInitialized); + }, 1000 / 60); + } + + return handle; + }; + + this.cancelAnimationFrame = function (handle) { + handle = webIDLConversions["unsigned long"](handle); + + removeAnimationFrameCallback(handle); + }; + + function runAnimationFrameCallbacks(now) { + // Converting to an array is important to get a sync snapshot and thus match spec semantics. + const callbackHandles = [...mapOfAnimationFrameCallbacks.keys()]; + for (const handle of callbackHandles) { + // This has() can be false if a callback calls cancelAnimationFrame(). + if (mapOfAnimationFrameCallbacks.has(handle)) { + const callback = mapOfAnimationFrameCallbacks.get(handle); + removeAnimationFrameCallback(handle); + try { + callback(now); + } catch (e) { + reportException(window, e, window.location.href); + } + } + } + } + + function removeAnimationFrameCallback(handle) { + if (mapOfAnimationFrameCallbacks.has(handle)) { + --numberOfOngoingAnimationFrameCallbacks; + if (numberOfOngoingAnimationFrameCallbacks === 0) { + clearInterval(animationFrameNodejsInterval); + } + } + + mapOfAnimationFrameCallbacks.delete(handle); + } + } + + function stopAllTimers() { + for (const nodejsTimer of listOfActiveTimers.values()) { + clearTimeout(nodejsTimer); + } + listOfActiveTimers.clear(); + + clearInterval(animationFrameNodejsInterval); + } + + function Option(text, value, defaultSelected, selected) { + if (text === undefined) { + text = ""; + } + text = webIDLConversions.DOMString(text); + + if (value !== undefined) { + value = webIDLConversions.DOMString(value); + } + + defaultSelected = webIDLConversions.boolean(defaultSelected); + selected = webIDLConversions.boolean(selected); + + const option = window._document.createElement("option"); + const impl = idlUtils.implForWrapper(option); + + if (text !== "") { + impl.text = text; + } + if (value !== undefined) { + impl.setAttributeNS(null, "value", value); + } + if (defaultSelected) { + impl.setAttributeNS(null, "selected", ""); + } + impl._selectedness = selected; + + return option; + } + Object.defineProperty(Option, "prototype", { + value: this.HTMLOptionElement.prototype, + configurable: false, + enumerable: false, + writable: false + }); + Object.defineProperty(window, "Option", { + value: Option, + configurable: true, + enumerable: false, + writable: true + }); + + function Image(...args) { + const img = window._document.createElement("img"); + const impl = idlUtils.implForWrapper(img); + + if (args.length > 0) { + impl.setAttributeNS(null, "width", String(args[0])); + } + if (args.length > 1) { + impl.setAttributeNS(null, "height", String(args[1])); + } + + return img; + } + Object.defineProperty(Image, "prototype", { + value: this.HTMLImageElement.prototype, + configurable: false, + enumerable: false, + writable: false + }); + Object.defineProperty(window, "Image", { + value: Image, + configurable: true, + enumerable: false, + writable: true + }); + + function Audio(src) { + const audio = window._document.createElement("audio"); + const impl = idlUtils.implForWrapper(audio); + impl.setAttributeNS(null, "preload", "auto"); + + if (src !== undefined) { + impl.setAttributeNS(null, "src", String(src)); + } + + return audio; + } + Object.defineProperty(Audio, "prototype", { + value: this.HTMLAudioElement.prototype, + configurable: false, + enumerable: false, + writable: false + }); + Object.defineProperty(window, "Audio", { + value: Audio, + configurable: true, + enumerable: false, + writable: true + }); + + this.postMessage = postMessage(window); + + this.atob = function (str) { + const result = atob(str); + if (result === null) { + throw DOMException.create(window, [ + "The string to be decoded contains invalid characters.", + "InvalidCharacterError" + ]); + } + return result; + }; + + this.btoa = function (str) { + const result = btoa(str); + if (result === null) { + throw DOMException.create(window, [ + "The string to be encoded contains invalid characters.", + "InvalidCharacterError" + ]); + } + return result; + }; + + this.stop = function () { + const manager = idlUtils.implForWrapper(this._document)._requestManager; + if (manager) { + manager.close(); + } + }; + + this.close = function () { + // Recursively close child frame windows, then ourselves (depth-first). + for (let i = 0; i < this.length; ++i) { + this[i].close(); + } + + // Clear out all listeners. Any in-flight or upcoming events should not get delivered. + idlUtils.implForWrapper(this)._eventListeners = Object.create(null); + + if (this._document) { + if (this._document.body) { + this._document.body.innerHTML = ""; + } + + if (this._document.close) { + // It's especially important to clear out the listeners here because document.close() causes a "load" event to + // fire. + idlUtils.implForWrapper(this._document)._eventListeners = Object.create(null); + this._document.close(); + } + const doc = idlUtils.implForWrapper(this._document); + if (doc._requestManager) { + doc._requestManager.close(); + } + delete this._document; + } + + stopAllTimers(); + WebSocketImpl.cleanUpWindow(this); + }; + + this.getComputedStyle = function (elt, pseudoElt = undefined) { + elt = Element.convert(elt); + if (pseudoElt !== undefined && pseudoElt !== null) { + pseudoElt = webIDLConversions.DOMString(pseudoElt); + } + + if (pseudoElt !== undefined && pseudoElt !== null && pseudoElt !== "") { + // TODO: Parse pseudoElt + + if (SHADOW_DOM_PSEUDO_REGEXP.test(pseudoElt)) { + throw new TypeError("Tried to get the computed style of a Shadow DOM pseudo-element."); + } + + notImplemented("window.computedStyle(elt, pseudoElt)", this); + } + + const declaration = new CSSStyleDeclaration(); + const { forEach } = Array.prototype; + const { style } = elt; + + forEachMatchingSheetRuleOfElement(elt, rule => { + forEach.call(rule.style, property => { + declaration.setProperty( + property, + rule.style.getPropertyValue(property), + rule.style.getPropertyPriority(property) + ); + }); + }); + + // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle + const declarations = Object.keys(propertiesWithResolvedValueImplemented); + forEach.call(declarations, property => { + declaration.setProperty(property, getResolvedValue(elt, property)); + }); + + forEach.call(style, property => { + declaration.setProperty(property, style.getPropertyValue(property), style.getPropertyPriority(property)); + }); + + return declaration; + }; + + this.getSelection = function () { + return window._document.getSelection(); + }; + + // The captureEvents() and releaseEvents() methods must do nothing + this.captureEvents = function () {}; + + this.releaseEvents = function () {}; + + // ### PUBLIC DATA PROPERTIES (TODO: should be getters) + + function wrapConsoleMethod(method) { + return (...args) => { + window._virtualConsole.emit(method, ...args); + }; + } + + this.console = { + assert: wrapConsoleMethod("assert"), + clear: wrapConsoleMethod("clear"), + count: wrapConsoleMethod("count"), + countReset: wrapConsoleMethod("countReset"), + debug: wrapConsoleMethod("debug"), + dir: wrapConsoleMethod("dir"), + dirxml: wrapConsoleMethod("dirxml"), + error: wrapConsoleMethod("error"), + group: wrapConsoleMethod("group"), + groupCollapsed: wrapConsoleMethod("groupCollapsed"), + groupEnd: wrapConsoleMethod("groupEnd"), + info: wrapConsoleMethod("info"), + log: wrapConsoleMethod("log"), + table: wrapConsoleMethod("table"), + time: wrapConsoleMethod("time"), + timeLog: wrapConsoleMethod("timeLog"), + timeEnd: wrapConsoleMethod("timeEnd"), + trace: wrapConsoleMethod("trace"), + warn: wrapConsoleMethod("warn") + }; + + function notImplementedMethod(name) { + return function () { + notImplemented(name, window); + }; + } + + define(this, { + name: "", + status: "", + devicePixelRatio: 1, + innerWidth: 1024, + innerHeight: 768, + outerWidth: 1024, + outerHeight: 768, + pageXOffset: 0, + pageYOffset: 0, + screenX: 0, + screenLeft: 0, + screenY: 0, + screenTop: 0, + scrollX: 0, + scrollY: 0, + + alert: notImplementedMethod("window.alert"), + blur: notImplementedMethod("window.blur"), + confirm: notImplementedMethod("window.confirm"), + focus: notImplementedMethod("window.focus"), + moveBy: notImplementedMethod("window.moveBy"), + moveTo: notImplementedMethod("window.moveTo"), + open: notImplementedMethod("window.open"), + print: notImplementedMethod("window.print"), + prompt: notImplementedMethod("window.prompt"), + resizeBy: notImplementedMethod("window.resizeBy"), + resizeTo: notImplementedMethod("window.resizeTo"), + scroll: notImplementedMethod("window.scroll"), + scrollBy: notImplementedMethod("window.scrollBy"), + scrollTo: notImplementedMethod("window.scrollTo") + }); + + // ### INITIALIZATION + + process.nextTick(() => { + if (!window.document) { + return; // window might've been closed already + } + + const documentImpl = idlUtils.implForWrapper(window._document); + + if (window.document.readyState === "complete") { + fireAnEvent("load", window, undefined, {}, documentImpl); + } else { + window.document.addEventListener("load", () => { + fireAnEvent("load", window, undefined, {}, documentImpl); + + if (!documentImpl._pageShowingFlag) { + documentImpl._pageShowingFlag = true; + fireAnEvent("pageshow", window, PageTransitionEvent, { persisted: false }, documentImpl); + } + }); + } + }); +} + +function contextifyWindow(window) { + if (vm.isContext(window)) { + return; + } + + vm.createContext(window); +} diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/default-stylesheet.js b/alarm/node_modules/jsdom/lib/jsdom/browser/default-stylesheet.js new file mode 100644 index 0000000..78f69bb --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/default-stylesheet.js @@ -0,0 +1,789 @@ +// Ideally, we would use +// https://html.spec.whatwg.org/multipage/rendering.html#the-css-user-agent-style-sheet-and-presentational-hints +// but for now, just use the version from blink. This file is copied from +// https://chromium.googlesource.com/chromium/blink/+/96aa3a280ab7d67178c8f122a04949ce5f8579e0/Source/core/css/html.css +// (removed a line which had octal literals inside since octal literals are not allowed in template strings) + +// We use a .js file because otherwise we can't browserify this. (brfs is unusable due to lack of ES2015 support) + +module.exports = ` +/* + * The default style sheet used to render HTML. + * + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +@namespace "http://www.w3.org/1999/xhtml"; + +html { + display: block +} + +:root { + scroll-blocks-on: start-touch wheel-event +} + +/* children of the <head> element all have display:none */ +head { + display: none +} + +meta { + display: none +} + +title { + display: none +} + +link { + display: none +} + +style { + display: none +} + +script { + display: none +} + +/* generic block-level elements */ + +body { + display: block; + margin: 8px +} + +p { + display: block; + -webkit-margin-before: 1__qem; + -webkit-margin-after: 1__qem; + -webkit-margin-start: 0; + -webkit-margin-end: 0; +} + +div { + display: block +} + +layer { + display: block +} + +article, aside, footer, header, hgroup, main, nav, section { + display: block +} + +marquee { + display: inline-block; +} + +address { + display: block +} + +blockquote { + display: block; + -webkit-margin-before: 1__qem; + -webkit-margin-after: 1em; + -webkit-margin-start: 40px; + -webkit-margin-end: 40px; +} + +figcaption { + display: block +} + +figure { + display: block; + -webkit-margin-before: 1em; + -webkit-margin-after: 1em; + -webkit-margin-start: 40px; + -webkit-margin-end: 40px; +} + +q { + display: inline +} + +/* nwmatcher does not support ::before and ::after, so we can't render q +correctly: https://html.spec.whatwg.org/multipage/rendering.html#phrasing-content-3 +TODO: add q::before and q::after selectors +*/ + +center { + display: block; + /* special centering to be able to emulate the html4/netscape behaviour */ + text-align: -webkit-center +} + +hr { + display: block; + -webkit-margin-before: 0.5em; + -webkit-margin-after: 0.5em; + -webkit-margin-start: auto; + -webkit-margin-end: auto; + border-style: inset; + border-width: 1px; + box-sizing: border-box +} + +map { + display: inline +} + +video { + object-fit: contain; +} + +/* heading elements */ + +h1 { + display: block; + font-size: 2em; + -webkit-margin-before: 0.67__qem; + -webkit-margin-after: 0.67em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + font-weight: bold +} + +article h1, +aside h1, +nav h1, +section h1 { + font-size: 1.5em; + -webkit-margin-before: 0.83__qem; + -webkit-margin-after: 0.83em; +} + +article article h1, +article aside h1, +article nav h1, +article section h1, +aside article h1, +aside aside h1, +aside nav h1, +aside section h1, +nav article h1, +nav aside h1, +nav nav h1, +nav section h1, +section article h1, +section aside h1, +section nav h1, +section section h1 { + font-size: 1.17em; + -webkit-margin-before: 1__qem; + -webkit-margin-after: 1em; +} + +/* Remaining selectors are deleted because nwmatcher does not support +:matches() and expanding the selectors manually would be far too verbose. +Also see https://html.spec.whatwg.org/multipage/rendering.html#sections-and-headings +TODO: rewrite to use :matches() when nwmatcher supports it. +*/ + +h2 { + display: block; + font-size: 1.5em; + -webkit-margin-before: 0.83__qem; + -webkit-margin-after: 0.83em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + font-weight: bold +} + +h3 { + display: block; + font-size: 1.17em; + -webkit-margin-before: 1__qem; + -webkit-margin-after: 1em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + font-weight: bold +} + +h4 { + display: block; + -webkit-margin-before: 1.33__qem; + -webkit-margin-after: 1.33em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + font-weight: bold +} + +h5 { + display: block; + font-size: .83em; + -webkit-margin-before: 1.67__qem; + -webkit-margin-after: 1.67em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + font-weight: bold +} + +h6 { + display: block; + font-size: .67em; + -webkit-margin-before: 2.33__qem; + -webkit-margin-after: 2.33em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + font-weight: bold +} + +/* tables */ + +table { + display: table; + border-collapse: separate; + border-spacing: 2px; + border-color: gray +} + +thead { + display: table-header-group; + vertical-align: middle; + border-color: inherit +} + +tbody { + display: table-row-group; + vertical-align: middle; + border-color: inherit +} + +tfoot { + display: table-footer-group; + vertical-align: middle; + border-color: inherit +} + +/* for tables without table section elements (can happen with XHTML or dynamically created tables) */ +table > tr { + vertical-align: middle; +} + +col { + display: table-column +} + +colgroup { + display: table-column-group +} + +tr { + display: table-row; + vertical-align: inherit; + border-color: inherit +} + +td, th { + display: table-cell; + vertical-align: inherit +} + +th { + font-weight: bold +} + +caption { + display: table-caption; + text-align: -webkit-center +} + +/* lists */ + +ul, menu, dir { + display: block; + list-style-type: disc; + -webkit-margin-before: 1__qem; + -webkit-margin-after: 1em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + -webkit-padding-start: 40px +} + +ol { + display: block; + list-style-type: decimal; + -webkit-margin-before: 1__qem; + -webkit-margin-after: 1em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; + -webkit-padding-start: 40px +} + +li { + display: list-item; + text-align: -webkit-match-parent; +} + +ul ul, ol ul { + list-style-type: circle +} + +ol ol ul, ol ul ul, ul ol ul, ul ul ul { + list-style-type: square +} + +dd { + display: block; + -webkit-margin-start: 40px +} + +dl { + display: block; + -webkit-margin-before: 1__qem; + -webkit-margin-after: 1em; + -webkit-margin-start: 0; + -webkit-margin-end: 0; +} + +dt { + display: block +} + +ol ul, ul ol, ul ul, ol ol { + -webkit-margin-before: 0; + -webkit-margin-after: 0 +} + +/* form elements */ + +form { + display: block; + margin-top: 0__qem; +} + +label { + cursor: default; +} + +legend { + display: block; + -webkit-padding-start: 2px; + -webkit-padding-end: 2px; + border: none +} + +fieldset { + display: block; + -webkit-margin-start: 2px; + -webkit-margin-end: 2px; + -webkit-padding-before: 0.35em; + -webkit-padding-start: 0.75em; + -webkit-padding-end: 0.75em; + -webkit-padding-after: 0.625em; + border: 2px groove ThreeDFace; + min-width: -webkit-min-content; +} + +button { + -webkit-appearance: button; +} + +/* Form controls don't go vertical. */ +input, textarea, select, button, meter, progress { + -webkit-writing-mode: horizontal-tb !important; +} + +input, textarea, select, button { + margin: 0__qem; + font: -webkit-small-control; + text-rendering: auto; /* FIXME: Remove when tabs work with optimizeLegibility. */ + color: initial; + letter-spacing: normal; + word-spacing: normal; + line-height: normal; + text-transform: none; + text-indent: 0; + text-shadow: none; + display: inline-block; + text-align: start; +} + +/* TODO: Add " i" to attribute matchers to support case-insensitive matching */ +input[type="hidden"] { + display: none +} + +input { + -webkit-appearance: textfield; + padding: 1px; + background-color: white; + border: 2px inset; + -webkit-rtl-ordering: logical; + -webkit-user-select: text; + cursor: auto; +} + +input[type="search"] { + -webkit-appearance: searchfield; + box-sizing: border-box; +} + +select { + border-radius: 5px; +} + +textarea { + -webkit-appearance: textarea; + background-color: white; + border: 1px solid; + -webkit-rtl-ordering: logical; + -webkit-user-select: text; + flex-direction: column; + resize: auto; + cursor: auto; + padding: 2px; + white-space: pre-wrap; + word-wrap: break-word; +} + +input[type="password"] { + -webkit-text-security: disc !important; +} + +input[type="hidden"], input[type="image"], input[type="file"] { + -webkit-appearance: initial; + padding: initial; + background-color: initial; + border: initial; +} + +input[type="file"] { + align-items: baseline; + color: inherit; + text-align: start !important; +} + +input[type="radio"], input[type="checkbox"] { + margin: 3px 0.5ex; + padding: initial; + background-color: initial; + border: initial; +} + +input[type="button"], input[type="submit"], input[type="reset"] { + -webkit-appearance: push-button; + -webkit-user-select: none; + white-space: pre +} + +input[type="button"], input[type="submit"], input[type="reset"], button { + align-items: flex-start; + text-align: center; + cursor: default; + color: ButtonText; + padding: 2px 6px 3px 6px; + border: 2px outset ButtonFace; + background-color: ButtonFace; + box-sizing: border-box +} + +input[type="range"] { + -webkit-appearance: slider-horizontal; + padding: initial; + border: initial; + margin: 2px; + color: #909090; +} + +input[type="button"]:disabled, input[type="submit"]:disabled, input[type="reset"]:disabled, +button:disabled, select:disabled, optgroup:disabled, option:disabled, +select[disabled]>option { + color: GrayText +} + +input[type="button"]:active, input[type="submit"]:active, input[type="reset"]:active, button:active { + border-style: inset +} + +input[type="button"]:active:disabled, input[type="submit"]:active:disabled, input[type="reset"]:active:disabled, button:active:disabled { + border-style: outset +} + +datalist { + display: none +} + +area { + display: inline; + cursor: pointer; +} + +param { + display: none +} + +input[type="checkbox"] { + -webkit-appearance: checkbox; + box-sizing: border-box; +} + +input[type="radio"] { + -webkit-appearance: radio; + box-sizing: border-box; +} + +input[type="color"] { + -webkit-appearance: square-button; + width: 44px; + height: 23px; + background-color: ButtonFace; + /* Same as native_theme_base. */ + border: 1px #a9a9a9 solid; + padding: 1px 2px; +} + +input[type="color"][list] { + -webkit-appearance: menulist; + width: 88px; + height: 23px +} + +select { + -webkit-appearance: menulist; + box-sizing: border-box; + align-items: center; + border: 1px solid; + white-space: pre; + -webkit-rtl-ordering: logical; + color: black; + background-color: white; + cursor: default; +} + +optgroup { + font-weight: bolder; + display: block; +} + +option { + font-weight: normal; + display: block; + padding: 0 2px 1px 2px; + white-space: pre; + min-height: 1.2em; +} + +output { + display: inline; +} + +/* meter */ + +meter { + -webkit-appearance: meter; + box-sizing: border-box; + display: inline-block; + height: 1em; + width: 5em; + vertical-align: -0.2em; +} + +/* progress */ + +progress { + -webkit-appearance: progress-bar; + box-sizing: border-box; + display: inline-block; + height: 1em; + width: 10em; + vertical-align: -0.2em; +} + +/* inline elements */ + +u, ins { + text-decoration: underline +} + +strong, b { + font-weight: bold +} + +i, cite, em, var, address, dfn { + font-style: italic +} + +tt, code, kbd, samp { + font-family: monospace +} + +pre, xmp, plaintext, listing { + display: block; + font-family: monospace; + white-space: pre; + margin: 1__qem 0 +} + +mark { + background-color: yellow; + color: black +} + +big { + font-size: larger +} + +small { + font-size: smaller +} + +s, strike, del { + text-decoration: line-through +} + +sub { + vertical-align: sub; + font-size: smaller +} + +sup { + vertical-align: super; + font-size: smaller +} + +nobr { + white-space: nowrap +} + +/* states */ + +:focus { + outline: auto 5px -webkit-focus-ring-color +} + +/* Read-only text fields do not show a focus ring but do still receive focus */ +html:focus, body:focus, input[readonly]:focus { + outline: none +} + +embed:focus, iframe:focus, object:focus { + outline: none +} + +input:focus, textarea:focus, select:focus { + outline-offset: -2px +} + +input[type="button"]:focus, +input[type="checkbox"]:focus, +input[type="file"]:focus, +input[type="hidden"]:focus, +input[type="image"]:focus, +input[type="radio"]:focus, +input[type="reset"]:focus, +input[type="search"]:focus, +input[type="submit"]:focus { + outline-offset: 0 +} + +/* HTML5 ruby elements */ + +ruby, rt { + text-indent: 0; /* blocks used for ruby rendering should not trigger this */ +} + +rt { + line-height: normal; + -webkit-text-emphasis: none; +} + +ruby > rt { + display: block; + font-size: 50%; + text-align: start; +} + +ruby > rp { + display: none; +} + +/* other elements */ + +noframes { + display: none +} + +frameset, frame { + display: block +} + +frameset { + border-color: inherit +} + +iframe { + border: 2px inset +} + +details { + display: block +} + +summary { + display: block +} + +template { + display: none +} + +bdi, output { + unicode-bidi: -webkit-isolate; +} + +bdo { + unicode-bidi: bidi-override; +} + +textarea[dir=auto] { + unicode-bidi: -webkit-plaintext; +} + +dialog:not([open]) { + display: none +} + +dialog { + position: absolute; + left: 0; + right: 0; + width: -webkit-fit-content; + height: -webkit-fit-content; + margin: auto; + border: solid; + padding: 1em; + background: white; + color: black +} + +[hidden] { + display: none +} + +/* noscript is handled internally, as it depends on settings. */ + +`; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/js-globals.json b/alarm/node_modules/jsdom/lib/jsdom/browser/js-globals.json new file mode 100644 index 0000000..c4de05f --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/js-globals.json @@ -0,0 +1,307 @@ +{ + "Object": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Function": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Number": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "parseFloat": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "parseInt": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Infinity": { + "writable": false, + "enumerable": false, + "configurable": false + }, + "NaN": { + "writable": false, + "enumerable": false, + "configurable": false + }, + "undefined": { + "writable": false, + "enumerable": false, + "configurable": false + }, + "Boolean": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "String": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Symbol": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Date": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Promise": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "RegExp": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Error": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "AggregateError": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "EvalError": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "RangeError": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "ReferenceError": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "SyntaxError": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "TypeError": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "URIError": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "globalThis": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "JSON": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Math": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Intl": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "ArrayBuffer": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Uint8Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Int8Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Uint16Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Int16Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Uint32Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Int32Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Float32Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Float64Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Uint8ClampedArray": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "BigUint64Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "BigInt64Array": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "DataView": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Map": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "BigInt": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Set": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "WeakMap": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "WeakSet": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Proxy": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Reflect": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "FinalizationRegistry": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "WeakRef": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "decodeURI": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "decodeURIComponent": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "encodeURI": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "encodeURIComponent": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "escape": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "unescape": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "eval": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "isFinite": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "isNaN": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "SharedArrayBuffer": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "Atomics": { + "writable": true, + "enumerable": false, + "configurable": true + }, + "WebAssembly": { + "writable": true, + "enumerable": false, + "configurable": true + } +} diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/not-implemented.js b/alarm/node_modules/jsdom/lib/jsdom/browser/not-implemented.js new file mode 100644 index 0000000..a87cc95 --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/not-implemented.js @@ -0,0 +1,13 @@ +"use strict"; + +module.exports = function (nameForErrorMessage, window) { + if (!window) { + // Do nothing for window-less documents. + return; + } + + const error = new Error(`Not implemented: ${nameForErrorMessage}`); + error.type = "not implemented"; + + window._virtualConsole.emit("jsdomError", error); +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/parser/html.js b/alarm/node_modules/jsdom/lib/jsdom/browser/parser/html.js new file mode 100644 index 0000000..198cc2c --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/parser/html.js @@ -0,0 +1,223 @@ +"use strict"; + +const parse5 = require("parse5"); + +const { createElement } = require("../../living/helpers/create-element"); +const { HTML_NS } = require("../../living/helpers/namespaces"); + +const DocumentType = require("../../living/generated/DocumentType"); +const DocumentFragment = require("../../living/generated/DocumentFragment"); +const Text = require("../../living/generated/Text"); +const Comment = require("../../living/generated/Comment"); + +const attributes = require("../../living/attributes"); +const nodeTypes = require("../../living/node-type"); + +const serializationAdapter = require("../../living/domparsing/parse5-adapter-serialization"); +const { + customElementReactionsStack, invokeCEReactions, lookupCEDefinition +} = require("../../living/helpers/custom-elements"); + +// Horrible monkey-patch to implement https://github.com/inikulin/parse5/issues/237 and +// https://github.com/inikulin/parse5/issues/285. +const OpenElementStack = require("parse5/lib/parser/open-element-stack"); + +const openElementStackOriginalPush = OpenElementStack.prototype.push; +OpenElementStack.prototype.push = function (...args) { + openElementStackOriginalPush.apply(this, args); + this.treeAdapter._currentElement = this.current; + + const after = this.items[this.stackTop]; + if (after._pushedOnStackOfOpenElements) { + after._pushedOnStackOfOpenElements(); + } +}; + +const openElementStackOriginalPop = OpenElementStack.prototype.pop; +OpenElementStack.prototype.pop = function (...args) { + const before = this.items[this.stackTop]; + + openElementStackOriginalPop.apply(this, args); + this.treeAdapter._currentElement = this.current; + + if (before._poppedOffStackOfOpenElements) { + before._poppedOffStackOfOpenElements(); + } +}; + +class JSDOMParse5Adapter { + constructor(documentImpl, options = {}) { + this._documentImpl = documentImpl; + this._globalObject = documentImpl._globalObject; + this._fragment = options.fragment || false; + + // Since the createElement hook doesn't provide the parent element, we keep track of this using _currentElement: + // https://github.com/inikulin/parse5/issues/285. See above horrible monkey-patch for how this is maintained. + this._currentElement = undefined; + } + + _ownerDocument() { + const { _currentElement } = this; + + // The _currentElement is undefined when parsing elements at the root of the document. + if (_currentElement) { + return _currentElement.localName === "template" && _currentElement.namespaceURI === HTML_NS ? + _currentElement.content._ownerDocument : + _currentElement._ownerDocument; + } + + return this._documentImpl; + } + + createDocument() { + // parse5's model assumes that parse(html) will call into here to create the new Document, then return it. However, + // jsdom's model assumes we can create a Window (and through that create an empty Document), do some other setup + // stuff, and then parse, stuffing nodes into that Document as we go. So to adapt between these two models, we just + // return the already-created Document when asked by parse5 to "create" a Document. + return this._documentImpl; + } + + createDocumentFragment() { + const ownerDocument = this._ownerDocument(); + return DocumentFragment.createImpl(this._globalObject, [], { ownerDocument }); + } + + // https://html.spec.whatwg.org/#create-an-element-for-the-token + createElement(localName, namespace, attrs) { + const ownerDocument = this._ownerDocument(); + + const isAttribute = attrs.find(attr => attr.name === "is"); + const isValue = isAttribute ? isAttribute.value : null; + + const definition = lookupCEDefinition(ownerDocument, namespace, localName); + + let willExecuteScript = false; + if (definition !== null && !this._fragment) { + willExecuteScript = true; + } + + if (willExecuteScript) { + ownerDocument._throwOnDynamicMarkupInsertionCounter++; + customElementReactionsStack.push([]); + } + + const element = createElement(ownerDocument, localName, namespace, null, isValue, willExecuteScript); + this.adoptAttributes(element, attrs); + + if (willExecuteScript) { + const queue = customElementReactionsStack.pop(); + invokeCEReactions(queue); + ownerDocument._throwOnDynamicMarkupInsertionCounter--; + } + + if ("_parserInserted" in element) { + element._parserInserted = true; + } + + return element; + } + + createCommentNode(data) { + const ownerDocument = this._ownerDocument(); + return Comment.createImpl(this._globalObject, [], { data, ownerDocument }); + } + + appendChild(parentNode, newNode) { + parentNode._append(newNode); + } + + insertBefore(parentNode, newNode, referenceNode) { + parentNode._insert(newNode, referenceNode); + } + + setTemplateContent(templateElement, contentFragment) { + // This code makes the glue between jsdom and parse5 HTMLTemplateElement parsing: + // + // * jsdom during the construction of the HTMLTemplateElement (for example when create via + // `document.createElement("template")`), creates a DocumentFragment and set it into _templateContents. + // * parse5 when parsing a <template> tag creates an HTMLTemplateElement (`createElement` adapter hook) and also + // create a DocumentFragment (`createDocumentFragment` adapter hook). + // + // At this point we now have to replace the one created in jsdom with one created by parse5. + const { _ownerDocument, _host } = templateElement._templateContents; + contentFragment._ownerDocument = _ownerDocument; + contentFragment._host = _host; + + templateElement._templateContents = contentFragment; + } + + setDocumentType(document, name, publicId, systemId) { + const ownerDocument = this._ownerDocument(); + const documentType = DocumentType.createImpl(this._globalObject, [], { name, publicId, systemId, ownerDocument }); + + document._append(documentType); + } + + setDocumentMode(document, mode) { + // TODO: the rest of jsdom ignores this + document._mode = mode; + } + + detachNode(node) { + node.remove(); + } + + insertText(parentNode, text) { + const { lastChild } = parentNode; + if (lastChild && lastChild.nodeType === nodeTypes.TEXT_NODE) { + lastChild.data += text; + } else { + const ownerDocument = this._ownerDocument(); + const textNode = Text.createImpl(this._globalObject, [], { data: text, ownerDocument }); + parentNode._append(textNode); + } + } + + insertTextBefore(parentNode, text, referenceNode) { + const { previousSibling } = referenceNode; + if (previousSibling && previousSibling.nodeType === nodeTypes.TEXT_NODE) { + previousSibling.data += text; + } else { + const ownerDocument = this._ownerDocument(); + const textNode = Text.createImpl(this._globalObject, [], { data: text, ownerDocument }); + parentNode._append(textNode, referenceNode); + } + } + + adoptAttributes(element, attrs) { + for (const attr of attrs) { + const prefix = attr.prefix === "" ? null : attr.prefix; + attributes.setAttributeValue(element, attr.name, attr.value, prefix, attr.namespace); + } + } +} + +// Assign shared adapters with serializer. +Object.assign(JSDOMParse5Adapter.prototype, serializationAdapter); + +function parseFragment(markup, contextElement) { + const ownerDocument = contextElement.localName === "template" && contextElement.namespaceURI === HTML_NS ? + contextElement.content._ownerDocument : + contextElement._ownerDocument; + + const config = { + ...ownerDocument._parseOptions, + treeAdapter: new JSDOMParse5Adapter(ownerDocument, { fragment: true }) + }; + + return parse5.parseFragment(contextElement, markup, config); +} + +function parseIntoDocument(markup, ownerDocument) { + const config = { + ...ownerDocument._parseOptions, + treeAdapter: new JSDOMParse5Adapter(ownerDocument) + }; + + return parse5.parse(markup, config); +} + +module.exports = { + parseFragment, + parseIntoDocument +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/parser/index.js b/alarm/node_modules/jsdom/lib/jsdom/browser/parser/index.js new file mode 100644 index 0000000..e09df95 --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/parser/index.js @@ -0,0 +1,37 @@ +"use strict"; + +const xmlParser = require("./xml"); +const htmlParser = require("./html"); + +// https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm +function parseFragment(markup, contextElement) { + const { _parsingMode } = contextElement._ownerDocument; + + let parseAlgorithm; + if (_parsingMode === "html") { + parseAlgorithm = htmlParser.parseFragment; + } else if (_parsingMode === "xml") { + parseAlgorithm = xmlParser.parseFragment; + } + + // Note: HTML and XML fragment parsing algorithm already return a document fragments; no need to do steps 3 and 4 + return parseAlgorithm(markup, contextElement); +} + +function parseIntoDocument(markup, ownerDocument) { + const { _parsingMode } = ownerDocument; + + let parseAlgorithm; + if (_parsingMode === "html") { + parseAlgorithm = htmlParser.parseIntoDocument; + } else if (_parsingMode === "xml") { + parseAlgorithm = xmlParser.parseIntoDocument; + } + + return parseAlgorithm(markup, ownerDocument); +} + +module.exports = { + parseFragment, + parseIntoDocument +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/parser/xml.js b/alarm/node_modules/jsdom/lib/jsdom/browser/parser/xml.js new file mode 100644 index 0000000..f5f13c3 --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/parser/xml.js @@ -0,0 +1,202 @@ +"use strict"; + +const { SaxesParser } = require("saxes"); +const DOMException = require("domexception/webidl2js-wrapper"); + +const { createElement } = require("../../living/helpers/create-element"); + +const DocumentFragment = require("../../living/generated/DocumentFragment"); +const DocumentType = require("../../living/generated/DocumentType"); +const CDATASection = require("../../living/generated/CDATASection"); +const Comment = require("../../living/generated/Comment"); +const ProcessingInstruction = require("../../living/generated/ProcessingInstruction"); +const Text = require("../../living/generated/Text"); + +const attributes = require("../../living/attributes"); +const { HTML_NS } = require("../../living/helpers/namespaces"); + +const HTML5_DOCTYPE = /<!doctype html>/i; +const PUBLIC_DOCTYPE = /<!doctype\s+([^\s]+)\s+public\s+"([^"]+)"\s+"([^"]+)"/i; +const SYSTEM_DOCTYPE = /<!doctype\s+([^\s]+)\s+system\s+"([^"]+)"/i; +const CUSTOM_NAME_DOCTYPE = /<!doctype\s+([^\s>]+)/i; + +function parseDocType(globalObject, ownerDocument, html) { + if (HTML5_DOCTYPE.test(html)) { + return createDocumentType(globalObject, ownerDocument, "html", "", ""); + } + + const publicPieces = PUBLIC_DOCTYPE.exec(html); + if (publicPieces) { + return createDocumentType(globalObject, ownerDocument, publicPieces[1], publicPieces[2], publicPieces[3]); + } + + const systemPieces = SYSTEM_DOCTYPE.exec(html); + if (systemPieces) { + return createDocumentType(globalObject, ownerDocument, systemPieces[1], "", systemPieces[2]); + } + + const namePiece = CUSTOM_NAME_DOCTYPE.exec(html)[1] || "html"; + return createDocumentType(globalObject, ownerDocument, namePiece, "", ""); +} + +function createDocumentType(globalObject, ownerDocument, name, publicId, systemId) { + return DocumentType.createImpl(globalObject, [], { ownerDocument, name, publicId, systemId }); +} + +function isHTMLTemplateElement(element) { + return element.tagName === "template" && element.namespaceURI === HTML_NS; +} + + +function createParser(rootNode, globalObject, saxesOptions) { + const parser = new SaxesParser({ + ...saxesOptions, + // Browsers always have namespace support. + xmlns: true, + // We force the parser to treat all documents (even documents declaring themselves to be XML 1.1 documents) as XML + // 1.0 documents. See https://github.com/jsdom/jsdom/issues/2677 for a discussion of the stakes. + defaultXMLVersion: "1.0", + forceXMLVersion: true + }); + const openStack = [rootNode]; + + function getOwnerDocument() { + const currentElement = openStack[openStack.length - 1]; + + return isHTMLTemplateElement(currentElement) ? + currentElement._templateContents._ownerDocument : + currentElement._ownerDocument; + } + + function appendChild(child) { + const parentElement = openStack[openStack.length - 1]; + + if (isHTMLTemplateElement(parentElement)) { + parentElement._templateContents._insert(child, null); + } else { + parentElement._insert(child, null); + } + } + + parser.on("text", saxesOptions.fragment ? + // In a fragment, all text events produced by saxes must result in a text + // node. + data => { + const ownerDocument = getOwnerDocument(); + appendChild(Text.createImpl(globalObject, [], { data, ownerDocument })); + } : + // When parsing a whole document, we must ignore those text nodes that are + // produced outside the root element. Saxes produces events for them, + // but DOM trees do not record text outside the root element. + data => { + if (openStack.length > 1) { + const ownerDocument = getOwnerDocument(); + appendChild(Text.createImpl(globalObject, [], { data, ownerDocument })); + } + }); + + parser.on("cdata", data => { + const ownerDocument = getOwnerDocument(); + appendChild(CDATASection.createImpl(globalObject, [], { data, ownerDocument })); + }); + + parser.on("opentag", tag => { + const { local: tagLocal, attributes: tagAttributes } = tag; + + const ownerDocument = getOwnerDocument(); + const tagNamespace = tag.uri === "" ? null : tag.uri; + const tagPrefix = tag.prefix === "" ? null : tag.prefix; + const isValue = tagAttributes.is === undefined ? null : tagAttributes.is.value; + + const elem = createElement(ownerDocument, tagLocal, tagNamespace, tagPrefix, isValue, true); + + // We mark a script element as "parser-inserted", which prevents it from + // being immediately executed. + if (tagLocal === "script" && tagNamespace === HTML_NS) { + elem._parserInserted = true; + } + + for (const key of Object.keys(tagAttributes)) { + const { prefix, local, uri, value } = tagAttributes[key]; + attributes.setAttributeValue(elem, local, value, prefix === "" ? null : prefix, uri === "" ? null : uri); + } + + appendChild(elem); + openStack.push(elem); + }); + + parser.on("closetag", () => { + const elem = openStack.pop(); + // Once a script is populated, we can execute it. + if (elem.localName === "script" && elem.namespaceURI === HTML_NS) { + elem._eval(); + } + }); + + parser.on("comment", data => { + const ownerDocument = getOwnerDocument(); + appendChild(Comment.createImpl(globalObject, [], { data, ownerDocument })); + }); + + parser.on("processinginstruction", ({ target, body }) => { + const ownerDocument = getOwnerDocument(); + appendChild(ProcessingInstruction.createImpl(globalObject, [], { target, data: body, ownerDocument })); + }); + + parser.on("doctype", dt => { + const ownerDocument = getOwnerDocument(); + appendChild(parseDocType(globalObject, ownerDocument, `<!doctype ${dt}>`)); + + const entityMatcher = /<!ENTITY ([^ ]+) "([^"]+)">/g; + let result; + while ((result = entityMatcher.exec(dt))) { + const [, name, value] = result; + if (!(name in parser.ENTITIES)) { + parser.ENTITIES[name] = value; + } + } + }); + + parser.on("error", err => { + throw DOMException.create(globalObject, [err.message, "SyntaxError"]); + }); + + return parser; +} + +function parseFragment(markup, contextElement) { + const { _globalObject, _ownerDocument } = contextElement; + + const fragment = DocumentFragment.createImpl(_globalObject, [], { ownerDocument: _ownerDocument }); + + // Only parseFragment needs resolvePrefix per the saxes documentation: + // https://github.com/lddubeau/saxes#parsing-xml-fragments + const parser = createParser(fragment, _globalObject, { + fragment: true, + resolvePrefix(prefix) { + // saxes wants undefined as the return value if the prefix is not defined, not null. + return contextElement.lookupNamespaceURI(prefix) || undefined; + } + }); + + parser.write(markup).close(); + + return fragment; +} + +function parseIntoDocument(markup, ownerDocument) { + const { _globalObject } = ownerDocument; + + const parser = createParser(ownerDocument, _globalObject, { + fileName: ownerDocument.location && ownerDocument.location.href + }); + + parser.write(markup).close(); + + return ownerDocument; +} + +module.exports = { + parseFragment, + parseIntoDocument +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/resources/async-resource-queue.js b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/async-resource-queue.js new file mode 100644 index 0000000..51c7bb7 --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/async-resource-queue.js @@ -0,0 +1,114 @@ +"use strict"; + +class QueueItem { + constructor(onLoad, onError, dependentItem) { + this.onLoad = onLoad; + this.onError = onError; + this.data = null; + this.error = null; + this.dependentItem = dependentItem; + } +} + +/** + * AsyncResourceQueue is the queue in charge of run the async scripts + * and notify when they finish. + */ +module.exports = class AsyncResourceQueue { + constructor() { + this.items = new Set(); + this.dependentItems = new Set(); + } + + count() { + return this.items.size + this.dependentItems.size; + } + + _notify() { + if (this._listener) { + this._listener(); + } + } + + _check(item) { + let promise; + + if (item.onError && item.error) { + promise = item.onError(item.error); + } else if (item.onLoad && item.data) { + promise = item.onLoad(item.data); + } + + promise + .then(() => { + this.items.delete(item); + this.dependentItems.delete(item); + + if (this.count() === 0) { + this._notify(); + } + }); + } + + setListener(listener) { + this._listener = listener; + } + + push(request, onLoad, onError, dependentItem) { + const q = this; + + const item = new QueueItem(onLoad, onError, dependentItem); + + q.items.add(item); + + return request + .then(data => { + item.data = data; + + if (dependentItem && !dependentItem.finished) { + q.dependentItems.add(item); + return q.items.delete(item); + } + + if (onLoad) { + return q._check(item); + } + + q.items.delete(item); + + if (q.count() === 0) { + q._notify(); + } + + return null; + }) + .catch(err => { + item.error = err; + + if (dependentItem && !dependentItem.finished) { + q.dependentItems.add(item); + return q.items.delete(item); + } + + if (onError) { + return q._check(item); + } + + q.items.delete(item); + + if (q.count() === 0) { + q._notify(); + } + + return null; + }); + } + + notifyItem(syncItem) { + for (const item of this.dependentItems) { + if (item.dependentItem === syncItem) { + this._check(item); + } + } + } +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/resources/no-op-resource-loader.js b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/no-op-resource-loader.js new file mode 100644 index 0000000..985509c --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/no-op-resource-loader.js @@ -0,0 +1,8 @@ +"use strict"; +const ResourceLoader = require("./resource-loader.js"); + +module.exports = class NoOpResourceLoader extends ResourceLoader { + fetch() { + return null; + } +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/resources/per-document-resource-loader.js b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/per-document-resource-loader.js new file mode 100644 index 0000000..8a10613 --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/per-document-resource-loader.js @@ -0,0 +1,95 @@ +"use strict"; +const idlUtils = require("../../living/generated/utils"); +const { fireAnEvent } = require("../../living/helpers/events"); + +module.exports = class PerDocumentResourceLoader { + constructor(document) { + this._document = document; + this._defaultEncoding = document._encoding; + this._resourceLoader = document._defaultView ? document._defaultView._resourceLoader : null; + this._requestManager = document._requestManager; + this._queue = document._queue; + this._deferQueue = document._deferQueue; + this._asyncQueue = document._asyncQueue; + } + + fetch(url, { element, onLoad, onError }) { + const request = this._resourceLoader.fetch(url, { + cookieJar: this._document._cookieJar, + element: idlUtils.wrapperForImpl(element), + referrer: this._document.URL + }); + + if (request === null) { + return null; + } + + this._requestManager.add(request); + + const onErrorWrapped = error => { + this._requestManager.remove(request); + + if (onError) { + onError(error); + } + + fireAnEvent("error", element); + + const err = new Error(`Could not load ${element.localName}: "${url}"`); + err.type = "resource loading"; + err.detail = error; + + this._document._defaultView._virtualConsole.emit("jsdomError", err); + + return Promise.resolve(); + }; + + const onLoadWrapped = data => { + this._requestManager.remove(request); + + this._addCookies(url, request.response ? request.response.headers : {}); + + try { + const result = onLoad ? onLoad(data) : undefined; + + return Promise.resolve(result) + .then(() => { + fireAnEvent("load", element); + + return Promise.resolve(); + }) + .catch(err => { + return onErrorWrapped(err); + }); + } catch (err) { + return onErrorWrapped(err); + } + }; + + if (element.localName === "script" && element.hasAttributeNS(null, "async")) { + this._asyncQueue.push(request, onLoadWrapped, onErrorWrapped, this._queue.getLastScript()); + } else if (element.localName === "script" && element.hasAttributeNS(null, "defer")) { + this._deferQueue.push(request, onLoadWrapped, onErrorWrapped, false, element); + } else { + this._queue.push(request, onLoadWrapped, onErrorWrapped, false, element); + } + + return request; + } + + _addCookies(url, headers) { + let cookies = headers["set-cookie"]; + + if (!cookies) { + return; + } + + if (!Array.isArray(cookies)) { + cookies = [cookies]; + } + + cookies.forEach(cookie => { + this._document._cookieJar.setCookieSync(cookie, url, { http: true, ignoreError: true }); + }); + } +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/resources/request-manager.js b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/request-manager.js new file mode 100644 index 0000000..dbf6ccb --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/request-manager.js @@ -0,0 +1,33 @@ +"use strict"; + +/** + * Manage all the request and it is able to abort + * all pending request. + */ +module.exports = class RequestManager { + constructor() { + this.openedRequests = []; + } + + add(req) { + this.openedRequests.push(req); + } + + remove(req) { + const idx = this.openedRequests.indexOf(req); + if (idx !== -1) { + this.openedRequests.splice(idx, 1); + } + } + + close() { + for (const openedRequest of this.openedRequests) { + openedRequest.abort(); + } + this.openedRequests = []; + } + + size() { + return this.openedRequests.length; + } +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/resources/resource-loader.js b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/resource-loader.js new file mode 100644 index 0000000..2af8d63 --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/resource-loader.js @@ -0,0 +1,142 @@ +"use strict"; +const fs = require("fs"); +const { fileURLToPath } = require("url"); +const { parseURL } = require("whatwg-url"); +const dataURLFromRecord = require("data-urls").fromURLRecord; +const packageVersion = require("../../../../package.json").version; +const agentFactory = require("../../living/helpers/agent-factory"); +const Request = require("../../living/helpers/http-request"); + +const IS_BROWSER = Object.prototype.toString.call(process) !== "[object process]"; + +module.exports = class ResourceLoader { + constructor({ + strictSSL = true, + proxy = undefined, + userAgent = `Mozilla/5.0 (${process.platform || "unknown OS"}) AppleWebKit/537.36 ` + + `(KHTML, like Gecko) jsdom/${packageVersion}` + } = {}) { + this._strictSSL = strictSSL; + this._proxy = proxy; + this._userAgent = userAgent; + } + + _readDataURL(urlRecord) { + const dataURL = dataURLFromRecord(urlRecord); + let timeoutId; + const promise = new Promise(resolve => { + timeoutId = setTimeout(resolve, 0, dataURL.body); + }); + promise.abort = () => { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + }; + return promise; + } + + _readFile(filePath) { + let readableStream, abort; // Native Promises doesn't have an "abort" method. + + // Creating a promise for two reason: + // 1. fetch always return a promise. + // 2. We need to add an abort handler. + const promise = new Promise((resolve, reject) => { + readableStream = fs.createReadStream(filePath); + let data = Buffer.alloc(0); + + abort = reject; + + readableStream.on("error", reject); + + readableStream.on("data", chunk => { + data = Buffer.concat([data, chunk]); + }); + + readableStream.on("end", () => { + resolve(data); + }); + }); + + promise.abort = () => { + readableStream.destroy(); + const error = new Error("request canceled by user"); + error.isAbortError = true; + abort(error); + }; + + return promise; + } + + fetch(urlString, { accept, cookieJar, referrer } = {}) { + const url = parseURL(urlString); + + if (!url) { + return Promise.reject(new Error(`Tried to fetch invalid URL ${urlString}`)); + } + + switch (url.scheme) { + case "data": { + return this._readDataURL(url); + } + + case "http": + case "https": { + const agents = agentFactory(this._proxy, this._strictSSL); + const headers = { + "User-Agent": this._userAgent, + "Accept-Language": "en", + "Accept-Encoding": "gzip", + "Accept": accept || "*/*" + }; + if (referrer && !IS_BROWSER) { + headers.Referer = referrer; + } + const requestClient = new Request( + urlString, + { followRedirects: true, cookieJar, agents }, + { headers } + ); + const promise = new Promise((resolve, reject) => { + const accumulated = []; + requestClient.once("response", res => { + promise.response = res; + const { statusCode } = res; + // TODO This deviates from the spec when it comes to + // loading resources such as images + if (statusCode < 200 || statusCode > 299) { + requestClient.abort(); + reject(new Error(`Resource was not loaded. Status: ${statusCode}`)); + } + }); + requestClient.on("data", chunk => { + accumulated.push(chunk); + }); + requestClient.on("end", () => resolve(Buffer.concat(accumulated))); + requestClient.on("error", reject); + }); + // The method fromURL in lib/api.js crashes without the following four + // properties defined on the Promise instance, causing the test suite to halt + requestClient.on("end", () => { + promise.href = requestClient.currentURL; + }); + promise.abort = requestClient.abort.bind(requestClient); + promise.getHeader = name => headers[name] || requestClient.getHeader(name); + requestClient.end(); + return promise; + } + + case "file": { + try { + return this._readFile(fileURLToPath(urlString)); + } catch (e) { + return Promise.reject(e); + } + } + + default: { + return Promise.reject(new Error(`Tried to fetch URL ${urlString} with invalid scheme ${url.scheme}`)); + } + } + } +}; diff --git a/alarm/node_modules/jsdom/lib/jsdom/browser/resources/resource-queue.js b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/resource-queue.js new file mode 100644 index 0000000..c7d8f0f --- /dev/null +++ b/alarm/node_modules/jsdom/lib/jsdom/browser/resources/resource-queue.js @@ -0,0 +1,142 @@ +"use strict"; + +/** + * Queue for all the resources to be download except async scripts. + * Async scripts have their own queue AsyncResourceQueue. + */ +module.exports = class ResourceQueue { + constructor({ paused, asyncQueue } = {}) { + this.paused = Boolean(paused); + this._asyncQueue = asyncQueue; + } + + getLastScript() { + let head = this.tail; + + while (head) { + if (head.isScript) { + return head; + } + head = head.prev; + } + + return null; + } + + _moreScripts() { + let found = false; + + let head = this.tail; + while (head && !found) { + found = head.isScript; + head = head.prev; + } + + return found; + } + + _notify() { + if (this._listener) { + this._listener(); + } + } + + setListener(listener) { + this._listener = listener; + } + + push(request, onLoad, onError, keepLast, element) { + const isScript = element ? element.localName === "script" : false; + + if (!request) { + if (isScript && !this._moreScripts()) { + return onLoad(); + } + + request = Promise.resolve(); + } + const q = this; + const item = { + isScript, + err: null, + element, + fired: false, + data: null, + keepLast, + prev: q.tail, + check() { + if (!q.paused && !this.prev && this.fired) { + let promise; + + if (this.err && onError) { + promise = onError(this.err); + } + + if (!this.err && onLoad) { + promise = onLoad(this.data); + } + + Promise.resolve(promise) + .then(() => { + if (this.next) { + this.next.prev = null; + this.next.check(); + } else { // q.tail===this + q.tail = null; + q._notify(); + } + + this.finished = true; + + if (q._asyncQueue) { + q._asyncQueue.notifyItem(this); + } + }); + } + } + }; + if (q.tail) { + if (q.tail.keepLast) { + // if the tail is the load event in document and we receive a new element to load + // we should add this new request before the load event. + if (q.tail.prev) { + q.tail.prev.next = item; + } + item.prev = q.tail.prev; + q.tail.prev = item; + item.next = q.tail; + } else { + q.tail.next = item; + q.tail = item; + } + } else { + q.tail = item; + } + return request + .then(data => { + item.fired = 1; + item.data = data; + item.check(); + }) + .catch(err => { + item.fired = true; + item.err = err; + item.check(); + }); + } + + resume() { + if (!this.paused) { + return; + } + this.paused = false; + + let head = this.tail; + while (head && head.prev) { + head = head.prev; + } + if (head) { + head.check(); + } + } +}; |