happy-dom 7.4.0 → 7.5.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 (29) hide show
  1. package/lib/css/declaration/AbstractCSSStyleDeclaration.js +9 -0
  2. package/lib/css/declaration/AbstractCSSStyleDeclaration.js.map +1 -1
  3. package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.d.ts +0 -2
  4. package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.js +54 -25
  5. package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.js.map +1 -1
  6. package/lib/nodes/character-data/CharacterData.js +3 -0
  7. package/lib/nodes/character-data/CharacterData.js.map +1 -1
  8. package/lib/nodes/document/Document.d.ts +1 -0
  9. package/lib/nodes/document/Document.js +2 -0
  10. package/lib/nodes/document/Document.js.map +1 -1
  11. package/lib/nodes/element/Element.js +7 -7
  12. package/lib/nodes/element/Element.js.map +1 -1
  13. package/lib/nodes/node/Node.js +9 -0
  14. package/lib/nodes/node/Node.js.map +1 -1
  15. package/lib/query-selector/QuerySelector.d.ts +21 -0
  16. package/lib/query-selector/QuerySelector.js +45 -4
  17. package/lib/query-selector/QuerySelector.js.map +1 -1
  18. package/lib/query-selector/SelectorItem.d.ts +14 -11
  19. package/lib/query-selector/SelectorItem.js +35 -19
  20. package/lib/query-selector/SelectorItem.js.map +1 -1
  21. package/package.json +2 -2
  22. package/src/css/declaration/AbstractCSSStyleDeclaration.ts +12 -0
  23. package/src/css/declaration/utilities/CSSStyleDeclarationElementStyle.ts +86 -36
  24. package/src/nodes/character-data/CharacterData.ts +4 -0
  25. package/src/nodes/document/Document.ts +4 -0
  26. package/src/nodes/element/Element.ts +9 -7
  27. package/src/nodes/node/Node.ts +12 -0
  28. package/src/query-selector/QuerySelector.ts +66 -5
  29. package/src/query-selector/SelectorItem.ts +66 -37
@@ -12,14 +12,29 @@ import CSSStyleRule from '../../rules/CSSStyleRule';
12
12
  import CSSStyleDeclarationElementDefaultCSS from './CSSStyleDeclarationElementDefaultCSS';
13
13
  import CSSStyleDeclarationElementInheritedProperties from './CSSStyleDeclarationElementInheritedProperties';
14
14
  import CSSStyleDeclarationCSSParser from './CSSStyleDeclarationCSSParser';
15
+ import QuerySelector from '../../../query-selector/QuerySelector';
15
16
 
16
17
  const CSS_VARIABLE_REGEXP = /var\( *(--[^) ]+)\)/g;
17
18
 
19
+ type IStyleAndElement = {
20
+ element: IElement | IShadowRoot | IDocument;
21
+ cssTexts: Array<{ cssText: string; priorityWeight: number }>;
22
+ };
23
+
18
24
  /**
19
25
  * CSS Style Declaration utility
20
26
  */
21
27
  export default class CSSStyleDeclarationElementStyle {
22
- private cache: { [k: string]: CSSStyleDeclarationPropertyManager } = {};
28
+ private cache: {
29
+ propertyManager: CSSStyleDeclarationPropertyManager;
30
+ cssText: string;
31
+ documentCacheID: number;
32
+ } = {
33
+ propertyManager: null,
34
+ cssText: null,
35
+ documentCacheID: null
36
+ };
37
+
23
38
  private element: IElement;
24
39
  private computed: boolean;
25
40
 
@@ -47,11 +62,12 @@ export default class CSSStyleDeclarationElementStyle {
47
62
  const cssText = this.element['_attributes']['style']?.value;
48
63
 
49
64
  if (cssText) {
50
- if (this.cache[cssText]) {
51
- return this.cache[cssText];
65
+ if (this.cache.propertyManager && this.cache.cssText === cssText) {
66
+ return this.cache.propertyManager;
52
67
  }
53
- this.cache[cssText] = new CSSStyleDeclarationPropertyManager({ cssText });
54
- return this.cache[cssText];
68
+ this.cache.cssText = cssText;
69
+ this.cache.propertyManager = new CSSStyleDeclarationPropertyManager({ cssText });
70
+ return this.cache.propertyManager;
55
71
  }
56
72
 
57
73
  return new CSSStyleDeclarationPropertyManager();
@@ -64,28 +80,37 @@ export default class CSSStyleDeclarationElementStyle {
64
80
  * @returns Style sheets.
65
81
  */
66
82
  private getComputedElementStyle(): CSSStyleDeclarationPropertyManager {
67
- const documentElements: Array<{ element: IElement; cssText: string }> = [];
68
- const parentElements: Array<{ element: IElement; cssText: string }> = [];
69
- let styleAndElement = {
83
+ const documentElements: Array<IStyleAndElement> = [];
84
+ const parentElements: Array<IStyleAndElement> = [];
85
+ let styleAndElement: IStyleAndElement = {
70
86
  element: <IElement | IShadowRoot | IDocument>this.element,
71
- cssText: ''
87
+ cssTexts: []
72
88
  };
73
- let shadowRootElements: Array<{ element: IElement; cssText: string }> = [];
89
+ let shadowRootElements: Array<IStyleAndElement> = [];
74
90
 
75
91
  if (!this.element.isConnected) {
76
92
  return new CSSStyleDeclarationPropertyManager();
77
93
  }
78
94
 
95
+ if (
96
+ this.cache.propertyManager &&
97
+ this.cache.documentCacheID === this.element.ownerDocument['_cacheID']
98
+ ) {
99
+ return this.cache.propertyManager;
100
+ }
101
+
102
+ this.cache.documentCacheID = this.element.ownerDocument['_cacheID'];
103
+
79
104
  // Walks through all parent elements and stores them in an array with element and matching CSS text.
80
105
  while (styleAndElement.element) {
81
106
  if (styleAndElement.element.nodeType === NodeTypeEnum.elementNode) {
82
107
  const rootNode = styleAndElement.element.getRootNode();
83
108
  if (rootNode.nodeType === NodeTypeEnum.documentNode) {
84
- documentElements.unshift(<{ element: IElement; cssText: string }>styleAndElement);
109
+ documentElements.unshift(styleAndElement);
85
110
  } else {
86
- shadowRootElements.unshift(<{ element: IElement; cssText: string }>styleAndElement);
111
+ shadowRootElements.unshift(styleAndElement);
87
112
  }
88
- parentElements.unshift(<{ element: IElement; cssText: string }>styleAndElement);
113
+ parentElements.unshift(styleAndElement);
89
114
  }
90
115
 
91
116
  if (styleAndElement.element === this.element.ownerDocument) {
@@ -103,7 +128,7 @@ export default class CSSStyleDeclarationElementStyle {
103
128
  }
104
129
  }
105
130
 
106
- styleAndElement = { element: null, cssText: '' };
131
+ styleAndElement = { element: null, cssTexts: [] };
107
132
  } else if ((<IShadowRoot>styleAndElement.element).host) {
108
133
  const styleSheets = <INodeList<IHTMLStyleElement>>(
109
134
  (<IShadowRoot>styleAndElement.element).querySelectorAll('style,link[rel="stylesheet"]')
@@ -111,7 +136,7 @@ export default class CSSStyleDeclarationElementStyle {
111
136
 
112
137
  styleAndElement = {
113
138
  element: <IElement>(<IShadowRoot>styleAndElement.element).host,
114
- cssText: ''
139
+ cssTexts: []
115
140
  };
116
141
 
117
142
  for (const styleSheet of styleSheets) {
@@ -120,39 +145,58 @@ export default class CSSStyleDeclarationElementStyle {
120
145
  this.parseCSSRules({
121
146
  elements: shadowRootElements,
122
147
  cssRules: sheet.cssRules,
123
- hostElement: <{ element: IElement; cssText: string }>styleAndElement
148
+ hostElement: styleAndElement
124
149
  });
125
150
  }
126
151
  }
127
152
  shadowRootElements = [];
128
153
  } else {
129
- styleAndElement = { element: <IElement>styleAndElement.element.parentNode, cssText: '' };
154
+ styleAndElement = { element: <IElement>styleAndElement.element.parentNode, cssTexts: [] };
130
155
  }
131
156
  }
132
157
 
133
158
  // Concatenates all parent element CSS to one string.
134
159
  const targetElement = parentElements[parentElements.length - 1];
135
- let inheritedCSSText = CSSStyleDeclarationElementDefaultCSS.default;
160
+ let inheritedCSSText = '';
136
161
 
137
162
  for (const parentElement of parentElements) {
138
163
  if (parentElement !== targetElement) {
139
- inheritedCSSText +=
140
- (CSSStyleDeclarationElementDefaultCSS[parentElement.element.tagName] || '') +
141
- parentElement.cssText +
142
- (parentElement.element['_attributes']['style']?.value || '');
164
+ parentElement.cssTexts.sort((a, b) => a.priorityWeight - b.priorityWeight);
165
+
166
+ if (CSSStyleDeclarationElementDefaultCSS[(<IElement>parentElement.element).tagName]) {
167
+ inheritedCSSText +=
168
+ CSSStyleDeclarationElementDefaultCSS[(<IElement>parentElement.element).tagName];
169
+ }
170
+
171
+ for (const cssText of parentElement.cssTexts) {
172
+ inheritedCSSText += cssText.cssText;
173
+ }
174
+
175
+ if (parentElement.element['_attributes']['style']?.value) {
176
+ inheritedCSSText += parentElement.element['_attributes']['style'].value;
177
+ }
143
178
  }
144
179
  }
145
180
 
146
181
  const cssVariables: { [k: string]: string } = {};
147
182
  const properties = {};
148
- const targetCSSText =
149
- (CSSStyleDeclarationElementDefaultCSS[targetElement.element.tagName] || '') +
150
- targetElement.cssText +
151
- (targetElement.element['_attributes']['style']?.value || '');
183
+ let targetCSSText =
184
+ CSSStyleDeclarationElementDefaultCSS[(<IElement>targetElement.element).tagName] || '';
185
+
186
+ targetElement.cssTexts.sort((a, b) => a.priorityWeight - b.priorityWeight);
187
+
188
+ for (const cssText of targetElement.cssTexts) {
189
+ targetCSSText += cssText.cssText;
190
+ }
191
+
192
+ if (targetElement.element['_attributes']['style']?.value) {
193
+ targetCSSText += targetElement.element['_attributes']['style'].value;
194
+ }
195
+
152
196
  const combinedCSSText = inheritedCSSText + targetCSSText;
153
197
 
154
- if (this.cache[combinedCSSText]) {
155
- return this.cache[combinedCSSText];
198
+ if (this.cache.propertyManager && this.cache.cssText === combinedCSSText) {
199
+ return this.cache.propertyManager;
156
200
  }
157
201
 
158
202
  // Parses the parent element CSS and stores CSS variables and inherited properties.
@@ -204,7 +248,8 @@ export default class CSSStyleDeclarationElementStyle {
204
248
  propertyManager.set(name, properties[name].value, properties[name].important);
205
249
  }
206
250
 
207
- this.cache[combinedCSSText] = propertyManager;
251
+ this.cache.cssText = combinedCSSText;
252
+ this.cache.propertyManager = propertyManager;
208
253
 
209
254
  return propertyManager;
210
255
  }
@@ -216,13 +261,11 @@ export default class CSSStyleDeclarationElementStyle {
216
261
  * @param options.elements Elements.
217
262
  * @param options.cssRules CSS rules.
218
263
  * @param [options.hostElement] Host element.
219
- * @param [options.hostElement.element] Element.
220
- * @param [options.hostElement.cssText] CSS text.
221
264
  */
222
265
  private parseCSSRules(options: {
223
266
  cssRules: CSSRule[];
224
- elements: Array<{ element: IElement; cssText: string }>;
225
- hostElement?: { element: IElement; cssText: string };
267
+ elements: Array<IStyleAndElement>;
268
+ hostElement?: IStyleAndElement;
226
269
  }): void {
227
270
  if (!options.elements.length) {
228
271
  return;
@@ -236,12 +279,19 @@ export default class CSSStyleDeclarationElementStyle {
236
279
  if (selectorText) {
237
280
  if (selectorText.startsWith(':host')) {
238
281
  if (options.hostElement) {
239
- options.hostElement.cssText += (<CSSStyleRule>rule)._cssText;
282
+ options.hostElement.cssTexts.push({
283
+ cssText: (<CSSStyleRule>rule)._cssText,
284
+ priorityWeight: 0
285
+ });
240
286
  }
241
287
  } else {
242
288
  for (const element of options.elements) {
243
- if (element.element.matches(selectorText)) {
244
- element.cssText += (<CSSStyleRule>rule)._cssText;
289
+ const matchResult = QuerySelector.match(element.element, selectorText);
290
+ if (matchResult.matches) {
291
+ element.cssTexts.push({
292
+ cssText: (<CSSStyleRule>rule)._cssText,
293
+ priorityWeight: matchResult.priorityWeight
294
+ });
245
295
  }
246
296
  }
247
297
  }
@@ -56,6 +56,10 @@ export default abstract class CharacterData extends Node implements ICharacterDa
56
56
  const oldValue = this._data;
57
57
  this._data = data;
58
58
 
59
+ if (this.isConnected) {
60
+ this.ownerDocument['_cacheID']++;
61
+ }
62
+
59
63
  // MutationObserver
60
64
  if (this._observers.length > 0) {
61
65
  for (const observer of this._observers) {
@@ -60,6 +60,10 @@ export default class Document extends Node implements IDocument {
60
60
  public readonly defaultView: IWindow;
61
61
  public readonly _readyStateManager: DocumentReadyStateManager;
62
62
  public _activeElement: IHTMLElement = null;
63
+
64
+ // Used as an unique identifier which is updated whenever the DOM gets modified.
65
+ public _cacheID = 0;
66
+
63
67
  protected _isFirstWrite = true;
64
68
  protected _isFirstWriteAfterOpen = false;
65
69
  private _cookie = '';
@@ -5,7 +5,6 @@ import DOMRect from './DOMRect';
5
5
  import DOMTokenList from '../../dom-token-list/DOMTokenList';
6
6
  import IDOMTokenList from '../../dom-token-list/IDOMTokenList';
7
7
  import QuerySelector from '../../query-selector/QuerySelector';
8
- import SelectorItem from '../../query-selector/SelectorItem';
9
8
  import MutationRecord from '../../mutation-observer/MutationRecord';
10
9
  import MutationTypeEnum from '../../mutation-observer/MutationTypeEnum';
11
10
  import NamespaceURI from '../../config/NamespaceURI';
@@ -744,12 +743,7 @@ export default class Element extends Node implements IElement {
744
743
  * @returns "true" if matching.
745
744
  */
746
745
  public matches(selector: string): boolean {
747
- for (const part of selector.split(',')) {
748
- if (new SelectorItem(part.trim()).match(this)) {
749
- return true;
750
- }
751
- }
752
- return false;
746
+ return QuerySelector.match(this, selector).matches;
753
747
  }
754
748
 
755
749
  /**
@@ -852,6 +846,10 @@ export default class Element extends Node implements IElement {
852
846
  (<IElement>attribute.ownerElement) = <IElement>this;
853
847
  (<IDocument>attribute.ownerDocument) = this.ownerDocument;
854
848
 
849
+ if (this.isConnected) {
850
+ this.ownerDocument['_cacheID']++;
851
+ }
852
+
855
853
  this._attributes[name] = attribute;
856
854
 
857
855
  this._updateDomListIndices();
@@ -937,6 +935,10 @@ export default class Element extends Node implements IElement {
937
935
  public removeAttributeNode(attribute: IAttr): void {
938
936
  delete this._attributes[attribute.name];
939
937
 
938
+ if (this.isConnected) {
939
+ this.ownerDocument['_cacheID']++;
940
+ }
941
+
940
942
  this._updateDomListIndices();
941
943
 
942
944
  if (
@@ -308,6 +308,10 @@ export default class Node extends EventTarget implements INode {
308
308
  }
309
309
  }
310
310
 
311
+ if (this.isConnected) {
312
+ (this.ownerDocument || this)['_cacheID']++;
313
+ }
314
+
311
315
  this.childNodes.push(node);
312
316
 
313
317
  (<Node>node)._connectToNode(this);
@@ -345,6 +349,10 @@ export default class Node extends EventTarget implements INode {
345
349
  throw new DOMException('Failed to remove node. Node is not child of parent.');
346
350
  }
347
351
 
352
+ if (this.isConnected) {
353
+ (this.ownerDocument || this)['_cacheID']++;
354
+ }
355
+
348
356
  this.childNodes.splice(index, 1);
349
357
 
350
358
  (<Node>node)._connectToNode(null);
@@ -404,6 +412,10 @@ export default class Node extends EventTarget implements INode {
404
412
  );
405
413
  }
406
414
 
415
+ if (this.isConnected) {
416
+ (this.ownerDocument || this)['_cacheID']++;
417
+ }
418
+
407
419
  if (newNode.parentNode) {
408
420
  const index = newNode.parentNode.childNodes.indexOf(newNode);
409
421
  if (index !== -1) {
@@ -8,9 +8,6 @@ import NodeListFactory from '../nodes/node/NodeListFactory';
8
8
 
9
9
  const SELECTOR_PART_REGEXP = /(\[[^\]]+\]|[a-zA-Z0-9-_.#"*:()\]]+)|([ ,>]+)/g;
10
10
 
11
- // The above one seem to work fine and is faster, but this one can be useful if more rules need to be added as it is more "correct".
12
- // Const SELECTOR_PART_REGEXP = /([a-zA-Z0-9-$.]+|\[[a-zA-Z0-9-]+\]|\[[a-zA-Z0-9$-~|^$*]+[ ]*=[ ]*"[^"]+"\])|([ ,]+)/g;
13
-
14
11
  /**
15
12
  * Utility for query selection in an HTML element.
16
13
  *
@@ -57,6 +54,70 @@ export default class QuerySelector {
57
54
  return null;
58
55
  }
59
56
 
57
+ /**
58
+ * Checks if a node matches a selector and returns priority weight.
59
+ *
60
+ * @param node Node to search in.
61
+ * @param selector Selector.
62
+ * @returns Result.
63
+ */
64
+ public static match(node: INode, selector: string): { priorityWeight: number; matches: boolean } {
65
+ for (const parts of this.getSelectorParts(selector)) {
66
+ const result = this.matchesSelector(node, node, parts.reverse());
67
+
68
+ if (result.matches) {
69
+ return result;
70
+ }
71
+ }
72
+
73
+ return { priorityWeight: 0, matches: false };
74
+ }
75
+
76
+ /**
77
+ * Checks if a node matches a selector.
78
+ *
79
+ * @param targetNode Target node.
80
+ * @param currentNode Current node.
81
+ * @param selectorParts Selector parts.
82
+ * @param [priorityWeight] Priority weight.
83
+ * @returns Result.
84
+ */
85
+ private static matchesSelector(
86
+ targetNode: INode,
87
+ currentNode: INode,
88
+ selectorParts: string[],
89
+ priorityWeight = 0
90
+ ): {
91
+ priorityWeight: number;
92
+ matches: boolean;
93
+ } {
94
+ const isDirectChild = selectorParts[0] === '>';
95
+ if (isDirectChild) {
96
+ selectorParts = selectorParts.slice(1);
97
+ if (selectorParts.length === 0) {
98
+ return { priorityWeight: 0, matches: false };
99
+ }
100
+ }
101
+
102
+ if (selectorParts.length === 0) {
103
+ return { priorityWeight, matches: true };
104
+ }
105
+
106
+ const selector = new SelectorItem(selectorParts[0]);
107
+ const result = selector.match(<Element>currentNode);
108
+
109
+ if (targetNode === currentNode && !result.matches) {
110
+ return { priorityWeight: 0, matches: false };
111
+ }
112
+
113
+ return this.matchesSelector(
114
+ isDirectChild ? currentNode.parentNode : targetNode,
115
+ currentNode.parentNode,
116
+ selectorParts.slice(1),
117
+ priorityWeight + result.priorityWeight
118
+ );
119
+ }
120
+
60
121
  /**
61
122
  * Finds elements based on a query selector for a part of a list of selectors separated with comma.
62
123
  *
@@ -81,7 +142,7 @@ export default class QuerySelector {
81
142
 
82
143
  for (const node of nodes) {
83
144
  if (node.nodeType === Node.ELEMENT_NODE) {
84
- if (selector.match(<Element>node)) {
145
+ if (selector.match(<Element>node).matches) {
85
146
  if (selectorParts.length === 1) {
86
147
  if (rootNode !== node) {
87
148
  matched.push(node);
@@ -125,7 +186,7 @@ export default class QuerySelector {
125
186
  const selector = selectorItem || new SelectorItem(selectorParts[0]);
126
187
 
127
188
  for (const node of nodes) {
128
- if (node.nodeType === Node.ELEMENT_NODE && selector.match(<Element>node)) {
189
+ if (node.nodeType === Node.ELEMENT_NODE && selector.match(<Element>node).matches) {
129
190
  if (selectorParts.length === 1) {
130
191
  if (rootNode !== node) {
131
192
  return <Element>node;
@@ -1,4 +1,5 @@
1
1
  import DOMException from '../exception/DOMException';
2
+ import IElement from '../nodes/element/IElement';
2
3
  import Element from '../nodes/element/Element';
3
4
 
4
5
  const ATTRIBUTE_REGEXP =
@@ -13,12 +14,12 @@ const ID_REGEXP = /#[A-Za-z][-A-Za-z0-9_]*/g;
13
14
  * Selector item.
14
15
  */
15
16
  export default class SelectorItem {
16
- public isAll: boolean;
17
- public isID: boolean;
18
- public isAttribute: boolean;
19
- public isPseudo: boolean;
20
- public isClass: boolean;
21
- public isTagName: boolean;
17
+ private isAll: boolean;
18
+ private isID: boolean;
19
+ private isAttribute: boolean;
20
+ private isPseudo: boolean;
21
+ private isClass: boolean;
22
+ private isTagName: boolean;
22
23
  private tagName = null;
23
24
  private selector: string;
24
25
  private id: string;
@@ -42,6 +43,7 @@ export default class SelectorItem {
42
43
  this.isTagName = this.tagName !== null;
43
44
  this.selector = selector;
44
45
  this.id = null;
46
+
45
47
  if (!this.isAll && this.isID) {
46
48
  const idMatches = baseSelector.match(ID_REGEXP);
47
49
  if (idMatches) {
@@ -54,46 +56,60 @@ export default class SelectorItem {
54
56
  * Matches a selector against an element.
55
57
  *
56
58
  * @param element HTML element.
57
- * @returns TRUE if matching.
59
+ * @returns Result.
58
60
  */
59
- public match(element: Element): boolean {
61
+ public match(element: IElement): { priorityWeight: number; matches: boolean } {
60
62
  const selector = this.selector;
61
63
 
64
+ let priorityWeight = 0;
65
+
62
66
  // Is all (*)
63
67
  if (this.isAll) {
64
- return true;
68
+ return { priorityWeight: 0, matches: true };
65
69
  }
66
70
 
67
71
  // ID Match
68
72
  if (this.isID) {
73
+ priorityWeight += 100;
74
+
69
75
  if (this.id !== element.id) {
70
- return false;
76
+ return { priorityWeight: 0, matches: false };
71
77
  }
72
78
  }
73
79
 
74
80
  // Tag name match
75
81
  if (this.isTagName) {
82
+ priorityWeight += 1;
83
+
76
84
  if (this.tagName !== element.tagName) {
77
- return false;
85
+ return { priorityWeight: 0, matches: false };
78
86
  }
79
87
  }
80
88
 
81
89
  // Class match
82
- if (this.isClass && !this.matchesClass(element, selector)) {
83
- return false;
90
+ if (this.isClass) {
91
+ const result = this.matchesClass(element, selector);
92
+ priorityWeight += result.priorityWeight;
93
+ if (!result.matches) {
94
+ return { priorityWeight: 0, matches: false };
95
+ }
84
96
  }
85
97
 
86
98
  // Pseudo match
87
99
  if (this.isPseudo && !this.matchesPsuedo(element, selector)) {
88
- return false;
100
+ return { priorityWeight: 0, matches: false };
89
101
  }
90
102
 
91
103
  // Attribute match
92
- if (this.isAttribute && !this.matchesAttribute(element, selector)) {
93
- return false;
104
+ if (this.isAttribute) {
105
+ const result = this.matchesAttribute(element, selector);
106
+ priorityWeight += result.priorityWeight;
107
+ if (!result.matches) {
108
+ return { priorityWeight: 0, matches: false };
109
+ }
94
110
  }
95
111
 
96
- return true;
112
+ return { priorityWeight, matches: true };
97
113
  }
98
114
 
99
115
  /**
@@ -103,7 +119,7 @@ export default class SelectorItem {
103
119
  * @param selector Selector.
104
120
  * @returns True if it is a match.
105
121
  */
106
- private matchesPsuedo(element: Element, selector: string): boolean {
122
+ private matchesPsuedo(element: IElement, selector: string): boolean {
107
123
  const regexp = new RegExp(PSUEDO_REGEXP, 'g');
108
124
  let match: RegExpMatchArray;
109
125
 
@@ -113,8 +129,8 @@ export default class SelectorItem {
113
129
  return false;
114
130
  } else if (
115
131
  match[3] &&
116
- ((isNotClass && this.matchesClass(element, match[3])) ||
117
- (!isNotClass && this.matchesAttribute(element, match[3])))
132
+ ((isNotClass && this.matchesClass(element, match[3]).matches) ||
133
+ (!isNotClass && this.matchesAttribute(element, match[3])).matches)
118
134
  ) {
119
135
  return false;
120
136
  } else if (match[4] && !this.matchesPsuedoExpression(element, match[4])) {
@@ -133,8 +149,8 @@ export default class SelectorItem {
133
149
  * @param place Place.
134
150
  * @returns True if it is a match.
135
151
  */
136
- private matchesNthChild(element: Element, psuedo: string, place: string): boolean {
137
- let children = element.parentNode ? (<Element>element.parentNode).children : [];
152
+ private matchesNthChild(element: IElement, psuedo: string, place: string): boolean {
153
+ let children = element.parentNode ? (<IElement>element.parentNode).children : [];
138
154
 
139
155
  switch (psuedo.toLowerCase()) {
140
156
  case 'nth-of-type':
@@ -188,8 +204,8 @@ export default class SelectorItem {
188
204
  * @param psuedo Psuedo name.
189
205
  * @returns True if it is a match.
190
206
  */
191
- private matchesPsuedoExpression(element: Element, psuedo: string): boolean {
192
- const parent = <Element>element.parentNode;
207
+ private matchesPsuedoExpression(element: IElement, psuedo: string): boolean {
208
+ const parent = <IElement>element.parentNode;
193
209
 
194
210
  if (!parent) {
195
211
  return false;
@@ -240,24 +256,31 @@ export default class SelectorItem {
240
256
  *
241
257
  * @param element Element.
242
258
  * @param selector Selector.
243
- * @returns True if it is a match.
259
+ * @returns Result.
244
260
  */
245
- private matchesAttribute(element: Element, selector: string): boolean {
261
+ private matchesAttribute(
262
+ element: IElement,
263
+ selector: string
264
+ ): { priorityWeight: number; matches: boolean } {
246
265
  const regexp = new RegExp(ATTRIBUTE_REGEXP, 'g');
247
266
  let match: RegExpMatchArray;
267
+ let priorityWeight = 0;
248
268
 
249
269
  while ((match = regexp.exec(selector))) {
250
270
  const isPsuedo = match.index > 0 && selector[match.index - 1] === '(';
271
+
272
+ priorityWeight += 10;
273
+
251
274
  if (
252
275
  !isPsuedo &&
253
276
  ((match[1] && !this.matchesAttributeName(element, match[1])) ||
254
277
  (match[2] && !this.matchesAttributeNameAndValue(element, match[2], match[4], match[3])))
255
278
  ) {
256
- return false;
279
+ return { priorityWeight: 0, matches: false };
257
280
  }
258
281
  }
259
282
 
260
- return true;
283
+ return { priorityWeight, matches: true };
261
284
  }
262
285
 
263
286
  /**
@@ -265,20 +288,26 @@ export default class SelectorItem {
265
288
  *
266
289
  * @param element Element.
267
290
  * @param selector Selector.
268
- * @returns True if it is a match.
291
+ * @returns Result.
269
292
  */
270
- private matchesClass(element: Element, selector: string): boolean {
293
+ private matchesClass(
294
+ element: IElement,
295
+ selector: string
296
+ ): { priorityWeight: number; matches: boolean } {
271
297
  const regexp = new RegExp(CLASS_REGEXP, 'g');
272
298
  const classList = element.className.split(' ');
299
+ const classSelector = selector.split(':')[0];
300
+ let priorityWeight = 0;
273
301
  let match: RegExpMatchArray;
274
302
 
275
- while ((match = regexp.exec(selector.split(':')[0]))) {
303
+ while ((match = regexp.exec(classSelector))) {
304
+ priorityWeight += 10;
276
305
  if (!classList.includes(match[1])) {
277
- return false;
306
+ return { priorityWeight: 0, matches: false };
278
307
  }
279
308
  }
280
309
 
281
- return true;
310
+ return { priorityWeight, matches: true };
282
311
  }
283
312
 
284
313
  /**
@@ -288,12 +317,12 @@ export default class SelectorItem {
288
317
  * @param attributeName Attribute name.
289
318
  * @returns True if it is a match.
290
319
  */
291
- private matchesAttributeName(element: Element, attributeName: string): boolean {
320
+ private matchesAttributeName(element: IElement, attributeName: string): boolean {
292
321
  if (ATTRIBUTE_NAME_REGEXP.test(attributeName)) {
293
322
  throw new DOMException(`The selector "${this.selector}" is not valid.`);
294
323
  }
295
324
 
296
- return !!element._attributes[attributeName.toLowerCase()];
325
+ return !!(<Element>element)._attributes[attributeName.toLowerCase()];
297
326
  }
298
327
 
299
328
  /** .
@@ -314,12 +343,12 @@ export default class SelectorItem {
314
343
  * @param matchType
315
344
  */
316
345
  private matchesAttributeNameAndValue(
317
- element: Element,
346
+ element: IElement,
318
347
  attributeName: string,
319
348
  attributeValue: string,
320
349
  matchType: string = null
321
350
  ): boolean {
322
- const attribute = element._attributes[attributeName.toLowerCase()];
351
+ const attribute = (<Element>element)._attributes[attributeName.toLowerCase()];
323
352
  const value = attributeValue;
324
353
 
325
354
  if (ATTRIBUTE_NAME_REGEXP.test(attributeName)) {