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.
- package/lib/cookie/Cookie.d.ts +18 -28
- package/lib/cookie/Cookie.d.ts.map +1 -1
- package/lib/cookie/Cookie.js +56 -83
- package/lib/cookie/Cookie.js.map +1 -1
- package/lib/cookie/CookieJar.d.ts +9 -13
- package/lib/cookie/CookieJar.d.ts.map +1 -1
- package/lib/cookie/CookieJar.js +41 -49
- package/lib/cookie/CookieJar.js.map +1 -1
- package/lib/cookie/CookieSameSiteEnum.d.ts +7 -0
- package/lib/cookie/CookieSameSiteEnum.d.ts.map +1 -0
- package/lib/cookie/CookieSameSiteEnum.js +10 -0
- package/lib/cookie/CookieSameSiteEnum.js.map +1 -0
- package/lib/fetch/Fetch.d.ts.map +1 -1
- package/lib/fetch/Fetch.js +2 -6
- package/lib/fetch/Fetch.js.map +1 -1
- package/lib/nodes/document/Document.js +2 -2
- package/lib/nodes/document/Document.js.map +1 -1
- package/lib/nodes/element/Dataset.d.ts +34 -0
- package/lib/nodes/element/Dataset.d.ts.map +1 -0
- package/lib/nodes/element/Dataset.js +97 -0
- package/lib/nodes/element/Dataset.js.map +1 -0
- package/lib/nodes/html-element/HTMLElement.d.ts.map +1 -1
- package/lib/nodes/html-element/HTMLElement.js +2 -63
- package/lib/nodes/html-element/HTMLElement.js.map +1 -1
- package/lib/nodes/svg-element/SVGElement.d.ts +1 -0
- package/lib/nodes/svg-element/SVGElement.d.ts.map +1 -1
- package/lib/nodes/svg-element/SVGElement.js +3 -7
- package/lib/nodes/svg-element/SVGElement.js.map +1 -1
- package/lib/xml-http-request/XMLHttpRequest.d.ts.map +1 -1
- package/lib/xml-http-request/XMLHttpRequest.js +4 -3
- package/lib/xml-http-request/XMLHttpRequest.js.map +1 -1
- package/package.json +1 -1
- package/src/cookie/Cookie.ts +58 -91
- package/src/cookie/CookieJar.ts +52 -53
- package/src/cookie/CookieSameSiteEnum.ts +6 -0
- package/src/fetch/Fetch.ts +6 -8
- package/src/nodes/document/Document.ts +2 -2
- package/src/nodes/element/Dataset.ts +105 -0
- package/src/nodes/html-element/HTMLElement.ts +3 -68
- package/src/nodes/svg-element/SVGElement.ts +3 -7
- package/src/xml-http-request/XMLHttpRequest.ts +7 -3
- package/lib/nodes/html-element/DatasetUtility.d.ts +0 -20
- package/lib/nodes/html-element/DatasetUtility.d.ts.map +0 -1
- package/lib/nodes/html-element/DatasetUtility.js +0 -33
- package/lib/nodes/html-element/DatasetUtility.js.map +0 -1
- package/src/nodes/html-element/DatasetUtility.ts +0 -30
package/src/cookie/Cookie.ts
CHANGED
@@ -1,61 +1,54 @@
|
|
1
|
-
|
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
|
-
|
8
|
-
//
|
10
|
+
// Required
|
9
11
|
public key = '';
|
10
|
-
public value =
|
11
|
-
public
|
12
|
+
public value: string | null = null;
|
13
|
+
public originURL: URL;
|
14
|
+
|
12
15
|
// Optional
|
13
16
|
public domain = '';
|
14
17
|
public path = '';
|
15
|
-
public
|
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
|
-
|
29
|
+
constructor(originURL, cookie: string) {
|
30
|
+
const parts = cookie.split(';');
|
31
|
+
const [key, value] = parts.shift().split('=');
|
27
32
|
|
28
|
-
|
33
|
+
this.originURL = originURL;
|
34
|
+
this.key = key.trim();
|
35
|
+
this.value = value !== undefined ? value : null;
|
29
36
|
|
30
|
-
|
31
|
-
|
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
|
-
|
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.
|
48
|
+
this.expires = new Date(value);
|
56
49
|
break;
|
57
50
|
case 'max-age':
|
58
|
-
this.
|
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
|
-
|
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
|
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
|
108
|
-
if (this.
|
86
|
+
public toString(): string {
|
87
|
+
if (this.value !== null) {
|
109
88
|
return `${this.key}=${this.value}`;
|
110
89
|
}
|
111
|
-
|
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.
|
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
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
return
|
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
|
}
|
package/src/cookie/CookieJar.ts
CHANGED
@@ -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
|
-
*
|
15
|
+
* Adds cookie string.
|
15
16
|
*
|
16
|
-
* @param
|
17
|
+
* @param originURL Origin URL.
|
18
|
+
* @param cookieString Cookie string.
|
17
19
|
*/
|
18
|
-
|
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
|
-
|
41
|
-
|
24
|
+
|
25
|
+
const newCookie = new Cookie(originURL, cookieString);
|
26
|
+
|
27
|
+
if (!newCookie.validate()) {
|
42
28
|
return;
|
43
29
|
}
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
this.cookies.
|
48
|
-
|
49
|
-
|
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
|
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
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
return cookies.map((cookie) => cookie.cookieString()).join('; ');
|
77
|
+
}
|
78
|
+
|
79
|
+
return cookieString;
|
81
80
|
}
|
82
81
|
}
|
package/src/fetch/Fetch.ts
CHANGED
@@ -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.
|
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
|
-
|
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.
|
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.
|
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
|
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:
|
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
|
-
|
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
|
-
|
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.
|
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.
|
980
|
+
this._ownerDocument.defaultView.document._cookie.addCookieString(originURL, cookie);
|
980
981
|
}
|
981
982
|
} else if (headers[header]) {
|
982
|
-
this._ownerDocument.defaultView.document._cookie.
|
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"}
|