Skip to content
File

Blob: src/workerd/api/tests/global-scope-test.js

javascript863 lines
1// Copyright (c) 2023 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4import {
5 deepStrictEqual,
6 strictEqual,
7 throws,
8 doesNotThrow,
9 notStrictEqual,
10 ok,
11} from 'node:assert';
12 
13import { AsyncLocalStorage } from 'node:async_hooks';
14import util from 'node:util';
15 
16export const navigatorUserAgent = {
17 async test() {
18 strictEqual(navigator.userAgent, 'Cloudflare-Workers');
19 },
20};
21 
22export const timeoutClamping = {
23 async test() {
24 // p2 should resolve, p1 should never resolve.
25 
26 const p1 = new Promise((resolve) => {
27 setTimeout(() => {
28 resolve('b');
29 }, 9223372036854775808); // Will be clamped to a max of 3153600000000
30 });
31 
32 const p2 = new Promise((resolve) => {
33 setTimeout(() => {
34 resolve('a');
35 }, -100); // Will be clamped to 0
36 });
37 
38 strictEqual(await Promise.race([p1, p2]), 'a');
39 },
40};
41 
42export const timeoutCount = {
43 test() {
44 for (let i = 0; i < 10000; ++i) {
45 setTimeout(() => {});
46 }
47 throws(() => setTimeout(() => {}), {
48 message:
49 'You have exceeded the number of active timeouts you may set. max active timeouts: 10000, current active timeouts: 10000, finished timeouts: 0',
50 });
51 },
52};
53 
54export const timeoutImplicitCancel = {
55 test() {
56 // Timeouts should be implicitly canceled after the IoContext is destroyed.
57 setTimeout(() => {
58 throw new Error('boom');
59 }, 100000);
60 },
61};
62 
63export const timeoutVarargs = {
64 async test() {
65 let resolve;
66 const promise = new Promise((a) => (resolve = a));
67 setTimeout(
68 (a, b, c) => {
69 resolve([a, b, c]);
70 },
71 10,
72 1,
73 2,
74 3
75 );
76 const results = await promise;
77 deepStrictEqual(results, [1, 2, 3]);
78 },
79};
80 
81export const intervalVarargs = {
82 async test() {
83 let resolve;
84 const promise = new Promise((a) => (resolve = a));
85 
86 // prettier-ignore
87 const i = setInterval((a,b,c) => {
88 resolve([a,b,c]);
89 clearInterval(i);
90 }, 10, 1, 2, 3);
91 
92 const results = await promise;
93 deepStrictEqual(results, [1, 2, 3]);
94 },
95};
96 
97export const mutableGlobals = {
98 async test() {
99 {
100 const oldCaches = globalThis.caches;
101 const oldCrypto = globalThis.crypto;
102 const oldSelf = globalThis.self;
103 const oldScheduler = globalThis.scheduler;
104 delete globalThis.caches;
105 delete globalThis.crypto;
106 delete globalThis.self;
107 delete globalThis.scheduler;
108 strictEqual(globalThis.caches, undefined);
109 strictEqual(globalThis.crypto, undefined);
110 strictEqual(globalThis.self, undefined);
111 strictEqual(globalThis.scheduler, undefined);
112 Object.defineProperties(globalThis, {
113 caches: { value: 'hello there', writable: true, configurable: true },
114 crypto: { value: 'we are', writable: true, configurable: true },
115 self: { value: 'mutable', writable: true, configurable: true },
116 scheduler: {
117 value: 'at global scope',
118 writable: true,
119 configurable: true,
120 },
121 });
122 strictEqual(globalThis.caches, 'hello there');
123 strictEqual(globalThis.crypto, 'we are');
124 strictEqual(globalThis.self, 'mutable');
125 strictEqual(globalThis.scheduler, 'at global scope');
126 globalThis.caches = oldCaches;
127 globalThis.crypto = oldCrypto;
128 globalThis.self = oldSelf;
129 globalThis.scheduler = oldScheduler;
130 }
131 },
132};
133 
134export const queueMicrotask = {
135 async test() {
136 [1, undefined, 'hello'].forEach((i) => {
137 throws(() => globalThis.queueMicrotask(i), {
138 message:
139 "Failed to execute 'queueMicrotask' on 'ServiceWorkerGlobalScope': " +
140 "parameter 1 is not of type 'function'.",
141 });
142 });
143 let resolve;
144 const promise = new Promise((a) => (resolve = a));
145 globalThis.queueMicrotask(resolve);
146 await promise;
147 },
148};
149 
150export const unhandledRejectionHandler = {
151 async test() {
152 let resolve;
153 const promise = new Promise((a) => (resolve = a));
154 addEventListener(
155 'unhandledrejection',
156 (event) => {
157 resolve();
158 },
159 { once: true }
160 );
161 // This should trigger the unhandledrejection handler
162 Promise.reject('boom');
163 await promise;
164 },
165};
166 
167export const unhandledRejectionHandler2 = {
168 async test() {
169 const handler = (event) => {
170 throw new Error('should not have fired');
171 };
172 addEventListener('unhandledrejection', handler);
173 try {
174 await Promise.reject('boom');
175 } catch {
176 // intentionally empty
177 }
178 await Promise.reject('boom').catch(() => {});
179 removeEventListener('unhandledrejection', handler);
180 },
181};
182 
183export const unhandledRejectionHandler3 = {
184 async test() {
185 let resolve, resolve2;
186 const promise = new Promise((a) => (resolve = a));
187 const promise2 = new Promise((a) => (resolve2 = a));
188 addEventListener('unhandledrejection', (event) => {
189 event.promise.catch(() => {});
190 resolve();
191 });
192 addEventListener('rejectionhandled', (event) => {
193 resolve2();
194 });
195 Promise.reject('boom');
196 await Promise.all([promise, promise2]);
197 },
198};
199 
200export const unhandledRejectionHandler4 = {
201 async test() {
202 addEventListener(
203 'unhandledrejection',
204 (event) => {
205 throw new Error('does not crash. safe to ignore in test logs');
206 },
207 { once: true }
208 );
209 
210 Promise.reject('boom');
211 },
212};
213 
214export const structuredClone = {
215 test() {
216 {
217 strictEqual(globalThis.structuredClone('hello'), 'hello');
218 }
219 
220 {
221 const a = {
222 b: {
223 c: [
224 {
225 d: 1,
226 },
227 ],
228 },
229 };
230 const clone = globalThis.structuredClone(a);
231 a.b.c[0].d = 2;
232 deepStrictEqual(clone.b.c[0].d, 1);
233 }
234 
235 {
236 const enc = new TextEncoder();
237 const dec = new TextDecoder();
238 const u8 = enc.encode('hello');
239 const clone = globalThis.structuredClone(u8);
240 strictEqual(dec.decode(clone), 'hello');
241 }
242 
243 {
244 const enc = new TextEncoder();
245 const dec = new TextDecoder();
246 const u8 = enc.encode('hello');
247 const clone = globalThis.structuredClone({ a: u8 });
248 u8[0] = 1;
249 strictEqual(dec.decode(u8), '\u0001ello');
250 strictEqual(dec.decode(clone.a), 'hello');
251 }
252 
253 {
254 const enc = new TextEncoder();
255 const dec = new TextDecoder();
256 const u8 = enc.encode('hello');
257 const clone = globalThis.structuredClone(
258 { a: u8 },
259 { transfer: [u8.buffer] }
260 );
261 strictEqual(u8.byteLength, 0);
262 strictEqual(dec.decode(clone.a), 'hello');
263 }
264 
265 {
266 const u8 = new Uint8Array(new SharedArrayBuffer(1));
267 const clone = globalThis.structuredClone(u8);
268 notStrictEqual(clone.buffer, u8.buffer);
269 u8[0] = 1;
270 strictEqual(clone[0], 1);
271 }
272 
273 {
274 const sab = new SharedArrayBuffer(2);
275 const a = {
276 b: new Uint8Array(sab),
277 c: new Uint16Array(sab),
278 };
279 const clone = globalThis.structuredClone(a);
280 ok(clone.b instanceof Uint8Array);
281 ok(clone.c instanceof Uint16Array);
282 
283 clone.c[0] = 0xffff;
284 
285 strictEqual(a.b[0], 255);
286 strictEqual(a.b[1], 255);
287 }
288 
289 {
290 const u8 = new Uint8Array(1);
291 globalThis.structuredClone(u8, { transfer: [u8.buffer, u8.buffer] });
292 }
293 
294 // Non-transferable objects throw
295 throws(() => globalThis.structuredClone({}, { transfer: {} }));
296 
297 const memory = new WebAssembly.Memory({ initial: 2, maximum: 2 });
298 throws(() => globalThis.structuredClone(memory));
299 
300 // This tests serialization of an API object. Only serializeable API objects (like `Headers`)
301 // can be cloned.
302 {
303 let orig = new Headers({ foo: 123, bar: 'abc' });
304 let cloned = globalThis.structuredClone(orig);
305 ok(cloned instanceof Headers);
306 strictEqual(cloned.get('foo'), '123');
307 strictEqual(cloned.get('bar'), 'abc');
308 }
309 
310 // Verify that trying to serialize a non-serializable API type throws.
311 throws(() => globalThis.structuredClone(new TextEncoder()), {
312 name: 'DataCloneError',
313 code: DOMException.DATA_CLONE_ERR,
314 message:
315 'Could not serialize object of type "TextEncoder". This type does not support ' +
316 'serialization.',
317 });
318 
319 // Test serialization of DOMException. This is technically an API object.
320 {
321 const de1 = new DOMException('hello', 'NotAllowedError');
322 
323 const de2 = globalThis.structuredClone(de1);
324 ok(de2 instanceof DOMException);
325 strictEqual(de1.name, de2.name);
326 strictEqual(de1.message, de2.message);
327 strictEqual(de1.stack, de2.stack);
328 strictEqual(de1.code, de2.code);
329 notStrictEqual(de1, de2);
330 }
331 },
332};
333 
334export const SabStructuredCloneTransfer = {
335 async test() {
336 // Can't structureClone a a SharedArrayBuffer with transfer.
337 const sab = new SharedArrayBuffer(64);
338 const view = new Uint8Array(sab);
339 
340 throws(() => globalThis.structuredClone(view, { transfer: [view] }), {
341 name: 'DataCloneError',
342 });
343 },
344};
345 
346export const base64 = {
347 test() {
348 function format_value(elem) {
349 return elem;
350 }
351 // Cloudflare note: The real format_value() is in testharness.js.
352 
353 function generate_tests(f, cases) {
354 // Cloudflare note: the real generate_tests() is in the real testharness.js, and presumably it
355 // calls test(t[0], () => { f(t[1]) }), or something, for each `t` in `cases`. We can forgo the
356 // description element (t[0]) for our purposes.
357 cases.forEach(([description, testCase]) => {
358 f(testCase);
359 });
360 }
361 
362 /**
363 * btoa() as defined by the HTML5 spec, which mostly just references RFC4648.
364 */
365 function mybtoa(s) {
366 // String conversion as required by WebIDL.
367 s = String(s);
368 
369 // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the
370 // method's first argument contains any character whose code point is
371 // greater than U+00FF."
372 for (var i = 0; i < s.length; i++) {
373 if (s.charCodeAt(i) > 255) {
374 return 'INVALID_CHARACTER_ERR';
375 }
376 }
377 
378 var out = '';
379 for (let i = 0; i < s.length; i += 3) {
380 var groupsOfSix = [undefined, undefined, undefined, undefined];
381 groupsOfSix[0] = s.charCodeAt(i) >> 2;
382 groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;
383 if (s.length > i + 1) {
384 groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;
385 groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;
386 }
387 if (s.length > i + 2) {
388 groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;
389 groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;
390 }
391 for (var j = 0; j < groupsOfSix.length; j++) {
392 if (typeof groupsOfSix[j] == 'undefined') {
393 out += '=';
394 } else {
395 out += btoaLookup(groupsOfSix[j]);
396 }
397 }
398 }
399 return out;
400 }
401 
402 /**
403 * Lookup table for mybtoa(), which converts a six-bit number into the
404 * corresponding ASCII character.
405 */
406 function btoaLookup(idx) {
407 if (idx < 26) {
408 return String.fromCharCode(idx + 'A'.charCodeAt(0));
409 }
410 if (idx < 52) {
411 return String.fromCharCode(idx - 26 + 'a'.charCodeAt(0));
412 }
413 if (idx < 62) {
414 return String.fromCharCode(idx - 52 + '0'.charCodeAt(0));
415 }
416 if (idx == 62) {
417 return '+';
418 }
419 if (idx == 63) {
420 return '/';
421 }
422 // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
423 }
424 
425 /**
426 * Implementation of atob() according to the HTML spec, except that instead of
427 * throwing INVALID_CHARACTER_ERR we return null.
428 */
429 function myatob(input) {
430 // WebIDL requires DOMStrings to just be converted using ECMAScript
431 // ToString, which in our case amounts to calling String().
432 input = String(input);
433 
434 // "Remove all space characters from input."
435 input = input.replace(/[ \t\n\f\r]/g, '');
436 
437 // "If the length of input divides by 4 leaving no remainder, then: if
438 // input ends with one or two U+003D EQUALS SIGN (=) characters, remove
439 // them from input."
440 if (input.length % 4 == 0 && /==?$/.test(input)) {
441 input = input.replace(/==?$/, '');
442 }
443 
444 // "If the length of input divides by 4 leaving a remainder of 1, throw an
445 // INVALID_CHARACTER_ERR exception and abort these steps."
446 //
447 // "If input contains a character that is not in the following list of
448 // characters and character ranges, throw an INVALID_CHARACTER_ERR
449 // exception and abort these steps:
450 //
451 // U+002B PLUS SIGN (+)
452 // U+002F SOLIDUS (/)
453 // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)
454 // U+0041 LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z
455 // U+0061 LATIN SMALL LETTER A to U+007A LATIN SMALL LETTER Z"
456 if (input.length % 4 == 1 || !/^[+/0-9A-Za-z]*$/.test(input)) {
457 return null;
458 }
459 
460 // "Let output be a string, initially empty."
461 var output = '';
462 
463 // "Let buffer be a buffer that can have bits appended to it, initially
464 // empty."
465 //
466 // We append bits via left-shift and or. accumulatedBits is used to track
467 // when we've gotten to 24 bits.
468 var buffer = 0;
469 var accumulatedBits = 0;
470 
471 // "While position does not point past the end of input, run these
472 // substeps:"
473 for (var i = 0; i < input.length; i++) {
474 // "Find the character pointed to by position in the first column of
475 // the following table. Let n be the number given in the second cell of
476 // the same row."
477 //
478 // "Append to buffer the six bits corresponding to number, most
479 // significant bit first."
480 //
481 // atobLookup() implements the table from the spec.
482 buffer <<= 6;
483 buffer |= atobLookup(input[i]);
484 
485 // "If buffer has accumulated 24 bits, interpret them as three 8-bit
486 // big-endian numbers. Append the three characters with code points
487 // equal to those numbers to output, in the same order, and then empty
488 // buffer."
489 accumulatedBits += 6;
490 if (accumulatedBits == 24) {
491 output += String.fromCharCode((buffer & 0xff0000) >> 16);
492 output += String.fromCharCode((buffer & 0xff00) >> 8);
493 output += String.fromCharCode(buffer & 0xff);
494 buffer = accumulatedBits = 0;
495 }
496 
497 // "Advance position by one character."
498 }
499 
500 // "If buffer is not empty, it contains either 12 or 18 bits. If it
501 // contains 12 bits, discard the last four and interpret the remaining
502 // eight as an 8-bit big-endian number. If it contains 18 bits, discard the
503 // last two and interpret the remaining 16 as two 8-bit big-endian numbers.
504 // Append the one or two characters with code points equal to those one or
505 // two numbers to output, in the same order."
506 if (accumulatedBits == 12) {
507 buffer >>= 4;
508 output += String.fromCharCode(buffer);
509 } else if (accumulatedBits == 18) {
510 buffer >>= 2;
511 output += String.fromCharCode((buffer & 0xff00) >> 8);
512 output += String.fromCharCode(buffer & 0xff);
513 }
514 
515 // "Return output."
516 return output;
517 }
518 
519 /**
520 * A lookup table for atob(), which converts an ASCII character to the
521 * corresponding six-bit number.
522 */
523 function atobLookup(chr) {
524 if (/[A-Z]/.test(chr)) {
525 return chr.charCodeAt(0) - 'A'.charCodeAt(0);
526 }
527 if (/[a-z]/.test(chr)) {
528 return chr.charCodeAt(0) - 'a'.charCodeAt(0) + 26;
529 }
530 if (/[0-9]/.test(chr)) {
531 return chr.charCodeAt(0) - '0'.charCodeAt(0) + 52;
532 }
533 if (chr == '+') {
534 return 62;
535 }
536 if (chr == '/') {
537 return 63;
538 }
539 // Throw exception; should not be hit in tests
540 }
541 
542 function testBtoa(input) {
543 // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the
544 // method's first argument contains any character whose code point is
545 // greater than U+00FF."
546 var normalizedInput = String(input);
547 for (var i = 0; i < normalizedInput.length; i++) {
548 if (normalizedInput.charCodeAt(i) > 255) {
549 throws(() => globalThis.btoa(input));
550 return;
551 }
552 }
553 strictEqual(globalThis.btoa(input), mybtoa(input));
554 strictEqual(globalThis.atob(globalThis.btoa(input)), String(input));
555 }
556 
557 let tests = [
558 'עברית',
559 '',
560 'ab',
561 'abc',
562 'abcd',
563 'abcde',
564 // This one is thrown in because IE9 seems to fail atob(btoa()) on it. Or
565 // possibly to fail btoa(). I actually can't tell what's happening here,
566 // but it doesn't hurt.
567 '\xff\xff\xc0',
568 // Is your DOM implementation binary-safe?
569 '\0a',
570 'a\0b',
571 // WebIDL tests.
572 undefined,
573 null,
574 7,
575 12,
576 1.5,
577 true,
578 false,
579 NaN,
580 +Infinity,
581 -Infinity,
582 0,
583 -0,
584 {
585 toString: function () {
586 return 'foo';
587 },
588 },
589 ];
590 for (var i = 0; i < 258; i++) {
591 tests.push(String.fromCharCode(i));
592 }
593 tests.push(String.fromCharCode(10000));
594 tests.push(String.fromCharCode(65534));
595 tests.push(String.fromCharCode(65535));
596 
597 // This is supposed to be U+10000.
598 tests.push(String.fromCharCode(0xd800, 0xdc00));
599 tests = tests.map(function (elem) {
600 var expected = mybtoa(elem);
601 if (expected === 'INVALID_CHARACTER_ERR') {
602 return [
603 'btoa(' + format_value(elem) + ') must raise INVALID_CHARACTER_ERR',
604 elem,
605 ];
606 }
607 return [
608 'btoa(' + format_value(elem) + ') == ' + format_value(mybtoa(elem)),
609 elem,
610 ];
611 });
612 
613 var everything = '';
614 for (let i = 0; i < 256; i++) {
615 everything += String.fromCharCode(i);
616 }
617 tests.push(['btoa(first 256 code points concatenated)', everything]);
618 
619 generate_tests(testBtoa, tests);
620 
621 function testAtob(input) {
622 var expected = myatob(input);
623 if (expected === null) {
624 throws(() => atob(input));
625 return;
626 }
627 
628 strictEqual(globalThis.atob(input), expected);
629 }
630 
631 tests = [
632 '',
633 'abcd',
634 ' abcd',
635 'abcd ',
636 ' abcd===',
637 'abcd=== ',
638 'abcd ===',
639 'a',
640 'ab',
641 'abc',
642 'abcde',
643 String.fromCharCode(0xd800, 0xdc00),
644 '=',
645 '==',
646 '===',
647 '====',
648 '=====',
649 'a=',
650 'a==',
651 'a===',
652 'a====',
653 'a=====',
654 'ab=',
655 'ab==',
656 'ab===',
657 'ab====',
658 'ab=====',
659 'abc=',
660 'abc==',
661 'abc===',
662 'abc====',
663 'abc=====',
664 'abcd=',
665 'abcd==',
666 'abcd===',
667 'abcd====',
668 'abcd=====',
669 'abcde=',
670 'abcde==',
671 'abcde===',
672 'abcde====',
673 'abcde=====',
674 '=a',
675 '=a=',
676 'a=b',
677 'a=b=',
678 'ab=c',
679 'ab=c=',
680 'abc=d',
681 'abc=d=',
682 // With whitespace
683 'ab\tcd',
684 'ab\ncd',
685 'ab\fcd',
686 'ab\rcd',
687 'ab cd',
688 'ab\u00a0cd',
689 'ab\t\n\f\r cd',
690 ' \t\n\f\r ab\t\n\f\r cd\t\n\f\r ',
691 'ab\t\n\f\r =\t\n\f\r =\t\n\f\r ',
692 // Test if any bits are set at the end. These should all be fine, since
693 // they end with A, which becomes 0:
694 'A',
695 '/A',
696 '//A',
697 '///A',
698 '////A',
699 // These are all bad, since they end in / (= 63, all bits set) but their
700 // length isn't a multiple of four characters, so they can't be output by
701 // btoa(). Thus one might expect some UAs to throw exceptions or otherwise
702 // object, since they could never be output by btoa(), so they're good to
703 // test.
704 '/',
705 'A/',
706 'AA/',
707 'AAAA/',
708 // But this one is possible:
709 'AAA/',
710 // Binary-safety tests
711 '\0nonsense',
712 'abcd\0nonsense',
713 // WebIDL tests
714 // TODO(conform): We need automatic stringification of arguments before these will work.
715 //undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0,
716 //{ toString: function () { return "foo" } },
717 //{ toString: function () { return "abcd" } },
718 ];
719 tests = tests.map(function (elem) {
720 if (myatob(elem) === null) {
721 return [
722 'atob(' + format_value(elem) + ') must raise InvalidCharacterError',
723 elem,
724 ];
725 }
726 return [
727 'atob(' + format_value(elem) + ') == ' + format_value(myatob(elem)),
728 elem,
729 ];
730 });
731 
732 generate_tests(testAtob, tests);
733 },
734};
735 
736export const webSocketPairIterable = {
737 test() {
738 const [a, b] = new WebSocketPair();
739 ok(a instanceof WebSocket);
740 ok(b instanceof WebSocket);
741 },
742};
743 
744export const queueMicrotaskError = {
745 async test() {
746 const als = new AsyncLocalStorage();
747 const { promise, resolve } = Promise.withResolvers();
748 const err = new Error('boom');
749 err.resolve = resolve;
750 
751 addEventListener(
752 'error',
753 (event) => {
754 // Verify that async context propagates correctly
755 // in the error event.
756 strictEqual(als.getStore(), 123);
757 // We got the correct expected error.
758 strictEqual(event.error, err);
759 event.error.resolve();
760 // Returning true suppresses the default logging.
761 return true;
762 },
763 { once: true }
764 );
765 
766 als.run(123, () =>
767 globalThis.queueMicrotask(() => {
768 // Throwing inside a queueMicrotask should trigger the error event.
769 strictEqual(als.getStore(), 123);
770 throw err;
771 })
772 );
773 
774 await promise;
775 },
776};
777 
778export const toStringTag = {
779 test() {
780 strictEqual('Response', Response.prototype[Symbol.toStringTag]);
781 strictEqual('Request', Request.prototype[Symbol.toStringTag]);
782 strictEqual('Headers', Headers.prototype[Symbol.toStringTag]);
783 strictEqual(
784 'URLSearchParams',
785 URLSearchParams.prototype[Symbol.toStringTag]
786 );
787 strictEqual('CryptoKey', CryptoKey.prototype[Symbol.toStringTag]);
788 strictEqual(
789 toString.call(new URLSearchParams()),
790 '[object URLSearchParams]'
791 );
792 
793 const internalFlag = Symbol.for('cloudflare:internal-class');
794 strictEqual(Headers.prototype[internalFlag], internalFlag);
795 strictEqual(new Headers()[internalFlag], internalFlag);
796 },
797};
798export const validateGlobalThis = {
799 test() {
800 util.inspect(globalThis);
801 },
802};
803 
804export const webSocketUrlValidation = {
805 async test() {
806 // Username and password should have been set in Authorization header
807 // but we silently ignore it to match the fetch() implementation.
808 doesNotThrow(() => new WebSocket('ws://username@domain.com'));
809 // Empty values should be rejected.
810 throws(() => new WebSocket(''), {
811 name: 'SyntaxError',
812 message: /invalid/,
813 });
814 },
815};
816 
817export const reuseCtx = {
818 async test(controller, env, ctx) {
819 ctx.reused = true;
820 await ctx.exports.reuseCtx.check(null);
821 await ctx.exports.reuseCtx.check(null);
822 },
823 
824 check(_, env, ctx) {
825 strictEqual(ctx.reused, undefined);
826 ctx.reused = true;
827 },
828};
829 
830export const structuredCloneError = {
831 test() {
832 // If it doesn't crash, we're good.
833 var globalProp = {
834 get p1() {
835 return this;
836 },
837 get trigger() {
838 function createSab() {
839 var sab = new SharedArrayBuffer(4096);
840 return sab;
841 }
842 var prop = {};
843 Object.defineProperty(prop, 'constructor', {
844 writable: true,
845 value: createSab,
846 });
847 return prop.constructor();
848 },
849 get p2() {
850 var gTCtor = globalThis.Intl.constructor;
851 var returnVal;
852 try {
853 returnVal = gTCtor.entries(this);
854 } catch (_e) {
855 // expected
856 }
857 return returnVal;
858 },
859 };
860 globalThis.structuredClone(globalProp);
861 },
862};