happy-dom 19.0.1 → 20.0.0

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.

Potentially problematic release.


This version of happy-dom might be problematic. Click here for more details.

Files changed (82) hide show
  1. package/lib/PropertySymbol.d.ts +1 -0
  2. package/lib/PropertySymbol.d.ts.map +1 -1
  3. package/lib/PropertySymbol.js +1 -0
  4. package/lib/PropertySymbol.js.map +1 -1
  5. package/lib/browser/DefaultBrowserSettings.d.ts.map +1 -1
  6. package/lib/browser/DefaultBrowserSettings.js +2 -0
  7. package/lib/browser/DefaultBrowserSettings.js.map +1 -1
  8. package/lib/browser/types/IBrowserSettings.d.ts +16 -1
  9. package/lib/browser/types/IBrowserSettings.d.ts.map +1 -1
  10. package/lib/browser/types/IOptionalBrowserSettings.d.ts +16 -1
  11. package/lib/browser/types/IOptionalBrowserSettings.d.ts.map +1 -1
  12. package/lib/browser/utilities/BrowserFrameNavigator.js +3 -3
  13. package/lib/browser/utilities/BrowserFrameNavigator.js.map +1 -1
  14. package/lib/html-parser/HTMLParser.d.ts.map +1 -1
  15. package/lib/html-parser/HTMLParser.js +3 -2
  16. package/lib/html-parser/HTMLParser.js.map +1 -1
  17. package/lib/module/ECMAScriptModuleCompiler.d.ts.map +1 -1
  18. package/lib/module/ECMAScriptModuleCompiler.js +6 -2
  19. package/lib/module/ECMAScriptModuleCompiler.js.map +1 -1
  20. package/lib/nodes/document/Document.d.ts +2 -2
  21. package/lib/nodes/document/Document.js +1 -1
  22. package/lib/nodes/element/ElementEventAttributeUtility.d.ts.map +1 -1
  23. package/lib/nodes/element/ElementEventAttributeUtility.js +5 -4
  24. package/lib/nodes/element/ElementEventAttributeUtility.js.map +1 -1
  25. package/lib/nodes/html-link-element/HTMLLinkElement.d.ts +1 -1
  26. package/lib/nodes/html-link-element/HTMLLinkElement.d.ts.map +1 -1
  27. package/lib/nodes/html-link-element/HTMLLinkElement.js +4 -4
  28. package/lib/nodes/html-link-element/HTMLLinkElement.js.map +1 -1
  29. package/lib/nodes/html-media-element/TextTrackList.d.ts +1 -0
  30. package/lib/nodes/html-media-element/TextTrackList.d.ts.map +1 -1
  31. package/lib/nodes/html-media-element/TextTrackList.js.map +1 -1
  32. package/lib/nodes/html-script-element/HTMLScriptElement.d.ts +1 -1
  33. package/lib/nodes/html-script-element/HTMLScriptElement.d.ts.map +1 -1
  34. package/lib/nodes/html-script-element/HTMLScriptElement.js +47 -37
  35. package/lib/nodes/html-script-element/HTMLScriptElement.js.map +1 -1
  36. package/lib/nodes/html-select-element/HTMLSelectElement.d.ts +1 -0
  37. package/lib/nodes/html-select-element/HTMLSelectElement.d.ts.map +1 -1
  38. package/lib/nodes/html-select-element/HTMLSelectElement.js.map +1 -1
  39. package/lib/query-selector/QuerySelector.d.ts.map +1 -1
  40. package/lib/query-selector/QuerySelector.js +13 -3
  41. package/lib/query-selector/QuerySelector.js.map +1 -1
  42. package/lib/query-selector/SelectorItem.d.ts +3 -3
  43. package/lib/query-selector/SelectorItem.d.ts.map +1 -1
  44. package/lib/query-selector/SelectorItem.js +4 -2
  45. package/lib/query-selector/SelectorItem.js.map +1 -1
  46. package/lib/query-selector/SelectorParser.d.ts +6 -7
  47. package/lib/query-selector/SelectorParser.d.ts.map +1 -1
  48. package/lib/query-selector/SelectorParser.js +4 -4
  49. package/lib/query-selector/SelectorParser.js.map +1 -1
  50. package/lib/version.js +1 -1
  51. package/lib/window/BrowserWindow.d.ts +11 -0
  52. package/lib/window/BrowserWindow.d.ts.map +1 -1
  53. package/lib/window/BrowserWindow.js +31 -0
  54. package/lib/window/BrowserWindow.js.map +1 -1
  55. package/lib/window/GlobalWindow.d.ts +7 -1
  56. package/lib/window/GlobalWindow.d.ts.map +1 -1
  57. package/lib/window/GlobalWindow.js +10 -1
  58. package/lib/window/GlobalWindow.js.map +1 -1
  59. package/lib/xml-parser/XMLParser.d.ts +0 -3
  60. package/lib/xml-parser/XMLParser.d.ts.map +1 -1
  61. package/lib/xml-parser/XMLParser.js +0 -3
  62. package/lib/xml-parser/XMLParser.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/PropertySymbol.ts +1 -0
  65. package/src/browser/DefaultBrowserSettings.ts +2 -0
  66. package/src/browser/types/IBrowserSettings.ts +18 -1
  67. package/src/browser/types/IOptionalBrowserSettings.ts +18 -1
  68. package/src/browser/utilities/BrowserFrameNavigator.ts +3 -3
  69. package/src/html-parser/HTMLParser.ts +3 -2
  70. package/src/module/ECMAScriptModuleCompiler.ts +6 -2
  71. package/src/nodes/document/Document.ts +3 -3
  72. package/src/nodes/element/ElementEventAttributeUtility.ts +5 -4
  73. package/src/nodes/html-link-element/HTMLLinkElement.ts +4 -4
  74. package/src/nodes/html-media-element/TextTrackList.ts +3 -0
  75. package/src/nodes/html-script-element/HTMLScriptElement.ts +51 -42
  76. package/src/nodes/html-select-element/HTMLSelectElement.ts +3 -0
  77. package/src/query-selector/QuerySelector.ts +16 -3
  78. package/src/query-selector/SelectorItem.ts +6 -5
  79. package/src/query-selector/SelectorParser.ts +16 -8
  80. package/src/window/BrowserWindow.ts +38 -0
  81. package/src/window/GlobalWindow.ts +14 -1
  82. package/src/xml-parser/XMLParser.ts +0 -3
@@ -5,6 +5,7 @@ import IBrowserSettings from './types/IBrowserSettings.js';
5
5
 
6
6
  export default <IBrowserSettings>{
7
7
  disableJavaScriptEvaluation: false,
8
+ enableJavaScriptEvaluation: false,
8
9
  disableJavaScriptFileLoading: false,
9
10
  disableCSSFileLoading: false,
10
11
  disableIframePageLoading: false,
@@ -13,6 +14,7 @@ export default <IBrowserSettings>{
13
14
  disableErrorCapturing: false,
14
15
  errorCapture: BrowserErrorCaptureEnum.tryAndCatch,
15
16
  enableFileSystemHttpRequests: false,
17
+ suppressCodeGenerationFromStringsWarning: false,
16
18
  timer: {
17
19
  maxTimeout: -1,
18
20
  maxIntervalTime: -1,
@@ -11,9 +11,23 @@ import BrowserWindow from '../../window/BrowserWindow.js';
11
11
  * Browser settings.
12
12
  */
13
13
  export default interface IBrowserSettings {
14
- /** Disables JavaScript evaluation. */
14
+ /**
15
+ * Disables JavaScript evaluation.
16
+ *
17
+ * @deprecated Javascript evaluation is now disabled by default. Use "enableJavaScriptEvaluation" if you want to enable it.
18
+ */
15
19
  disableJavaScriptEvaluation: boolean;
16
20
 
21
+ /**
22
+ * Enables JavaScript evaluation.
23
+ *
24
+ * A VM Context is not an isolated environment, and if you run untrusted code you are at risk of RCE (Remote Code Execution) attacks.
25
+ * It is recommended to disable code generation at process level by running node with the "--disallow-code-generation-from-strings" flag enabled to protect against these types of attacks.
26
+ *
27
+ * @see https://github.com/capricorn86/happy-dom/wiki/Code-Generation-From-Strings-Warning
28
+ */
29
+ enableJavaScriptEvaluation: boolean;
30
+
17
31
  /** Disables JavaScript file loading. */
18
32
  disableJavaScriptFileLoading: boolean;
19
33
 
@@ -26,6 +40,9 @@ export default interface IBrowserSettings {
26
40
  /** Handle disabled resource loading as success */
27
41
  handleDisabledFileLoadingAsSuccess: boolean;
28
42
 
43
+ /** Suppresses the warning that is printed when code generation from strings is enabled at process level. */
44
+ suppressCodeGenerationFromStringsWarning: boolean;
45
+
29
46
  /**
30
47
  * Settings for timers
31
48
  */
@@ -8,9 +8,23 @@ import IOptionalTimerLoopsLimit from '../../window/IOptionalTimerLoopsLimit.js';
8
8
  import BrowserWindow from '../../window/BrowserWindow.js';
9
9
 
10
10
  export default interface IOptionalBrowserSettings {
11
- /** Disables JavaScript evaluation. */
11
+ /**
12
+ * Disables JavaScript evaluation.
13
+ *
14
+ * @deprecated Javascript evaluation is now disabled by default. Use "enableJavaScriptEvaluation" if you want to enable it.
15
+ */
12
16
  disableJavaScriptEvaluation?: boolean;
13
17
 
18
+ /**
19
+ * Enables JavaScript evaluation.
20
+ *
21
+ * A VM Context is not an isolated environment, and if you run untrusted code you are at risk of RCE (Remote Code Execution) attacks.
22
+ * It is recommended to disable code generation at process level by running node with the "--disallow-code-generation-from-strings" flag enabled to protect against these types of attacks.
23
+ *
24
+ * @see https://github.com/capricorn86/happy-dom/wiki/Code-Generation-From-Strings-Warning
25
+ */
26
+ enableJavaScriptEvaluation?: boolean;
27
+
14
28
  /** Disables JavaScript file loading. */
15
29
  disableJavaScriptFileLoading?: boolean;
16
30
 
@@ -23,6 +37,9 @@ export default interface IOptionalBrowserSettings {
23
37
  /** Handle disabled file loading as success */
24
38
  handleDisabledFileLoadingAsSuccess?: boolean;
25
39
 
40
+ /** Suppresses the warning that is printed when code generation from strings is enabled at process level. */
41
+ suppressCodeGenerationFromStringsWarning?: boolean;
42
+
26
43
  /** Settings for timers */
27
44
  timer?: {
28
45
  maxTimeout?: number;
@@ -89,12 +89,12 @@ export default class BrowserFrameNavigator {
89
89
 
90
90
  // Javascript protocol
91
91
  if (targetURL.protocol === 'javascript:') {
92
- if (frame && !frame.page.context.browser.settings.disableJavaScriptEvaluation) {
92
+ if (frame && frame.page.context.browser.settings.enableJavaScriptEvaluation) {
93
93
  const readyStateManager = frame.window[PropertySymbol.readyStateManager];
94
94
  const asyncTaskManager = frame[PropertySymbol.asyncTaskManager];
95
95
 
96
96
  const taskID = readyStateManager.startTask();
97
- const code = `${targetURL.href.replace('javascript:', '')}\n//# sourceURL=${frame.url}`;
97
+ const code = targetURL.href.replace('javascript:', '');
98
98
 
99
99
  // The browser will wait for the next tick before executing the script.
100
100
  // Fixes issue where evaluating the response can throw an error.
@@ -110,7 +110,7 @@ export default class BrowserFrameNavigator {
110
110
  clearImmediate(immediate);
111
111
  resolve(null);
112
112
  });
113
- frame.window.eval(code);
113
+ frame.window[PropertySymbol.evaluateScript](code, { filename: frame.url });
114
114
  });
115
115
  });
116
116
 
@@ -776,10 +776,11 @@ export default class HTMLParser {
776
776
  // However, they are allowed to be executed when document.write() is used.
777
777
  // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement
778
778
  if (upperTagName === 'SCRIPT') {
779
- (<HTMLScriptElement>this.currentNode)[PropertySymbol.evaluateScript] = this.evaluateScripts;
779
+ (<HTMLScriptElement>this.currentNode)[PropertySymbol.disableEvaluation] =
780
+ !this.evaluateScripts;
780
781
  } else if (upperTagName === 'LINK') {
781
782
  // An assumption that the same rule should be applied for the HTMLLinkElement is made here.
782
- (<HTMLLinkElement>this.currentNode)[PropertySymbol.evaluateCSS] = this.evaluateScripts;
783
+ (<HTMLLinkElement>this.currentNode)[PropertySymbol.disableEvaluation] = !this.evaluateScripts;
783
784
  }
784
785
 
785
786
  // Plain text elements such as <script> and <style> should only contain text.
@@ -401,10 +401,14 @@ export default class ECMAScriptModuleCompiler {
401
401
  }
402
402
 
403
403
  newCode += '})';
404
- newCode += `\n//# sourceURL=${sourceURL || moduleURL}`;
405
404
 
406
405
  try {
407
- return { imports, execute: this.window.eval(newCode) };
406
+ return {
407
+ imports,
408
+ execute: this.window[PropertySymbol.evaluateScript](newCode, {
409
+ filename: sourceURL || moduleURL
410
+ })
411
+ };
408
412
  } catch (e) {
409
413
  const errorMessage = this.getError(moduleURL, code, sourceURL) || (<Error>e).message;
410
414
  const error = new this.window.SyntaxError(
@@ -1472,7 +1472,7 @@ export default class Document extends Node {
1472
1472
  ): NodeList<IHTMLElementTagNameMap[K]>;
1473
1473
 
1474
1474
  /**
1475
- * Query CSS selector to find matching elments.
1475
+ * Query CSS selector to find matching elements.
1476
1476
  *
1477
1477
  * @param selector CSS selector.
1478
1478
  * @returns Matching elements.
@@ -1482,7 +1482,7 @@ export default class Document extends Node {
1482
1482
  ): NodeList<ISVGElementTagNameMap[K]>;
1483
1483
 
1484
1484
  /**
1485
- * Query CSS selector to find matching elments.
1485
+ * Query CSS selector to find matching elements.
1486
1486
  *
1487
1487
  * @param selector CSS selector.
1488
1488
  * @returns Matching elements.
@@ -1490,7 +1490,7 @@ export default class Document extends Node {
1490
1490
  public querySelectorAll(selector: string): NodeList<Element>;
1491
1491
 
1492
1492
  /**
1493
- * Query CSS selector to find matching elments.
1493
+ * Query CSS selector to find matching elements.
1494
1494
  *
1495
1495
  * @param selector CSS selector.
1496
1496
  * @returns Matching elements.
@@ -32,7 +32,7 @@ export default class ElementEventAttributeUtility {
32
32
 
33
33
  const browserSettings = new WindowBrowserContext(window).getSettings();
34
34
 
35
- if (!browserSettings) {
35
+ if (!browserSettings || !browserSettings.enableJavaScriptEvaluation) {
36
36
  return null;
37
37
  }
38
38
 
@@ -63,13 +63,14 @@ export default class ElementEventAttributeUtility {
63
63
  }
64
64
 
65
65
  newCode += '})';
66
- newCode += `\n//# sourceURL=${window.location.href}`;
67
66
 
68
67
  let listener: ((event: Event) => void) | null = null;
69
68
 
70
69
  try {
71
- listener = window.eval(newCode).bind(element, {
72
- dispatchError: window[PropertySymbol.dispatchError]
70
+ listener = window[PropertySymbol.evaluateScript](newCode, {
71
+ filename: window.location.href
72
+ }).bind(element, {
73
+ dispatchError: window[PropertySymbol.dispatchError].bind(window)
73
74
  });
74
75
  } catch (e) {
75
76
  const error = new window.SyntaxError(
@@ -24,7 +24,7 @@ import IResourceFetchResponse from '../../fetch/types/IResourceFetchResponse.js'
24
24
  export default class HTMLLinkElement extends HTMLElement {
25
25
  // Internal properties
26
26
  public [PropertySymbol.sheet]: CSSStyleSheet | null = null;
27
- public [PropertySymbol.evaluateCSS] = true;
27
+ public [PropertySymbol.disableEvaluation] = false;
28
28
  public [PropertySymbol.relList]: DOMTokenList | null = null;
29
29
  #loadedStyleSheetURL: string | null = null;
30
30
 
@@ -305,7 +305,7 @@ export default class HTMLLinkElement extends HTMLElement {
305
305
  !browserSettings ||
306
306
  !this[PropertySymbol.isConnected] ||
307
307
  browserSettings.disableJavaScriptFileLoading ||
308
- browserSettings.disableJavaScriptEvaluation
308
+ !browserSettings.enableJavaScriptEvaluation
309
309
  ) {
310
310
  return;
311
311
  }
@@ -351,7 +351,7 @@ export default class HTMLLinkElement extends HTMLElement {
351
351
 
352
352
  if (
353
353
  as === 'script' &&
354
- (browserSettings.disableJavaScriptFileLoading || browserSettings.disableJavaScriptEvaluation)
354
+ (browserSettings.disableJavaScriptFileLoading || !browserSettings.enableJavaScriptEvaluation)
355
355
  ) {
356
356
  return;
357
357
  }
@@ -422,7 +422,7 @@ export default class HTMLLinkElement extends HTMLElement {
422
422
 
423
423
  const browserSettings = browserFrame.page.context.browser.settings;
424
424
 
425
- if (!this[PropertySymbol.evaluateCSS] || !this[PropertySymbol.isConnected]) {
425
+ if (this[PropertySymbol.disableEvaluation] || !this[PropertySymbol.isConnected]) {
426
426
  return;
427
427
  }
428
428
 
@@ -9,6 +9,9 @@ import ClassMethodBinder from '../../utilities/ClassMethodBinder.js';
9
9
  * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrackList
10
10
  */
11
11
  export default class TextTrackList extends EventTarget {
12
+ // Index signature
13
+ [index: number]: TextTrack | undefined;
14
+
12
15
  // Internal properties
13
16
  public [PropertySymbol.items]: TextTrack[] = [];
14
17
 
@@ -25,7 +25,7 @@ export default class HTMLScriptElement extends HTMLElement {
25
25
  public declare cloneNode: (deep?: boolean) => HTMLScriptElement;
26
26
 
27
27
  // Internal properties
28
- public [PropertySymbol.evaluateScript] = true;
28
+ public [PropertySymbol.disableEvaluation] = false;
29
29
  public [PropertySymbol.blocking]: DOMTokenList | null = null;
30
30
 
31
31
  // Private properties
@@ -349,32 +349,34 @@ export default class HTMLScriptElement extends HTMLElement {
349
349
 
350
350
  super[PropertySymbol.connectedToDocument]();
351
351
 
352
- if (this[PropertySymbol.evaluateScript]) {
353
- const src = this.getAttribute('src');
352
+ if (this[PropertySymbol.disableEvaluation]) {
353
+ return;
354
+ }
354
355
 
355
- if (src !== null) {
356
- if (this.getAttribute('type') === 'module') {
357
- this.#loadModule(src);
358
- } else {
359
- this.#loadScript(src);
360
- }
361
- } else if (browserSettings && !browserSettings.disableJavaScriptEvaluation) {
362
- const source = this.textContent;
363
- const type = this.getAttribute('type');
364
-
365
- if (source) {
366
- if (type === 'module') {
367
- this.#evaluateModule(source);
368
- } else if (type === 'importmap') {
369
- this.#evaluateImportMap(source);
370
- } else if (
371
- type === null ||
372
- type === 'application/x-ecmascript' ||
373
- type === 'application/x-javascript' ||
374
- type.startsWith('text/javascript')
375
- ) {
376
- this.#evaluateScript(source);
377
- }
356
+ const src = this.getAttribute('src');
357
+
358
+ if (src !== null) {
359
+ if (this.getAttribute('type') === 'module') {
360
+ this.#loadModule(src);
361
+ } else {
362
+ this.#loadScript(src);
363
+ }
364
+ } else if (browserSettings && browserSettings.enableJavaScriptEvaluation) {
365
+ const source = this.textContent;
366
+ const type = this.getAttribute('type');
367
+
368
+ if (source) {
369
+ if (type === 'module') {
370
+ this.#evaluateModule(source);
371
+ } else if (type === 'importmap') {
372
+ this.#evaluateImportMap(source);
373
+ } else if (
374
+ type === null ||
375
+ type === 'application/x-ecmascript' ||
376
+ type === 'application/x-javascript' ||
377
+ type.startsWith('text/javascript')
378
+ ) {
379
+ this.#evaluateScript(source);
378
380
  }
379
381
  }
380
382
  }
@@ -413,7 +415,7 @@ export default class HTMLScriptElement extends HTMLElement {
413
415
  const browserSettings = new WindowBrowserContext(window).getSettings();
414
416
  const browserFrame = new WindowBrowserContext(window).getBrowserFrame();
415
417
 
416
- if (!browserFrame || !browserSettings) {
418
+ if (!browserFrame || !browserSettings || !browserSettings.enableJavaScriptEvaluation) {
417
419
  return;
418
420
  }
419
421
 
@@ -446,7 +448,12 @@ export default class HTMLScriptElement extends HTMLElement {
446
448
  const browserSettings = new WindowBrowserContext(window).getSettings();
447
449
  const browserFrame = new WindowBrowserContext(window).getBrowserFrame();
448
450
 
449
- if (!browserFrame || !browserSettings || window[PropertySymbol.moduleImportMap]) {
451
+ if (
452
+ !browserFrame ||
453
+ !browserSettings ||
454
+ window[PropertySymbol.moduleImportMap] ||
455
+ !browserSettings.enableJavaScriptEvaluation
456
+ ) {
450
457
  return;
451
458
  }
452
459
 
@@ -510,9 +517,9 @@ export default class HTMLScriptElement extends HTMLElement {
510
517
  /**
511
518
  * Evaluates a script.
512
519
  *
513
- * @param source Source.
520
+ * @param code Code.
514
521
  */
515
- #evaluateScript(source: string): void {
522
+ #evaluateScript(code: string): void {
516
523
  const window = this[PropertySymbol.window];
517
524
  const browserSettings = new WindowBrowserContext(window).getSettings();
518
525
 
@@ -522,16 +529,18 @@ export default class HTMLScriptElement extends HTMLElement {
522
529
 
523
530
  this[PropertySymbol.ownerDocument][PropertySymbol.currentScript] = this;
524
531
 
525
- const code = `${source}\n//# sourceURL=${this[PropertySymbol.ownerDocument].location.href}`;
526
-
527
532
  if (
528
533
  browserSettings.disableErrorCapturing ||
529
534
  browserSettings.errorCapture !== BrowserErrorCaptureEnum.tryAndCatch
530
535
  ) {
531
- window.eval(code);
536
+ window[PropertySymbol.evaluateScript](code, {
537
+ filename: this[PropertySymbol.ownerDocument].location.href
538
+ });
532
539
  } else {
533
540
  try {
534
- window.eval(code);
541
+ window[PropertySymbol.evaluateScript](code, {
542
+ filename: this[PropertySymbol.ownerDocument].location.href
543
+ });
535
544
  } catch (error) {
536
545
  window[PropertySymbol.dispatchError](<Error>error);
537
546
  }
@@ -560,7 +569,7 @@ export default class HTMLScriptElement extends HTMLElement {
560
569
 
561
570
  if (
562
571
  browserSettings &&
563
- (browserSettings.disableJavaScriptFileLoading || browserSettings.disableJavaScriptEvaluation)
572
+ (browserSettings.disableJavaScriptFileLoading || !browserSettings.enableJavaScriptEvaluation)
564
573
  ) {
565
574
  if (browserSettings.handleDisabledFileLoadingAsSuccess) {
566
575
  this.dispatchEvent(new Event('load'));
@@ -639,7 +648,7 @@ export default class HTMLScriptElement extends HTMLElement {
639
648
 
640
649
  if (
641
650
  browserSettings &&
642
- (browserSettings.disableJavaScriptFileLoading || browserSettings.disableJavaScriptEvaluation)
651
+ (browserSettings.disableJavaScriptFileLoading || !browserSettings.enableJavaScriptEvaluation)
643
652
  ) {
644
653
  if (browserSettings.handleDisabledFileLoadingAsSuccess) {
645
654
  this.dispatchEvent(new Event('load'));
@@ -693,18 +702,18 @@ export default class HTMLScriptElement extends HTMLElement {
693
702
 
694
703
  this[PropertySymbol.ownerDocument][PropertySymbol.currentScript] = this;
695
704
 
696
- const code = `${response.content}\n//# sourceURL=${
697
- response.virtualServerFile || absoluteURLString
698
- }`;
699
-
700
705
  if (
701
706
  browserSettings.disableErrorCapturing ||
702
707
  browserSettings.errorCapture !== BrowserErrorCaptureEnum.tryAndCatch
703
708
  ) {
704
- this[PropertySymbol.window].eval(code);
709
+ this[PropertySymbol.window][PropertySymbol.evaluateScript](response.content, {
710
+ filename: response.virtualServerFile || absoluteURLString
711
+ });
705
712
  } else {
706
713
  try {
707
- this[PropertySymbol.window].eval(code);
714
+ this[PropertySymbol.window][PropertySymbol.evaluateScript](response.content, {
715
+ filename: response.virtualServerFile || absoluteURLString
716
+ });
708
717
  } catch (error) {
709
718
  this[PropertySymbol.ownerDocument][PropertySymbol.currentScript] = null;
710
719
  window[PropertySymbol.dispatchError](<Error>error);
@@ -25,6 +25,9 @@ import BrowserWindow from '../../window/BrowserWindow.js';
25
25
  * https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement.
26
26
  */
27
27
  export default class HTMLSelectElement extends HTMLElement {
28
+ // Index signature
29
+ [index: number]: HTMLOptionElement | undefined;
30
+
28
31
  // Injected by WindowContextClassExtender
29
32
  protected declare [PropertySymbol.window]: BrowserWindow;
30
33
 
@@ -115,7 +115,11 @@ export default class QuerySelector {
115
115
  }
116
116
  }
117
117
 
118
- const groups = SelectorParser.getSelectorGroups(selector, { scope: node });
118
+ const scope =
119
+ node[PropertySymbol.nodeType] === NodeTypeEnum.documentNode
120
+ ? (<Document>node).documentElement
121
+ : node;
122
+ const groups = SelectorParser.getSelectorGroups(selector, { scope });
119
123
  const items: Element[] = [];
120
124
  const nodeList = new NodeList<Element>(PropertySymbol.illegalConstructor, items);
121
125
  const matchesMap: Map<string, Element> = new Map();
@@ -251,7 +255,11 @@ export default class QuerySelector {
251
255
 
252
256
  const matchesMap: Map<string, Element> = new Map();
253
257
  const matchedPositions: string[] = [];
254
- for (const items of SelectorParser.getSelectorGroups(selector, { scope: node })) {
258
+ const scope =
259
+ node[PropertySymbol.nodeType] === NodeTypeEnum.documentNode
260
+ ? (<Document>node).documentElement
261
+ : node;
262
+ for (const items of SelectorParser.getSelectorGroups(selector, { scope })) {
255
263
  const match =
256
264
  node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode
257
265
  ? this.findFirst(<Element>node, [<Element>node], items, cachedItem)
@@ -350,9 +358,14 @@ export default class QuerySelector {
350
358
  );
351
359
  }
352
360
 
361
+ const scopeOrElement = options?.scope || element;
362
+ const scope =
363
+ scopeOrElement[PropertySymbol.nodeType] === NodeTypeEnum.documentNode
364
+ ? (<Document>scopeOrElement).documentElement
365
+ : scopeOrElement;
353
366
  for (const items of SelectorParser.getSelectorGroups(selector, {
354
367
  ...options,
355
- scope: options?.scope || element
368
+ scope
356
369
  })) {
357
370
  const result = this.matchSelector(element, items.reverse(), cachedItem);
358
371
 
@@ -5,7 +5,6 @@ import SelectorCombinatorEnum from './SelectorCombinatorEnum.js';
5
5
  import ISelectorAttribute from './ISelectorAttribute.js';
6
6
  import ISelectorMatch from './ISelectorMatch.js';
7
7
  import ISelectorPseudo from './ISelectorPseudo.js';
8
- import Document from '../nodes/document/Document.js';
9
8
  import DocumentFragment from '../nodes/document-fragment/DocumentFragment.js';
10
9
 
11
10
  const SPACE_REGEXP = /\s+/;
@@ -14,7 +13,8 @@ const SPACE_REGEXP = /\s+/;
14
13
  * Selector item.
15
14
  */
16
15
  export default class SelectorItem {
17
- public scope: Element | Document | DocumentFragment | null;
16
+ public root: Element | DocumentFragment | null;
17
+ public scope: Element | DocumentFragment | null;
18
18
  public tagName: string | null;
19
19
  public id: string | null;
20
20
  public classNames: string[] | null;
@@ -39,7 +39,7 @@ export default class SelectorItem {
39
39
  * @param [options.ignoreErrors] Ignore errors.
40
40
  */
41
41
  constructor(options?: {
42
- scope?: Element | Document | DocumentFragment;
42
+ scope?: Element | DocumentFragment;
43
43
  tagName?: string;
44
44
  id?: string;
45
45
  classNames?: string[];
@@ -49,6 +49,7 @@ export default class SelectorItem {
49
49
  combinator?: SelectorCombinatorEnum;
50
50
  ignoreErrors?: boolean;
51
51
  }) {
52
+ this.root = options?.scope ? options.scope[PropertySymbol.ownerDocument].documentElement : null;
52
53
  this.scope = options?.scope || null;
53
54
  this.tagName = options?.tagName || null;
54
55
  this.id = options?.id || null;
@@ -251,7 +252,7 @@ export default class SelectorItem {
251
252
  ? { priorityWeight: 10 }
252
253
  : null;
253
254
  case 'root':
254
- return element[PropertySymbol.tagName] === 'HTML' ? { priorityWeight: 10 } : null;
255
+ return this.root && element === this.root ? { priorityWeight: 10 } : null;
255
256
  case 'not':
256
257
  for (const selectorItem of pseudo.selectorItems!) {
257
258
  if (selectorItem.match(element)) {
@@ -385,7 +386,7 @@ export default class SelectorItem {
385
386
  ? { priorityWeight: 10 }
386
387
  : null;
387
388
  case 'scope':
388
- return this.scope === element ? { priorityWeight: 10 } : null;
389
+ return this.scope && this.scope === element ? { priorityWeight: 10 } : null;
389
390
  default:
390
391
  return null;
391
392
  }
@@ -3,7 +3,6 @@ import SelectorCombinatorEnum from './SelectorCombinatorEnum.js';
3
3
  import DOMException from '../exception/DOMException.js';
4
4
  import ISelectorPseudo from './ISelectorPseudo.js';
5
5
  import Element from '../nodes/element/Element.js';
6
- import Document from '../nodes/document/Document.js';
7
6
  import DocumentFragment from '../nodes/document-fragment/DocumentFragment.js';
8
7
 
9
8
  /**
@@ -72,13 +71,16 @@ export default class SelectorParser {
72
71
  *
73
72
  * @param selector Selector.
74
73
  * @param options Options.
75
- * @param options.scope Scope.
74
+ * @param [options.scope] Scope.
76
75
  * @param [options.ignoreErrors] Ignores errors.
77
76
  * @returns Selector item.
78
77
  */
79
78
  public static getSelectorItem(
80
79
  selector: string,
81
- options?: { scope?: Element | Document | DocumentFragment; ignoreErrors?: boolean }
80
+ options?: {
81
+ scope?: Element | DocumentFragment;
82
+ ignoreErrors?: boolean;
83
+ }
82
84
  ): SelectorItem {
83
85
  return this.getSelectorGroups(selector, options)[0][0];
84
86
  }
@@ -88,13 +90,16 @@ export default class SelectorParser {
88
90
  *
89
91
  * @param selector Selector.
90
92
  * @param options Options.
91
- * @param options.scope Scope.
93
+ * @param [options.scope] Scope.
92
94
  * @param [options.ignoreErrors] Ignores errors.
93
95
  * @returns Selector groups.
94
96
  */
95
97
  public static getSelectorGroups(
96
98
  selector: string,
97
- options?: { scope?: Element | Document | DocumentFragment; ignoreErrors?: boolean }
99
+ options?: {
100
+ scope?: Element | DocumentFragment;
101
+ ignoreErrors?: boolean;
102
+ }
98
103
  ): Array<Array<SelectorItem>> {
99
104
  selector = selector.trim();
100
105
  const ignoreErrors = options?.ignoreErrors;
@@ -296,15 +301,18 @@ export default class SelectorParser {
296
301
  *
297
302
  * @param name Pseudo name.
298
303
  * @param args Pseudo arguments.
299
- * @param options Options.
300
- * @param options.scope Scope.
304
+ * @param [options] Options.
305
+ * @param [options.scope] Scope.
301
306
  * @param [options.ignoreErrors] Ignores errors.
302
307
  * @returns Pseudo.
303
308
  */
304
309
  private static getPseudo(
305
310
  name: string,
306
311
  args: string | null | undefined,
307
- options?: { scope?: Element | Document | DocumentFragment; ignoreErrors?: boolean }
312
+ options?: {
313
+ scope?: Element | DocumentFragment;
314
+ ignoreErrors?: boolean;
315
+ }
308
316
  ): ISelectorPseudo {
309
317
  const lowerName = name.toLowerCase();
310
318
 
@@ -331,6 +331,16 @@ const TIMER = {
331
331
 
332
332
  const IS_NODE_JS_TIMEOUT_ENVIRONMENT = setTimeout.toString().includes('new Timeout');
333
333
 
334
+ const IS_PROCESS_LEVEL_CODE_GENERATION_FROM_STRINGS_ALLOWED = (() => {
335
+ try {
336
+ // eslint-disable-next-line no-new-func
337
+ new Function('return true;')();
338
+ return true;
339
+ } catch {
340
+ return false;
341
+ }
342
+ })();
343
+
334
344
  /**
335
345
  * Class for PerformanceObserverEntryList as it is only available as an interface from Node.js.
336
346
  */
@@ -779,6 +789,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
779
789
  public declare encodeURI: typeof encodeURI;
780
790
  public declare encodeURIComponent: typeof encodeURIComponent;
781
791
  public declare eval: typeof eval;
792
+
782
793
  /**
783
794
  * @deprecated
784
795
  */
@@ -847,6 +858,21 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
847
858
  constructor(browserFrame: IBrowserFrame, options?: { url?: string }) {
848
859
  super();
849
860
 
861
+ if (
862
+ IS_PROCESS_LEVEL_CODE_GENERATION_FROM_STRINGS_ALLOWED &&
863
+ browserFrame.page.context.browser.settings.enableJavaScriptEvaluation &&
864
+ !browserFrame.page.context.browser.settings.suppressCodeGenerationFromStringsWarning
865
+ ) {
866
+ // eslint-disable-next-line no-console
867
+ console.warn(
868
+ '\nWarning! Happy DOM has JavaScript evaluation enabled and is running in an environment with code generation from strings (eval) enabled at process level.' +
869
+ '\n\nA VM Context is not an isolated environment, and if you run untrusted code you are at risk of RCE (Remote Code Execution) attacks. The attacker can use code generation to escape the VM and run code at process level.' +
870
+ '\n\nIt is recommended to disable code generation at process level by running node with the "--disallow-code-generation-from-strings" flag enabled when Javascript evaluation is enabled in Happy DOM.' +
871
+ ' You can suppress this warning by setting "suppressCodeGenerationFromStringsWarning" to "true" at your own risk.' +
872
+ '\n\nFor more information, see https://github.com/capricorn86/happy-dom/wiki/Code-Generation-From-Strings-Warning\n\n'
873
+ );
874
+ }
875
+
850
876
  this.#browserFrame = browserFrame;
851
877
 
852
878
  this.console = browserFrame.page.console;
@@ -1806,6 +1832,18 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
1806
1832
  this.dispatchEvent(new ErrorEvent('error', { message: error.message, error }));
1807
1833
  }
1808
1834
 
1835
+ /**
1836
+ * Evaluates code in a VM context.
1837
+ *
1838
+ * @param code Code.
1839
+ * @param [options] Options.
1840
+ * @param [options.filename] Filename.
1841
+ * @returns any.
1842
+ */
1843
+ public [PropertySymbol.evaluateScript](code: string, options?: { filename?: string }): any {
1844
+ return new VM.Script(code, options).runInContext(this);
1845
+ }
1846
+
1809
1847
  /**
1810
1848
  * Setup of VM context.
1811
1849
  */