@postlab/google-workspace-mcp 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1022 @@
1
+ "use strict";
2
+ const vm = require("vm");
3
+ const webIDLConversions = require("webidl-conversions");
4
+ const { CSSStyleDeclaration } = require("cssstyle");
5
+ const whatwgURL = require("whatwg-url");
6
+ const { notImplementedMethod } = require("./not-implemented");
7
+ const { installInterfaces } = require("../living/interfaces");
8
+ const { define, mixin } = require("../utils");
9
+ const Element = require("../living/generated/Element");
10
+ const EventTarget = require("../living/generated/EventTarget");
11
+ const EventHandlerNonNull = require("../living/generated/EventHandlerNonNull");
12
+ const IDLFunction = require("../living/generated/Function");
13
+ const OnBeforeUnloadEventHandlerNonNull = require("../living/generated/OnBeforeUnloadEventHandlerNonNull");
14
+ const OnErrorEventHandlerNonNull = require("../living/generated/OnErrorEventHandlerNonNull");
15
+ const { fireAPageTransitionEvent } = require("../living/helpers/page-transition-event");
16
+ const windowProperties = require("../living/window-properties");
17
+ const DOMException = require("../living/generated/DOMException");
18
+ const idlUtils = require("../living/generated/utils");
19
+ const WebSocketImpl = require("../living/websockets/WebSocket-impl").implementation;
20
+ const BarProp = require("../living/generated/BarProp");
21
+ const documents = require("../living/documents.js");
22
+ const External = require("../living/generated/External");
23
+ const Navigator = require("../living/generated/Navigator");
24
+ const Performance = require("../living/generated/Performance");
25
+ const Screen = require("../living/generated/Screen");
26
+ const Crypto = require("../living/generated/Crypto");
27
+ const Storage = require("../living/generated/Storage");
28
+ const Selection = require("../living/generated/Selection");
29
+ const reportException = require("../living/helpers/runtime-script-errors");
30
+ const { getCurrentEventHandlerValue } = require("../living/helpers/create-event-accessor.js");
31
+ const { fireAnEvent } = require("../living/helpers/events");
32
+ const SessionHistory = require("../living/window/SessionHistory");
33
+ const { getDeclarationForElement, getResolvedValue, propertiesWithResolvedValueImplemented,
34
+ SHADOW_DOM_PSEUDO_REGEXP } = require("../living/helpers/style-rules.js");
35
+ const CustomElementRegistry = require("../living/generated/CustomElementRegistry");
36
+ const MessageEvent = require("../living/generated/MessageEvent");
37
+ const jsGlobals = require("./js-globals.json");
38
+
39
+ const GlobalEventHandlersImpl = require("../living/nodes/GlobalEventHandlers-impl").implementation;
40
+ const globalEventHandlersEvents = require("../living/nodes/GlobalEventHandlers-impl").events;
41
+ const WindowEventHandlersImpl = require("../living/nodes/WindowEventHandlers-impl").implementation;
42
+
43
+ const events = new Set([
44
+ // GlobalEventHandlers (but we have to remove error below)
45
+ ...globalEventHandlersEvents,
46
+
47
+ // WindowEventHandlers
48
+ "afterprint",
49
+ "beforeprint",
50
+ "hashchange",
51
+ "languagechange",
52
+ "message",
53
+ "messageerror",
54
+ "offline",
55
+ "online",
56
+ "pagehide",
57
+ "pageshow",
58
+ "popstate",
59
+ "rejectionhandled",
60
+ "storage",
61
+ "unhandledrejection",
62
+ "unload"
63
+
64
+ // "error" and "beforeunload" are added separately
65
+ ]);
66
+ events.delete("error");
67
+
68
+ const jsGlobalEntriesToInstall = Object.entries(jsGlobals).filter(([name]) => name in global);
69
+
70
+ exports.createWindow = options => {
71
+ const makeVMContext = options.runScripts === "outside-only" || options.runScripts === "dangerously";
72
+
73
+ // Bootstrap with an empty object from the Node.js realm. We'll muck with its prototype chain shortly.
74
+ let window = {};
75
+
76
+ // Make window into a global object: either via vm, or just aliasing the Node.js globals.
77
+ // Also set _globalObject and _globalProxy.
78
+ //
79
+ // TODO: don't expose _globalObject and _globalProxy as public properties. While you're there, audit usage sites to
80
+ // see how necessary they really are.
81
+ if (makeVMContext) {
82
+ window = vm.createContext(vm.constants.DONT_CONTEXTIFY);
83
+
84
+ window._globalObject = window;
85
+ window._globalProxy = vm.runInContext("this", window);
86
+
87
+ // Without this, these globals will only appear to scripts running inside the context using vm.runScript; they will
88
+ // not appear to scripts running from the outside, including to JSDOM implementation code.
89
+ for (const [globalName, globalPropDesc] of jsGlobalEntriesToInstall) {
90
+ const propDesc = { ...globalPropDesc, value: vm.runInContext(globalName, window) };
91
+ Object.defineProperty(window, globalName, propDesc);
92
+ }
93
+ } else {
94
+ window._globalObject = window._globalProxy = window;
95
+
96
+ // Without contextifying the window, none of the globals will exist. So, let's at least alias them from the Node.js
97
+ // context. See https://github.com/jsdom/jsdom/issues/2727 for more background and discussion.
98
+ for (const [globalName, globalPropDesc] of jsGlobalEntriesToInstall) {
99
+ const propDesc = { ...globalPropDesc, value: global[globalName] };
100
+ Object.defineProperty(window, globalName, propDesc);
101
+ }
102
+ }
103
+
104
+ // Create instances of all the web platform interfaces and install them on the window.
105
+ installInterfaces(window, ["Window"]);
106
+
107
+ // Now we have an EventTarget contructor so we can work on the prototype chain.
108
+
109
+ // eslint-disable-next-line func-name-matching, func-style
110
+ const WindowConstructor = function Window() {
111
+ throw new TypeError("Illegal constructor");
112
+ };
113
+ Object.setPrototypeOf(WindowConstructor, window.EventTarget);
114
+
115
+ Object.defineProperty(window, "Window", {
116
+ configurable: true,
117
+ writable: true,
118
+ value: WindowConstructor
119
+ });
120
+
121
+ const windowPropertiesObject = windowProperties.create(window.EventTarget.prototype, window);
122
+
123
+ const windowPrototype = Object.create(windowPropertiesObject);
124
+ Object.defineProperties(windowPrototype, {
125
+ constructor: {
126
+ value: WindowConstructor,
127
+ writable: true,
128
+ configurable: true
129
+ },
130
+ [Symbol.toStringTag]: {
131
+ value: "Window",
132
+ configurable: true
133
+ }
134
+ });
135
+
136
+ WindowConstructor.prototype = windowPrototype;
137
+ Object.setPrototypeOf(window, windowPrototype);
138
+ if (makeVMContext) {
139
+ Object.setPrototypeOf(window._globalProxy, windowPrototype);
140
+ Object.setPrototypeOf(window.EventTarget.prototype, window.Object.prototype);
141
+ }
142
+
143
+ // Now that the prototype chain is fully set up, call the superclass setup.
144
+ EventTarget.setup(window, window);
145
+
146
+ installEventHandlers(window);
147
+
148
+ installOwnProperties(window, options);
149
+
150
+ // Not sure why this is necessary... TODO figure it out.
151
+ Object.defineProperty(idlUtils.implForWrapper(window), idlUtils.wrapperSymbol, { get: () => window._globalProxy });
152
+
153
+ // Fire or prepare to fire load and pageshow events.
154
+ process.nextTick(() => {
155
+ if (!window.document) {
156
+ return; // window might've been closed already
157
+ }
158
+
159
+ if (window.document.readyState === "complete") {
160
+ fireAnEvent("load", window, undefined, {}, true);
161
+ } else {
162
+ window.document.addEventListener("load", () => {
163
+ fireAnEvent("load", window, undefined, {}, true);
164
+ if (!window._document) {
165
+ return; // window might've been closed already
166
+ }
167
+
168
+ const documentImpl = idlUtils.implForWrapper(window._document);
169
+ if (!documentImpl._pageShowingFlag) {
170
+ documentImpl._pageShowingFlag = true;
171
+ fireAPageTransitionEvent("pageshow", window, false);
172
+ }
173
+ });
174
+ }
175
+ });
176
+
177
+ return window;
178
+ };
179
+
180
+ function installEventHandlers(window) {
181
+ mixin(window, WindowEventHandlersImpl.prototype);
182
+ mixin(window, GlobalEventHandlersImpl.prototype);
183
+ window._initGlobalEvents();
184
+
185
+ Object.defineProperty(window, "onbeforeunload", {
186
+ configurable: true,
187
+ enumerable: true,
188
+ get() {
189
+ return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(window, "beforeunload"));
190
+ },
191
+ set(V) {
192
+ if (!idlUtils.isObject(V)) {
193
+ V = null;
194
+ } else {
195
+ V = OnBeforeUnloadEventHandlerNonNull.convert(window, V, {
196
+ context: "Failed to set the 'onbeforeunload' property on 'Window': The provided value"
197
+ });
198
+ }
199
+ window._setEventHandlerFor("beforeunload", V);
200
+ }
201
+ });
202
+
203
+ Object.defineProperty(window, "onerror", {
204
+ configurable: true,
205
+ enumerable: true,
206
+ get() {
207
+ return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(window, "error"));
208
+ },
209
+ set(V) {
210
+ if (!idlUtils.isObject(V)) {
211
+ V = null;
212
+ } else {
213
+ V = OnErrorEventHandlerNonNull.convert(window, V, {
214
+ context: "Failed to set the 'onerror' property on 'Window': The provided value"
215
+ });
216
+ }
217
+ window._setEventHandlerFor("error", V);
218
+ }
219
+ });
220
+
221
+ for (const event of events) {
222
+ Object.defineProperty(window, `on${event}`, {
223
+ configurable: true,
224
+ enumerable: true,
225
+ get() {
226
+ return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(window, event));
227
+ },
228
+ set(V) {
229
+ if (!idlUtils.isObject(V)) {
230
+ V = null;
231
+ } else {
232
+ V = EventHandlerNonNull.convert(window, V, {
233
+ context: `Failed to set the 'on${event}' property on 'Window': The provided value`
234
+ });
235
+ }
236
+ window._setEventHandlerFor(event, V);
237
+ }
238
+ });
239
+ }
240
+ }
241
+
242
+ function installOwnProperties(window, options) {
243
+ const windowInitialized = performance.now();
244
+
245
+ // ### PRIVATE DATA PROPERTIES
246
+
247
+ window._resourceLoader = options.resourceLoader;
248
+
249
+ // List options explicitly to be clear which are passed through
250
+ window._document = documents.createWrapper(window, {
251
+ parsingMode: options.parsingMode,
252
+ contentType: options.contentType,
253
+ encoding: options.encoding,
254
+ cookieJar: options.cookieJar,
255
+ url: options.url,
256
+ lastModified: options.lastModified,
257
+ referrer: options.referrer,
258
+ parseOptions: options.parseOptions,
259
+ defaultView: window._globalProxy,
260
+ global: window,
261
+ parentOrigin: options.parentOrigin
262
+ }, { alwaysUseDocumentClass: true });
263
+
264
+ const documentOrigin = idlUtils.implForWrapper(window._document)._origin;
265
+ window._origin = documentOrigin;
266
+
267
+ // https://html.spec.whatwg.org/#session-history
268
+ window._sessionHistory = new SessionHistory({
269
+ document: idlUtils.implForWrapper(window._document),
270
+ url: idlUtils.implForWrapper(window._document)._URL,
271
+ stateObject: null
272
+ }, window);
273
+
274
+ window._virtualConsole = options.virtualConsole;
275
+
276
+ window._runScripts = options.runScripts;
277
+
278
+ // Set up the window as if it's a top level window.
279
+ // If it's not, then references will be corrected by frame/iframe code.
280
+ window._parent = window._top = window._globalProxy;
281
+ window._frameElement = null;
282
+
283
+ // This implements window.frames.length, since window.frames returns a
284
+ // self reference to the window object. This value is incremented in the
285
+ // HTMLFrameElement implementation.
286
+ window._length = 0;
287
+
288
+ // https://dom.spec.whatwg.org/#window-current-event
289
+ window._currentEvent = undefined;
290
+
291
+ window._pretendToBeVisual = options.pretendToBeVisual;
292
+ window._storageQuota = options.storageQuota;
293
+
294
+ // Some properties (such as localStorage and sessionStorage) share data
295
+ // between windows in the same origin. This object is intended
296
+ // to contain such data.
297
+ if (options.commonForOrigin && options.commonForOrigin[documentOrigin]) {
298
+ window._commonForOrigin = options.commonForOrigin;
299
+ } else {
300
+ window._commonForOrigin = {
301
+ [documentOrigin]: {
302
+ localStorageArea: new Map(),
303
+ sessionStorageArea: new Map(),
304
+ windowsInSameOrigin: [window]
305
+ }
306
+ };
307
+ }
308
+
309
+ window._currentOriginData = window._commonForOrigin[documentOrigin];
310
+
311
+ // ### WEB STORAGE
312
+
313
+ window._localStorage = Storage.create(window, [], {
314
+ associatedWindow: window,
315
+ storageArea: window._currentOriginData.localStorageArea,
316
+ type: "localStorage",
317
+ url: window._document.documentURI,
318
+ storageQuota: window._storageQuota
319
+ });
320
+ window._sessionStorage = Storage.create(window, [], {
321
+ associatedWindow: window,
322
+ storageArea: window._currentOriginData.sessionStorageArea,
323
+ type: "sessionStorage",
324
+ url: window._document.documentURI,
325
+ storageQuota: window._storageQuota
326
+ });
327
+
328
+ // ### SELECTION
329
+
330
+ // https://w3c.github.io/selection-api/#dfn-selection
331
+ window._selection = Selection.createImpl(window);
332
+
333
+ // https://w3c.github.io/selection-api/#dom-window
334
+ window.getSelection = function () {
335
+ return window._selection;
336
+ };
337
+
338
+ // ### GETTERS
339
+
340
+ const locationbar = BarProp.create(window);
341
+ const menubar = BarProp.create(window);
342
+ const personalbar = BarProp.create(window);
343
+ const scrollbars = BarProp.create(window);
344
+ const statusbar = BarProp.create(window);
345
+ const toolbar = BarProp.create(window);
346
+ const external = External.create(window);
347
+ const navigator = Navigator.create(window, [], { userAgent: window._resourceLoader._userAgent });
348
+ const performanceImpl = Performance.create(window, [], {
349
+ timeOrigin: performance.timeOrigin + windowInitialized,
350
+ nowAtTimeOrigin: windowInitialized
351
+ });
352
+ const screen = Screen.create(window);
353
+ const crypto = Crypto.create(window);
354
+ window._customElementRegistry = CustomElementRegistry.create(window);
355
+
356
+ let status = "";
357
+
358
+ define(window, {
359
+ name: "",
360
+ get status() {
361
+ return status;
362
+ },
363
+ set status(value) {
364
+ status = webIDLConversions.DOMString(value);
365
+ },
366
+ get devicePixelRatio() {
367
+ return 1;
368
+ },
369
+ get innerWidth() {
370
+ return 1024;
371
+ },
372
+ get innerHeight() {
373
+ return 768;
374
+ },
375
+ get outerWidth() {
376
+ return 1024;
377
+ },
378
+ get outerHeight() {
379
+ return 768;
380
+ },
381
+ get pageXOffset() {
382
+ return 0;
383
+ },
384
+ get pageYOffset() {
385
+ return 0;
386
+ },
387
+ get screenX() {
388
+ return 0;
389
+ },
390
+ get screenLeft() {
391
+ return 0;
392
+ },
393
+ get screenY() {
394
+ return 0;
395
+ },
396
+ get screenTop() {
397
+ return 0;
398
+ },
399
+ get scrollX() {
400
+ return 0;
401
+ },
402
+ get scrollY() {
403
+ return 0;
404
+ },
405
+ get length() {
406
+ return window._length;
407
+ },
408
+ get window() {
409
+ return window._globalProxy;
410
+ },
411
+ get frameElement() {
412
+ return idlUtils.wrapperForImpl(window._frameElement);
413
+ },
414
+ get frames() {
415
+ return window._globalProxy;
416
+ },
417
+ get self() {
418
+ return window._globalProxy;
419
+ },
420
+ get parent() {
421
+ return window._parent;
422
+ },
423
+ get top() {
424
+ return window._top;
425
+ },
426
+ get document() {
427
+ return window._document;
428
+ },
429
+ get external() {
430
+ return external;
431
+ },
432
+ get location() {
433
+ return idlUtils.wrapperForImpl(idlUtils.implForWrapper(window._document)._location);
434
+ },
435
+ // [PutForwards=href]:
436
+ set location(value) {
437
+ Reflect.set(window.location, "href", value);
438
+ },
439
+ get history() {
440
+ return idlUtils.wrapperForImpl(idlUtils.implForWrapper(window._document)._history);
441
+ },
442
+ get navigator() {
443
+ return navigator;
444
+ },
445
+ get locationbar() {
446
+ return locationbar;
447
+ },
448
+ get menubar() {
449
+ return menubar;
450
+ },
451
+ get personalbar() {
452
+ return personalbar;
453
+ },
454
+ get scrollbars() {
455
+ return scrollbars;
456
+ },
457
+ get statusbar() {
458
+ return statusbar;
459
+ },
460
+ get toolbar() {
461
+ return toolbar;
462
+ },
463
+ get performance() {
464
+ return performanceImpl;
465
+ },
466
+ get screen() {
467
+ return screen;
468
+ },
469
+ get crypto() {
470
+ return crypto;
471
+ },
472
+ get origin() {
473
+ return window._origin;
474
+ },
475
+ get localStorage() {
476
+ if (idlUtils.implForWrapper(window._document)._origin === "null") {
477
+ throw DOMException.create(window, [
478
+ "localStorage is not available for opaque origins",
479
+ "SecurityError"
480
+ ]);
481
+ }
482
+
483
+ return window._localStorage;
484
+ },
485
+ get sessionStorage() {
486
+ if (idlUtils.implForWrapper(window._document)._origin === "null") {
487
+ throw DOMException.create(window, [
488
+ "sessionStorage is not available for opaque origins",
489
+ "SecurityError"
490
+ ]);
491
+ }
492
+
493
+ return window._sessionStorage;
494
+ },
495
+ get customElements() {
496
+ return window._customElementRegistry;
497
+ },
498
+ get event() {
499
+ return window._currentEvent ? idlUtils.wrapperForImpl(window._currentEvent) : undefined;
500
+ }
501
+ });
502
+
503
+ Object.defineProperties(window, {
504
+ // [Replaceable]:
505
+ self: makeReplaceablePropertyDescriptor("self", window),
506
+ locationbar: makeReplaceablePropertyDescriptor("locationbar", window),
507
+ menubar: makeReplaceablePropertyDescriptor("menubar", window),
508
+ personalbar: makeReplaceablePropertyDescriptor("personalbar", window),
509
+ scrollbars: makeReplaceablePropertyDescriptor("scrollbars", window),
510
+ statusbar: makeReplaceablePropertyDescriptor("statusbar", window),
511
+ toolbar: makeReplaceablePropertyDescriptor("toolbar", window),
512
+ frames: makeReplaceablePropertyDescriptor("frames", window),
513
+ parent: makeReplaceablePropertyDescriptor("parent", window),
514
+ external: makeReplaceablePropertyDescriptor("external", window),
515
+ length: makeReplaceablePropertyDescriptor("length", window),
516
+ screen: makeReplaceablePropertyDescriptor("screen", window),
517
+ origin: makeReplaceablePropertyDescriptor("origin", window),
518
+ event: makeReplaceablePropertyDescriptor("event", window),
519
+ innerWidth: makeReplaceablePropertyDescriptor("innerWidth", window),
520
+ innerHeight: makeReplaceablePropertyDescriptor("innerHeight", window),
521
+ scrollX: makeReplaceablePropertyDescriptor("scrollX", window),
522
+ pageXOffset: makeReplaceablePropertyDescriptor("pageXOffset", window),
523
+ scrollY: makeReplaceablePropertyDescriptor("scrollY", window),
524
+ pageYOffset: makeReplaceablePropertyDescriptor("pageYOffset", window),
525
+ screenX: makeReplaceablePropertyDescriptor("screenX", window),
526
+ screenLeft: makeReplaceablePropertyDescriptor("screenLeft", window),
527
+ screenY: makeReplaceablePropertyDescriptor("screenY", window),
528
+ screenTop: makeReplaceablePropertyDescriptor("screenTop", window),
529
+ outerWidth: makeReplaceablePropertyDescriptor("outerWidth", window),
530
+ outerHeight: makeReplaceablePropertyDescriptor("outerHeight", window),
531
+ devicePixelRatio: makeReplaceablePropertyDescriptor("devicePixelRatio", window),
532
+
533
+ // [LegacyUnforgeable]:
534
+ window: { configurable: false },
535
+ document: { configurable: false },
536
+ location: { configurable: false },
537
+ top: { configurable: false }
538
+ });
539
+
540
+
541
+ // ### METHODS
542
+
543
+ // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers
544
+
545
+ // 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
546
+ // we can call Node.js-side clearTimeout() when clearing, and thus allow process shutdown faster.
547
+ const listOfActiveTimers = new Map();
548
+ let latestTimerId = 0;
549
+
550
+ window.setTimeout = function (handler, timeout = 0, ...args) {
551
+ if (typeof handler !== "function") {
552
+ handler = webIDLConversions.DOMString(handler);
553
+ }
554
+ timeout = webIDLConversions.long(timeout);
555
+
556
+ return timerInitializationSteps(handler, timeout, args, { methodContext: window, repeat: false });
557
+ };
558
+ window.setInterval = function (handler, timeout = 0, ...args) {
559
+ if (typeof handler !== "function") {
560
+ handler = webIDLConversions.DOMString(handler);
561
+ }
562
+ timeout = webIDLConversions.long(timeout);
563
+
564
+ return timerInitializationSteps(handler, timeout, args, { methodContext: window, repeat: true });
565
+ };
566
+
567
+ window.clearTimeout = function (handle = 0) {
568
+ handle = webIDLConversions.long(handle);
569
+
570
+ const nodejsTimer = listOfActiveTimers.get(handle);
571
+ if (nodejsTimer) {
572
+ clearTimeout(nodejsTimer);
573
+ listOfActiveTimers.delete(handle);
574
+ }
575
+ };
576
+ window.clearInterval = function (handle = 0) {
577
+ handle = webIDLConversions.long(handle);
578
+
579
+ const nodejsTimer = listOfActiveTimers.get(handle);
580
+ if (nodejsTimer) {
581
+ // We use setTimeout() in timerInitializationSteps even for window.setInterval().
582
+ clearTimeout(nodejsTimer);
583
+ listOfActiveTimers.delete(handle);
584
+ }
585
+ };
586
+
587
+ function timerInitializationSteps(handler, timeout, args, { methodContext, repeat, previousHandle }) {
588
+ // This appears to be unspecced, but matches browser behavior for close()ed windows.
589
+ if (!methodContext._document) {
590
+ return 0;
591
+ }
592
+
593
+ // TODO: implement timer nesting level behavior.
594
+
595
+ const methodContextProxy = methodContext._globalProxy;
596
+ const handle = previousHandle !== undefined ? previousHandle : ++latestTimerId;
597
+
598
+ function task() {
599
+ if (!listOfActiveTimers.has(handle)) {
600
+ return;
601
+ }
602
+
603
+ try {
604
+ if (typeof handler === "function") {
605
+ handler.apply(methodContextProxy, args);
606
+ } else if (window._runScripts === "dangerously") {
607
+ vm.runInContext(handler, window, { filename: window.location.href, displayErrors: false });
608
+ }
609
+ } catch (e) {
610
+ reportException(window, e, window.location.href);
611
+ }
612
+
613
+ if (listOfActiveTimers.has(handle)) {
614
+ if (repeat) {
615
+ timerInitializationSteps(handler, timeout, args, { methodContext, repeat: true, previousHandle: handle });
616
+ } else {
617
+ listOfActiveTimers.delete(handle);
618
+ }
619
+ }
620
+ }
621
+
622
+ if (timeout < 0) {
623
+ timeout = 0;
624
+ }
625
+
626
+ const nodejsTimer = setTimeout(task, timeout);
627
+ listOfActiveTimers.set(handle, nodejsTimer);
628
+
629
+ return handle;
630
+ }
631
+
632
+ // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#microtask-queuing
633
+
634
+ window.queueMicrotask = function (callback) {
635
+ callback = IDLFunction.convert(window, callback);
636
+
637
+ queueMicrotask(() => {
638
+ try {
639
+ callback();
640
+ } catch (e) {
641
+ reportException(window, e, window.location.href);
642
+ }
643
+ });
644
+ };
645
+
646
+ // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animation-frames
647
+
648
+ let animationFrameCallbackId = 0;
649
+ const mapOfAnimationFrameCallbacks = new Map();
650
+ let animationFrameNodejsInterval = null;
651
+
652
+ // Unlike the spec, where an animation frame happens every 60 Hz regardless, we optimize so that if there are no
653
+ // requestAnimationFrame() calls outstanding, we don't fire the timer. This helps us track that.
654
+ let numberOfOngoingAnimationFrameCallbacks = 0;
655
+
656
+ if (window._pretendToBeVisual) {
657
+ window.requestAnimationFrame = function (callback) {
658
+ callback = IDLFunction.convert(window, callback);
659
+
660
+ const handle = ++animationFrameCallbackId;
661
+ mapOfAnimationFrameCallbacks.set(handle, callback);
662
+
663
+ ++numberOfOngoingAnimationFrameCallbacks;
664
+ if (numberOfOngoingAnimationFrameCallbacks === 1) {
665
+ animationFrameNodejsInterval = setInterval(() => {
666
+ runAnimationFrameCallbacks(performance.now() - windowInitialized);
667
+ }, 1000 / 60);
668
+ }
669
+
670
+ return handle;
671
+ };
672
+
673
+ window.cancelAnimationFrame = function (handle) {
674
+ handle = webIDLConversions["unsigned long"](handle);
675
+
676
+ removeAnimationFrameCallback(handle);
677
+ };
678
+
679
+ function runAnimationFrameCallbacks(now) {
680
+ // Converting to an array is important to get a sync snapshot and thus match spec semantics.
681
+ const callbackHandles = [...mapOfAnimationFrameCallbacks.keys()];
682
+ for (const handle of callbackHandles) {
683
+ // This has() can be false if a callback calls cancelAnimationFrame().
684
+ if (mapOfAnimationFrameCallbacks.has(handle)) {
685
+ const callback = mapOfAnimationFrameCallbacks.get(handle);
686
+ removeAnimationFrameCallback(handle);
687
+ try {
688
+ callback(now);
689
+ } catch (e) {
690
+ reportException(window, e, window.location.href);
691
+ }
692
+ }
693
+ }
694
+ }
695
+
696
+ function removeAnimationFrameCallback(handle) {
697
+ if (mapOfAnimationFrameCallbacks.has(handle)) {
698
+ --numberOfOngoingAnimationFrameCallbacks;
699
+ if (numberOfOngoingAnimationFrameCallbacks === 0) {
700
+ clearInterval(animationFrameNodejsInterval);
701
+ }
702
+ }
703
+
704
+ mapOfAnimationFrameCallbacks.delete(handle);
705
+ }
706
+ }
707
+
708
+ function stopAllTimers() {
709
+ for (const nodejsTimer of listOfActiveTimers.values()) {
710
+ clearTimeout(nodejsTimer);
711
+ }
712
+ listOfActiveTimers.clear();
713
+
714
+ clearInterval(animationFrameNodejsInterval);
715
+ }
716
+
717
+ function Option(text, value, defaultSelected, selected) {
718
+ if (text === undefined) {
719
+ text = "";
720
+ }
721
+ text = webIDLConversions.DOMString(text);
722
+
723
+ if (value !== undefined) {
724
+ value = webIDLConversions.DOMString(value);
725
+ }
726
+
727
+ defaultSelected = webIDLConversions.boolean(defaultSelected);
728
+ selected = webIDLConversions.boolean(selected);
729
+
730
+ const option = window._document.createElement("option");
731
+ const impl = idlUtils.implForWrapper(option);
732
+
733
+ if (text !== "") {
734
+ impl.text = text;
735
+ }
736
+ if (value !== undefined) {
737
+ impl.setAttributeNS(null, "value", value);
738
+ }
739
+ if (defaultSelected) {
740
+ impl.setAttributeNS(null, "selected", "");
741
+ }
742
+ impl._selectedness = selected;
743
+
744
+ return option;
745
+ }
746
+ Object.defineProperty(Option, "prototype", {
747
+ value: window.HTMLOptionElement.prototype,
748
+ configurable: false,
749
+ enumerable: false,
750
+ writable: false
751
+ });
752
+ Object.defineProperty(window, "Option", {
753
+ value: Option,
754
+ configurable: true,
755
+ enumerable: false,
756
+ writable: true
757
+ });
758
+
759
+ function Image(...args) {
760
+ const img = window._document.createElement("img");
761
+ const impl = idlUtils.implForWrapper(img);
762
+
763
+ if (args.length > 0) {
764
+ impl.setAttributeNS(null, "width", String(args[0]));
765
+ }
766
+ if (args.length > 1) {
767
+ impl.setAttributeNS(null, "height", String(args[1]));
768
+ }
769
+
770
+ return img;
771
+ }
772
+ Object.defineProperty(Image, "prototype", {
773
+ value: window.HTMLImageElement.prototype,
774
+ configurable: false,
775
+ enumerable: false,
776
+ writable: false
777
+ });
778
+ Object.defineProperty(window, "Image", {
779
+ value: Image,
780
+ configurable: true,
781
+ enumerable: false,
782
+ writable: true
783
+ });
784
+
785
+ function Audio(src) {
786
+ const audio = window._document.createElement("audio");
787
+ const impl = idlUtils.implForWrapper(audio);
788
+ impl.setAttributeNS(null, "preload", "auto");
789
+
790
+ if (src !== undefined) {
791
+ impl.setAttributeNS(null, "src", String(src));
792
+ }
793
+
794
+ return audio;
795
+ }
796
+ Object.defineProperty(Audio, "prototype", {
797
+ value: window.HTMLAudioElement.prototype,
798
+ configurable: false,
799
+ enumerable: false,
800
+ writable: false
801
+ });
802
+ Object.defineProperty(window, "Audio", {
803
+ value: Audio,
804
+ configurable: true,
805
+ enumerable: false,
806
+ writable: true
807
+ });
808
+
809
+ window.postMessage = function (message, targetOrigin) {
810
+ if (arguments.length < 2) {
811
+ throw new TypeError("'postMessage' requires 2 arguments: 'message' and 'targetOrigin'");
812
+ }
813
+
814
+ targetOrigin = webIDLConversions.DOMString(targetOrigin);
815
+
816
+ if (targetOrigin === "/") {
817
+ // TODO: targetOrigin === "/" requires getting incumbent settings object.
818
+ // Maybe could be done with Error stack traces??
819
+ return;
820
+ } else if (targetOrigin !== "*") {
821
+ const parsedURL = whatwgURL.parseURL(targetOrigin);
822
+ if (parsedURL === null) {
823
+ throw DOMException.create(window, [
824
+ "Failed to execute 'postMessage' on 'Window': " +
825
+ "Invalid target origin '" + targetOrigin + "' in a call to 'postMessage'.",
826
+ "SyntaxError"
827
+ ]);
828
+ }
829
+ targetOrigin = whatwgURL.serializeURLOrigin(parsedURL);
830
+
831
+ if (targetOrigin !== idlUtils.implForWrapper(window._document)._origin) {
832
+ // Not implemented.
833
+ return;
834
+ }
835
+ }
836
+
837
+ // TODO: event.source - requires reference to incumbent window
838
+ // TODO: event.origin - requires reference to incumbent window
839
+ // TODO: event.ports
840
+ // TODO: event.data - requires structured cloning
841
+ setTimeout(() => {
842
+ fireAnEvent("message", window, MessageEvent, { data: message });
843
+ }, 0);
844
+ };
845
+
846
+ window.atob = function (str) {
847
+ try {
848
+ return atob(str);
849
+ } catch {
850
+ // Convert Node.js DOMException to one from our global.
851
+ throw DOMException.create(window, [
852
+ "The string to be decoded contains invalid characters.",
853
+ "InvalidCharacterError"
854
+ ]);
855
+ }
856
+ };
857
+
858
+ window.btoa = function (str) {
859
+ try {
860
+ return btoa(str);
861
+ } catch {
862
+ // Convert Node.js DOMException to one from our global.
863
+ throw DOMException.create(window, [
864
+ "The string to be encoded contains invalid characters.",
865
+ "InvalidCharacterError"
866
+ ]);
867
+ }
868
+ };
869
+
870
+ window.stop = function () {
871
+ const manager = idlUtils.implForWrapper(window._document)._requestManager;
872
+ if (manager) {
873
+ manager.close();
874
+ }
875
+ };
876
+
877
+ window.close = function () {
878
+ // Recursively close child frame windows, then ourselves (depth-first).
879
+ for (let i = 0; i < window.length; ++i) {
880
+ window[i].close?.();
881
+ }
882
+
883
+ // Clear out all listeners. Any in-flight or upcoming events should not get delivered.
884
+ idlUtils.implForWrapper(window)._eventListeners = Object.create(null);
885
+
886
+ if (window._document) {
887
+ if (window._document.body) {
888
+ window._document.body.innerHTML = "";
889
+ }
890
+
891
+ if (window._document.close) {
892
+ // It's especially important to clear out the listeners here because document.close() causes a "load" event to
893
+ // fire.
894
+ idlUtils.implForWrapper(window._document)._eventListeners = Object.create(null);
895
+ window._document.close();
896
+ }
897
+ const doc = idlUtils.implForWrapper(window._document);
898
+ if (doc._requestManager) {
899
+ doc._requestManager.close();
900
+ }
901
+ delete window._document;
902
+ }
903
+
904
+ stopAllTimers();
905
+ WebSocketImpl.cleanUpWindow(window);
906
+ };
907
+
908
+ window.getComputedStyle = function (elt, pseudoElt = undefined) {
909
+ elt = Element.convert(window, elt);
910
+ if (pseudoElt !== undefined && pseudoElt !== null) {
911
+ pseudoElt = webIDLConversions.DOMString(pseudoElt);
912
+ }
913
+
914
+ if (pseudoElt !== undefined && pseudoElt !== null && pseudoElt !== "") {
915
+ // TODO: Parse pseudoElt
916
+
917
+ if (SHADOW_DOM_PSEUDO_REGEXP.test(pseudoElt)) {
918
+ throw new TypeError("Tried to get the computed style of a Shadow DOM pseudo-element.");
919
+ }
920
+
921
+ notImplementedMethod(window, "Window", "getComputedStyle", "with pseudo-elements");
922
+ }
923
+
924
+ const declaration = new CSSStyleDeclaration();
925
+ const { forEach } = Array.prototype;
926
+
927
+ const elementDeclaration = getDeclarationForElement(elt);
928
+ forEach.call(elementDeclaration, property => {
929
+ declaration.setProperty(
930
+ property,
931
+ elementDeclaration.getPropertyValue(property),
932
+ elementDeclaration.getPropertyPriority(property)
933
+ );
934
+ });
935
+
936
+ // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle
937
+ const declarations = Object.keys(propertiesWithResolvedValueImplemented);
938
+ forEach.call(declarations, property => {
939
+ declaration.setProperty(property, getResolvedValue(elt, property));
940
+ });
941
+
942
+ return declaration;
943
+ };
944
+
945
+ window.getSelection = function () {
946
+ return window._document.getSelection();
947
+ };
948
+
949
+ // The captureEvents() and releaseEvents() methods must do nothing
950
+ window.captureEvents = function () {};
951
+
952
+ window.releaseEvents = function () {};
953
+
954
+ // ### PUBLIC DATA PROPERTIES (TODO: should be getters)
955
+
956
+ function wrapConsoleMethod(method) {
957
+ return (...args) => {
958
+ window._virtualConsole.emit(method, ...args);
959
+ };
960
+ }
961
+
962
+ window.console = {
963
+ assert: wrapConsoleMethod("assert"),
964
+ clear: wrapConsoleMethod("clear"),
965
+ count: wrapConsoleMethod("count"),
966
+ countReset: wrapConsoleMethod("countReset"),
967
+ debug: wrapConsoleMethod("debug"),
968
+ dir: wrapConsoleMethod("dir"),
969
+ dirxml: wrapConsoleMethod("dirxml"),
970
+ error: wrapConsoleMethod("error"),
971
+ group: wrapConsoleMethod("group"),
972
+ groupCollapsed: wrapConsoleMethod("groupCollapsed"),
973
+ groupEnd: wrapConsoleMethod("groupEnd"),
974
+ info: wrapConsoleMethod("info"),
975
+ log: wrapConsoleMethod("log"),
976
+ table: wrapConsoleMethod("table"),
977
+ time: wrapConsoleMethod("time"),
978
+ timeLog: wrapConsoleMethod("timeLog"),
979
+ timeEnd: wrapConsoleMethod("timeEnd"),
980
+ trace: wrapConsoleMethod("trace"),
981
+ warn: wrapConsoleMethod("warn")
982
+ };
983
+
984
+ function notImplementedMethodWrapper(name) {
985
+ return function () {
986
+ notImplementedMethod(window, "Window", name);
987
+ };
988
+ }
989
+
990
+ define(window, {
991
+ alert: notImplementedMethodWrapper("alert"),
992
+ blur: notImplementedMethodWrapper("blur"),
993
+ confirm: notImplementedMethodWrapper("confirm"),
994
+ focus: notImplementedMethodWrapper("focus"),
995
+ moveBy: notImplementedMethodWrapper("moveBy"),
996
+ moveTo: notImplementedMethodWrapper("moveTo"),
997
+ open: notImplementedMethodWrapper("open"),
998
+ print: notImplementedMethodWrapper("print"),
999
+ prompt: notImplementedMethodWrapper("prompt"),
1000
+ resizeBy: notImplementedMethodWrapper("resizeBy"),
1001
+ resizeTo: notImplementedMethodWrapper("resizeTo"),
1002
+ scroll: notImplementedMethodWrapper("scroll"),
1003
+ scrollBy: notImplementedMethodWrapper("scrollBy"),
1004
+ scrollTo: notImplementedMethodWrapper("scrollTo")
1005
+ });
1006
+ }
1007
+
1008
+ function makeReplaceablePropertyDescriptor(property, window) {
1009
+ const desc = {
1010
+ set(value) {
1011
+ Object.defineProperty(window, property, {
1012
+ configurable: true,
1013
+ enumerable: true,
1014
+ writable: true,
1015
+ value
1016
+ });
1017
+ }
1018
+ };
1019
+
1020
+ Object.defineProperty(desc.set, "name", { value: `set ${property}` });
1021
+ return desc;
1022
+ }