@shgysk8zer0/polyfills 0.0.2
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.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/abort.js +194 -0
- package/all.js +30 -0
- package/animation.js +33 -0
- package/aom.js +43 -0
- package/appBadge.js +27 -0
- package/array.js +258 -0
- package/assets/CookieStore.js +230 -0
- package/assets/Lock.js +31 -0
- package/assets/LockManager.js +278 -0
- package/assets/Sanitizer.js +59 -0
- package/assets/SanitizerConfig.js +348 -0
- package/assets/SanitizerConfigBase.js +18 -0
- package/assets/SanitizerConfigEdge.js +335 -0
- package/assets/SanitizerConfigW3C.js +766 -0
- package/assets/Scheduler.js +124 -0
- package/assets/TextDecoder.js +83 -0
- package/assets/TextEncoder.js +41 -0
- package/assets/TrustedTypes.js +595 -0
- package/assets/attributes.js +15 -0
- package/assets/csp.js +29 -0
- package/assets/error.js +8 -0
- package/assets/sanitizerUtils.js +270 -0
- package/assets/trust.js +190 -0
- package/assets/utility.js +101 -0
- package/cookieStore.js +2 -0
- package/crypto.js +8 -0
- package/dialog.js +63 -0
- package/element.js +171 -0
- package/elementInternals.js +616 -0
- package/errors.js +21 -0
- package/function.js +31 -0
- package/globalThis.js +36 -0
- package/iterator.js +197 -0
- package/legacy/array.js +15 -0
- package/legacy/element.js +140 -0
- package/legacy/map.js +168 -0
- package/legacy/object.js +42 -0
- package/legacy.js +9 -0
- package/locks.js +33 -0
- package/map.js +32 -0
- package/match-media.js +58 -0
- package/math.js +69 -0
- package/navigator.js +55 -0
- package/number.js +14 -0
- package/package.json +52 -0
- package/performance.js +36 -0
- package/promise.js +70 -0
- package/sanitizer.js +3 -0
- package/scheduler.js +5 -0
- package/secure-context.js +31 -0
- package/set.js +73 -0
- package/share.js +17 -0
- package/symbols.js +9 -0
- package/textEncoder.js +16 -0
- package/trustedTypes.js +3 -0
- package/weakMap.js +28 -0
- package/window.js +85 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2023 Chris Zuber <admin@kernvalley.us>
|
|
3
|
+
* @SEE https://wicg.github.io/cookie-store/
|
|
4
|
+
* @SEE https://wicg.github.io/cookie-store/explainer.html
|
|
5
|
+
* @NOTE: This offers similar methods but is not a substitute and cannot get extended
|
|
6
|
+
* cookie properties, such as expires, etc.
|
|
7
|
+
*
|
|
8
|
+
* @TODO verify spec compliance as best as is possible
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const nativeSupport = 'cookieStore' in globalThis;
|
|
12
|
+
|
|
13
|
+
const symbols = {
|
|
14
|
+
constructor: Symbol('constructor'),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function getAllCookies() {
|
|
18
|
+
if (document.cookie.length === 0) {
|
|
19
|
+
return [];
|
|
20
|
+
} else {
|
|
21
|
+
return document.cookie.split(';').map(str => {
|
|
22
|
+
const [name, value = null] = str.split('=');
|
|
23
|
+
return {
|
|
24
|
+
name: decodeURIComponent(name.trim()),
|
|
25
|
+
value: typeof value === 'string' ? decodeURIComponent(value.trim()) : null,
|
|
26
|
+
path: undefined,
|
|
27
|
+
expires: undefined,
|
|
28
|
+
domain: undefined,
|
|
29
|
+
sameSite: undefined,
|
|
30
|
+
secure: undefined,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function defaultParams({
|
|
37
|
+
name,
|
|
38
|
+
value = null,
|
|
39
|
+
domain = null,
|
|
40
|
+
path = '/',
|
|
41
|
+
expires = null,
|
|
42
|
+
maxAge = null,
|
|
43
|
+
sameSite = 'strict',
|
|
44
|
+
secure = false,
|
|
45
|
+
httpOnly = false,
|
|
46
|
+
}) {
|
|
47
|
+
return { name, value, domain, path, expires, maxAge, sameSite, secure, httpOnly };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getter({ name = null, value = null } = {}) {
|
|
51
|
+
if (typeof name === 'string') {
|
|
52
|
+
name = decodeURIComponent(name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof value === 'string') {
|
|
56
|
+
value = decodeURIComponent(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof name === 'string' && typeof value === 'string') {
|
|
60
|
+
return getAllCookies().filter((cookie) =>
|
|
61
|
+
cookie.name === name && cookie.value === value
|
|
62
|
+
);
|
|
63
|
+
} else if (typeof name === 'string') {
|
|
64
|
+
return getAllCookies().filter(cookie => cookie.name === name);
|
|
65
|
+
} else if (typeof value === 'string') {
|
|
66
|
+
return getAllCookies().filter(cookie => cookie.value === value);
|
|
67
|
+
} else {
|
|
68
|
+
return getAllCookies();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function setter({
|
|
73
|
+
name,
|
|
74
|
+
value,
|
|
75
|
+
expires = null,
|
|
76
|
+
maxAge = null,
|
|
77
|
+
path = '/',
|
|
78
|
+
sameSite = 'strict',
|
|
79
|
+
domain = null,
|
|
80
|
+
secure = false,
|
|
81
|
+
httpOnly = false,
|
|
82
|
+
}) {
|
|
83
|
+
if (Number.isInteger(maxAge)) {
|
|
84
|
+
setter({
|
|
85
|
+
name, value, expires: Date.now() + maxAge, path, sameSite, domain, secure, httpOnly,
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
let cookie = `${encodeURIComponent(name)}=`;
|
|
89
|
+
|
|
90
|
+
if (value) {
|
|
91
|
+
cookie += encodeURIComponent(value);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (Number.isInteger(expires)) {
|
|
95
|
+
cookie += `;expires=${new Date(expires).toUTCString()}`;
|
|
96
|
+
} else if (expires instanceof Date) {
|
|
97
|
+
cookie += `;expires=${expires.toUTCString()}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (typeof path === 'string') {
|
|
101
|
+
cookie += `;path=${path}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof domain === 'string') {
|
|
105
|
+
cookie += `;domain=${domain}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof sameSite === 'string') {
|
|
109
|
+
cookie += `;sameSite=${sameSite}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (secure === true) {
|
|
113
|
+
cookie += ';secure';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Does not work in any browser, but set regardless
|
|
118
|
+
*/
|
|
119
|
+
if (httpOnly === true) {
|
|
120
|
+
cookie += ';httponly';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
document.cookie = cookie;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class CookieStore extends EventTarget {
|
|
128
|
+
constructor(key) {
|
|
129
|
+
if (key !== symbols.constructor) {
|
|
130
|
+
throw new DOMException('Invalid constructor');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
super();
|
|
134
|
+
|
|
135
|
+
Object.defineProperty(this, symbols.onchange, {
|
|
136
|
+
enumerable: false,
|
|
137
|
+
configurable: false,
|
|
138
|
+
writable: true,
|
|
139
|
+
value: null,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
get onchange() {
|
|
144
|
+
return this[symbols.onchange];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
set onchange(callback) {
|
|
148
|
+
if (this[symbols.onchange] instanceof Function) {
|
|
149
|
+
this.removeEventListener('change', this[symbols.onchange]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (callback instanceof Function) {
|
|
153
|
+
this.addEventListener('change', callback);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async get(args) {
|
|
158
|
+
const cookies = await this.getAll(args);
|
|
159
|
+
|
|
160
|
+
if (Array.isArray(cookies) && cookies.length !== 0) {
|
|
161
|
+
return cookies[0];
|
|
162
|
+
} else {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getAll(args) {
|
|
168
|
+
if (typeof args === 'string') {
|
|
169
|
+
return getter({ name: args });
|
|
170
|
+
} else {
|
|
171
|
+
return getter(args);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async set(...args) {
|
|
176
|
+
if (args.length === 1 && typeof args[0].name === 'string') {
|
|
177
|
+
const cookie = defaultParams(args[0]);
|
|
178
|
+
setter(cookie);
|
|
179
|
+
const event = new Event('change');
|
|
180
|
+
event.changed = [cookie];
|
|
181
|
+
event.deleted = [];
|
|
182
|
+
|
|
183
|
+
this.dispatchEvent(event);
|
|
184
|
+
} else if (args.length === 2) {
|
|
185
|
+
this.set({ name: args[0], value: args[1] });
|
|
186
|
+
} else {
|
|
187
|
+
throw new Error('Invalid arguments');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async delete(args = {}) {
|
|
192
|
+
if (typeof args === 'string') {
|
|
193
|
+
this.delete({ name: args });
|
|
194
|
+
} else if (typeof args.name === 'string') {
|
|
195
|
+
const cookies = await this.getAll(args);
|
|
196
|
+
|
|
197
|
+
if (cookies.length !== 0) {
|
|
198
|
+
const event = new Event('change');
|
|
199
|
+
event.changed = [];
|
|
200
|
+
event.deleted = new Array(cookies.length);
|
|
201
|
+
|
|
202
|
+
cookies.forEach((cookie, i) => {
|
|
203
|
+
cookie.path = args.path || '/';
|
|
204
|
+
cookie.domain = args.domain || null;
|
|
205
|
+
cookie.secure = args.secure || null;
|
|
206
|
+
delete cookie.value;
|
|
207
|
+
cookie.sameSite = args.sameSite || 'strict';
|
|
208
|
+
|
|
209
|
+
event.deleted[i] = cookie;
|
|
210
|
+
setter({...cookie, value: null, expires: 1 });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
this.dispatchEvent(event);
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
throw new TypeError('Failed to execute \'delete\' on \'CookieStore\': required member name is undefined.');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export const cookieStore = new CookieStore(symbols.constructor);
|
|
222
|
+
|
|
223
|
+
export function polyfill() {
|
|
224
|
+
if (! nativeSupport) {
|
|
225
|
+
globalThis.cookieStore = cookieStore;
|
|
226
|
+
return true;
|
|
227
|
+
} else {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
package/assets/Lock.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2023 Chris Zuber <admin@kernvalley.us>
|
|
3
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Lock
|
|
4
|
+
*/
|
|
5
|
+
const protectedData = new WeakMap();
|
|
6
|
+
|
|
7
|
+
export class Lock {
|
|
8
|
+
constructor(name, mode = 'exclusive') {
|
|
9
|
+
if (! ['exclusive', 'shared'].includes(mode)) {
|
|
10
|
+
throw new TypeError(`'${mode}' (value of 'mode' member of LockOptions) is not a valid value for enumeration LockMode.`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof name !== 'string') {
|
|
14
|
+
name = name.toString();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (name.startsWith('-')) {
|
|
18
|
+
throw new DOMException('Names starting with `-` are reserved');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protectedData.set(this, { name, mode });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get name() {
|
|
25
|
+
return protectedData.get(this).name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get mode() {
|
|
29
|
+
return protectedData.get(this).mode;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2023 Chris Zuber <admin@kernvalley.us>
|
|
3
|
+
*/
|
|
4
|
+
import { getDeferred, isAsync } from './utility.js';
|
|
5
|
+
import { Lock } from './Lock.js';
|
|
6
|
+
const locks = new Map();
|
|
7
|
+
const symbols = {
|
|
8
|
+
lockKey: Symbol('lock-key'),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const nativeSupport = 'locks' in navigator && navigator.locks.request instanceof Function;
|
|
12
|
+
export async function checkSupport() {
|
|
13
|
+
return await new Promise(resolve => {
|
|
14
|
+
if ('locks' in navigator && navigator.locks.request instanceof Function) {
|
|
15
|
+
navigator.locks.query().then(() => resolve(true)).catch(() => resolve(false));
|
|
16
|
+
} else {
|
|
17
|
+
resolve(false);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Some browsing contexts (iframes) have `navigator.locks` but methods only throw
|
|
23
|
+
*/
|
|
24
|
+
export const actuallySupported = checkSupport();
|
|
25
|
+
|
|
26
|
+
async function callFunction(callback, arg = null) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
if (isAsync(callback)) {
|
|
29
|
+
callback.call(globalThis, arg).then(resolve, reject);
|
|
30
|
+
} else {
|
|
31
|
+
queueMicrotask(() => {
|
|
32
|
+
try {
|
|
33
|
+
const result = callback.call(globalThis, arg);
|
|
34
|
+
resolve(result);
|
|
35
|
+
} catch(err) {
|
|
36
|
+
reject(err);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function shouldPend({ name, mode }) {
|
|
44
|
+
switch(mode) {
|
|
45
|
+
case 'exclusive': return getLocks().some(lock => lock.name === name);
|
|
46
|
+
case 'shared': return getLocks().some(lock => lock.name === name && lock.mode === 'exclusive');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function stealLocks(name) {
|
|
51
|
+
[...locks.entries()].filter(([lock]) => lock.name === name).forEach(([lock, { reject, controller }]) => {
|
|
52
|
+
requestIdleCallback(() => {
|
|
53
|
+
controller.abort();
|
|
54
|
+
locks.delete(lock);
|
|
55
|
+
});
|
|
56
|
+
reject(new DOMException('The lock request is aborted'));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function whenReleased(lock) {
|
|
61
|
+
if (locks.has(lock)) {
|
|
62
|
+
await locks.get(lock).promise.catch(console.error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function queueTask(name, mode, callback) {
|
|
67
|
+
const { resolve, reject, promise } = getDeferred();
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
const lock = new Lock(name, mode);
|
|
70
|
+
const pending = shouldPend(lock);
|
|
71
|
+
locks.set(lock, { resolve, reject, promise, callback, pending, controller });
|
|
72
|
+
return lock;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function setPending(lock, pending = true) {
|
|
76
|
+
if (locks.has(lock)) {
|
|
77
|
+
locks.set(lock, {...locks.get(lock), pending });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isPending(lock) {
|
|
82
|
+
return lock instanceof Lock && locks.has(lock) && locks.get(lock).pending;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getLocks(name) {
|
|
86
|
+
if (typeof name === 'string') {
|
|
87
|
+
return [...locks.keys()].filter(lock => lock.name === name);
|
|
88
|
+
} else {
|
|
89
|
+
return [...locks.keys()];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getHeldLocks(name) {
|
|
94
|
+
return getLocks(name).filter(lock => ! isPending(lock));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getPendingLocks(name) {
|
|
98
|
+
return getLocks(name).filter(lock => isPending(lock));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function whenNotBlocked(lock) {
|
|
102
|
+
switch(lock.mode) {
|
|
103
|
+
case 'exclusive':
|
|
104
|
+
await Promise.allSettled(getLocks()
|
|
105
|
+
.filter(l => l.name === lock.name && l !== lock).map(whenReleased));
|
|
106
|
+
break;
|
|
107
|
+
case 'shared':
|
|
108
|
+
await Promise.allSettled(getLocks()
|
|
109
|
+
.filter(l => l.name === lock.name && l.mode === 'exclusive' && l !== lock).map(whenReleased));
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getLockSignal(lock) {
|
|
115
|
+
if (locks.has(lock)) {
|
|
116
|
+
return locks.get(lock).controller.signal;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function executeLock(lock) {
|
|
121
|
+
if (locks.has(lock)) {
|
|
122
|
+
const { resolve, reject, promise, callback, pending, controller } = locks.get(lock);
|
|
123
|
+
if (pending) {
|
|
124
|
+
setPending(lock, false);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
callFunction(callback, lock).then(resolve, reject).finally(() => {
|
|
128
|
+
locks.delete(lock);
|
|
129
|
+
requestIdleCallback(() => controller.abort());
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return promise;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @see https://w3c.github.io/web-locks/
|
|
138
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/LockManager
|
|
139
|
+
*/
|
|
140
|
+
export class LockManager {
|
|
141
|
+
constructor(key) {
|
|
142
|
+
if (key !== symbols.lockKey) {
|
|
143
|
+
throw new TypeError('Invalid constructor');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request
|
|
148
|
+
* @param {[type]} name [description]
|
|
149
|
+
* @param {[type]} args [description]
|
|
150
|
+
* @return {Promise} [description]
|
|
151
|
+
* static async request(name, callback)
|
|
152
|
+
* static async request(name, { mode = 'exclusive', ifAvailable = false, steal = false, signal }, callback)
|
|
153
|
+
*/
|
|
154
|
+
static async request(name, ...args) {
|
|
155
|
+
let opts = {}, callback;
|
|
156
|
+
if (args[0] instanceof Function) {
|
|
157
|
+
callback = args[0];
|
|
158
|
+
} else if (args[1] instanceof Function) {
|
|
159
|
+
[opts, callback] = args;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (typeof name !== 'string') {
|
|
163
|
+
name = name.toString();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const { mode = 'exclusive', ifAvailable = false, steal = false, signal } = opts;
|
|
167
|
+
|
|
168
|
+
if (steal && ifAvailable) {
|
|
169
|
+
throw new DOMException('LockManager.request: `steal` and `ifAvailable` cannot be used together');
|
|
170
|
+
} else if (name.startsWith('-')) {
|
|
171
|
+
throw new DOMException('LockManager.request: Names starting with `-` are reserved');
|
|
172
|
+
} else if (! ['exclusive', 'shared'].includes(mode)) {
|
|
173
|
+
throw new TypeError(`LockManager.request: '${mode}' (value of 'mode' member of LockOptions) is not a valid value for enumeration LockMode.`);
|
|
174
|
+
} else if (signal instanceof AbortSignal && signal.aborted) {
|
|
175
|
+
throw signal.reason;
|
|
176
|
+
} else if (mode === 'shared' && steal) {
|
|
177
|
+
throw new DOMException('LockManager.request: `steal` is only supported for exclusive lock requests');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const held = getHeldLocks(name);
|
|
181
|
+
const pending = getPendingLocks(name);
|
|
182
|
+
const alreadyLocked = [...held, ...pending].some(lock => lock.name === name);
|
|
183
|
+
|
|
184
|
+
if (steal && alreadyLocked) {
|
|
185
|
+
stealLocks(name);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* If shared & held lock found (none exclusive), then this lock may be held as well
|
|
190
|
+
* If exclusive & held or pending lock found, this is pending
|
|
191
|
+
*/
|
|
192
|
+
const lock = queueTask(name, mode, callback);
|
|
193
|
+
if (signal instanceof AbortSignal) {
|
|
194
|
+
signal.addEventListener('abort', () => {
|
|
195
|
+
if (locks.has(lock)) {
|
|
196
|
+
const { reject, controller } = locks.get(lock);
|
|
197
|
+
locks.delete(lock);
|
|
198
|
+
reject(new DOMException('The lock request is aborted'));
|
|
199
|
+
controller.abort();
|
|
200
|
+
}
|
|
201
|
+
}, { once: true, signal: getLockSignal(lock) });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
switch(mode) {
|
|
205
|
+
case 'exclusive': {
|
|
206
|
+
if (ifAvailable && (held.length !== 0 || pending.length !== 0)) {
|
|
207
|
+
const controller = locks.get(lock).controller;
|
|
208
|
+
locks.delete(lock);
|
|
209
|
+
return await callFunction(callback, null).then(result => {
|
|
210
|
+
requestIdleCallback(() => controller.abort());
|
|
211
|
+
return result;
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
await whenNotBlocked(lock);
|
|
215
|
+
return await executeLock(lock);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case 'shared': {
|
|
220
|
+
if (! ifAvailable) {
|
|
221
|
+
await whenNotBlocked(lock);
|
|
222
|
+
return await executeLock(lock);
|
|
223
|
+
} else if ([...held, ...pending].some(lock => lock.mode === 'exclusive')) {
|
|
224
|
+
const controller = locks.get(lock).controller;
|
|
225
|
+
locks.delete(lock);
|
|
226
|
+
return await callFunction(callback, null).then(result => {
|
|
227
|
+
requestIdleCallback(() => controller.abort());
|
|
228
|
+
return result;
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
await whenNotBlocked(lock);
|
|
232
|
+
return await executeLock(lock);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
default:
|
|
237
|
+
throw new TypeError(`LockManager.request: '${mode}' (value of 'mode' member of LockOptions) is not a valid value for enumeration LockMode.`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/LockManager/query
|
|
243
|
+
* @return {Promise} { held: [], pending: [] }
|
|
244
|
+
*/
|
|
245
|
+
static async query() {
|
|
246
|
+
return {
|
|
247
|
+
held: getHeldLocks().map(({ name, mode }) => ({ name, mode, clientId: null })),
|
|
248
|
+
pending: getPendingLocks().map(({ name, mode }) => ({ name, mode, clientId: null })),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function request(...args) {
|
|
254
|
+
if (await actuallySupported) {
|
|
255
|
+
return navigator.locks.request(...args);
|
|
256
|
+
} else {
|
|
257
|
+
return LockManager.request(...args);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export async function query() {
|
|
262
|
+
if (await actuallySupported) {
|
|
263
|
+
return navigator.locks.query();
|
|
264
|
+
} else {
|
|
265
|
+
return LockManager.query();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function polyfill() {
|
|
270
|
+
if (! nativeSupport) {
|
|
271
|
+
globalThis.Lock = Lock;
|
|
272
|
+
globalThis.LockManager = LockManager;
|
|
273
|
+
navigator.locks = LockManager;
|
|
274
|
+
} else if (! await actuallySupported) {
|
|
275
|
+
navigator.locks.request = (...args) => LockManager.request(...args);
|
|
276
|
+
navigator.locks.query = () => LockManager.query();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2022-2023 Chris Zuber <admin@kernvalley.us>
|
|
3
|
+
*/
|
|
4
|
+
import { nativeSupport, getSantizerUtils, sanitize, sanitizeFor, trustPolicies } from './sanitizerUtils.js';
|
|
5
|
+
import { SanitizerConfig as defaultConfig } from './SanitizerConfigW3C.js';
|
|
6
|
+
|
|
7
|
+
const protectedData = new WeakMap();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Need to create a policy for the Sanitizer API since
|
|
11
|
+
* `trustedTypes.defaultPolicy.createHTML` will most likely use `new Sanitizer().sanitize()`
|
|
12
|
+
* which would create infinite recursion.
|
|
13
|
+
* @type {TrustedTypePolicy}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @SEE https://wicg.github.io/sanitizer-api/
|
|
19
|
+
* @SEE https://developer.mozilla.org/en-US/docs/Web/API/Sanitizer/Sanitizer
|
|
20
|
+
* @TODO: Figure out how to handle `allowElements`, `allowAttributes`, and how each
|
|
21
|
+
* works with their `block*` and/or `drop*` counterparts.
|
|
22
|
+
* @TODO: Handle `svg:*` and `mathml:*`
|
|
23
|
+
*
|
|
24
|
+
* @NOTE: The spec is still under development and is likely to change.
|
|
25
|
+
* @NOTE: This is a very imperfect implementation and may not perform very well,
|
|
26
|
+
* as it may involve a lot of querying & modifying.
|
|
27
|
+
*/
|
|
28
|
+
export class Sanitizer {
|
|
29
|
+
constructor({
|
|
30
|
+
allowElements, allowAttributes, blockElements, dropAttributes,
|
|
31
|
+
dropElements, allowComments = defaultConfig.allowComments,
|
|
32
|
+
allowCustomElements = defaultConfig.allowCustomElements,
|
|
33
|
+
allowUnknownMarkup = defaultConfig.allowUnknownMarkup,
|
|
34
|
+
} = Sanitizer.getDefaultConfiguration()) {
|
|
35
|
+
protectedData.set(this, {
|
|
36
|
+
allowElements, allowComments, allowAttributes, allowCustomElements,
|
|
37
|
+
blockElements, dropAttributes, dropElements, allowUnknownMarkup,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getConfiguration() {
|
|
42
|
+
return protectedData.get(this);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
sanitize(input) {
|
|
46
|
+
return sanitize(input, { config: this.getConfiguration() });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
sanitizeFor(tag, content) {
|
|
50
|
+
return sanitizeFor(tag, content, { config: this.getConfiguration() });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static getDefaultConfiguration() {
|
|
54
|
+
return defaultConfig;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { setHTML, polyfill } = getSantizerUtils(Sanitizer, defaultConfig);
|
|
59
|
+
export { nativeSupport, setHTML, polyfill, trustPolicies };
|