UNPKG

jsdom

Version:

A JavaScript implementation of many web standards

953 lines (822 loc) 31.3 kB
"use strict"; const vm = require("node:vm"); const webIDLConversions = require("webidl-conversions"); const whatwgURL = require("whatwg-url"); const { notImplementedMethod } = require("./not-implemented"); const { installInterfaces } = require("../living/interfaces"); const { define, mixin } = require("../utils"); const Element = require("../../generated/idl/Element"); const EventTarget = require("../../generated/idl/EventTarget"); const EventHandlerNonNull = require("../../generated/idl/EventHandlerNonNull"); const IDLFunction = require("../../generated/idl/Function"); const OnBeforeUnloadEventHandlerNonNull = require("../../generated/idl/OnBeforeUnloadEventHandlerNonNull"); const OnErrorEventHandlerNonNull = require("../../generated/idl/OnErrorEventHandlerNonNull"); const { fireAPageTransitionEvent } = require("../living/helpers/page-transition-event"); const windowProperties = require("../living/window-properties"); const DOMException = require("../../generated/idl/DOMException"); const idlUtils = require("../../generated/idl/utils"); const WebSocketImpl = require("../living/websockets/WebSocket-impl").implementation; const BarProp = require("../../generated/idl/BarProp"); const documents = require("../living/documents.js"); const External = require("../../generated/idl/External"); const Navigator = require("../../generated/idl/Navigator"); const Performance = require("../../generated/idl/Performance"); const Screen = require("../../generated/idl/Screen"); const Crypto = require("../../generated/idl/Crypto"); const Storage = require("../../generated/idl/Storage"); const Selection = require("../../generated/idl/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 { SHADOW_DOM_PSEUDO_REGEXP, getComputedStyleDeclaration } = require("../living/css/helpers/computed-style.js"); const CustomElementRegistry = require("../../generated/idl/CustomElementRegistry"); const MessageEvent = require("../../generated/idl/MessageEvent"); const jsGlobals = require("../../generated/js-globals.json"); const GlobalEventHandlersImpl = require("../living/nodes/GlobalEventHandlers-impl").implementation; const WindowEventHandlersImpl = require("../living/nodes/WindowEventHandlers-impl").implementation; const { globalEventHandlersEvents, windowEventHandlersEvents } = require("../../generated/event-sets"); const allWindowEvents = new Set([...globalEventHandlersEvents, ...windowEventHandlersEvents]); const jsGlobalEntriesToInstall = Object.entries(jsGlobals).filter(([name]) => name in global); exports.createWindow = options => { const makeVMContext = options.runScripts === "outside-only" || options.runScripts === "dangerously"; // Bootstrap with an empty object from the Node.js realm. We'll muck with its prototype chain shortly. let window = {}; // Make window into a global object: either via vm, or just aliasing the Node.js globals. // Also set _globalObject and _globalProxy. // // TODO: don't expose _globalObject and _globalProxy as public properties. While you're there, audit usage sites to // see how necessary they really are. if (makeVMContext) { window = vm.createContext(vm.constants.DONT_CONTEXTIFY); window._globalObject = window; window._globalProxy = vm.runInContext("this", window); // 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, window) }; Object.defineProperty(window, globalName, propDesc); } } else { window._globalObject = window._globalProxy = window; // 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(window, globalName, propDesc); } } // Create instances of all the web platform interfaces and install them on the window. installInterfaces(window, ["Window"]); // Now we have an EventTarget contructor so we can work on the prototype chain. // eslint-disable-next-line func-name-matching, func-style const WindowConstructor = function Window() { throw new TypeError("Illegal constructor"); }; Object.setPrototypeOf(WindowConstructor, window.EventTarget); Object.defineProperty(window, "Window", { configurable: true, writable: true, value: WindowConstructor }); const windowPropertiesObject = windowProperties.create(window.EventTarget.prototype, window); const windowPrototype = Object.create(windowPropertiesObject); Object.defineProperties(windowPrototype, { constructor: { value: WindowConstructor, writable: true, configurable: true }, [Symbol.toStringTag]: { value: "Window", configurable: true } }); WindowConstructor.prototype = windowPrototype; Object.setPrototypeOf(window, windowPrototype); if (makeVMContext) { Object.setPrototypeOf(window._globalProxy, windowPrototype); Object.setPrototypeOf(window.EventTarget.prototype, window.Object.prototype); } // Now that the prototype chain is fully set up, call the superclass setup. EventTarget.setup(window, window); installEventHandlers(window); installOwnProperties(window, options); // Not sure why this is necessary... TODO figure it out. Object.defineProperty(idlUtils.implForWrapper(window), idlUtils.wrapperSymbol, { get: () => window._globalProxy }); // Fire or prepare to fire load and pageshow events. process.nextTick(() => { if (!window.document) { return; // window might've been closed already } if (window.document.readyState === "complete") { fireAnEvent("load", window, undefined, {}, true); } else { window.document.addEventListener("load", () => { fireAnEvent("load", window, undefined, {}, true); if (!window._document) { return; // window might've been closed already } const documentImpl = idlUtils.implForWrapper(window._document); if (!documentImpl._pageShowingFlag) { documentImpl._pageShowingFlag = true; fireAPageTransitionEvent("pageshow", window, false); } }); } }); return window; }; function installEventHandlers(window) { mixin(window, WindowEventHandlersImpl.prototype); mixin(window, GlobalEventHandlersImpl.prototype); window._initGlobalEvents(); for (const event of allWindowEvents) { let Converter = EventHandlerNonNull; if (event === "error") { Converter = OnErrorEventHandlerNonNull; } else if (event === "beforeunload") { Converter = OnBeforeUnloadEventHandlerNonNull; } Object.defineProperty(window, `on${event}`, { configurable: true, enumerable: true, get() { return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(window, event)); }, set(V) { if (!idlUtils.isObject(V)) { V = null; } else { V = Converter.convert(window, V, { context: `Failed to set the 'on${event}' property on 'Window': The provided value` }); } window._setEventHandlerFor(event, V); } }); } } function installOwnProperties(window, options) { const windowInitialized = performance.now(); // ### PRIVATE DATA PROPERTIES window._dispatcher = options.dispatcher; window._loadSubresources = options.loadSubresources; window._userAgent = options.userAgent; // List options explicitly to be clear which are passed through window._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: window._globalProxy, global: window, parentOrigin: options.parentOrigin }, { alwaysUseDocumentClass: true }); const documentOrigin = idlUtils.implForWrapper(window._document)._origin; window._origin = documentOrigin; // https://html.spec.whatwg.org/#session-history window._sessionHistory = new SessionHistory({ document: idlUtils.implForWrapper(window._document), url: idlUtils.implForWrapper(window._document)._URL, stateObject: null }, window); window._virtualConsole = options.virtualConsole; window._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. window._parent = window._top = window._globalProxy; window._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. window._length = 0; // https://dom.spec.whatwg.org/#window-current-event window._currentEvent = undefined; window._pretendToBeVisual = options.pretendToBeVisual; window._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]) { window._commonForOrigin = options.commonForOrigin; } else { window._commonForOrigin = { [documentOrigin]: { localStorageArea: new Map(), sessionStorageArea: new Map(), windowsInSameOrigin: [window] } }; } window._currentOriginData = window._commonForOrigin[documentOrigin]; // ### WEB STORAGE window._localStorage = Storage.create(window, [], { associatedWindow: window, storageArea: window._currentOriginData.localStorageArea, type: "localStorage", url: window._document.documentURI, storageQuota: window._storageQuota }); window._sessionStorage = Storage.create(window, [], { associatedWindow: window, storageArea: window._currentOriginData.sessionStorageArea, type: "sessionStorage", url: window._document.documentURI, storageQuota: window._storageQuota }); // ### SELECTION // https://w3c.github.io/selection-api/#dfn-selection window._selection = Selection.createImpl(window); // https://w3c.github.io/selection-api/#dom-window window.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); const performanceImpl = Performance.create(window, [], { timeOrigin: performance.timeOrigin + windowInitialized, nowAtTimeOrigin: windowInitialized }); const screen = Screen.create(window); const crypto = Crypto.create(window); window._customElementRegistry = CustomElementRegistry.create(window); let status = ""; define(window, { name: "", get status() { return status; }, set status(value) { status = webIDLConversions.DOMString(value); }, get devicePixelRatio() { return 1; }, get innerWidth() { return 1024; }, get innerHeight() { return 768; }, get outerWidth() { return 1024; }, get outerHeight() { return 768; }, get pageXOffset() { return 0; }, get pageYOffset() { return 0; }, get screenX() { return 0; }, get screenLeft() { return 0; }, get screenY() { return 0; }, get screenTop() { return 0; }, get scrollX() { return 0; }, get scrollY() { return 0; }, 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); }, // [PutForwards=href]: set location(value) { Reflect.set(window.location, "href", value); }, 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 performanceImpl; }, get screen() { return screen; }, get crypto() { return crypto; }, get origin() { return window._origin; }, get localStorage() { if (idlUtils.implForWrapper(window._document)._origin === "null") { throw DOMException.create(window, [ "localStorage is not available for opaque origins", "SecurityError" ]); } return window._localStorage; }, get sessionStorage() { if (idlUtils.implForWrapper(window._document)._origin === "null") { throw DOMException.create(window, [ "sessionStorage is not available for opaque origins", "SecurityError" ]); } return window._sessionStorage; }, get customElements() { return window._customElementRegistry; }, get event() { return window._currentEvent ? idlUtils.wrapperForImpl(window._currentEvent) : undefined; } }); Object.defineProperties(window, { // [Replaceable]: self: makeReplaceablePropertyDescriptor("self", window), locationbar: makeReplaceablePropertyDescriptor("locationbar", window), menubar: makeReplaceablePropertyDescriptor("menubar", window), personalbar: makeReplaceablePropertyDescriptor("personalbar", window), scrollbars: makeReplaceablePropertyDescriptor("scrollbars", window), statusbar: makeReplaceablePropertyDescriptor("statusbar", window), toolbar: makeReplaceablePropertyDescriptor("toolbar", window), frames: makeReplaceablePropertyDescriptor("frames", window), parent: makeReplaceablePropertyDescriptor("parent", window), external: makeReplaceablePropertyDescriptor("external", window), length: makeReplaceablePropertyDescriptor("length", window), screen: makeReplaceablePropertyDescriptor("screen", window), origin: makeReplaceablePropertyDescriptor("origin", window), event: makeReplaceablePropertyDescriptor("event", window), innerWidth: makeReplaceablePropertyDescriptor("innerWidth", window), innerHeight: makeReplaceablePropertyDescriptor("innerHeight", window), scrollX: makeReplaceablePropertyDescriptor("scrollX", window), pageXOffset: makeReplaceablePropertyDescriptor("pageXOffset", window), scrollY: makeReplaceablePropertyDescriptor("scrollY", window), pageYOffset: makeReplaceablePropertyDescriptor("pageYOffset", window), screenX: makeReplaceablePropertyDescriptor("screenX", window), screenLeft: makeReplaceablePropertyDescriptor("screenLeft", window), screenY: makeReplaceablePropertyDescriptor("screenY", window), screenTop: makeReplaceablePropertyDescriptor("screenTop", window), outerWidth: makeReplaceablePropertyDescriptor("outerWidth", window), outerHeight: makeReplaceablePropertyDescriptor("outerHeight", window), devicePixelRatio: makeReplaceablePropertyDescriptor("devicePixelRatio", window), // [LegacyUnforgeable]: window: { configurable: false }, document: { configurable: false }, location: { configurable: false }, top: { configurable: false } }); // ### 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; window.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 }); }; window.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 }); }; window.clearTimeout = function (handle = 0) { handle = webIDLConversions.long(handle); const nodejsTimer = listOfActiveTimers.get(handle); if (nodejsTimer) { clearTimeout(nodejsTimer); listOfActiveTimers.delete(handle); } }; window.clearInterval = function (handle = 0) { handle = webIDLConversions.long(handle); const nodejsTimer = listOfActiveTimers.get(handle); if (nodejsTimer) { // We use setTimeout() in timerInitializationSteps even for window.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 window.queueMicrotask = function (callback) { callback = IDLFunction.convert(window, callback); queueMicrotask(() => { 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 (window._pretendToBeVisual) { window.requestAnimationFrame = function (callback) { callback = IDLFunction.convert(window, callback); const handle = ++animationFrameCallbackId; mapOfAnimationFrameCallbacks.set(handle, callback); ++numberOfOngoingAnimationFrameCallbacks; if (numberOfOngoingAnimationFrameCallbacks === 1) { animationFrameNodejsInterval = setInterval(() => { runAnimationFrameCallbacks(performance.now() - windowInitialized); }, 1000 / 60); } return handle; }; window.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: window.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: window.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: window.HTMLAudioElement.prototype, configurable: false, enumerable: false, writable: false }); Object.defineProperty(window, "Audio", { value: Audio, configurable: true, enumerable: false, writable: true }); window.postMessage = function (message, targetOrigin) { if (arguments.length < 2) { throw new TypeError("'postMessage' requires 2 arguments: 'message' and 'targetOrigin'"); } targetOrigin = webIDLConversions.DOMString(targetOrigin); if (targetOrigin === "/") { // TODO: targetOrigin === "/" requires getting incumbent settings object. // Maybe could be done with Error stack traces?? return; } else if (targetOrigin !== "*") { const parsedURL = whatwgURL.parseURL(targetOrigin); if (parsedURL === null) { throw DOMException.create(window, [ "Failed to execute 'postMessage' on 'Window': " + "Invalid target origin '" + targetOrigin + "' in a call to 'postMessage'.", "SyntaxError" ]); } targetOrigin = whatwgURL.serializeURLOrigin(parsedURL); if (targetOrigin !== idlUtils.implForWrapper(window._document)._origin) { // Not implemented. return; } } // TODO: event.source - requires reference to incumbent window // TODO: event.origin - requires reference to incumbent window // TODO: event.ports // TODO: event.data - requires structured cloning setTimeout(() => { fireAnEvent("message", window, MessageEvent, { data: message }); }, 0); }; window.atob = function (str) { try { return atob(str); } catch { // Convert Node.js DOMException to one from our global. throw DOMException.create(window, [ "The string to be decoded contains invalid characters.", "InvalidCharacterError" ]); } }; window.btoa = function (str) { try { return btoa(str); } catch { // Convert Node.js DOMException to one from our global. throw DOMException.create(window, [ "The string to be encoded contains invalid characters.", "InvalidCharacterError" ]); } }; window.stop = function () { const manager = idlUtils.implForWrapper(window._document)._requestManager; if (manager) { manager.close(); } }; window.close = function () { // Recursively close child frame windows, then ourselves (depth-first). for (let i = 0; i < window.length; ++i) { window[i].close?.(); } // Clear out all listeners. Any in-flight or upcoming events should not get delivered. idlUtils.implForWrapper(window)._eventListeners = Object.create(null); if (window._document) { if (window._document.body) { window._document.body.innerHTML = ""; } if (window._document.close) { // It's especially important to clear out the listeners here because document.close() causes a "load" event to // fire. idlUtils.implForWrapper(window._document)._eventListeners = Object.create(null); window._document.close(); } const doc = idlUtils.implForWrapper(window._document); if (doc._requestManager) { doc._requestManager.close(); } delete window._document; } stopAllTimers(); WebSocketImpl.cleanUpWindow(window); }; window.getComputedStyle = function (elt, pseudoElt = undefined) { const elementImpl = Element.convert(window, elt); if (pseudoElt !== undefined && pseudoElt !== null) { pseudoElt = webIDLConversions.DOMString(pseudoElt); if (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."); } notImplementedMethod(window, "Window", "getComputedStyle", "with pseudo-elements"); } } return idlUtils.wrapperForImpl(getComputedStyleDeclaration(elementImpl)); }; window.getSelection = function () { return window._document.getSelection(); }; // The captureEvents() and releaseEvents() methods must do nothing window.captureEvents = function () {}; window.releaseEvents = function () {}; // ### PUBLIC DATA PROPERTIES (TODO: should be getters) function wrapConsoleMethod(method) { return (...args) => { window._virtualConsole.emit(method, ...args); }; } window.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 notImplementedMethodWrapper(name) { return function () { notImplementedMethod(window, "Window", name); }; } define(window, { alert: notImplementedMethodWrapper("alert"), blur: notImplementedMethodWrapper("blur"), confirm: notImplementedMethodWrapper("confirm"), focus: notImplementedMethodWrapper("focus"), moveBy: notImplementedMethodWrapper("moveBy"), moveTo: notImplementedMethodWrapper("moveTo"), open: notImplementedMethodWrapper("open"), print: notImplementedMethodWrapper("print"), prompt: notImplementedMethodWrapper("prompt"), resizeBy: notImplementedMethodWrapper("resizeBy"), resizeTo: notImplementedMethodWrapper("resizeTo"), scroll: notImplementedMethodWrapper("scroll"), scrollBy: notImplementedMethodWrapper("scrollBy"), scrollTo: notImplementedMethodWrapper("scrollTo") }); } function makeReplaceablePropertyDescriptor(property, window) { const desc = { set(value) { Object.defineProperty(window, property, { configurable: true, enumerable: true, writable: true, value }); } }; Object.defineProperty(desc.set, "name", { value: `set ${property}` }); return desc; }