happy-dom 9.1.9 → 9.2.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 (46) hide show
  1. package/lib/cookie/Cookie.d.ts +18 -28
  2. package/lib/cookie/Cookie.d.ts.map +1 -1
  3. package/lib/cookie/Cookie.js +56 -83
  4. package/lib/cookie/Cookie.js.map +1 -1
  5. package/lib/cookie/CookieJar.d.ts +9 -13
  6. package/lib/cookie/CookieJar.d.ts.map +1 -1
  7. package/lib/cookie/CookieJar.js +41 -49
  8. package/lib/cookie/CookieJar.js.map +1 -1
  9. package/lib/cookie/CookieSameSiteEnum.d.ts +7 -0
  10. package/lib/cookie/CookieSameSiteEnum.d.ts.map +1 -0
  11. package/lib/cookie/CookieSameSiteEnum.js +10 -0
  12. package/lib/cookie/CookieSameSiteEnum.js.map +1 -0
  13. package/lib/fetch/Fetch.d.ts.map +1 -1
  14. package/lib/fetch/Fetch.js +2 -6
  15. package/lib/fetch/Fetch.js.map +1 -1
  16. package/lib/nodes/document/Document.js +2 -2
  17. package/lib/nodes/document/Document.js.map +1 -1
  18. package/lib/nodes/element/Dataset.d.ts +34 -0
  19. package/lib/nodes/element/Dataset.d.ts.map +1 -0
  20. package/lib/nodes/element/Dataset.js +97 -0
  21. package/lib/nodes/element/Dataset.js.map +1 -0
  22. package/lib/nodes/html-element/HTMLElement.d.ts.map +1 -1
  23. package/lib/nodes/html-element/HTMLElement.js +2 -63
  24. package/lib/nodes/html-element/HTMLElement.js.map +1 -1
  25. package/lib/nodes/svg-element/SVGElement.d.ts +1 -0
  26. package/lib/nodes/svg-element/SVGElement.d.ts.map +1 -1
  27. package/lib/nodes/svg-element/SVGElement.js +3 -7
  28. package/lib/nodes/svg-element/SVGElement.js.map +1 -1
  29. package/lib/xml-http-request/XMLHttpRequest.d.ts.map +1 -1
  30. package/lib/xml-http-request/XMLHttpRequest.js +4 -3
  31. package/lib/xml-http-request/XMLHttpRequest.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/cookie/Cookie.ts +58 -91
  34. package/src/cookie/CookieJar.ts +52 -53
  35. package/src/cookie/CookieSameSiteEnum.ts +6 -0
  36. package/src/fetch/Fetch.ts +6 -8
  37. package/src/nodes/document/Document.ts +2 -2
  38. package/src/nodes/element/Dataset.ts +105 -0
  39. package/src/nodes/html-element/HTMLElement.ts +3 -68
  40. package/src/nodes/svg-element/SVGElement.ts +3 -7
  41. package/src/xml-http-request/XMLHttpRequest.ts +7 -3
  42. package/lib/nodes/html-element/DatasetUtility.d.ts +0 -20
  43. package/lib/nodes/html-element/DatasetUtility.d.ts.map +0 -1
  44. package/lib/nodes/html-element/DatasetUtility.js +0 -33
  45. package/lib/nodes/html-element/DatasetUtility.js.map +0 -1
  46. package/src/nodes/html-element/DatasetUtility.ts +0 -30
@@ -1,61 +1,54 @@
1
- const CookiePairRegex = /([^=]+)(?:=([\s\S]*))?/;
1
+ import DOMException from '../exception/DOMException';
2
+ import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum';
3
+ import CookieSameSiteEnum from './CookieSameSiteEnum';
4
+ import { URL } from 'url';
2
5
 
3
6
  /**
4
7
  * Cookie.
5
8
  */
6
9
  export default class Cookie {
7
- private pairs: { [key: string]: string } = {};
8
- //
10
+ // Required
9
11
  public key = '';
10
- public value = '';
11
- public size = 0;
12
+ public value: string | null = null;
13
+ public originURL: URL;
14
+
12
15
  // Optional
13
16
  public domain = '';
14
17
  public path = '';
15
- public expriesOrMaxAge: Date = null;
18
+ public expires: Date | null = null;
16
19
  public httpOnly = false;
17
20
  public secure = false;
18
- public sameSite = '';
21
+ public sameSite: CookieSameSiteEnum = CookieSameSiteEnum.lax;
19
22
 
20
23
  /**
21
24
  * Constructor.
22
25
  *
26
+ * @param originURL Origin URL.
23
27
  * @param cookie Cookie.
24
28
  */
25
- constructor(cookie: string) {
26
- let match: RegExpExecArray | null;
29
+ constructor(originURL, cookie: string) {
30
+ const parts = cookie.split(';');
31
+ const [key, value] = parts.shift().split('=');
27
32
 
28
- const parts = cookie.split(';').filter(Boolean);
33
+ this.originURL = originURL;
34
+ this.key = key.trim();
35
+ this.value = value !== undefined ? value : null;
29
36
 
30
- // Part[0] is the key-value pair.
31
- match = new RegExp(CookiePairRegex).exec(parts[0]);
32
- if (!match) {
33
- throw new Error(`Invalid cookie: ${cookie}`);
34
- }
35
- this.key = match[1].trim();
36
- this.value = match[2];
37
- // Set key is empty if match[2] is undefined.
38
- if (!match[2] && parts[0][this.key.length] !== '=') {
39
- this.value = this.key;
40
- this.key = '';
37
+ if (!this.key) {
38
+ throw new DOMException(`Invalid cookie: ${cookie}.`, DOMExceptionNameEnum.syntaxError);
41
39
  }
42
- this.pairs[this.key] = this.value;
43
- this.size = this.key.length + this.value.length;
44
- // Attribute.
45
- for (const part of parts.slice(1)) {
46
- match = new RegExp(CookiePairRegex).exec(part);
47
- if (!match) {
48
- throw new Error(`Invalid cookie: ${part}`);
49
- }
50
- const key = match[1].trim();
51
- const value = match[2];
52
40
 
53
- switch (key.toLowerCase()) {
41
+ for (const part of parts) {
42
+ const keyAndValue = part.split('=');
43
+ const key = keyAndValue[0].trim().toLowerCase();
44
+ const value = keyAndValue[1];
45
+
46
+ switch (key) {
54
47
  case 'expires':
55
- this.expriesOrMaxAge = new Date(value);
48
+ this.expires = new Date(value);
56
49
  break;
57
50
  case 'max-age':
58
- this.expriesOrMaxAge = new Date(parseInt(value, 10) * 1000 + Date.now());
51
+ this.expires = new Date(parseInt(value, 10) * 1000 + Date.now());
59
52
  break;
60
53
  case 'domain':
61
54
  this.domain = value;
@@ -70,53 +63,42 @@ export default class Cookie {
70
63
  this.secure = true;
71
64
  break;
72
65
  case 'samesite':
73
- this.sameSite = value;
66
+ switch (value.toLowerCase()) {
67
+ case 'strict':
68
+ this.sameSite = CookieSameSiteEnum.strict;
69
+ break;
70
+ case 'lax':
71
+ this.sameSite = CookieSameSiteEnum.lax;
72
+ break;
73
+ case 'none':
74
+ this.sameSite = CookieSameSiteEnum.none;
75
+ }
74
76
  break;
75
- default:
76
- continue; // Skip.
77
77
  }
78
- // Skip unknown key-value pair.
79
- if (
80
- ['expires', 'max-age', 'domain', 'path', 'httponly', 'secure', 'samesite'].indexOf(
81
- key.toLowerCase()
82
- ) === -1
83
- ) {
84
- continue;
85
- }
86
- this.pairs[key] = value;
87
78
  }
88
79
  }
89
80
 
90
81
  /**
91
- * Returns a raw string of the cookie.
92
- */
93
- public rawString(): string {
94
- return Object.keys(this.pairs)
95
- .map((key) => {
96
- if (key) {
97
- return `${key}=${this.pairs[key]}`;
98
- }
99
- return this.pairs[key];
100
- })
101
- .join('; ');
102
- }
103
-
104
- /**
82
+ * Returns cookie string.
105
83
  *
84
+ * @returns Cookie string.
106
85
  */
107
- public cookieString(): string {
108
- if (this.key) {
86
+ public toString(): string {
87
+ if (this.value !== null) {
109
88
  return `${this.key}=${this.value}`;
110
89
  }
111
- return this.value;
90
+
91
+ return this.key;
112
92
  }
113
93
 
114
94
  /**
95
+ * Returns "true" if expired.
115
96
  *
97
+ * @returns "true" if expired.
116
98
  */
117
99
  public isExpired(): boolean {
118
100
  // If the expries/maxage is set, then determine whether it is expired.
119
- if (this.expriesOrMaxAge && this.expriesOrMaxAge.getTime() < Date.now()) {
101
+ if (this.expires && this.expires.getTime() < Date.now()) {
120
102
  return true;
121
103
  }
122
104
  // If the expries/maxage is not set, it's a session-level cookie that will expire when the browser is closed.
@@ -125,34 +107,19 @@ export default class Cookie {
125
107
  }
126
108
 
127
109
  /**
110
+ * Validate cookie.
128
111
  *
112
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes
113
+ * @returns "true" if valid.
129
114
  */
130
- public isHttpOnly(): boolean {
131
- return this.httpOnly;
132
- }
133
-
134
- /**
135
- *
136
- */
137
- public isSecure(): boolean {
138
- return this.secure;
139
- }
140
-
141
- /**
142
- * Parse a cookie string.
143
- *
144
- * @param cookieString
145
- */
146
- public static parse(cookieString: string): Cookie {
147
- return new Cookie(cookieString);
148
- }
149
-
150
- /**
151
- * Stringify a Cookie object.
152
- *
153
- * @param cookie
154
- */
155
- public static stringify(cookie: Cookie): string {
156
- return cookie.toString();
115
+ public validate(): boolean {
116
+ const lowerKey = this.key.toLowerCase();
117
+ if (lowerKey.startsWith('__secure-') && !this.secure) {
118
+ return false;
119
+ }
120
+ if (lowerKey.startsWith('__host-') && (!this.secure || this.path !== '/' || this.domain)) {
121
+ return false;
122
+ }
123
+ return true;
157
124
  }
158
125
  }
@@ -1,5 +1,6 @@
1
- import Location from 'src/location/Location';
2
1
  import Cookie from './Cookie';
2
+ import CookieSameSiteEnum from './CookieSameSiteEnum';
3
+ import { URL } from 'url';
3
4
 
4
5
  /**
5
6
  * CookieJar.
@@ -11,72 +12,70 @@ export default class CookieJar {
11
12
  private cookies: Cookie[] = [];
12
13
 
13
14
  /**
14
- * Validate cookie.
15
+ * Adds cookie string.
15
16
  *
16
- * @param cookie
17
+ * @param originURL Origin URL.
18
+ * @param cookieString Cookie string.
17
19
  */
18
- private validateCookie(cookie: Cookie): boolean {
19
- if (cookie.key.toLowerCase().startsWith('__secure-') && !cookie.isSecure()) {
20
- return false;
21
- }
22
- if (
23
- cookie.key.toLowerCase().startsWith('__host-') &&
24
- (!cookie.isSecure() || cookie.path !== '/' || cookie.domain)
25
- ) {
26
- return false;
27
- }
28
- return true;
29
- }
30
-
31
- /**
32
- * Set cookie.
33
- *
34
- * @param cookieString
35
- */
36
- public setCookiesString(cookieString: string): void {
20
+ public addCookieString(originURL: URL, cookieString: string): void {
37
21
  if (!cookieString) {
38
22
  return;
39
23
  }
40
- const newCookie = new Cookie(cookieString);
41
- if (!this.validateCookie(newCookie)) {
24
+
25
+ const newCookie = new Cookie(originURL, cookieString);
26
+
27
+ if (!newCookie.validate()) {
42
28
  return;
43
29
  }
44
- this.cookies
45
- .filter((cookie) => cookie.key === newCookie.key)
46
- .forEach((cookie) => {
47
- this.cookies.splice(this.cookies.indexOf(cookie), 1);
48
- });
49
- this.cookies.push(newCookie);
30
+
31
+ for (let i = 0, max = this.cookies.length; i < max; i++) {
32
+ if (
33
+ this.cookies[i].key === newCookie.key &&
34
+ this.cookies[i].originURL.hostname === newCookie.originURL.hostname &&
35
+ // Cookies with or without values are treated differently in the browser.
36
+ // Therefore, the cookie should only be replaced if either both has a value or if both has no value.
37
+ // The cookie value is null if it has no value set.
38
+ // This is a bit unlogical, so it would be nice with a link to the spec here.
39
+ typeof this.cookies[i].value === typeof newCookie.value
40
+ ) {
41
+ this.cookies.splice(i, 1);
42
+ break;
43
+ }
44
+ }
45
+
46
+ if (!newCookie.isExpired()) {
47
+ this.cookies.push(newCookie);
48
+ }
50
49
  }
51
50
 
52
51
  /**
53
- * Get cookie.
52
+ * Get cookie string.
54
53
  *
55
- * @param location Location.
54
+ * @param targetURL Target URL.
56
55
  * @param fromDocument If true, the caller is a document.
57
56
  * @returns Cookie string.
58
57
  */
59
- public getCookiesString(location: Location, fromDocument: boolean): string {
60
- const cookies = this.cookies.filter((cookie) => {
61
- // Skip when use document.cookie and the cookie is httponly.
62
- if (fromDocument && cookie.isHttpOnly()) {
63
- return false;
64
- }
65
- if (cookie.isExpired()) {
66
- return false;
67
- }
68
- if (cookie.isSecure() && location.protocol !== 'https:') {
69
- return false;
70
- }
71
- if (cookie.domain && !location.hostname.endsWith(cookie.domain)) {
72
- return false;
73
- }
74
- if (cookie.path && !location.pathname.startsWith(cookie.path)) {
75
- return false;
58
+ public getCookieString(targetURL: URL, fromDocument: boolean): string {
59
+ let cookieString = '';
60
+
61
+ for (const cookie of this.cookies) {
62
+ if (
63
+ (!fromDocument || !cookie.httpOnly) &&
64
+ !cookie.isExpired() &&
65
+ (!cookie.secure || targetURL.protocol === 'https:') &&
66
+ (!cookie.domain || targetURL.hostname.endsWith(cookie.domain)) &&
67
+ (!cookie.path || targetURL.pathname.startsWith(cookie.path)) &&
68
+ // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
69
+ ((cookie.sameSite === CookieSameSiteEnum.none && cookie.secure) ||
70
+ cookie.originURL.hostname === targetURL.hostname)
71
+ ) {
72
+ if (cookieString) {
73
+ cookieString += '; ';
74
+ }
75
+ cookieString += cookie.toString();
76
76
  }
77
- // TODO: Check same site behaviour.
78
- return true;
79
- });
80
- return cookies.map((cookie) => cookie.cookieString()).join('; ');
77
+ }
78
+
79
+ return cookieString;
81
80
  }
82
81
  }
@@ -0,0 +1,6 @@
1
+ enum CookieSameSiteEnum {
2
+ strict = 'Strict',
3
+ lax = 'Lax',
4
+ none = 'None'
5
+ }
6
+ export default CookieSameSiteEnum;
@@ -16,6 +16,7 @@ import { Socket } from 'net';
16
16
  import Stream from 'stream';
17
17
  import DataURIParser from './data-uri/DataURIParser';
18
18
  import FetchCORSUtility from './utilities/FetchCORSUtility';
19
+ import CookieJar from '../cookie/CookieJar';
19
20
 
20
21
  const SUPPORTED_SCHEMAS = ['data:', 'http:', 'https:'];
21
22
  const REDIRECT_STATUS_CODES = [301, 302, 303, 307, 308];
@@ -557,7 +558,10 @@ export default class Fetch {
557
558
  this.request.credentials === 'include' ||
558
559
  (this.request.credentials === 'same-origin' && !isCORS)
559
560
  ) {
560
- const cookie = document.defaultView.document.cookie;
561
+ const cookie = document.defaultView.document._cookie.getCookieString(
562
+ this.ownerDocument.defaultView.location,
563
+ false
564
+ );
561
565
  if (cookie) {
562
566
  headers.set('Cookie', cookie);
563
567
  }
@@ -614,13 +618,7 @@ export default class Fetch {
614
618
  // Handles setting cookie headers to the document.
615
619
  // "set-cookie" and "set-cookie2" are not allowed in response headers according to spec.
616
620
  if (lowerKey === 'set-cookie' || lowerKey === 'set-cookie2') {
617
- const isCORS = FetchCORSUtility.isCORS(this.ownerDocument.location, this.request._url);
618
- if (
619
- this.request.credentials === 'include' ||
620
- (this.request.credentials === 'same-origin' && !isCORS)
621
- ) {
622
- this.ownerDocument.cookie = header;
623
- }
621
+ (<CookieJar>this.ownerDocument['_cookie']).addCookieString(this.request._url, header);
624
622
  } else {
625
623
  headers.append(key, header);
626
624
  }
@@ -290,7 +290,7 @@ export default class Document extends Node implements IDocument {
290
290
  * @returns Cookie.
291
291
  */
292
292
  public get cookie(): string {
293
- return this._cookie.getCookiesString(this.defaultView.location, true);
293
+ return this._cookie.getCookieString(this.defaultView.location, true);
294
294
  }
295
295
 
296
296
  /**
@@ -299,7 +299,7 @@ export default class Document extends Node implements IDocument {
299
299
  * @param cookie Cookie string.
300
300
  */
301
301
  public set cookie(cookie: string) {
302
- this._cookie.setCookiesString(cookie);
302
+ this._cookie.addCookieString(this.defaultView.location, cookie);
303
303
  }
304
304
 
305
305
  /**
@@ -0,0 +1,105 @@
1
+ import Element from '../element/Element';
2
+
3
+ /**
4
+ * Storage type for a dataset proxy.
5
+ */
6
+ type DatasetRecord = Record<string, string>;
7
+
8
+ /**
9
+ * Dataset helper proxy.
10
+ *
11
+ * Reference:
12
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset
13
+ */
14
+ export default class Dataset {
15
+ public readonly proxy: DatasetRecord;
16
+
17
+ /**
18
+ * @param element The parent element.
19
+ */
20
+ constructor(element: Element) {
21
+ // Build the initial dataset record from all data attributes.
22
+ const dataset: DatasetRecord = {};
23
+ const attributes = element._attributes;
24
+ for (const name of Object.keys(attributes)) {
25
+ if (name.startsWith('data-')) {
26
+ const key = Dataset.kebabToCamelCase(name.replace('data-', ''));
27
+ dataset[key] = attributes[name].value;
28
+ }
29
+ }
30
+
31
+ // Documentation for Proxy:
32
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
33
+ this.proxy = new Proxy(dataset, {
34
+ get(dataset: DatasetRecord, key: string): string {
35
+ const name = 'data-' + Dataset.camelCaseToKebab(key);
36
+ if (name in attributes) {
37
+ return (dataset[key] = attributes[name].value);
38
+ }
39
+ delete dataset[key];
40
+ return undefined;
41
+ },
42
+ set(dataset: DatasetRecord, key: string, value: string): boolean {
43
+ element.setAttribute('data-' + Dataset.camelCaseToKebab(key), value);
44
+ dataset[key] = value;
45
+ return true;
46
+ },
47
+ deleteProperty(dataset: DatasetRecord, key: string): boolean {
48
+ const name = 'data-' + Dataset.camelCaseToKebab(key);
49
+ const result1 = delete attributes[name];
50
+ const result2 = delete dataset[key];
51
+ return result1 && result2;
52
+ },
53
+ ownKeys(dataset: DatasetRecord): string[] {
54
+ // According to Mozilla we have to update the dataset object (target) to contain the same keys as what we return:
55
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys
56
+ // "The result List must contain the keys of all non-configurable own properties of the target object."
57
+ const keys = [];
58
+ const deleteKeys = [];
59
+ for (const name of Object.keys(attributes)) {
60
+ if (name.startsWith('data-')) {
61
+ const key = Dataset.kebabToCamelCase(name.replace('data-', ''));
62
+ keys.push(key);
63
+ dataset[key] = attributes[name].value;
64
+ if (!dataset[key]) {
65
+ deleteKeys.push(key);
66
+ }
67
+ }
68
+ }
69
+ for (const key of deleteKeys) {
70
+ delete dataset[key];
71
+ }
72
+ return keys;
73
+ },
74
+ has(_dataset: DatasetRecord, key: string): boolean {
75
+ return !!attributes['data-' + Dataset.camelCaseToKebab(key)];
76
+ }
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Transforms a kebab cased string to camel case.
82
+ *
83
+ * @param text Text string.
84
+ * @returns Camel cased string.
85
+ */
86
+ public static kebabToCamelCase(text: string): string {
87
+ const parts = text.split('-');
88
+ for (let i = 0, max = parts.length; i < max; i++) {
89
+ parts[i] = i > 0 ? parts[i].charAt(0).toUpperCase() + parts[i].slice(1) : parts[i];
90
+ }
91
+ return parts.join('');
92
+ }
93
+
94
+ /**
95
+ * Transforms a camel cased string to kebab case.
96
+ *
97
+ * @param text Text string.
98
+ * @returns Kebab cased string.
99
+ */
100
+ public static camelCaseToKebab(text: string): string {
101
+ return text
102
+ .toString()
103
+ .replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase());
104
+ }
105
+ }
@@ -4,7 +4,7 @@ import CSSStyleDeclaration from '../../css/declaration/CSSStyleDeclaration';
4
4
  import IAttr from '../attr/IAttr';
5
5
  import FocusEvent from '../../event/events/FocusEvent';
6
6
  import PointerEvent from '../../event/events/PointerEvent';
7
- import DatasetUtility from './DatasetUtility';
7
+ import Dataset from '../element/Dataset';
8
8
  import NodeTypeEnum from '../node/NodeTypeEnum';
9
9
  import DOMException from '../../exception/DOMException';
10
10
  import Event from '../../event/Event';
@@ -28,7 +28,7 @@ export default class HTMLElement extends Element implements IHTMLElement {
28
28
  public readonly clientWidth = 0;
29
29
 
30
30
  private _style: CSSStyleDeclaration = null;
31
- private _dataset: { [key: string]: string } = null;
31
+ private _dataset: Dataset = null;
32
32
 
33
33
  // Events
34
34
  public oncopy: (event: Event) => void | null = null;
@@ -216,72 +216,7 @@ export default class HTMLElement extends Element implements IHTMLElement {
216
216
  * @returns Data set.
217
217
  */
218
218
  public get dataset(): { [key: string]: string } {
219
- if (this._dataset) {
220
- return this._dataset;
221
- }
222
-
223
- const dataset: { [key: string]: string } = {};
224
- const attributes = this._attributes;
225
-
226
- for (const name of Object.keys(attributes)) {
227
- if (name.startsWith('data-')) {
228
- const key = DatasetUtility.kebabToCamelCase(name.replace('data-', ''));
229
- dataset[key] = attributes[name].value;
230
- }
231
- }
232
-
233
- // Documentation for Proxy:
234
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
235
- this._dataset = new Proxy(dataset, {
236
- get: (dataset: { [key: string]: string }, key: string): string => {
237
- const name = 'data-' + DatasetUtility.camelCaseToKebab(key);
238
- if (this._attributes[name]) {
239
- dataset[key] = this._attributes[name].value;
240
- return this._attributes[name].value;
241
- }
242
- if (dataset[key] !== undefined) {
243
- delete dataset[key];
244
- }
245
- return undefined;
246
- },
247
- set: (dataset: { [key: string]: string }, key: string, value: string): boolean => {
248
- this.setAttribute('data-' + DatasetUtility.camelCaseToKebab(key), value);
249
- dataset[key] = value;
250
- return true;
251
- },
252
- deleteProperty: (dataset: { [key: string]: string }, key: string) => {
253
- const name = 'data-' + DatasetUtility.camelCaseToKebab(key);
254
- const result1 = delete attributes[name];
255
- const result2 = delete dataset[key];
256
- return result1 && result2;
257
- },
258
- ownKeys: (dataset: { [key: string]: string }) => {
259
- // According to Mozilla we have to update the dataset object (target) to contain the same keys as what we return:
260
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys
261
- // "The result List must contain the keys of all non-configurable own properties of the target object."
262
- const keys = [];
263
- const deleteKeys = [];
264
- for (const name of Object.keys(attributes)) {
265
- if (name.startsWith('data-')) {
266
- const key = DatasetUtility.kebabToCamelCase(name.replace('data-', ''));
267
- keys.push(key);
268
- dataset[key] = attributes[name].value;
269
- if (!dataset[key]) {
270
- deleteKeys.push(key);
271
- }
272
- }
273
- }
274
- for (const key of deleteKeys) {
275
- delete dataset[key];
276
- }
277
- return keys;
278
- },
279
- has: (_dataset: { [key: string]: string }, key: string) => {
280
- return !!attributes['data-' + DatasetUtility.camelCaseToKebab(key)];
281
- }
282
- });
283
-
284
- return this._dataset;
219
+ return (this._dataset ??= new Dataset(this)).proxy;
285
220
  }
286
221
 
287
222
  /**
@@ -4,6 +4,7 @@ import ISVGElement from './ISVGElement';
4
4
  import ISVGSVGElement from './ISVGSVGElement';
5
5
  import IAttr from '../attr/IAttr';
6
6
  import Event from '../../event/Event';
7
+ import Dataset from '../element/Dataset';
7
8
 
8
9
  /**
9
10
  * SVG Element.
@@ -21,6 +22,7 @@ export default class SVGElement extends Element implements ISVGElement {
21
22
  public onunload: (event: Event) => void | null = null;
22
23
 
23
24
  private _style: CSSStyleDeclaration = null;
25
+ private _dataset: Dataset = null;
24
26
 
25
27
  /**
26
28
  * Returns viewport.
@@ -54,13 +56,7 @@ export default class SVGElement extends Element implements ISVGElement {
54
56
  * @returns Data set.
55
57
  */
56
58
  public get dataset(): { [key: string]: string } {
57
- const dataset = {};
58
- for (const name of Object.keys(this._attributes)) {
59
- if (name.startsWith('data-')) {
60
- dataset[name.replace('data-', '')] = this._attributes[name].value;
61
- }
62
- }
63
- return dataset;
59
+ return (this._dataset ??= new Dataset(this)).proxy;
64
60
  }
65
61
 
66
62
  /**
@@ -558,7 +558,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
558
558
  accept: '*/*',
559
559
  referer: location.href,
560
560
  'user-agent': navigator.userAgent,
561
- cookie: document.cookie
561
+ cookie: document._cookie.getCookieString(location, false)
562
562
  };
563
563
  }
564
564
 
@@ -973,13 +973,17 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget {
973
973
  * @param headers String array.
974
974
  */
975
975
  private _setCookies(headers: string[] | HTTP.IncomingHttpHeaders): void {
976
+ const originURL = new URL(this._settings.url, this._ownerDocument.defaultView.location);
976
977
  for (const header of ['set-cookie', 'set-cookie2']) {
977
978
  if (Array.isArray(headers[header])) {
978
979
  for (const cookie of headers[header]) {
979
- this._ownerDocument.defaultView.document._cookie.setCookiesString(cookie);
980
+ this._ownerDocument.defaultView.document._cookie.addCookieString(originURL, cookie);
980
981
  }
981
982
  } else if (headers[header]) {
982
- this._ownerDocument.defaultView.document._cookie.setCookiesString(headers[header]);
983
+ this._ownerDocument.defaultView.document._cookie.addCookieString(
984
+ originURL,
985
+ headers[header]
986
+ );
983
987
  }
984
988
  }
985
989
  }
@@ -1,20 +0,0 @@
1
- /**
2
- * Dataset utility.
3
- */
4
- export default class DatasetUtility {
5
- /**
6
- * Transforms a kebab cased string to camel case.
7
- *
8
- * @param text Text string.
9
- * @returns Camel cased string.
10
- */
11
- static kebabToCamelCase(text: string): string;
12
- /**
13
- * Transforms a camel cased string to kebab case.
14
- *
15
- * @param text Text string.
16
- * @returns Kebab cased string.
17
- */
18
- static camelCaseToKebab(text: string): string;
19
- }
20
- //# sourceMappingURL=DatasetUtility.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DatasetUtility.d.ts","sourceRoot":"","sources":["../../../src/nodes/html-element/DatasetUtility.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,cAAc;IAClC;;;;;OAKG;WACW,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAQpD;;;;;OAKG;WACW,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAKpD"}