happy-dom 2.43.0 → 2.45.1

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.

@@ -8,7 +8,7 @@ const CLASS_REGEXP = /\.([a-zA-Z0-9-_$]+)/g;
8
8
  const TAG_NAME_REGEXP = /^[a-zA-Z0-9-]+/;
9
9
 
10
10
  /**
11
- *
11
+ * Selector item.
12
12
  */
13
13
  export default class SelectorItem {
14
14
  public isAll: boolean;
@@ -29,8 +29,7 @@ export default class SelectorItem {
29
29
  constructor(selector: string) {
30
30
  this.isAll = selector === '*';
31
31
  this.isID = !this.isAll ? selector.startsWith('#') : false;
32
- this.isAttribute =
33
- !this.isAll && !this.isID && selector.includes('[') && !selector.includes(':not(');
32
+ this.isAttribute = !this.isAll && !this.isID && selector.includes('[');
34
33
  this.isPseudo = !this.isAll && !this.isID && selector.includes(':');
35
34
  this.isClass = !this.isAll && !this.isID && new RegExp(CLASS_REGEXP, 'g').test(selector);
36
35
  this.tagName = !this.isAll && !this.isID ? selector.match(TAG_NAME_REGEXP) : null;
@@ -48,7 +47,6 @@ export default class SelectorItem {
48
47
  */
49
48
  public match(element: Element): boolean {
50
49
  const selector = this.selector;
51
- let match;
52
50
 
53
51
  // Is all (*)
54
52
  if (this.isAll) {
@@ -68,29 +66,13 @@ export default class SelectorItem {
68
66
  }
69
67
 
70
68
  // Class match
71
- if (this.isClass) {
72
- const regexp = new RegExp(CLASS_REGEXP, 'g');
73
-
74
- while ((match = regexp.exec(selector))) {
75
- if (!element.classList.contains(match[1])) {
76
- return false;
77
- }
78
- }
69
+ if (this.isClass && !this.matchesClass(element, selector)) {
70
+ return false;
79
71
  }
80
72
 
81
73
  // Pseudo match
82
- if (this.isPseudo) {
83
- const regexp = new RegExp(PSUEDO_REGEXP, 'g');
84
-
85
- while ((match = regexp.exec(selector))) {
86
- if (match[1] && !this.matchesNthChild(element, match[1], match[2])) {
87
- return false;
88
- } else if (match[3] && this.matchesAttribute(element, match[3])) {
89
- return false;
90
- } else if (match[4] && !this.matchesPsuedo(element, match[4])) {
91
- return false;
92
- }
93
- }
74
+ if (this.isPseudo && !this.matchesPsuedo(element, selector)) {
75
+ return false;
94
76
  }
95
77
 
96
78
  // Attribute match
@@ -101,6 +83,35 @@ export default class SelectorItem {
101
83
  return true;
102
84
  }
103
85
 
86
+ /**
87
+ * Matches a psuedo selector.
88
+ *
89
+ * @param element Element.
90
+ * @param selector Selector.
91
+ * @returns True if it is a match.
92
+ */
93
+ private matchesPsuedo(element: Element, selector: string): boolean {
94
+ const regexp = new RegExp(PSUEDO_REGEXP, 'g');
95
+ let match: RegExpMatchArray;
96
+
97
+ while ((match = regexp.exec(selector))) {
98
+ const isNotClass = match[3] && match[3].trim()[0] === '.';
99
+ if (match[1] && !this.matchesNthChild(element, match[1], match[2])) {
100
+ return false;
101
+ } else if (
102
+ match[3] &&
103
+ ((isNotClass && this.matchesClass(element, match[3])) ||
104
+ (!isNotClass && this.matchesAttribute(element, match[3])))
105
+ ) {
106
+ return false;
107
+ } else if (match[4] && !this.matchesPsuedoExpression(element, match[4])) {
108
+ return false;
109
+ }
110
+ }
111
+
112
+ return true;
113
+ }
114
+
104
115
  /**
105
116
  * Matches a nth-child selector.
106
117
  *
@@ -158,13 +169,13 @@ export default class SelectorItem {
158
169
  }
159
170
 
160
171
  /**
161
- * Matches a psuedo selector.
172
+ * Matches a psuedo selector expression.
162
173
  *
163
174
  * @param element Element.
164
175
  * @param psuedo Psuedo name.
165
176
  * @returns True if it is a match.
166
177
  */
167
- private matchesPsuedo(element: Element, psuedo: string): boolean {
178
+ private matchesPsuedoExpression(element: Element, psuedo: string): boolean {
168
179
  const parent = <Element>element.parentNode;
169
180
 
170
181
  if (!parent) {
@@ -220,10 +231,10 @@ export default class SelectorItem {
220
231
  */
221
232
  private matchesAttribute(element: Element, selector: string): boolean {
222
233
  const regexp = new RegExp(ATTRIBUTE_REGEXP, 'g');
223
- let match;
234
+ let match: RegExpMatchArray;
224
235
 
225
236
  while ((match = regexp.exec(selector))) {
226
- const isPsuedo = match.index > 0 && selector[match.index] === '(';
237
+ const isPsuedo = match.index > 0 && selector[match.index - 1] === '(';
227
238
  if (
228
239
  !isPsuedo &&
229
240
  ((match[1] && !this.matchesAttributeName(element, match[1])) ||
@@ -236,6 +247,29 @@ export default class SelectorItem {
236
247
  return true;
237
248
  }
238
249
 
250
+ /**
251
+ * Matches class.
252
+ *
253
+ * @param element Element.
254
+ * @param selector Selector.
255
+ * @returns True if it is a match.
256
+ */
257
+ private matchesClass(element: Element, selector: string): boolean {
258
+ const regexp = new RegExp(CLASS_REGEXP, 'g');
259
+ const classList = element.className.split(' ');
260
+ let previousIndex = 0;
261
+ let match: RegExpMatchArray;
262
+
263
+ while ((match = regexp.exec(selector)) && match.index === previousIndex) {
264
+ if (!classList.includes(match[1])) {
265
+ return false;
266
+ }
267
+ previousIndex = match.index + match[0].length - 1;
268
+ }
269
+
270
+ return true;
271
+ }
272
+
239
273
  /**
240
274
  * Matches attribute name only.
241
275
  *
@@ -70,6 +70,11 @@ import HTMLCollection from '../nodes/element/HTMLCollection';
70
70
  import NodeList from '../nodes/node/NodeList';
71
71
  import Selection from '../selection/Selection';
72
72
  import IEventTarget from '../event/IEventTarget';
73
+ import Navigator from '../navigator/Navigator';
74
+ import MimeType from '../navigator/MimeType';
75
+ import MimeTypeArray from '../navigator/MimeTypeArray';
76
+ import Plugin from '../navigator/Plugin';
77
+ import PluginArray from '../navigator/PluginArray';
73
78
 
74
79
  /**
75
80
  * Window.
@@ -151,6 +156,11 @@ export default interface IWindow extends IEventTarget {
151
156
  readonly CSSUnitValue: typeof CSSUnitValue;
152
157
  readonly CSS: CSS;
153
158
  readonly Selection: typeof Selection;
159
+ readonly Navigator: typeof Navigator;
160
+ readonly MimeType: typeof MimeType;
161
+ readonly MimeTypeArray: typeof MimeTypeArray;
162
+ readonly Plugin: typeof Plugin;
163
+ readonly PluginArray: typeof PluginArray;
154
164
 
155
165
  // Events
156
166
  onload: (event: Event) => void;
@@ -161,7 +171,7 @@ export default interface IWindow extends IEventTarget {
161
171
  readonly customElements: CustomElementRegistry;
162
172
  readonly location: Location;
163
173
  readonly history: History;
164
- readonly navigator: { userAgent: string };
174
+ readonly navigator: Navigator;
165
175
  readonly console: Console;
166
176
  readonly self: IWindow;
167
177
  readonly top: IWindow;
@@ -75,6 +75,11 @@ import NodeList from '../nodes/node/NodeList';
75
75
  import MediaQueryList from '../match-media/MediaQueryList';
76
76
  import Selection from '../selection/Selection';
77
77
  import * as PerfHooks from 'perf_hooks';
78
+ import Navigator from '../navigator/Navigator';
79
+ import MimeType from '../navigator/MimeType';
80
+ import MimeTypeArray from '../navigator/MimeTypeArray';
81
+ import Plugin from '../navigator/Plugin';
82
+ import PluginArray from '../navigator/PluginArray';
78
83
 
79
84
  const FETCH_RESPONSE_TYPE_METHODS = ['blob', 'json', 'text'];
80
85
 
@@ -162,6 +167,11 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
162
167
  public readonly MediaQueryList = MediaQueryList;
163
168
  public readonly CSSUnitValue = CSSUnitValue;
164
169
  public readonly Selection = Selection;
170
+ public readonly Navigator = Navigator;
171
+ public readonly MimeType = MimeType;
172
+ public readonly MimeTypeArray = MimeTypeArray;
173
+ public readonly Plugin = Plugin;
174
+ public readonly PluginArray = PluginArray;
165
175
 
166
176
  // Events
167
177
  public onload: (event: Event) => void = null;
@@ -172,7 +182,7 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
172
182
  public readonly customElements: CustomElementRegistry = new CustomElementRegistry();
173
183
  public readonly location = new Location();
174
184
  public readonly history = new History();
175
- public readonly navigator = { userAgent: 'happy-dom' };
185
+ public readonly navigator = new Navigator();
176
186
  public readonly console = global ? global.console : null;
177
187
  public readonly self = this;
178
188
  public readonly top = this;