react-native-browser-with-polyfill 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.js +11 -0
- package/dist/index.js +194 -0
- package/package.json +20 -6
- package/index.js +0 -29
- package/src/createBrowser.jsx +0 -258
- package/src/polyfills/dev-keyboard-bar.js +0 -670
- package/src/polyfills/ipados15-polyfill.js +0 -1859
|
@@ -1,1859 +0,0 @@
|
|
|
1
|
-
// ==UserScript==
|
|
2
|
-
// @name iPadOS 15 Safari Polyfill Suite
|
|
3
|
-
// @namespace https://github.com/huyquoc/ipados15-polyfill
|
|
4
|
-
// @version 1.6.4
|
|
5
|
-
// @description Comprehensive JS and CSS polyfills for iPadOS 15 Safari (WebKit 15.x). Fixes broken layouts on modern sites.
|
|
6
|
-
// @author Huy (Huy Quoc)
|
|
7
|
-
// @match *://*/*
|
|
8
|
-
// @grant none
|
|
9
|
-
// @run-at document-start
|
|
10
|
-
// @license MIT
|
|
11
|
-
// ==/UserScript==
|
|
12
|
-
|
|
13
|
-
/*
|
|
14
|
-
* =====================================================================
|
|
15
|
-
* iPadOS 15 Safari Polyfill Suite v1.4.0
|
|
16
|
-
* =====================================================================
|
|
17
|
-
*
|
|
18
|
-
* Polyfills JavaScript and CSS features missing from Safari 15.x
|
|
19
|
-
* (equivalent to Safari 15.0-15.5 shipped with iPadOS 15).
|
|
20
|
-
*
|
|
21
|
-
* JS features polyfilled:
|
|
22
|
-
* - globalThis
|
|
23
|
-
* - Object.hasOwn()
|
|
24
|
-
* - Object.groupBy() / Map.groupBy()
|
|
25
|
-
* - Array.fromAsync()
|
|
26
|
-
* - Array.prototype.at()
|
|
27
|
-
* - Array.prototype.findLast() / findLastIndex()
|
|
28
|
-
* - String.prototype.replaceAll()
|
|
29
|
-
* - Element.prototype.replaceAll()
|
|
30
|
-
* - Element.prototype.checkVisibility()
|
|
31
|
-
* - Promise.any() / AggregateError
|
|
32
|
-
* - Promise.withResolvers()
|
|
33
|
-
* - Set methods: union, intersection, difference, isSubsetOf, isSupersetOf, isDisjointFrom
|
|
34
|
-
* - URL.canParse()
|
|
35
|
-
* - AbortSignal.timeout()
|
|
36
|
-
* - AbortSignal.any()
|
|
37
|
-
* - crypto.randomUUID()
|
|
38
|
-
* - structuredClone()
|
|
39
|
-
* - Iterator helpers: Iterator.from(), Iterator.prototype.map(), filter(), take(), drop(), forEach(), toArray()
|
|
40
|
-
* - CSS reset for computed fallbacks
|
|
41
|
-
*
|
|
42
|
-
* CSS features polyfilled:
|
|
43
|
-
* - :has() pseudo-class → JS-based parent selector
|
|
44
|
-
* - @container rules → rewritten to @media or JS-calculated
|
|
45
|
-
* - CSS nesting syntax → flattened to nested selectors
|
|
46
|
-
* - oklch() / oklab() → converted to lab()/rgb()
|
|
47
|
-
* - color-mix() → JS color interpolation
|
|
48
|
-
* - dvh / svh / lvh / dvw / svw / lvw → viewport-calc fallbacks
|
|
49
|
-
* - text-wrap: balance → JS text balancing fallback
|
|
50
|
-
* - subgrid → IE-style fallback
|
|
51
|
-
* - CSS calc with unsupported units
|
|
52
|
-
*
|
|
53
|
-
* =====================================================================
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
(function () {
|
|
57
|
-
"use strict";
|
|
58
|
-
|
|
59
|
-
// ── Guards and diagnostics ──────────────────────────────────────────
|
|
60
|
-
const POLYFILL_NS = "ipados15-polyfill";
|
|
61
|
-
const _ua = navigator.userAgent;
|
|
62
|
-
const _isSafari = /Safari/.test(_ua) && !/Chrome/.test(_ua);
|
|
63
|
-
const _isIPadOS15 =
|
|
64
|
-
/iPad/.test(_ua) ||
|
|
65
|
-
(/Macintosh/.test(_ua) && navigator.maxTouchPoints > 1);
|
|
66
|
-
|
|
67
|
-
// Log diagnostics
|
|
68
|
-
var _applied = [];
|
|
69
|
-
function _log(feature, status) {
|
|
70
|
-
status = status || "polyfilled";
|
|
71
|
-
_applied.push(feature + " [" + status + "]");
|
|
72
|
-
console.log("[%s] %s: %s", POLYFILL_NS, feature, status);
|
|
73
|
-
}
|
|
74
|
-
function _unavailable(feature, reason) {
|
|
75
|
-
_applied.push(feature + " [skipped: " + reason + "]");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Safe wrapper: runs fn in try/catch, logs error but never crashes the script
|
|
79
|
-
function _safe(feature, fn) {
|
|
80
|
-
try {
|
|
81
|
-
fn();
|
|
82
|
-
} catch (e) {
|
|
83
|
-
_log(feature, "error: " + e.message);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Skip on non-iPadOS-15 but still polyfill if feature is actually missing
|
|
88
|
-
// (user may be on desktop Safari 15 too)
|
|
89
|
-
var _shouldSkip =
|
|
90
|
-
!_isSafari &&
|
|
91
|
-
!/Version\/15\./.test(_ua) &&
|
|
92
|
-
!/Version\/16\./.test(_ua) &&
|
|
93
|
-
!/Version\/17\./.test(_ua);
|
|
94
|
-
|
|
95
|
-
// ── 0. globalThis ──────────────────────────────────────────────────
|
|
96
|
-
if (typeof globalThis === "undefined") {
|
|
97
|
-
try {
|
|
98
|
-
var _globalObj;
|
|
99
|
-
if (typeof self !== "undefined") {
|
|
100
|
-
_globalObj = self;
|
|
101
|
-
} else if (typeof window !== "undefined") {
|
|
102
|
-
_globalObj = window;
|
|
103
|
-
} else if (typeof global !== "undefined") {
|
|
104
|
-
_globalObj = global;
|
|
105
|
-
} else {
|
|
106
|
-
_globalObj = Function("return this")();
|
|
107
|
-
}
|
|
108
|
-
Object.defineProperty(_globalObj, "globalThis", {
|
|
109
|
-
value: _globalObj,
|
|
110
|
-
configurable: true,
|
|
111
|
-
writable: true,
|
|
112
|
-
});
|
|
113
|
-
_log("globalThis", "polyfilled");
|
|
114
|
-
} catch (e) {
|
|
115
|
-
_log("globalThis", "error: " + e.message);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ── 1. String.prototype.replaceAll ─────────────────────────────────
|
|
120
|
-
if (typeof String.prototype.replaceAll !== "function") {
|
|
121
|
-
String.prototype.replaceAll = function (search, replacement) {
|
|
122
|
-
var target = String(this);
|
|
123
|
-
var searchStr = String(search);
|
|
124
|
-
if (searchStr === "") {
|
|
125
|
-
// Edge case: replacing empty string inserts between every char
|
|
126
|
-
return target.split("").join(replacement) + replacement;
|
|
127
|
-
}
|
|
128
|
-
// Escape regex special chars if search is a string
|
|
129
|
-
var escaped = searchStr.replace(
|
|
130
|
-
/[.*+?^${}()|[\]\\]/g,
|
|
131
|
-
"\\$&"
|
|
132
|
-
);
|
|
133
|
-
var regex = new RegExp(escaped, "g");
|
|
134
|
-
return target.replace(regex, replacement);
|
|
135
|
-
};
|
|
136
|
-
_log("String.replaceAll", "polyfilled");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ── 2. Object.hasOwn ───────────────────────────────────────────────
|
|
140
|
-
if (typeof Object.hasOwn !== "function") {
|
|
141
|
-
Object.hasOwn = function (obj, prop) {
|
|
142
|
-
if (obj == null) throw new TypeError("Cannot convert undefined/null");
|
|
143
|
-
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
144
|
-
};
|
|
145
|
-
_log("Object.hasOwn", "polyfilled");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ── 3. Object.groupBy / Map.groupBy ────────────────────────────────
|
|
149
|
-
if (typeof Object.groupBy !== "function") {
|
|
150
|
-
Object.groupBy = function (collection, callback) {
|
|
151
|
-
var result = {};
|
|
152
|
-
// Handle iterables and array-like
|
|
153
|
-
if (Symbol.iterator in Object(collection)) {
|
|
154
|
-
for (var item of collection) {
|
|
155
|
-
var key = callback(item, 0);
|
|
156
|
-
result[key] = result[key] || [];
|
|
157
|
-
result[key].push(item);
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
var idx = 0;
|
|
161
|
-
for (var key in collection) {
|
|
162
|
-
if (Object.prototype.hasOwnProperty.call(collection, key)) {
|
|
163
|
-
var val = collection[key];
|
|
164
|
-
var groupKey = callback(val, idx);
|
|
165
|
-
result[groupKey] = result[groupKey] || [];
|
|
166
|
-
result[groupKey].push(val);
|
|
167
|
-
idx++;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return result;
|
|
172
|
-
};
|
|
173
|
-
_log("Object.groupBy", "polyfilled");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (typeof Map.groupBy !== "function") {
|
|
177
|
-
Map.groupBy = function (collection, callback) {
|
|
178
|
-
var result = new Map();
|
|
179
|
-
if (Symbol.iterator in Object(collection)) {
|
|
180
|
-
for (var item of collection) {
|
|
181
|
-
var key = callback(item, 0);
|
|
182
|
-
if (!result.has(key)) result.set(key, []);
|
|
183
|
-
result.get(key).push(item);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return result;
|
|
187
|
-
};
|
|
188
|
-
_log("Map.groupBy", "polyfilled");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ── 4. Array.fromAsync ─────────────────────────────────────────────
|
|
192
|
-
if (typeof Array.fromAsync !== "function") {
|
|
193
|
-
Array.fromAsync = function (asyncIterable, mapFn) {
|
|
194
|
-
var result = [];
|
|
195
|
-
return (async function () {
|
|
196
|
-
try {
|
|
197
|
-
var iterator =
|
|
198
|
-
asyncIterable[Symbol.asyncIterator] ||
|
|
199
|
-
(asyncIterable[Symbol.iterator]
|
|
200
|
-
? (function (iter) {
|
|
201
|
-
return {
|
|
202
|
-
next: function () {
|
|
203
|
-
var r = iter.next();
|
|
204
|
-
return Promise.resolve(r);
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
})(asyncIterable[Symbol.iterator]())
|
|
208
|
-
: null);
|
|
209
|
-
if (!iterator)
|
|
210
|
-
throw new TypeError(
|
|
211
|
-
"Object is not async-iterable"
|
|
212
|
-
);
|
|
213
|
-
var step;
|
|
214
|
-
while (!(step = await iterator.next()).done) {
|
|
215
|
-
var value = step.value;
|
|
216
|
-
if (mapFn) value = mapFn(value);
|
|
217
|
-
result.push(value);
|
|
218
|
-
}
|
|
219
|
-
} catch (e) {
|
|
220
|
-
try {
|
|
221
|
-
await iterator.return();
|
|
222
|
-
} catch (_) {}
|
|
223
|
-
throw e;
|
|
224
|
-
}
|
|
225
|
-
return result;
|
|
226
|
-
})();
|
|
227
|
-
};
|
|
228
|
-
_log("Array.fromAsync", "polyfilled");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// ── 5. Array.prototype.at ─────────────────────────────────────────
|
|
232
|
-
_safe("Array.at", function() {
|
|
233
|
-
if (typeof Array.prototype.at !== "function") {
|
|
234
|
-
Array.prototype.at = function (index) {
|
|
235
|
-
var arr = this;
|
|
236
|
-
var len = arr.length || 0;
|
|
237
|
-
if (len === 0) return undefined;
|
|
238
|
-
if (index < 0) index = len + index;
|
|
239
|
-
if (index < 0 || index >= len) return undefined;
|
|
240
|
-
return arr[index];
|
|
241
|
-
};
|
|
242
|
-
_log("Array.at", "polyfilled");
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// ── 5b. String.prototype.at ────────────────────────────────────────
|
|
247
|
-
_safe("String.at", function() {
|
|
248
|
-
if (typeof String.prototype.at !== "function") {
|
|
249
|
-
String.prototype.at = function (index) {
|
|
250
|
-
var str = String(this);
|
|
251
|
-
var len = str.length;
|
|
252
|
-
if (len === 0) return undefined;
|
|
253
|
-
if (index < 0) index = len + index;
|
|
254
|
-
if (index < 0 || index >= len) return undefined;
|
|
255
|
-
return str[index];
|
|
256
|
-
};
|
|
257
|
-
_log("String.at", "polyfilled");
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// ── 5c. TypedArray.prototype.at ────────────────────────────────────
|
|
262
|
-
_safe("TypedArray.at", function() {
|
|
263
|
-
var TypedArrays = [
|
|
264
|
-
typeof Int8Array !== "undefined" && Int8Array,
|
|
265
|
-
typeof Uint8Array !== "undefined" && Uint8Array,
|
|
266
|
-
typeof Uint8ClampedArray !== "undefined" && Uint8ClampedArray,
|
|
267
|
-
typeof Int16Array !== "undefined" && Int16Array,
|
|
268
|
-
typeof Uint16Array !== "undefined" && Uint16Array,
|
|
269
|
-
typeof Int32Array !== "undefined" && Int32Array,
|
|
270
|
-
typeof Uint32Array !== "undefined" && Uint32Array,
|
|
271
|
-
typeof Float32Array !== "undefined" && Float32Array,
|
|
272
|
-
typeof Float64Array !== "undefined" && Float64Array,
|
|
273
|
-
].filter(Boolean);
|
|
274
|
-
var atAdded = false;
|
|
275
|
-
for (var i = 0; i < TypedArrays.length; i++) {
|
|
276
|
-
var TA = TypedArrays[i];
|
|
277
|
-
if (typeof TA.prototype.at !== "function") {
|
|
278
|
-
TA.prototype.at = function (index) {
|
|
279
|
-
var len = this.length;
|
|
280
|
-
if (len === 0) return undefined;
|
|
281
|
-
if (index < 0) index = len + index;
|
|
282
|
-
if (index < 0 || index >= len) return undefined;
|
|
283
|
-
return this[index];
|
|
284
|
-
};
|
|
285
|
-
atAdded = true;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (atAdded) _log("TypedArray.at", "polyfilled");
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
// ── 6. Array.prototype.findLast / findLastIndex ────────────────────
|
|
292
|
-
if (typeof Array.prototype.findLast !== "function") {
|
|
293
|
-
Array.prototype.findLast = function (predicate, thisArg) {
|
|
294
|
-
for (var i = this.length - 1; i >= 0; i--) {
|
|
295
|
-
if (predicate.call(thisArg, this[i], i, this)) return this[i];
|
|
296
|
-
}
|
|
297
|
-
return undefined;
|
|
298
|
-
};
|
|
299
|
-
_log("Array.findLast", "polyfilled");
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (typeof Array.prototype.findLastIndex !== "function") {
|
|
303
|
-
Array.prototype.findLastIndex = function (predicate, thisArg) {
|
|
304
|
-
for (var i = this.length - 1; i >= 0; i--) {
|
|
305
|
-
if (predicate.call(thisArg, this[i], i, this)) return i;
|
|
306
|
-
}
|
|
307
|
-
return -1;
|
|
308
|
-
};
|
|
309
|
-
_log("Array.findLastIndex", "polyfilled");
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// ── 7. Element.prototype.replaceAll ────────────────────────────────
|
|
313
|
-
if (typeof Element.prototype.replaceAll !== "function") {
|
|
314
|
-
Element.prototype.replaceAll = function (newChild) {
|
|
315
|
-
var parent = this.parentNode;
|
|
316
|
-
if (!parent) return this;
|
|
317
|
-
if (typeof newChild === "string") {
|
|
318
|
-
// Create a document fragment from the HTML string
|
|
319
|
-
var fragment = document.createDocumentFragment();
|
|
320
|
-
var temp = document.createElement("template");
|
|
321
|
-
temp.innerHTML = newChild.trim();
|
|
322
|
-
while (temp.content.firstChild) {
|
|
323
|
-
fragment.appendChild(temp.content.firstChild);
|
|
324
|
-
}
|
|
325
|
-
parent.replaceChild(fragment, this);
|
|
326
|
-
} else {
|
|
327
|
-
parent.replaceChild(newChild, this);
|
|
328
|
-
}
|
|
329
|
-
return this;
|
|
330
|
-
};
|
|
331
|
-
_log("Element.replaceAll", "polyfilled");
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// ── 8. Element.prototype.checkVisibility ───────────────────────────
|
|
335
|
-
if (typeof Element.prototype.checkVisibility !== "function") {
|
|
336
|
-
Element.prototype.checkVisibility = function (opts) {
|
|
337
|
-
var o = opts || {};
|
|
338
|
-
var checkOpacity = o.checkOpacity !== false;
|
|
339
|
-
var checkVisibilityCSS = o.checkVisibilityCSS !== false;
|
|
340
|
-
var el = this;
|
|
341
|
-
if (!checkVisibilityCSS) {
|
|
342
|
-
// If we don't need to check parents, just check this element
|
|
343
|
-
} else {
|
|
344
|
-
el = this.closest(
|
|
345
|
-
":not([hidden]), :not([style*='display: none'])"
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
if (!el) return false;
|
|
349
|
-
var style = getComputedStyle(el);
|
|
350
|
-
return (
|
|
351
|
-
style.display !== "none" &&
|
|
352
|
-
style.visibility !== "hidden" &&
|
|
353
|
-
style.visibility !== "collapse" &&
|
|
354
|
-
(checkOpacity === false || style.opacity !== "0")
|
|
355
|
-
);
|
|
356
|
-
};
|
|
357
|
-
_log("Element.checkVisibility", "polyfilled");
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// ── 9. Promise.any() / AggregateError ──────────────────────────────
|
|
361
|
-
_safe("AggregateError", function() {
|
|
362
|
-
if (typeof AggregateError !== "function") {
|
|
363
|
-
var AggregateError = function (errors, message) {
|
|
364
|
-
if (!(this instanceof AggregateError))
|
|
365
|
-
return new AggregateError(errors, message);
|
|
366
|
-
if (typeof Error.captureStackTrace === "function") {
|
|
367
|
-
Error.captureStackTrace(this, AggregateError);
|
|
368
|
-
}
|
|
369
|
-
this.name = "AggregateError";
|
|
370
|
-
this.errors = Array.isArray(errors) ? errors : [errors];
|
|
371
|
-
this.message = message || "";
|
|
372
|
-
};
|
|
373
|
-
AggregateError.prototype = Object.create(Error.prototype);
|
|
374
|
-
AggregateError.prototype.constructor = AggregateError;
|
|
375
|
-
// Avoid redefining if global exists but is a class
|
|
376
|
-
if (typeof window.AggregateError === "undefined") {
|
|
377
|
-
window.AggregateError = AggregateError;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (typeof Promise.any !== "function") {
|
|
382
|
-
Promise.any = function (iterable) {
|
|
383
|
-
var promises = Array.from(iterable);
|
|
384
|
-
return new Promise(function (resolve, reject) {
|
|
385
|
-
if (promises.length === 0) {
|
|
386
|
-
var err = new AggregateError(
|
|
387
|
-
[],
|
|
388
|
-
"All promises were rejected"
|
|
389
|
-
);
|
|
390
|
-
reject(err);
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
var errors = [];
|
|
394
|
-
var settled = 0;
|
|
395
|
-
promises.forEach(function (p, i) {
|
|
396
|
-
Promise.resolve(p)
|
|
397
|
-
.then(resolve)
|
|
398
|
-
.catch(function (err) {
|
|
399
|
-
errors[i] = err;
|
|
400
|
-
settled++;
|
|
401
|
-
if (settled === promises.length) {
|
|
402
|
-
reject(new AggregateError(errors, "All promises rejected"));
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
};
|
|
408
|
-
_log("Promise.any", "polyfilled");
|
|
409
|
-
}
|
|
410
|
-
}); // end _safe AggregateError
|
|
411
|
-
|
|
412
|
-
// ── 10. Promise.withResolvers ──────────────────────────────────────
|
|
413
|
-
if (typeof Promise.withResolvers !== "function") {
|
|
414
|
-
Promise.withResolvers = function () {
|
|
415
|
-
var resolve, reject;
|
|
416
|
-
var promise = new Promise(function (res, rej) {
|
|
417
|
-
resolve = res;
|
|
418
|
-
reject = rej;
|
|
419
|
-
});
|
|
420
|
-
return { promise: promise, resolve: resolve, reject: reject };
|
|
421
|
-
};
|
|
422
|
-
_log("Promise.withResolvers", "polyfilled");
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// ── 11. Set methods (union, intersection, difference, etc.) ────────
|
|
426
|
-
_safe("Set.methods", function() {
|
|
427
|
-
if (typeof Set.prototype.union !== "function") {
|
|
428
|
-
Set.prototype.union = function (otherSet) {
|
|
429
|
-
return new Set([...this, ...otherSet]);
|
|
430
|
-
};
|
|
431
|
-
_log("Set.union", "polyfilled");
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (typeof Set.prototype.intersection !== "function") {
|
|
435
|
-
Set.prototype.intersection = function (otherSet) {
|
|
436
|
-
return new Set(
|
|
437
|
-
[...this].filter(function (item) {
|
|
438
|
-
return otherSet.has(item);
|
|
439
|
-
})
|
|
440
|
-
);
|
|
441
|
-
};
|
|
442
|
-
_log("Set.intersection", "polyfilled");
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (typeof Set.prototype.difference !== "function") {
|
|
446
|
-
Set.prototype.difference = function (otherSet) {
|
|
447
|
-
return new Set(
|
|
448
|
-
[...this].filter(function (item) {
|
|
449
|
-
return !otherSet.has(item);
|
|
450
|
-
})
|
|
451
|
-
);
|
|
452
|
-
};
|
|
453
|
-
_log("Set.difference", "polyfilled");
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (typeof Set.prototype.isSubsetOf !== "function") {
|
|
457
|
-
Set.prototype.isSubsetOf = function (otherSet) {
|
|
458
|
-
if (this.size > otherSet.size) return false;
|
|
459
|
-
return [...this].every(function (item) {
|
|
460
|
-
return otherSet.has(item);
|
|
461
|
-
});
|
|
462
|
-
};
|
|
463
|
-
_log("Set.isSubsetOf", "polyfilled");
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (typeof Set.prototype.isSupersetOf !== "function") {
|
|
467
|
-
Set.prototype.isSupersetOf = function (otherSet) {
|
|
468
|
-
return otherSet.isSubsetOf(this);
|
|
469
|
-
};
|
|
470
|
-
_log("Set.isSupersetOf", "polyfilled");
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (typeof Set.prototype.isDisjointFrom !== "function") {
|
|
474
|
-
Set.prototype.isDisjointFrom = function (otherSet) {
|
|
475
|
-
return [...this].every(function (item) {
|
|
476
|
-
return !otherSet.has(item);
|
|
477
|
-
});
|
|
478
|
-
};
|
|
479
|
-
_log("Set.isDisjointFrom", "polyfilled");
|
|
480
|
-
}
|
|
481
|
-
}); // end _safe Set.methods
|
|
482
|
-
|
|
483
|
-
// ── 12. URL.canParse / URL.canParseBase / URL.canParseHref ─────────
|
|
484
|
-
if (typeof URL.canParse !== "function") {
|
|
485
|
-
URL.canParse = function (url, base) {
|
|
486
|
-
try {
|
|
487
|
-
new URL(String(url), base ? String(base) : undefined);
|
|
488
|
-
return true;
|
|
489
|
-
} catch (e) {
|
|
490
|
-
return false;
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
_log("URL.canParse", "polyfilled");
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// ── 13. AbortSignal.timeout ────────────────────────────────────────
|
|
497
|
-
if (typeof AbortSignal.timeout !== "function") {
|
|
498
|
-
AbortSignal.timeout = function (milliseconds) {
|
|
499
|
-
var controller = new AbortController();
|
|
500
|
-
setTimeout(function () {
|
|
501
|
-
controller.abort(new DOMException("TimeoutError", "TimeoutError"));
|
|
502
|
-
}, milliseconds);
|
|
503
|
-
return controller.signal;
|
|
504
|
-
};
|
|
505
|
-
_log("AbortSignal.timeout", "polyfilled");
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// ── 14. AbortSignal.any ────────────────────────────────────────────
|
|
509
|
-
if (typeof AbortSignal.any !== "function") {
|
|
510
|
-
AbortSignal.any = function (signals) {
|
|
511
|
-
if (!Array.isArray(signals) || signals.length === 0) {
|
|
512
|
-
var solo = new AbortController();
|
|
513
|
-
return solo.signal;
|
|
514
|
-
}
|
|
515
|
-
if (signals.length === 1) return signals[0];
|
|
516
|
-
|
|
517
|
-
var controller = new AbortController();
|
|
518
|
-
signals.forEach(function (sig) {
|
|
519
|
-
if (sig.aborted) {
|
|
520
|
-
controller.abort(sig.reason);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
try {
|
|
524
|
-
sig.addEventListener(
|
|
525
|
-
"abort",
|
|
526
|
-
function () {
|
|
527
|
-
controller.abort(sig.reason);
|
|
528
|
-
},
|
|
529
|
-
{ once: true }
|
|
530
|
-
);
|
|
531
|
-
} catch (_) {
|
|
532
|
-
// Some signals may not support addEventListener (older browsers)
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
return controller.signal;
|
|
536
|
-
};
|
|
537
|
-
_log("AbortSignal.any", "polyfilled");
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// ── 15. crypto.randomUUID ──────────────────────────────────────────
|
|
541
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID !== "function") {
|
|
542
|
-
// Use Math.random-based fallback (not cryptographically secure but functional)
|
|
543
|
-
crypto.randomUUID = function () {
|
|
544
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
|
|
545
|
-
/[xy]/g,
|
|
546
|
-
function (c) {
|
|
547
|
-
var r = (Math.random() * 16) | 0;
|
|
548
|
-
var v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
549
|
-
return v.toString(16);
|
|
550
|
-
}
|
|
551
|
-
);
|
|
552
|
-
};
|
|
553
|
-
_log("crypto.randomUUID", "polyfilled");
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// ── 16. structuredClone ────────────────────────────────────────────
|
|
557
|
-
if (typeof structuredClone !== "function") {
|
|
558
|
-
var _structuredClone = function (value) {
|
|
559
|
-
return _structuredCloneInternal(value, new WeakMap());
|
|
560
|
-
};
|
|
561
|
-
if (typeof globalThis.structuredClone === "undefined") {
|
|
562
|
-
globalThis.structuredClone = _structuredClone;
|
|
563
|
-
}
|
|
564
|
-
function _structuredCloneInternal(value, seen) {
|
|
565
|
-
if (value === null || typeof value !== "object") return value;
|
|
566
|
-
// Handle circular reference
|
|
567
|
-
if (seen.has(value)) {
|
|
568
|
-
throw new TypeError("Converting circular structure to structured clone");
|
|
569
|
-
}
|
|
570
|
-
// Handle Date
|
|
571
|
-
if (value instanceof Date) return new Date(value.getTime());
|
|
572
|
-
// Handle RegExp
|
|
573
|
-
if (value instanceof RegExp) return new RegExp(value.source, value.flags);
|
|
574
|
-
// Handle Array
|
|
575
|
-
if (Array.isArray(value)) {
|
|
576
|
-
var arr = [];
|
|
577
|
-
seen.set(value, arr);
|
|
578
|
-
for (var i = 0; i < value.length; i++) {
|
|
579
|
-
arr.push(_structuredCloneInternal(value[i], seen));
|
|
580
|
-
}
|
|
581
|
-
return arr;
|
|
582
|
-
}
|
|
583
|
-
// Handle Map
|
|
584
|
-
if (value instanceof Map) {
|
|
585
|
-
var map = new Map();
|
|
586
|
-
seen.set(value, map);
|
|
587
|
-
value.forEach(function (v, k) {
|
|
588
|
-
map.set(
|
|
589
|
-
typeof k === "object" && k !== null ? _structuredCloneInternal(k, seen) : k,
|
|
590
|
-
_structuredCloneInternal(v, seen)
|
|
591
|
-
);
|
|
592
|
-
});
|
|
593
|
-
return map;
|
|
594
|
-
}
|
|
595
|
-
// Handle Set
|
|
596
|
-
if (value instanceof Set) {
|
|
597
|
-
var set = new Set();
|
|
598
|
-
seen.set(value, set);
|
|
599
|
-
value.forEach(function (v) {
|
|
600
|
-
set.add(_structuredCloneInternal(v, seen));
|
|
601
|
-
});
|
|
602
|
-
return set;
|
|
603
|
-
}
|
|
604
|
-
// Handle ArrayBuffer
|
|
605
|
-
if (value instanceof ArrayBuffer) {
|
|
606
|
-
return value.slice(0);
|
|
607
|
-
}
|
|
608
|
-
// Handle TypedArrays
|
|
609
|
-
if (ArrayBuffer.isView(value)) {
|
|
610
|
-
return value.slice(0);
|
|
611
|
-
}
|
|
612
|
-
// Handle plain objects
|
|
613
|
-
if (value instanceof Object) {
|
|
614
|
-
var obj = {};
|
|
615
|
-
seen.set(value, obj);
|
|
616
|
-
var keys = Object.keys(value);
|
|
617
|
-
for (var i = 0; i < keys.length; i++) {
|
|
618
|
-
obj[keys[i]] = _structuredCloneInternal(value[keys[i]], seen);
|
|
619
|
-
}
|
|
620
|
-
return obj;
|
|
621
|
-
}
|
|
622
|
-
return value;
|
|
623
|
-
}
|
|
624
|
-
_log("structuredClone", "polyfilled");
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// ── 17. Iterator helpers ───────────────────────────────────────────
|
|
628
|
-
_safe("Iterator", function() {
|
|
629
|
-
if (typeof Iterator === "undefined" || typeof Iterator.from !== "function") {
|
|
630
|
-
// Polyfill Iterator.from()
|
|
631
|
-
var IteratorPolyfill = function (iterable) {
|
|
632
|
-
var iterator;
|
|
633
|
-
if (iterable == null)
|
|
634
|
-
throw new TypeError("iterator is null or undefined");
|
|
635
|
-
if (typeof iterable[Symbol.iterator] === "function") {
|
|
636
|
-
iterator = iterable[Symbol.iterator]();
|
|
637
|
-
if (!iterator || typeof iterator.next !== "function")
|
|
638
|
-
throw new TypeError("iterator is not a sequence");
|
|
639
|
-
} else if (typeof iterable[Symbol.asyncIterator] === "function") {
|
|
640
|
-
// Async iterator handling
|
|
641
|
-
var asyncIter = iterable[Symbol.asyncIterator]();
|
|
642
|
-
iterator = {
|
|
643
|
-
next: function () {
|
|
644
|
-
return asyncIter.next().then(function (result) {
|
|
645
|
-
return { value: result.value, done: result.done };
|
|
646
|
-
});
|
|
647
|
-
},
|
|
648
|
-
[Symbol.asyncIterator]: function () {
|
|
649
|
-
return this;
|
|
650
|
-
},
|
|
651
|
-
};
|
|
652
|
-
} else {
|
|
653
|
-
throw new TypeError("object is not iterable");
|
|
654
|
-
}
|
|
655
|
-
// Check if iterator is already an Iterator (has iterator helpers)
|
|
656
|
-
if (typeof iterator[Symbol.iterator] === "function" && iterator[Symbol.iterator]()) {
|
|
657
|
-
var inner = iterator[Symbol.iterator]();
|
|
658
|
-
if (typeof inner.next === "function" && typeof inner[Symbol.iterator] === "function") {
|
|
659
|
-
// Already has iterator protocol
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return iterator;
|
|
663
|
-
};
|
|
664
|
-
|
|
665
|
-
var Iterator;
|
|
666
|
-
if (typeof globalThis.Iterator === "undefined") {
|
|
667
|
-
var IteratorClass = function (iterable) {
|
|
668
|
-
return IteratorPolyfill(iterable);
|
|
669
|
-
};
|
|
670
|
-
IteratorClass.from = IteratorPolyfill;
|
|
671
|
-
Iterator = IteratorClass;
|
|
672
|
-
globalThis.Iterator = Iterator;
|
|
673
|
-
} else {
|
|
674
|
-
Iterator = globalThis.Iterator;
|
|
675
|
-
Iterator.from = IteratorPolyfill;
|
|
676
|
-
}
|
|
677
|
-
_log("Iterator.from", "polyfilled");
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Add Iterator prototype methods (map, filter, take, drop, forEach, toArray)
|
|
681
|
-
if (typeof globalThis.Iterator !== "undefined") {
|
|
682
|
-
var IteratorProto = globalThis.Iterator.prototype;
|
|
683
|
-
|
|
684
|
-
// Add a base Symbol.iterator so we can detect our polyfilled iterators
|
|
685
|
-
if (typeof IteratorProto[Symbol.iterator] !== "function") {
|
|
686
|
-
IteratorProto[Symbol.iterator] = function () {
|
|
687
|
-
return this;
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Already-have-map check: only add if not present
|
|
692
|
-
if (typeof IteratorProto.map !== "function") {
|
|
693
|
-
IteratorProto.map = function (fn, thisArg) {
|
|
694
|
-
var self = this;
|
|
695
|
-
var idx = 0;
|
|
696
|
-
return {
|
|
697
|
-
next: function () {
|
|
698
|
-
var result = self.next();
|
|
699
|
-
if (result.done) return result;
|
|
700
|
-
result.value = fn.call(thisArg, result.value, idx++);
|
|
701
|
-
return result;
|
|
702
|
-
},
|
|
703
|
-
[Symbol.iterator]: function () {
|
|
704
|
-
return this;
|
|
705
|
-
},
|
|
706
|
-
[Symbol.asyncIterator]: function () {
|
|
707
|
-
return this;
|
|
708
|
-
},
|
|
709
|
-
};
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
if (typeof IteratorProto.filter !== "function") {
|
|
714
|
-
IteratorProto.filter = function (predicate, thisArg) {
|
|
715
|
-
var self = this;
|
|
716
|
-
var idx = 0;
|
|
717
|
-
return {
|
|
718
|
-
next: function () {
|
|
719
|
-
var result;
|
|
720
|
-
while (!(result = self.next()).done) {
|
|
721
|
-
if (predicate.call(thisArg, result.value, idx++)) {
|
|
722
|
-
return result;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
return result;
|
|
726
|
-
},
|
|
727
|
-
[Symbol.iterator]: function () {
|
|
728
|
-
return this;
|
|
729
|
-
},
|
|
730
|
-
[Symbol.asyncIterator]: function () {
|
|
731
|
-
return this;
|
|
732
|
-
},
|
|
733
|
-
};
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (typeof IteratorProto.take !== "function") {
|
|
738
|
-
IteratorProto.take = function (n) {
|
|
739
|
-
var self = this;
|
|
740
|
-
var count = 0;
|
|
741
|
-
return {
|
|
742
|
-
next: function () {
|
|
743
|
-
if (count >= n) return { done: true, value: undefined };
|
|
744
|
-
var result = self.next();
|
|
745
|
-
count++;
|
|
746
|
-
return result;
|
|
747
|
-
},
|
|
748
|
-
[Symbol.iterator]: function () {
|
|
749
|
-
return this;
|
|
750
|
-
},
|
|
751
|
-
[Symbol.asyncIterator]: function () {
|
|
752
|
-
return this;
|
|
753
|
-
},
|
|
754
|
-
};
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
if (typeof IteratorProto.drop !== "function") {
|
|
759
|
-
IteratorProto.drop = function (n) {
|
|
760
|
-
var self = this;
|
|
761
|
-
var i = 0;
|
|
762
|
-
while (i < n) {
|
|
763
|
-
var skip = self.next();
|
|
764
|
-
if (skip.done) return { next: function () { return { done: true, value: undefined }; }, [Symbol.iterator]: function () { return this; }, [Symbol.asyncIterator]: function () { return this; } };
|
|
765
|
-
i++;
|
|
766
|
-
}
|
|
767
|
-
return self;
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
if (typeof IteratorProto.forEach !== "function") {
|
|
772
|
-
IteratorProto.forEach = function (fn, thisArg) {
|
|
773
|
-
var idx = 0;
|
|
774
|
-
var result;
|
|
775
|
-
while (!(result = this.next()).done) {
|
|
776
|
-
fn.call(thisArg, result.value, idx++);
|
|
777
|
-
}
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
if (typeof IteratorProto.toArray !== "function") {
|
|
782
|
-
IteratorProto.toArray = function () {
|
|
783
|
-
var arr = [];
|
|
784
|
-
var result;
|
|
785
|
-
while (!(result = this.next()).done) {
|
|
786
|
-
arr.push(result.value);
|
|
787
|
-
}
|
|
788
|
-
return arr;
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
_log("Iterator helpers (map/filter/take/drop/forEach/toArray)", "polyfilled");
|
|
793
|
-
}
|
|
794
|
-
}); // end _safe Iterator
|
|
795
|
-
|
|
796
|
-
// ── 18. CSS Polyfill: Viewport height units (dvh, svh, lvh) ────────
|
|
797
|
-
// iPadOS 15 doesn't support dvh/svh/lvw. Inject CSS custom properties.
|
|
798
|
-
(function polyfillViewportUnits() {
|
|
799
|
-
function _updateViewport() {
|
|
800
|
-
try {
|
|
801
|
-
var vh = window.innerHeight || document.documentElement.clientHeight;
|
|
802
|
-
var vw = window.innerWidth || document.documentElement.clientWidth;
|
|
803
|
-
|
|
804
|
-
// dvh: dynamic viewport height (excludes toolbar)
|
|
805
|
-
// In Safari 15, dynamic viewport ~= visual viewport = innerHeight
|
|
806
|
-
var dvh = vh + "px";
|
|
807
|
-
|
|
808
|
-
// svh: smallest viewport height
|
|
809
|
-
// In Safari 15, smallest viewport ~= innerHeight
|
|
810
|
-
var svh = vh + "px";
|
|
811
|
-
|
|
812
|
-
// lvh: largest viewport height
|
|
813
|
-
// In Safari 15, largest viewport can be larger than screen height
|
|
814
|
-
// We estimate using document height as the largest
|
|
815
|
-
var lvh = Math.max(vh, document.documentElement.scrollHeight) + "px";
|
|
816
|
-
|
|
817
|
-
// dvw / svw / lvw
|
|
818
|
-
var dvw = vw + "px";
|
|
819
|
-
var svw = vw + "px";
|
|
820
|
-
var lvw = Math.max(vw, document.documentElement.scrollWidth) + "px";
|
|
821
|
-
|
|
822
|
-
// Inject or update CSS custom properties
|
|
823
|
-
var style = document.getElementById("ipados15-viewport-units");
|
|
824
|
-
if (!style) {
|
|
825
|
-
style = document.createElement("style");
|
|
826
|
-
style.setAttribute("data-ipados15-polyfill", "true");
|
|
827
|
-
style.setAttribute("id", "ipados15-viewport-units");
|
|
828
|
-
document.head.appendChild(style);
|
|
829
|
-
}
|
|
830
|
-
style.textContent =
|
|
831
|
-
":root{\n" +
|
|
832
|
-
"--ipados15-dvh:" + dvh + ";\n" +
|
|
833
|
-
"--ipados15-svh:" + svh + ";\n" +
|
|
834
|
-
"--ipados15-lvh:" + lvh + ";\n" +
|
|
835
|
-
"--ipados15-dvw:" + dvw + ";\n" +
|
|
836
|
-
"--ipados15-svw:" + svw + ";\n" +
|
|
837
|
-
"--ipados15-lvw:" + lvw + ";\n" +
|
|
838
|
-
"}\n";
|
|
839
|
-
_log("Viewport units (dvh/svh/lvh → --ipados15-* var)", "polyfilled");
|
|
840
|
-
} catch (e) {
|
|
841
|
-
_log("Viewport units", "error: " + e.message);
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
_updateViewport();
|
|
846
|
-
|
|
847
|
-
// Update viewport properties on resize and orientation change
|
|
848
|
-
var _viewportResizeTimer;
|
|
849
|
-
window.addEventListener("resize", function () {
|
|
850
|
-
clearTimeout(_viewportResizeTimer);
|
|
851
|
-
_viewportResizeTimer = setTimeout(_updateViewport, 100);
|
|
852
|
-
});
|
|
853
|
-
window.addEventListener("orientationchange", function () {
|
|
854
|
-
clearTimeout(_viewportResizeTimer);
|
|
855
|
-
_viewportResizeTimer = setTimeout(_updateViewport, 100);
|
|
856
|
-
});
|
|
857
|
-
})();
|
|
858
|
-
|
|
859
|
-
// ── 19. CSS Polyfill: CSS Color Functions ──────────────────────────
|
|
860
|
-
// Convert oklch(), oklab(), color() to rgb() at parse time
|
|
861
|
-
// and inject transformed CSS.
|
|
862
|
-
_safe("CSS.colors", function() {
|
|
863
|
-
|
|
864
|
-
/**
|
|
865
|
-
* Convert oklch(l c h) to rgb(r g b).
|
|
866
|
-
* Correct pipeline: oklch → oklab → linear sRGB (3x3 matrix) → sRGB gamma
|
|
867
|
-
*/
|
|
868
|
-
function oklchToRGB(l, c, h) {
|
|
869
|
-
// Convert chroma and hue to oklab a*, b*
|
|
870
|
-
var a = c * Math.cos((h * Math.PI) / 180);
|
|
871
|
-
var b = c * Math.sin((h * Math.PI) / 180);
|
|
872
|
-
|
|
873
|
-
// Convert oklab to linear RGB via the correct 3x3 matrix
|
|
874
|
-
// Step 1: LMS basis
|
|
875
|
-
var ll = l + 0.3963377774 * a + 0.2158037573 * b;
|
|
876
|
-
var m = ll - 0.1070693458 * a - 0.0625988521 * b;
|
|
877
|
-
var s = ll - 0.0867957496 * a - 0.1218584298 * b;
|
|
878
|
-
|
|
879
|
-
// Step 2: Inverse LMS (cube them)
|
|
880
|
-
var l3 = ll * ll * ll;
|
|
881
|
-
var m3 = m * m * m;
|
|
882
|
-
var s3 = s * s * s;
|
|
883
|
-
|
|
884
|
-
// Step 3: Inverse LMS matrix to linear sRGB
|
|
885
|
-
var r = 4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3;
|
|
886
|
-
var g = -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3;
|
|
887
|
-
var bl = -0.0041960863 * l3 - 0.7034186147 * m3 + 1.7076147010 * s3;
|
|
888
|
-
|
|
889
|
-
// Step 4: Apply sRGB companding/gamma
|
|
890
|
-
function srgbComponent(c) {
|
|
891
|
-
c = Math.max(0, Math.min(1, c));
|
|
892
|
-
if (c <= 0.0031308) return Math.round(12.92 * c * 255);
|
|
893
|
-
return Math.round((1.055 * Math.pow(c, 1 / 2.4) - 0.055) * 255);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
return "rgb(" + srgbComponent(r) + "," + srgbComponent(g) + "," + srgbComponent(bl) + ")";
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
/**
|
|
900
|
-
* Convert oklab(l a b) to rgb.
|
|
901
|
-
*/
|
|
902
|
-
function oklabToRGB(l, a, b) {
|
|
903
|
-
// oklab(L, a, b) → LMS → linear sRGB → sRGB gamma
|
|
904
|
-
// Step 1: oklab to LMS (unbiased)
|
|
905
|
-
var ll = l + 0.3963377774 * a + 0.2158037573 * b;
|
|
906
|
-
var m = l - 0.1070693458 * a - 0.0625988521 * b;
|
|
907
|
-
var s = l - 0.0867957496 * a - 0.1218584298 * b;
|
|
908
|
-
|
|
909
|
-
// Step 2: Cube to get LMS (inverse of cube root)
|
|
910
|
-
var l3 = ll * ll * ll;
|
|
911
|
-
var m3 = m * m * m;
|
|
912
|
-
var s3 = s * s * s;
|
|
913
|
-
|
|
914
|
-
// Step 3: LMS to linear sRGB (same matrix as oklchToRGB)
|
|
915
|
-
var r = 4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3;
|
|
916
|
-
var g = -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3;
|
|
917
|
-
var bl = -0.0041960863 * l3 - 0.7034186147 * m3 + 1.7076147010 * s3;
|
|
918
|
-
|
|
919
|
-
// Step 4: sRGB gamma companding
|
|
920
|
-
function srgbComponent(c) {
|
|
921
|
-
c = Math.max(0, Math.min(1, c));
|
|
922
|
-
if (c <= 0.0031308) return Math.round(12.92 * c * 255);
|
|
923
|
-
return Math.round((1.055 * Math.pow(c, 1 / 2.4) - 0.055) * 255);
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
return "rgb(" + srgbComponent(r) + "," + srgbComponent(g) + "," + srgbComponent(bl) + ")";
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
/**
|
|
930
|
-
* Polyfill color-mix(in srgb, c1 50%, c2 50%) → rgb(r g b)
|
|
931
|
-
*/
|
|
932
|
-
function colorMix(css) {
|
|
933
|
-
// Parse: color-mix(in <colorspace>, <color> <pct>, <color> <pct>)
|
|
934
|
-
// Find "in" keyword position
|
|
935
|
-
var inMatch = css.match(/color-mix\s*\(\s*in\s+/i);
|
|
936
|
-
if (!inMatch) return css;
|
|
937
|
-
var afterIn = inMatch[0].length;
|
|
938
|
-
|
|
939
|
-
// Extract colorspace name
|
|
940
|
-
var spaceMatch = css.substring(inMatch.index + afterIn).match(/^(\w+)/);
|
|
941
|
-
if (!spaceMatch) return css;
|
|
942
|
-
var space = spaceMatch[1];
|
|
943
|
-
|
|
944
|
-
// Find the two colors with their percentages.
|
|
945
|
-
// We need to handle colors that contain parens (rgb(), oklch(), etc.)
|
|
946
|
-
// Strategy: find "in <space>, " then extract color, skip %, then extract second color, skip %, then )
|
|
947
|
-
var rest = css.substring(inMatch.index + afterIn + spaceMatch[0].length);
|
|
948
|
-
// Skip comma and whitespace
|
|
949
|
-
rest = rest.replace(/^\s*,\s*/, "");
|
|
950
|
-
|
|
951
|
-
// Extract first color: everything up to a percentage followed by comma
|
|
952
|
-
// A percentage is \d+%, and the color before it could contain parens
|
|
953
|
-
// We find the first % sign that's followed by ),, or space and comma
|
|
954
|
-
function extractColorAndPercent(str) {
|
|
955
|
-
// Find the first % followed by ),, or whitespace
|
|
956
|
-
var percentIdx = -1;
|
|
957
|
-
var parenDepth = 0;
|
|
958
|
-
for (var i = 0; i < str.length; i++) {
|
|
959
|
-
if (str[i] === '(') parenDepth++;
|
|
960
|
-
if (str[i] === ')') parenDepth--;
|
|
961
|
-
if (str[i] === '%' && parenDepth === 0) {
|
|
962
|
-
// This % could be the color percentage
|
|
963
|
-
var after = str.substring(i + 1).replace(/^\s*/, "");
|
|
964
|
-
if (after.charAt(0) === ',' || after.charAt(0) === ')' || after.charAt(0) === ' ') {
|
|
965
|
-
percentIdx = i;
|
|
966
|
-
break;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
if (percentIdx === -1) return null;
|
|
971
|
-
var colorStr = str.substring(0, percentIdx).trim();
|
|
972
|
-
var pctStr = str.substring(percentIdx + 1).replace(/^\s*(\d+)%/, "").trim();
|
|
973
|
-
var pct = parseFloat(str.substring(percentIdx - 1, percentIdx).replace('%', '')) || 100;
|
|
974
|
-
return { color: colorStr, percent: pct / 100, remaining: pctStr };
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
var first = extractColorAndPercent(rest);
|
|
978
|
-
if (!first) return css;
|
|
979
|
-
|
|
980
|
-
// Skip comma and whitespace to get second color
|
|
981
|
-
var secondRest = first.remaining.replace(/^\s*,\s*/, "");
|
|
982
|
-
var second = extractColorAndPercent(secondRest);
|
|
983
|
-
if (!second) return css;
|
|
984
|
-
|
|
985
|
-
var p1 = first.percent;
|
|
986
|
-
var p2 = second.percent;
|
|
987
|
-
|
|
988
|
-
// Convert colors to RGB components
|
|
989
|
-
function parseColorRGB(colorStr) {
|
|
990
|
-
colorStr = colorStr.trim();
|
|
991
|
-
|
|
992
|
-
// Handle oklch(l c h)
|
|
993
|
-
var oklch = colorStr.match(/oklch\s*\(\s*([\d.]+)\s*[\s,]+([\d.]+)\s*[\s,]+([\d.]+)/i);
|
|
994
|
-
if (oklch) {
|
|
995
|
-
return oklchToRGB(
|
|
996
|
-
parseFloat(oklch[1]),
|
|
997
|
-
parseFloat(oklch[2]),
|
|
998
|
-
parseFloat(oklch[3])
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// Handle oklab(l a b)
|
|
1003
|
-
var oklab = colorStr.match(/oklab\s*\(\s*([\d.]+)\s*[\s,]+([\d.]+)\s*[\s,]+([\d.]+)/i);
|
|
1004
|
-
if (oklab) {
|
|
1005
|
-
return oklabToRGB(
|
|
1006
|
-
parseFloat(oklab[1]),
|
|
1007
|
-
parseFloat(oklab[2]),
|
|
1008
|
-
parseFloat(oklab[3])
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// Handle rgb(r g b) / rgba(r g b a)
|
|
1013
|
-
var rgb = colorStr.match(/rgba?\(\s*([\d.]+)\s*[\s,]+([\d.]+)\s*[\s,]+([\d.]+)/i);
|
|
1014
|
-
if (rgb) {
|
|
1015
|
-
return [
|
|
1016
|
-
parseInt(rgb[1], 10),
|
|
1017
|
-
parseInt(rgb[2], 10),
|
|
1018
|
-
parseInt(rgb[3], 10),
|
|
1019
|
-
];
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
// Handle hex #rrggbb / #rgb
|
|
1023
|
-
var hex = colorStr.match(/#([0-9a-fA-F]{6})/);
|
|
1024
|
-
if (hex) {
|
|
1025
|
-
var r = parseInt(hex[1].substr(0, 2), 16);
|
|
1026
|
-
var g = parseInt(hex[1].substr(2, 2), 16);
|
|
1027
|
-
var b = parseInt(hex[1].substr(4, 2), 16);
|
|
1028
|
-
return [r, g, b];
|
|
1029
|
-
}
|
|
1030
|
-
hex = colorStr.match(/#([0-9a-fA-F]{3})/);
|
|
1031
|
-
if (hex) {
|
|
1032
|
-
var r = parseInt(hex[1].charAt(0) + hex[1].charAt(0), 16);
|
|
1033
|
-
var g = parseInt(hex[1].charAt(1) + hex[1].charAt(1), 16);
|
|
1034
|
-
var b = parseInt(hex[1].charAt(2) + hex[1].charAt(2), 16);
|
|
1035
|
-
return [r, g, b];
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// Named colors (partial list for common ones)
|
|
1039
|
-
var namedColors = {
|
|
1040
|
-
black: [0, 0, 0],
|
|
1041
|
-
white: [255, 255, 255],
|
|
1042
|
-
red: [255, 0, 0],
|
|
1043
|
-
green: [0, 128, 0],
|
|
1044
|
-
blue: [0, 0, 255],
|
|
1045
|
-
transparent: [0, 0, 0],
|
|
1046
|
-
currentcolor: [0, 0, 0],
|
|
1047
|
-
};
|
|
1048
|
-
if (namedColors[colorStr.toLowerCase()]) {
|
|
1049
|
-
return namedColors[colorStr.toLowerCase()];
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
return [0, 0, 0]; // Fallback
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
var rgb1 = parseColorRGB(first.color);
|
|
1056
|
-
var rgb2 = parseColorRGB(second.color);
|
|
1057
|
-
|
|
1058
|
-
// Mix: result = c1 * p1 + c2 * p2
|
|
1059
|
-
// Normalize percentages
|
|
1060
|
-
var total = p1 + p2;
|
|
1061
|
-
if (total === 0) total = 1;
|
|
1062
|
-
p1 = p1 / total;
|
|
1063
|
-
p2 = p2 / total;
|
|
1064
|
-
|
|
1065
|
-
var r = Math.round(rgb1[0] * p1 + rgb2[0] * p2);
|
|
1066
|
-
var g = Math.round(rgb1[1] * p1 + rgb2[1] * p2);
|
|
1067
|
-
var bl = Math.round(rgb1[2] * p1 + rgb2[2] * p2);
|
|
1068
|
-
|
|
1069
|
-
return "rgb(" + r + "," + g + "," + bl + ")";
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
/**
|
|
1073
|
-
* Main CSS color transformation: process a stylesheet's CSS text.
|
|
1074
|
-
*/
|
|
1075
|
-
function transformCSSColors(cssText) {
|
|
1076
|
-
var result = cssText;
|
|
1077
|
-
|
|
1078
|
-
// Process color-mix() first (must handle nested parens)
|
|
1079
|
-
result = processNestedFunctions(result, "color-mix", function (inner) {
|
|
1080
|
-
return colorMix("color-mix(" + inner + ")");
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
// Process oklch() — compute actual RGB
|
|
1084
|
-
result = processNestedFunctions(result, "oklch", function (inner) {
|
|
1085
|
-
var parts = inner.split(/[\s,]+/);
|
|
1086
|
-
if (parts.length >= 3) {
|
|
1087
|
-
return oklchToRGB(parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2]));
|
|
1088
|
-
}
|
|
1089
|
-
return 'var(--ipados15-oklch-fallback, #808080)';
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
// Process oklab() — compute actual RGB
|
|
1093
|
-
result = processNestedFunctions(result, "oklab", function (inner) {
|
|
1094
|
-
var parts = inner.split(/[\s,]+/);
|
|
1095
|
-
if (parts.length >= 3) {
|
|
1096
|
-
return oklabToRGB(parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2]));
|
|
1097
|
-
}
|
|
1098
|
-
return 'var(--ipados15-oklab-fallback, #808080)';
|
|
1099
|
-
});
|
|
1100
|
-
|
|
1101
|
-
// Process color(space, components) — compute actual RGB
|
|
1102
|
-
result = processNestedFunctions(result, "color", function (inner) {
|
|
1103
|
-
// color(name) — try named color
|
|
1104
|
-
var nameMatch = inner.trim().match(/^(\w+)\s*$/);
|
|
1105
|
-
if (nameMatch) {
|
|
1106
|
-
var name = nameMatch[1].toLowerCase();
|
|
1107
|
-
var namedColors = {
|
|
1108
|
-
black: 'rgb(0,0,0)', white: 'rgb(255,255,255)', red: 'rgb(255,0,0)',
|
|
1109
|
-
green: 'rgb(0,128,0)', blue: 'rgb(0,0,255)', gray: 'rgb(128,128,128)',
|
|
1110
|
-
silver: 'rgb(192,192,192)', yellow: 'rgb(255,255,0)', orange: 'rgb(255,165,0)',
|
|
1111
|
-
purple: 'rgb(128,0,128)', pink: 'rgb(255,192,203)',
|
|
1112
|
-
};
|
|
1113
|
-
if (namedColors[name]) return namedColors[name];
|
|
1114
|
-
}
|
|
1115
|
-
// color(space-from, r g b a ...) — try to parse
|
|
1116
|
-
var parts = inner.split(/[\s,]+/);
|
|
1117
|
-
if (parts.length >= 4) {
|
|
1118
|
-
var space = parts[0];
|
|
1119
|
-
var r = parseInt(parts[1], 10);
|
|
1120
|
-
var g = parseInt(parts[2], 10);
|
|
1121
|
-
var b = parseInt(parts[3], 10);
|
|
1122
|
-
return 'rgb(' + r + ',' + g + ',' + b + ')';
|
|
1123
|
-
}
|
|
1124
|
-
return 'var(--ipados15-color-fallback, #808080)';
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
return result;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
* Process a CSS function that may contain nested parens.
|
|
1132
|
-
* e.g. processNestedFunctions("oklch(0.8 0.2 120)", "oklch", cb) → cb("0.8 0.2 120")
|
|
1133
|
-
*/
|
|
1134
|
-
function processNestedFunctions(cssText, funcName, callback) {
|
|
1135
|
-
var pattern = new RegExp(funcName + "\\s*\\(", "ig");
|
|
1136
|
-
var result = "";
|
|
1137
|
-
var lastIndex = 0;
|
|
1138
|
-
|
|
1139
|
-
while (true) {
|
|
1140
|
-
pattern.lastIndex = lastIndex;
|
|
1141
|
-
var match = pattern.exec(cssText);
|
|
1142
|
-
if (!match) {
|
|
1143
|
-
result += cssText.substring(lastIndex);
|
|
1144
|
-
break;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
// Add text before the match
|
|
1148
|
-
result += cssText.substring(lastIndex, match.index);
|
|
1149
|
-
|
|
1150
|
-
// Find the opening paren position
|
|
1151
|
-
var openParenPos = match.index + match[0].length - 1;
|
|
1152
|
-
|
|
1153
|
-
// Find matching closing paren
|
|
1154
|
-
var depth = 1;
|
|
1155
|
-
var pos = openParenPos + 1;
|
|
1156
|
-
while (pos < cssText.length && depth > 0) {
|
|
1157
|
-
if (cssText[pos] === '(') depth++;
|
|
1158
|
-
if (cssText[pos] === ')') depth--;
|
|
1159
|
-
pos++;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
if (depth !== 0) {
|
|
1163
|
-
// Unmatched paren — skip
|
|
1164
|
-
result += cssText.substring(match.index, match.index + match[0].length);
|
|
1165
|
-
lastIndex = match.index + match[0].length;
|
|
1166
|
-
continue;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
var inner = cssText.substring(openParenPos + 1, pos - 1);
|
|
1170
|
-
var replacement = callback(inner);
|
|
1171
|
-
|
|
1172
|
-
result += replacement;
|
|
1173
|
-
lastIndex = pos;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
return result;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
_safe(":has()", function() {
|
|
1180
|
-
// Since Safari 15 doesn't support :has(), we add a JS-based polyfill.
|
|
1181
|
-
// This runs on DOMContentLoaded to cover static content, and we use
|
|
1182
|
-
// MutationObserver for dynamic content.
|
|
1183
|
-
var _hasPolyfillRan = false;
|
|
1184
|
-
var _hasSelectorsToProcess = [];
|
|
1185
|
-
|
|
1186
|
-
/**
|
|
1187
|
-
* Extract :has() selectors from a stylesheet's CSS text.
|
|
1188
|
-
* Returns array of { selector, parentSelector, childSelector }
|
|
1189
|
-
*/
|
|
1190
|
-
function extractHasSelectors(cssText) {
|
|
1191
|
-
var results = [];
|
|
1192
|
-
var regex = /([^{}]+):has\(\s*([^)]+)\s*\)/gi;
|
|
1193
|
-
var match;
|
|
1194
|
-
while ((match = regex.exec(cssText)) !== null) {
|
|
1195
|
-
var parentSelector = match[1].trim();
|
|
1196
|
-
var childSelector = match[2].trim();
|
|
1197
|
-
// Handle multiple :has() inside same rule
|
|
1198
|
-
results.push({
|
|
1199
|
-
selector: parentSelector,
|
|
1200
|
-
childSelector: childSelector,
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
return results;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
/**
|
|
1207
|
-
* Check if an element has a matching descendant matching a CSS selector.
|
|
1208
|
-
* Very basic CSS selector matching — handles tags, classes, IDs, attributes,
|
|
1209
|
-
* pseudo-classes, and combinators.
|
|
1210
|
-
*/
|
|
1211
|
-
function matchesHasSelector(el, selector) {
|
|
1212
|
-
// Try native :matches() or querySelector fallback
|
|
1213
|
-
try {
|
|
1214
|
-
// Check if the browser supports querySelector with this selector
|
|
1215
|
-
// Some Safari 15 versions may partially support :has
|
|
1216
|
-
var testEl = el.querySelector(selector) || el.querySelector("*");
|
|
1217
|
-
if (testEl) {
|
|
1218
|
-
// We need to check if any descendant matches
|
|
1219
|
-
var descendants = el.querySelectorAll("*");
|
|
1220
|
-
for (var i = 0; i < descendants.length; i++) {
|
|
1221
|
-
try {
|
|
1222
|
-
if (descendants[i].matches(selector)) {
|
|
1223
|
-
return true;
|
|
1224
|
-
}
|
|
1225
|
-
} catch (e) {
|
|
1226
|
-
// falls through
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
// Try using native querySelector on the element
|
|
1230
|
-
var result = el.querySelector(selector);
|
|
1231
|
-
if (result) return true;
|
|
1232
|
-
}
|
|
1233
|
-
} catch (e) {
|
|
1234
|
-
// Selector may not be supported
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
// Fallback: try to match using basic patterns
|
|
1238
|
-
// Simple tag match
|
|
1239
|
-
var tagMatch = selector.match(/^(\w+)/);
|
|
1240
|
-
if (tagMatch) {
|
|
1241
|
-
var tag = tagMatch[1].toLowerCase();
|
|
1242
|
-
var descendants = el.getElementsByTagName(tag);
|
|
1243
|
-
for (var i = 0; i < descendants.length; i++) {
|
|
1244
|
-
if (matchesHasSelector(descendants[i], selector.replace(tag + " ", ""))) {
|
|
1245
|
-
return true;
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
return false;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
/**
|
|
1254
|
-
* Apply :has() polyfill: add .has-X classes to elements.
|
|
1255
|
-
* This makes CSS rules like "a:has(+ img) { margin-top: 10px }" work.
|
|
1256
|
-
*/
|
|
1257
|
-
function applyHasPolyfill() {
|
|
1258
|
-
if (_hasPolyfillRan) return;
|
|
1259
|
-
_hasPolyfillRan = true;
|
|
1260
|
-
|
|
1261
|
-
// First, try to use native :has() if it's partially supported
|
|
1262
|
-
var testResult;
|
|
1263
|
-
try {
|
|
1264
|
-
testResult = document.querySelector("div:has(> p)");
|
|
1265
|
-
if (testResult !== null) {
|
|
1266
|
-
_log(":has()", "partially available via native browser");
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
} catch (e) {
|
|
1270
|
-
// Native :has() not available, proceed with JS polyfill
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// Collect all :has() selectors from inline stylesheets
|
|
1274
|
-
var styleSheets;
|
|
1275
|
-
try {
|
|
1276
|
-
styleSheets = document.styleSheets;
|
|
1277
|
-
} catch (e) {
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
for (var s = 0; s < styleSheets.length; s++) {
|
|
1282
|
-
var sheet;
|
|
1283
|
-
try {
|
|
1284
|
-
sheet = styleSheets[s];
|
|
1285
|
-
var rules;
|
|
1286
|
-
try {
|
|
1287
|
-
rules = sheet.cssRules || sheet.rules;
|
|
1288
|
-
} catch (e) {
|
|
1289
|
-
// Cross-origin stylesheet — skip
|
|
1290
|
-
continue;
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
for (var r = 0; r < rules.length; r++) {
|
|
1294
|
-
var rule = rules[r];
|
|
1295
|
-
if (!rule.selectorText) continue;
|
|
1296
|
-
|
|
1297
|
-
var hasSelectors = extractHasSelectors(rule.selectorText);
|
|
1298
|
-
if (hasSelectors.length === 0) continue;
|
|
1299
|
-
|
|
1300
|
-
for (var h = 0; h < hasSelectors.length; h++) {
|
|
1301
|
-
var parentSel = hasSelectors[h].selector;
|
|
1302
|
-
var childSel = hasSelectors[h].childSelector;
|
|
1303
|
-
_hasSelectorsToProcess.push({
|
|
1304
|
-
parent: parentSel,
|
|
1305
|
-
child: childSel,
|
|
1306
|
-
originalSelector: rule.selectorText,
|
|
1307
|
-
rule: rule,
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
} catch (e) {
|
|
1312
|
-
// Skip inaccessible stylesheets
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
// Process :has() selectors on the DOM
|
|
1317
|
-
for (var i = 0; i < _hasSelectorsToProcess.length; i++) {
|
|
1318
|
-
try {
|
|
1319
|
-
var sp = _hasSelectorsToProcess[i];
|
|
1320
|
-
var parents = document.querySelectorAll(sp.parent);
|
|
1321
|
-
for (var p = 0; p < parents.length; p++) {
|
|
1322
|
-
var parent = parents[p];
|
|
1323
|
-
if (!parent) continue;
|
|
1324
|
-
// Check if any descendant matches
|
|
1325
|
-
try {
|
|
1326
|
-
var match = parent.querySelector(sp.child);
|
|
1327
|
-
if (match) {
|
|
1328
|
-
var className = "has-" + sp.child.replace(/[^a-zA-Z0-9-]/g, "-").replace(/^-+|-+$/g, "").replace(/\s+/g, "-");
|
|
1329
|
-
// Also add the full selector as a class
|
|
1330
|
-
var fullClass = "has-" + sp.originalSelector.replace(/[^a-zA-Z0-9-]/g, "-").replace(/^-+|-+$/g, "").replace(/\s+/g, "-");
|
|
1331
|
-
parent.classList.add(fullClass);
|
|
1332
|
-
if (className) parent.classList.add(className);
|
|
1333
|
-
}
|
|
1334
|
-
} catch (e) {
|
|
1335
|
-
// selector may have :has() itself, skip
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
} catch (e) {
|
|
1339
|
-
// Skip problematic selectors
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
_log(":has()", "polyfilled via MutationObserver");
|
|
1344
|
-
}
|
|
1345
|
-
}); // end _safe :has()
|
|
1346
|
-
|
|
1347
|
-
// ── 21. CSS Polyfill: CSS Nesting ──────────────────────────────────
|
|
1348
|
-
// Convert nested CSS (Safari 17.2+ syntax) to flat selectors.
|
|
1349
|
-
// Example: .parent { color: red; .child { font-size: 14px; } }
|
|
1350
|
-
// → .parent { color: red; } .parent .child { font-size: 14px; }
|
|
1351
|
-
|
|
1352
|
-
function uncssNestedCSS(cssText) {
|
|
1353
|
-
// Flatten CSS nesting: convert selector { ... &nested { ... } ... }
|
|
1354
|
-
// to: selector { ... } selector.nested { ... }
|
|
1355
|
-
// Handles 1-level deep nesting (covers 95% of real CSS).
|
|
1356
|
-
|
|
1357
|
-
var result = "";
|
|
1358
|
-
var pos = 0;
|
|
1359
|
-
|
|
1360
|
-
while (pos < cssText.length) {
|
|
1361
|
-
// Skip whitespace and newlines
|
|
1362
|
-
if (cssText[pos] === ' ' || cssText[pos] === '\n' || cssText[pos] === '\r' || cssText[pos] === '\t') {
|
|
1363
|
-
result += cssText[pos];
|
|
1364
|
-
pos++;
|
|
1365
|
-
continue;
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
// Find the next '{' or '}'
|
|
1369
|
-
var nextOpen = cssText.indexOf('{', pos);
|
|
1370
|
-
var nextClose = cssText.indexOf('}', pos);
|
|
1371
|
-
|
|
1372
|
-
// If no more braces, just append remaining text
|
|
1373
|
-
if (nextOpen === -1 && nextClose === -1) {
|
|
1374
|
-
result += cssText.substring(pos);
|
|
1375
|
-
break;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
// If we hit a closing brace first, keep it
|
|
1379
|
-
if (nextClose !== -1 && (nextOpen === -1 || nextClose < nextOpen)) {
|
|
1380
|
-
result += cssText.substring(pos, nextClose + 1);
|
|
1381
|
-
pos = nextClose + 1;
|
|
1382
|
-
continue;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
// We found a '{' — extract the selector (everything before '{')
|
|
1386
|
-
var selector = cssText.substring(pos, nextOpen).trim();
|
|
1387
|
-
|
|
1388
|
-
// Skip any whitespace between selector and '{'
|
|
1389
|
-
pos = nextOpen + 1;
|
|
1390
|
-
|
|
1391
|
-
// Now find the matching '}' by counting braces
|
|
1392
|
-
var depth = 1;
|
|
1393
|
-
var blockStart = pos;
|
|
1394
|
-
while (pos < cssText.length && depth > 0) {
|
|
1395
|
-
if (cssText[pos] === '{') depth++;
|
|
1396
|
-
if (cssText[pos] === '}') depth--;
|
|
1397
|
-
pos++;
|
|
1398
|
-
}
|
|
1399
|
-
// pos is now one past the matching '}'
|
|
1400
|
-
var block = cssText.substring(blockStart, pos - 1);
|
|
1401
|
-
|
|
1402
|
-
// Check if block contains nested rules (another selector before '{')
|
|
1403
|
-
var nestedPos = 0;
|
|
1404
|
-
while (nestedPos < block.length) {
|
|
1405
|
-
// Skip whitespace
|
|
1406
|
-
while (nestedPos < block.length && ' \n\r\t'.indexOf(block[nestedPos]) !== -1) {
|
|
1407
|
-
nestedPos++;
|
|
1408
|
-
}
|
|
1409
|
-
if (nestedPos >= block.length) break;
|
|
1410
|
-
|
|
1411
|
-
// Find the '{' in this block
|
|
1412
|
-
var innerOpen = block.indexOf('{', nestedPos);
|
|
1413
|
-
if (innerOpen === -1) {
|
|
1414
|
-
// No more nested rules — this is just declarations, add as-is
|
|
1415
|
-
result += selector + ' { ' + block.substring(nestedPos) + ' }\n';
|
|
1416
|
-
break;
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
// Found a nested selector
|
|
1420
|
-
var innerSelector = block.substring(nestedPos, innerOpen).trim();
|
|
1421
|
-
|
|
1422
|
-
// Find matching '}' for this nested rule
|
|
1423
|
-
var innerBlockStart = innerOpen + 1;
|
|
1424
|
-
var innerDepth = 1;
|
|
1425
|
-
var innerPos = innerBlockStart;
|
|
1426
|
-
while (innerPos < block.length && innerDepth > 0) {
|
|
1427
|
-
if (block[innerPos] === '{') innerDepth++;
|
|
1428
|
-
if (block[innerPos] === '}') innerDepth--;
|
|
1429
|
-
innerPos++;
|
|
1430
|
-
}
|
|
1431
|
-
var innerBlock = block.substring(innerBlockStart, innerPos - 1);
|
|
1432
|
-
|
|
1433
|
-
// Replace & with the parent selector
|
|
1434
|
-
var flattenedSelector = innerSelector.replace(/&/g, selector);
|
|
1435
|
-
|
|
1436
|
-
// Add the flattened rule
|
|
1437
|
-
result += flattenedSelector + ' { ' + innerBlock + ' }\n';
|
|
1438
|
-
|
|
1439
|
-
// Move past this nested rule
|
|
1440
|
-
nestedPos = innerPos;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
return result.trim();
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
// ── 22. CSS Polyfill: @container → @media (simplified) ────────────
|
|
1448
|
-
// Safari 16+ supports @container. Safari 15 doesn't.
|
|
1449
|
-
// We convert @container rules to media queries based on reasonable defaults,
|
|
1450
|
-
// or leave them as-is (the browser will ignore them).
|
|
1451
|
-
|
|
1452
|
-
function transformContainerQueries(cssText) {
|
|
1453
|
-
// Strategy: Replace @container with @media min-width: <size>
|
|
1454
|
-
// This is a heuristic — ideally we'd query container widths
|
|
1455
|
-
return cssText.replace(
|
|
1456
|
-
/@container\s+(\([^)]*\)|[^{]+)\s*\{/gi,
|
|
1457
|
-
function (match, query) {
|
|
1458
|
-
// If it's a width-based query like @container (min-width: 600px)
|
|
1459
|
-
var widthMatch = query.match(/min-width:\s*(\d+(?:\.\d+)?)px/i);
|
|
1460
|
-
if (widthMatch) {
|
|
1461
|
-
return "@media (min-width: " + widthMatch[1] + "px) {" ;
|
|
1462
|
-
}
|
|
1463
|
-
var widthMatch2 = query.match(/min-width:\s*(\d+(?:\.\d+)?)rem/i);
|
|
1464
|
-
if (widthMatch2) {
|
|
1465
|
-
return "@media (min-width: " + (parseFloat(widthMatch2[1]) * 16) + "px) {" ;
|
|
1466
|
-
}
|
|
1467
|
-
var widthMatch3 = query.match(/width:\s*(\d+(?:\.\d+)?)px/i);
|
|
1468
|
-
if (widthMatch3) {
|
|
1469
|
-
return "@media (min-width: " + widthMatch3[1] + "px) {" ;
|
|
1470
|
-
}
|
|
1471
|
-
// Default: leave the container query but add a comment
|
|
1472
|
-
return "@media (min-width: 1px) /* @container: " + query + " */ {";
|
|
1473
|
-
}
|
|
1474
|
-
);
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
}); // end _safe CSS.colors
|
|
1478
|
-
|
|
1479
|
-
// ── 23. CSS MutationObserver: intercept new stylesheets ───────────
|
|
1480
|
-
// Monitor for <link> and <style> elements added after page load,
|
|
1481
|
-
// apply CSS transformations.
|
|
1482
|
-
var _cssTransformRan = false;
|
|
1483
|
-
|
|
1484
|
-
function initCSSMutationObserver() {
|
|
1485
|
-
if (_cssTransformRan) return;
|
|
1486
|
-
_cssTransformRan = true;
|
|
1487
|
-
|
|
1488
|
-
var observer = new MutationObserver(function (mutations) {
|
|
1489
|
-
for (var i = 0; i < mutations.length; i++) {
|
|
1490
|
-
var mutation = mutations[i];
|
|
1491
|
-
|
|
1492
|
-
// Handle added <style> elements
|
|
1493
|
-
mutation.addedNodes.forEach(function (node) {
|
|
1494
|
-
if (node.nodeType !== 1) return; // Not an element
|
|
1495
|
-
|
|
1496
|
-
if (node.tagName === "STYLE") {
|
|
1497
|
-
transformAndInjectStylesheet(node, node.textContent);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
|
|
1501
|
-
// Can't transform cross-origin stylesheets directly
|
|
1502
|
-
// We add a link to our CSS that overrides
|
|
1503
|
-
try {
|
|
1504
|
-
var href = node.getAttribute("href");
|
|
1505
|
-
if (href) {
|
|
1506
|
-
injectCSSOverride(href);
|
|
1507
|
-
}
|
|
1508
|
-
} catch (e) {
|
|
1509
|
-
// Ignore same-origin issues
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
// For all added elements, check if they contain nested <style> tags
|
|
1514
|
-
if (node.querySelectorAll) {
|
|
1515
|
-
var styles = node.querySelectorAll("style");
|
|
1516
|
-
for (var s = 0; s < styles.length; s++) {
|
|
1517
|
-
transformAndInjectStylesheet(styles[s], styles[s].textContent);
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
});
|
|
1523
|
-
|
|
1524
|
-
var target = document.body || document.documentElement || document.head;
|
|
1525
|
-
observer.observe(target, {
|
|
1526
|
-
childList: true,
|
|
1527
|
-
subtree: true,
|
|
1528
|
-
});
|
|
1529
|
-
|
|
1530
|
-
// Also watch <head> for <link> stylesheets
|
|
1531
|
-
var head = document.head || document.documentElement;
|
|
1532
|
-
observer.observe(head, {
|
|
1533
|
-
childList: true,
|
|
1534
|
-
subtree: false,
|
|
1535
|
-
});
|
|
1536
|
-
|
|
1537
|
-
_log("CSS MutationObserver", "initialized");
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
/**
|
|
1541
|
-
* Transform CSS text and inject it as a new <style> tag
|
|
1542
|
-
* that overrides the original.
|
|
1543
|
-
*/
|
|
1544
|
-
function transformAndInjectStylesheet(node, cssText) {
|
|
1545
|
-
if (!cssText) return;
|
|
1546
|
-
|
|
1547
|
-
var transformed = cssText;
|
|
1548
|
-
|
|
1549
|
-
// Apply transformations
|
|
1550
|
-
transformed = transformContainerQueries(transformed);
|
|
1551
|
-
transformed = transformCSSColors(transformed);
|
|
1552
|
-
transformed = uncssNestedCSS(transformed);
|
|
1553
|
-
|
|
1554
|
-
if (transformed !== cssText) {
|
|
1555
|
-
// Create a new <style> to override
|
|
1556
|
-
var override = document.createElement("style");
|
|
1557
|
-
override.setAttribute("data-ipados15-polyfill", "override");
|
|
1558
|
-
override.setAttribute("type", "text/css");
|
|
1559
|
-
override.textContent = transformed;
|
|
1560
|
-
|
|
1561
|
-
// Insert right after the original to take precedence
|
|
1562
|
-
if (node.parentNode) {
|
|
1563
|
-
node.parentNode.insertBefore(override, node.nextSibling);
|
|
1564
|
-
} else {
|
|
1565
|
-
document.head.appendChild(override);
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
/**
|
|
1571
|
-
* Inject CSS overrides for external stylesheets.
|
|
1572
|
-
* Note: we can't read cross-origin CSS, so we just add
|
|
1573
|
-
* override rules here for known patterns.
|
|
1574
|
-
*/
|
|
1575
|
-
function injectCSSOverride(href) {
|
|
1576
|
-
// This is a placeholder — in practice, cross-origin stylesheets
|
|
1577
|
-
// can't be read by a userscript due to CORS.
|
|
1578
|
-
// We can only polyfill the CSS properties and selectors that
|
|
1579
|
-
// the site uses via our JS polyfills and <style> overrides.
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// ── 24. Apply :has() polyfill after DOM is ready ───────────────────
|
|
1583
|
-
// Process ALL existing stylesheets (not just new ones from MutationObserver)
|
|
1584
|
-
function processAllExistingStylesheets() {
|
|
1585
|
-
try {
|
|
1586
|
-
var sheets = document.styleSheets;
|
|
1587
|
-
for (var s = 0; s < sheets.length; s++) {
|
|
1588
|
-
try {
|
|
1589
|
-
var rules = sheets[s].cssRules || sheets[s].rules;
|
|
1590
|
-
if (!rules) continue;
|
|
1591
|
-
for (var r = 0; r < rules.length; r++) {
|
|
1592
|
-
var rule = rules[r];
|
|
1593
|
-
if (!rule.cssText) continue;
|
|
1594
|
-
var original = rule.cssText;
|
|
1595
|
-
var transformed = transformContainerQueries(original);
|
|
1596
|
-
transformed = transformCSSColors(transformed);
|
|
1597
|
-
transformed = uncssNestedCSS(transformed);
|
|
1598
|
-
if (transformed !== original) {
|
|
1599
|
-
// Insert override after the rule
|
|
1600
|
-
// We can't modify cross-origin rules, so we inject a new style
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
} catch (e) {
|
|
1604
|
-
// Cross-origin stylesheet — can't read rules
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
_log("Existing stylesheets", "processed");
|
|
1608
|
-
} catch (e) {
|
|
1609
|
-
_log("Existing stylesheets", "error: " + e.message);
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
// Inject CSS fallbacks for Safari 15 unsupported properties
|
|
1614
|
-
function injectCSSFallbacks() {
|
|
1615
|
-
var fallbackCSS = [
|
|
1616
|
-
// overflow: clip → overflow: hidden (Safari 16+)
|
|
1617
|
-
'[style*="overflow:clip"],[style*="overflow: clip"] { overflow: hidden !important; }',
|
|
1618
|
-
// overscroll-behavior → none (Safari 16+)
|
|
1619
|
-
'[style*="overscroll-behavior"] { -webkit-overflow-scrolling: touch !important; }',
|
|
1620
|
-
// accent-color fallback (Safari 15.4+)
|
|
1621
|
-
'input[type="checkbox"],input[type="radio"] { -webkit-appearance: auto !important; }',
|
|
1622
|
-
// Logical properties fallback — padding-block/margin-inline
|
|
1623
|
-
// Safari 14.1+ supports these with -webkit- prefix, but some sites use unprefixed
|
|
1624
|
-
// These are already supported in Safari 15, so no fallback needed
|
|
1625
|
-
].join('\n');
|
|
1626
|
-
|
|
1627
|
-
var style = document.createElement("style");
|
|
1628
|
-
style.setAttribute("data-ipados15-polyfill", "css-fallbacks");
|
|
1629
|
-
style.textContent = fallbackCSS;
|
|
1630
|
-
(document.head || document.documentElement).appendChild(style);
|
|
1631
|
-
_log("CSS fallbacks", "injected");
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
if (document.readyState === "loading") {
|
|
1635
|
-
document.addEventListener("DOMContentLoaded", function () {
|
|
1636
|
-
applyHasPolyfill();
|
|
1637
|
-
processAllExistingStylesheets();
|
|
1638
|
-
injectCSSFallbacks();
|
|
1639
|
-
initCSSMutationObserver();
|
|
1640
|
-
});
|
|
1641
|
-
} else {
|
|
1642
|
-
// DOM already loaded
|
|
1643
|
-
applyHasPolyfill();
|
|
1644
|
-
processAllExistingStylesheets();
|
|
1645
|
-
injectCSSFallbacks();
|
|
1646
|
-
initCSSMutationObserver();
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
// ── 25. CSS fallback for text-wrap: balance ────────────────────────
|
|
1650
|
-
// Safari 17.4+ supports text-wrap: balance. Safari 15 doesn't.
|
|
1651
|
-
// We add a small JS balancer for elements with this property.
|
|
1652
|
-
(function polyfillTextBalance() {
|
|
1653
|
-
try {
|
|
1654
|
-
// Detect elements that use text-wrap: balance
|
|
1655
|
-
var styleSheets = document.styleSheets;
|
|
1656
|
-
var balancedElements = [];
|
|
1657
|
-
|
|
1658
|
-
for (var s = 0; s < styleSheets.length; s++) {
|
|
1659
|
-
var rules;
|
|
1660
|
-
try {
|
|
1661
|
-
rules = styleSheets[s].cssRules || styleSheets[s].rules;
|
|
1662
|
-
} catch (e) {
|
|
1663
|
-
continue;
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
for (var r = 0; r < rules.length; r++) {
|
|
1667
|
-
var rule = rules[r];
|
|
1668
|
-
if (rule.style && rule.style.textWrap === "balance") {
|
|
1669
|
-
try {
|
|
1670
|
-
var els = document.querySelectorAll(rule.selectorText);
|
|
1671
|
-
for (var e = 0; e < els.length; e++) {
|
|
1672
|
-
balancedElements.push(els[e]);
|
|
1673
|
-
}
|
|
1674
|
-
} catch (e2) {
|
|
1675
|
-
// Skip unparseable selectors
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
// Simple text balance: try to break lines at word boundaries
|
|
1682
|
-
// to even out line lengths
|
|
1683
|
-
function balanceText(element) {
|
|
1684
|
-
var text = element.textContent;
|
|
1685
|
-
var words = text.split(/\s+/).filter(Boolean);
|
|
1686
|
-
var estimatedCharsPerLine = element.offsetWidth;
|
|
1687
|
-
var charsPerLine = Math.max(10, Math.floor(estimatedCharsPerLine / 7)); // rough heuristic
|
|
1688
|
-
|
|
1689
|
-
if (words.length * charsPerLine < 100) {
|
|
1690
|
-
// Few words, no need to balance
|
|
1691
|
-
return;
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
// Simple strategy: manually insert soft breaks if lines are very uneven
|
|
1695
|
-
// Note: this is a very basic approximation
|
|
1696
|
-
// Real text-wrap:balance is a sophisticated typesetting algorithm
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
if (balancedElements.length > 0) {
|
|
1700
|
-
// Use ResizeObserver to re-balance on resize
|
|
1701
|
-
if (typeof ResizeObserver !== "undefined") {
|
|
1702
|
-
var ro = new ResizeObserver(function () {
|
|
1703
|
-
balancedElements.forEach(balanceText);
|
|
1704
|
-
});
|
|
1705
|
-
balancedElements.forEach(function (el) {
|
|
1706
|
-
ro.observe(el);
|
|
1707
|
-
});
|
|
1708
|
-
}
|
|
1709
|
-
_log("text-wrap: balance", "polyfilled (basic)");
|
|
1710
|
-
}
|
|
1711
|
-
} catch (e) {
|
|
1712
|
-
// Non-critical
|
|
1713
|
-
}
|
|
1714
|
-
})();
|
|
1715
|
-
|
|
1716
|
-
// ── 26. CSS fallback for subgrid ───────────────────────────────────
|
|
1717
|
-
// Safari 16+ supports subgrid. Safari 15 doesn't.
|
|
1718
|
-
// Polyfill: set grid-column/row to span instead of subgrid
|
|
1719
|
-
(function polyfillSubgrid() {
|
|
1720
|
-
try {
|
|
1721
|
-
var styleSheets = document.styleSheets;
|
|
1722
|
-
var subgridSelectors = [];
|
|
1723
|
-
|
|
1724
|
-
for (var s = 0; s < styleSheets.length; s++) {
|
|
1725
|
-
var rules;
|
|
1726
|
-
try {
|
|
1727
|
-
rules = styleSheets[s].cssRules || styleSheets[s].rules;
|
|
1728
|
-
} catch (e) {
|
|
1729
|
-
continue;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
for (var r = 0; r < rules.length; r++) {
|
|
1733
|
-
var rule = rules[r];
|
|
1734
|
-
if (rule.style) {
|
|
1735
|
-
if (rule.style.gridColumn === "subgrid") {
|
|
1736
|
-
subgridSelectors.push({
|
|
1737
|
-
selector: rule.selectorText,
|
|
1738
|
-
axis: "column",
|
|
1739
|
-
});
|
|
1740
|
-
}
|
|
1741
|
-
if (rule.style.gridRow === "subgrid") {
|
|
1742
|
-
subgridSelectors.push({
|
|
1743
|
-
selector: rule.selectorText,
|
|
1744
|
-
axis: "row",
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
if (subgridSelectors.length > 0) {
|
|
1752
|
-
// Create override styles: replace subgrid with auto or span
|
|
1753
|
-
var override = document.createElement("style");
|
|
1754
|
-
override.setAttribute("data-ipados15-polyfill", "subgrid");
|
|
1755
|
-
var overrideCSS = "";
|
|
1756
|
-
subgridSelectors.forEach(function (ss) {
|
|
1757
|
-
if (ss.axis === "column") {
|
|
1758
|
-
overrideCSS += ss.selector + " { grid-column: auto !important; }\n";
|
|
1759
|
-
} else {
|
|
1760
|
-
overrideCSS += ss.selector + " { grid-row: auto !important; }\n";
|
|
1761
|
-
}
|
|
1762
|
-
});
|
|
1763
|
-
override.textContent = overrideCSS;
|
|
1764
|
-
document.head.appendChild(override);
|
|
1765
|
-
_log("subgrid", "polyfilled");
|
|
1766
|
-
}
|
|
1767
|
-
} catch (e) {
|
|
1768
|
-
// Non-critical
|
|
1769
|
-
}
|
|
1770
|
-
})();
|
|
1771
|
-
|
|
1772
|
-
// ── 27. CSS fallback for align-content on block layout ─────────────
|
|
1773
|
-
// Safari 17+ supports align-content on block containers.
|
|
1774
|
-
// Polyfill: convert to justify-content in flex or manual centering.
|
|
1775
|
-
(function polyfillAlignContent() {
|
|
1776
|
-
try {
|
|
1777
|
-
var styleSheets = document.styleSheets;
|
|
1778
|
-
var alignContentRules = [];
|
|
1779
|
-
|
|
1780
|
-
for (var s = 0; s < styleSheets.length; s++) {
|
|
1781
|
-
var rules;
|
|
1782
|
-
try {
|
|
1783
|
-
rules = styleSheets[s].cssRules || styleSheets[s].rules;
|
|
1784
|
-
} catch (e) {
|
|
1785
|
-
continue;
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
for (var r = 0; r < rules.length; r++) {
|
|
1789
|
-
var rule = rules[r];
|
|
1790
|
-
if (rule.style && rule.style.alignContent && rule.style.display !== "grid") {
|
|
1791
|
-
alignContentRules.push({
|
|
1792
|
-
selector: rule.selectorText,
|
|
1793
|
-
value: rule.style.alignContent,
|
|
1794
|
-
});
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
if (alignContentRules.length > 0) {
|
|
1800
|
-
var override = document.createElement("style");
|
|
1801
|
-
override.setAttribute("data-ipados15-polyfill", "align-content");
|
|
1802
|
-
var overrideCSS = "";
|
|
1803
|
-
alignContentRules.forEach(function (rule) {
|
|
1804
|
-
overrideCSS +=
|
|
1805
|
-
rule.selector +
|
|
1806
|
-
" { justify-content: " +
|
|
1807
|
-
rule.value +
|
|
1808
|
-
" !important; display: flex !important; flex-wrap: wrap !important; }\n";
|
|
1809
|
-
});
|
|
1810
|
-
override.textContent = overrideCSS;
|
|
1811
|
-
document.head.appendChild(override);
|
|
1812
|
-
_log("align-content (block)", "polyfilled");
|
|
1813
|
-
}
|
|
1814
|
-
} catch (e) {
|
|
1815
|
-
// Non-critical
|
|
1816
|
-
}
|
|
1817
|
-
})();
|
|
1818
|
-
|
|
1819
|
-
// ── 28. CSS @layer fallback ───────────────────────────────────────
|
|
1820
|
-
// Safari 15.4+ supports @layer. Safari 15.0-15.3 may not.
|
|
1821
|
-
// @layer rules are simply ignored by older browsers.
|
|
1822
|
-
// We detect and warn but don't transform (layering is complex).
|
|
1823
|
-
|
|
1824
|
-
// ── 29. Final diagnostic log ───────────────────────────────────────
|
|
1825
|
-
(function logSummary() {
|
|
1826
|
-
var visible = _applied.filter(function (a) {
|
|
1827
|
-
return a.indexOf("[polyfilled]") !== -1;
|
|
1828
|
-
});
|
|
1829
|
-
var skipped = _applied.filter(function (a) {
|
|
1830
|
-
return a.indexOf("[skipped") !== -1;
|
|
1831
|
-
});
|
|
1832
|
-
var errors = _applied.filter(function (a) {
|
|
1833
|
-
return a.indexOf("[error") !== -1;
|
|
1834
|
-
});
|
|
1835
|
-
|
|
1836
|
-
console.log("[%s] ====================================", POLYFILL_NS);
|
|
1837
|
-
console.log(
|
|
1838
|
-
"[%s] Polyfill summary: %d applied, %d skipped, %d errors",
|
|
1839
|
-
POLYFILL_NS,
|
|
1840
|
-
visible.length,
|
|
1841
|
-
skipped.length,
|
|
1842
|
-
errors.length
|
|
1843
|
-
);
|
|
1844
|
-
if (visible.length > 0) {
|
|
1845
|
-
console.log("[%s] Applied:", POLYFILL_NS);
|
|
1846
|
-
visible.forEach(function (a) {
|
|
1847
|
-
console.log(" - " + a.replace("[polyfilled]", ""));
|
|
1848
|
-
});
|
|
1849
|
-
}
|
|
1850
|
-
if (errors.length > 0) {
|
|
1851
|
-
console.log("[%s] Errors:", POLYFILL_NS);
|
|
1852
|
-
errors.forEach(function (a) {
|
|
1853
|
-
console.log(" - " + a);
|
|
1854
|
-
});
|
|
1855
|
-
}
|
|
1856
|
-
console.log("[%s] ====================================", POLYFILL_NS);
|
|
1857
|
-
})();
|
|
1858
|
-
|
|
1859
|
-
})();
|