sibujs 4.7.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/browser.cjs +48 -6
  2. package/dist/browser.d.cts +26 -2
  3. package/dist/browser.d.ts +26 -2
  4. package/dist/browser.js +6 -5
  5. package/dist/build.cjs +539 -96
  6. package/dist/build.d.cts +22 -8
  7. package/dist/build.d.ts +22 -8
  8. package/dist/build.js +68 -21
  9. package/dist/cdn.dev.global.js +15 -13
  10. package/dist/cdn.full.dev.global.js +15 -13
  11. package/dist/cdn.full.global.js +14 -12
  12. package/dist/cdn.global.js +14 -12
  13. package/dist/{chunk-Z3OHK6QT.js → chunk-2K3Z3XO4.js} +5 -5
  14. package/dist/chunk-3ACAAQIN.js +224 -0
  15. package/dist/{chunk-WOLJZUFQ.js → chunk-562UVHMM.js} +32 -15
  16. package/dist/{chunk-UOL2ECCS.js → chunk-5MLWTIQE.js} +18 -12
  17. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  18. package/dist/{chunk-3QWBSL5R.js → chunk-7OV5Q2UU.js} +8 -5
  19. package/dist/{chunk-SLM3IA34.js → chunk-CDBKFGXN.js} +1 -1
  20. package/dist/{chunk-4AWA2PVD.js → chunk-CF2KCWRE.js} +10 -5
  21. package/dist/{chunk-BSY63EM6.js → chunk-DRMLIF6M.js} +20 -5
  22. package/dist/{chunk-2INLLLMZ.js → chunk-EXXSN3BW.js} +202 -26
  23. package/dist/{chunk-HURREPU2.js → chunk-FC6KCJSA.js} +87 -150
  24. package/dist/{chunk-BC2SECJD.js → chunk-FHCZMHHE.js} +1 -1
  25. package/dist/{chunk-XYV3EDB7.js → chunk-FNOK662L.js} +63 -38
  26. package/dist/{chunk-JWKYU5GV.js → chunk-G2WHYEL7.js} +1 -1
  27. package/dist/{chunk-DKXRACVN.js → chunk-K6K5ACO6.js} +6 -6
  28. package/dist/{chunk-VZSG24LS.js → chunk-KG25XMGP.js} +45 -17
  29. package/dist/{chunk-FNJXNGYZ.js → chunk-MPWDSTYK.js} +58 -6
  30. package/dist/{chunk-UNWRJRKC.js → chunk-NQMKWBKU.js} +1 -1
  31. package/dist/{chunk-7LUJQAOJ.js → chunk-QBKCK6MJ.js} +1 -1
  32. package/dist/{chunk-WEQ3DMVL.js → chunk-SBTZEYNI.js} +1 -1
  33. package/dist/{chunk-FQRUXCEE.js → chunk-SYHGGDNW.js} +1 -1
  34. package/dist/{chunk-XZR4PXRE.js → chunk-TV464N2M.js} +13 -10
  35. package/dist/{chunk-RUSSKG6G.js → chunk-X7NRHFZR.js} +1 -1
  36. package/dist/{chunk-2BPG2XDA.js → chunk-XJVTQUIV.js} +18 -36
  37. package/dist/{chunk-ZVL7TY4K.js → chunk-YW6HQKGO.js} +25 -10
  38. package/dist/{customElement-MmInOW1U.d.ts → customElement-pYDKUzWl.d.cts} +11 -3
  39. package/dist/{customElement-MmInOW1U.d.cts → customElement-pYDKUzWl.d.ts} +11 -3
  40. package/dist/data.cjs +29 -9
  41. package/dist/data.d.cts +23 -4
  42. package/dist/data.d.ts +23 -4
  43. package/dist/data.js +6 -6
  44. package/dist/devtools.cjs +1 -1
  45. package/dist/devtools.js +5 -5
  46. package/dist/ecosystem.cjs +400 -37
  47. package/dist/ecosystem.js +9 -8
  48. package/dist/extras.cjs +672 -269
  49. package/dist/extras.d.cts +2 -2
  50. package/dist/extras.d.ts +2 -2
  51. package/dist/extras.js +24 -23
  52. package/dist/index.cjs +490 -88
  53. package/dist/index.d.cts +34 -2
  54. package/dist/index.d.ts +34 -2
  55. package/dist/index.js +16 -12
  56. package/dist/motion.cjs +1 -1
  57. package/dist/motion.js +3 -3
  58. package/dist/patterns.cjs +10 -7
  59. package/dist/patterns.js +6 -6
  60. package/dist/performance.cjs +31 -2
  61. package/dist/performance.d.cts +12 -2
  62. package/dist/performance.d.ts +12 -2
  63. package/dist/performance.js +8 -6
  64. package/dist/plugins.cjs +571 -240
  65. package/dist/plugins.d.cts +14 -3
  66. package/dist/plugins.d.ts +14 -3
  67. package/dist/plugins.js +35 -37
  68. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  69. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.cts} +6 -0
  70. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.ts} +6 -0
  71. package/dist/ssr.cjs +484 -138
  72. package/dist/ssr.d.cts +59 -5
  73. package/dist/ssr.d.ts +59 -5
  74. package/dist/ssr.js +11 -9
  75. package/dist/testing.cjs +3 -3
  76. package/dist/testing.js +4 -4
  77. package/dist/ui.cjs +609 -241
  78. package/dist/ui.d.cts +1 -1
  79. package/dist/ui.d.ts +1 -1
  80. package/dist/ui.js +9 -8
  81. package/dist/widgets.cjs +1 -1
  82. package/dist/widgets.js +7 -7
  83. package/package.json +5 -4
package/dist/plugins.cjs CHANGED
@@ -152,7 +152,7 @@ function stripControlChars(value) {
152
152
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
153
153
  }
154
154
  function canonicalAttrName(name) {
155
- return name.replace(/[A-Z]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32));
155
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
156
156
  }
157
157
  function isEventHandlerAttr(name) {
158
158
  if (name.length < 3) return false;
@@ -163,6 +163,9 @@ function sanitizeUrl(url) {
163
163
  const probe = stripControlChars(url).trim();
164
164
  if (!probe) return "";
165
165
  const output2 = url.replace(OUTER_TRIM, "");
166
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
167
+ return "";
168
+ }
166
169
  const lower = probe.toLowerCase();
167
170
  let schemeEnd = -1;
168
171
  for (let i2 = 0; i2 < lower.length; i2++) {
@@ -179,18 +182,57 @@ function sanitizeUrl(url) {
179
182
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
180
183
  return output2;
181
184
  }
185
+ function isSrcsetWhitespace(ch) {
186
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
187
+ }
182
188
  function sanitizeSrcset(value) {
183
- const parts = value.split(",");
189
+ const input2 = String(value);
184
190
  const out = [];
185
- for (let i2 = 0; i2 < parts.length; i2++) {
186
- const part = parts[i2].trim();
187
- if (!part) continue;
188
- const m = part.match(/^(\S+)(\s+.+)?$/);
189
- if (!m) continue;
190
- if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
191
- const safe = sanitizeUrl(m[1]);
191
+ let i2 = 0;
192
+ const n = input2.length;
193
+ while (i2 < n) {
194
+ while (i2 < n && (isSrcsetWhitespace(input2[i2]) || input2[i2] === ",")) i2++;
195
+ if (i2 >= n) break;
196
+ const urlStart = i2;
197
+ while (i2 < n && !isSrcsetWhitespace(input2[i2])) i2++;
198
+ let url = input2.slice(urlStart, i2);
199
+ const descriptors = [];
200
+ if (url.endsWith(",")) {
201
+ url = url.replace(/,+$/, "");
202
+ } else {
203
+ let current = "";
204
+ let inParens = false;
205
+ while (i2 < n) {
206
+ const ch = input2[i2];
207
+ if (inParens) {
208
+ current += ch;
209
+ if (ch === ")") inParens = false;
210
+ i2++;
211
+ continue;
212
+ }
213
+ if (isSrcsetWhitespace(ch)) {
214
+ if (current) descriptors.push(current);
215
+ current = "";
216
+ i2++;
217
+ continue;
218
+ }
219
+ if (ch === ",") {
220
+ i2++;
221
+ break;
222
+ }
223
+ if (ch === "(") inParens = true;
224
+ current += ch;
225
+ i2++;
226
+ }
227
+ if (current) descriptors.push(current);
228
+ }
229
+ if (url === "") continue;
230
+ if (descriptors.length > 1) continue;
231
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
232
+ const safe = sanitizeUrl(url);
192
233
  if (!safe) continue;
193
- out.push(m[2] ? `${safe}${m[2]}` : safe);
234
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
235
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
194
236
  }
195
237
  return out.join(", ");
196
238
  }
@@ -259,7 +301,9 @@ function warnDroppedDeclaration(value, reason, ctx) {
259
301
  "behavior: loads and runs a remote HTC script",
260
302
  "@import pulls in a remote stylesheet",
261
303
  "image-set() fetches remote images, the same exfiltration channel as url()",
262
- "filter:progid activates legacy scriptable filters"
304
+ "filter:progid activates legacy scriptable filters",
305
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
306
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
263
307
  ];
264
308
  const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
265
309
  const where = describeElement(ctx?.element);
@@ -274,27 +318,110 @@ function warnDroppedDeclaration(value, reason, ctx) {
274
318
  return `style declaration "${declaration}" was dropped by the style sanitizer on ${where} \u2014 ${reasons[reason]}. The element renders without it, which usually looks like a missing image rather than an error. Sanctioned alternatives: for a content image use an <img> element (or a <picture>); for decoration, put the rule in a stylesheet and set a class instead \u2014 stylesheet CSS is not sanitized, only inline style attributes are. If this declaration is not yours, it came from untrusted data and the drop just prevented an exfiltration.`;
275
319
  });
276
320
  }
321
+ function normalizeCssForScan(value) {
322
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
323
+ return normalized.toLowerCase().replace(/\s+/g, "");
324
+ }
277
325
  function blockedCssReason(value) {
278
326
  if (!CSS_DANGER_GATE.test(value)) return -1;
279
- const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
280
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
327
+ const lower = normalizeCssForScan(value);
281
328
  for (let i2 = 0; i2 < BLOCKED_CSS_NEEDLES.length; i2++) {
282
329
  if (lower.includes(BLOCKED_CSS_NEEDLES[i2])) return i2;
283
330
  }
284
331
  return -1;
285
332
  }
286
- function sanitizeCSSValue(value, context) {
287
- const reason = blockedCssReason(value);
333
+ function sanitizeCSSDeclaration(property, value, context) {
334
+ const reason = blockedDeclarationReason(property, value);
288
335
  if (reason === -1) return value;
289
- warnDroppedDeclaration(value, reason, context);
336
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
290
337
  return "";
291
338
  }
339
+ function blockedDeclarationReason(property, value) {
340
+ const reason = blockedCssReason(value);
341
+ if (reason !== -1) return reason;
342
+ const name = canonicalAttrName(property);
343
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
344
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
345
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
346
+ return CSS_REASON_PROGID;
347
+ }
348
+ return -1;
349
+ }
350
+ function parseStyleDeclarations(css) {
351
+ const chunks = [];
352
+ let buf = "";
353
+ let depth = 0;
354
+ let quote2 = "";
355
+ for (let i2 = 0; i2 < css.length; i2++) {
356
+ const ch = css[i2];
357
+ if (quote2) {
358
+ buf += ch;
359
+ if (ch === "\\" && i2 + 1 < css.length) buf += css[++i2];
360
+ else if (ch === quote2) quote2 = "";
361
+ continue;
362
+ }
363
+ if (ch === "\\") {
364
+ buf += ch;
365
+ if (i2 + 1 < css.length) buf += css[++i2];
366
+ continue;
367
+ }
368
+ if (ch === "/" && css[i2 + 1] === "*") {
369
+ const end = css.indexOf("*/", i2 + 2);
370
+ if (end === -1) return null;
371
+ i2 = end + 1;
372
+ continue;
373
+ }
374
+ if (ch === '"' || ch === "'") quote2 = ch;
375
+ else if (ch === "(" || ch === "[") depth++;
376
+ else if (ch === ")" || ch === "]") {
377
+ if (--depth < 0) return null;
378
+ } else if (ch === "{" || ch === "}") return null;
379
+ else if (ch === ";" && depth === 0) {
380
+ chunks.push(buf);
381
+ buf = "";
382
+ continue;
383
+ }
384
+ buf += ch;
385
+ }
386
+ if (quote2 || depth !== 0) return null;
387
+ chunks.push(buf);
388
+ const out = [];
389
+ for (const chunk of chunks) {
390
+ const colon = chunk.indexOf(":");
391
+ if (colon === -1) continue;
392
+ const rawName = chunk.slice(0, colon).trim();
393
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
394
+ let value = chunk.slice(colon + 1).trim();
395
+ const bang = /!\s*important\s*$/i.exec(value);
396
+ if (bang) value = value.slice(0, bang.index).trim();
397
+ if (value === "") continue;
398
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
399
+ out.push([property, value, bang !== null]);
400
+ }
401
+ return out;
402
+ }
403
+ function sanitizeStyleDeclarationList(cssText, context) {
404
+ const input2 = String(cssText);
405
+ const declarations = parseStyleDeclarations(input2);
406
+ if (declarations === null) {
407
+ if (blockedCssReason(input2) !== -1) warnDroppedDeclaration(input2, blockedCssReason(input2), context);
408
+ return "";
409
+ }
410
+ const kept = [];
411
+ for (const [property, value, important] of declarations) {
412
+ const reason = blockedDeclarationReason(property, value);
413
+ if (reason !== -1) {
414
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
415
+ continue;
416
+ }
417
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
418
+ }
419
+ return kept.join("; ");
420
+ }
292
421
  function sanitizeStyleAttribute(cssText, context) {
293
422
  const input2 = String(cssText);
294
423
  if (input2.trim() === "") return "";
295
- if (typeof document === "undefined") {
296
- return sanitizeCSSValue(input2, context) === "" ? "" : input2;
297
- }
424
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input2, context);
298
425
  const probe = document.createElement("div");
299
426
  try {
300
427
  probe.style.cssText = input2;
@@ -305,8 +432,7 @@ function sanitizeStyleAttribute(cssText, context) {
305
432
  for (let i2 = 0; i2 < probe.style.length; i2++) {
306
433
  const property = probe.style[i2];
307
434
  const value = probe.style.getPropertyValue(property);
308
- let reason = blockedCssReason(value);
309
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
435
+ const reason = blockedDeclarationReason(property, value);
310
436
  if (reason !== -1) {
311
437
  warnDroppedDeclaration(value, reason, { property, element: context?.element });
312
438
  continue;
@@ -333,14 +459,21 @@ function sanitizeAttributeString(attr, value, context) {
333
459
  if (lower === "style") return sanitizeStyleAttribute(value, context);
334
460
  return value;
335
461
  }
336
- var SAFE_URL_PROTOCOLS, OUTER_TRIM, SRCSET_DESCRIPTOR, CSS_DANGER_GATE, BLOCKED_CSS_NEEDLES, warnedDeclarations, MAX_WARNED_DECLARATIONS, declarationCapAnnounced, URL_ATTRIBUTES, HTML_CONTENT_ATTRIBUTES;
462
+ function resolveAttributeValue(attr, value, context) {
463
+ if (!isPolicyAttribute(attr)) return value;
464
+ const safe = sanitizeAttributeString(attr, value, context);
465
+ return safe === "" ? null : safe;
466
+ }
467
+ var ASCII_UPPER, ASCII_UPPER_G, SAFE_URL_PROTOCOLS, OUTER_TRIM, SRCSET_DESCRIPTOR, CSS_DANGER_GATE, BLOCKED_CSS_NEEDLES, CSS_REASON_MOZ_BINDING, CSS_REASON_BEHAVIOR, CSS_REASON_PROGID, warnedDeclarations, MAX_WARNED_DECLARATIONS, declarationCapAnnounced, URL_ATTRIBUTES, HTML_CONTENT_ATTRIBUTES;
337
468
  var init_sanitize = __esm({
338
469
  "src/utils/sanitize.ts"() {
339
470
  "use strict";
340
471
  init_dev();
472
+ ASCII_UPPER = /[A-Z]/;
473
+ ASCII_UPPER_G = /[A-Z]/g;
341
474
  SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
342
475
  OUTER_TRIM = /^[\x00-\x20\x7f-\x9f]+|[\x00-\x20\x7f-\x9f]+$/g;
343
- SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
476
+ SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
344
477
  CSS_DANGER_GATE = /[(:@\\]/;
345
478
  BLOCKED_CSS_NEEDLES = [
346
479
  "url(",
@@ -351,8 +484,16 @@ var init_sanitize = __esm({
351
484
  "behavior:",
352
485
  "@import",
353
486
  "image-set(",
354
- "filter:progid"
487
+ "filter:progid",
488
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
489
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
490
+ // today, which is exactly when a blocklist should get ahead of it.
491
+ "image(",
492
+ "src("
355
493
  ];
494
+ CSS_REASON_MOZ_BINDING = 4;
495
+ CSS_REASON_BEHAVIOR = 5;
496
+ CSS_REASON_PROGID = 8;
356
497
  warnedDeclarations = /* @__PURE__ */ new Set();
357
498
  MAX_WARNED_DECLARATIONS = 100;
358
499
  declarationCapAnnounced = false;
@@ -378,6 +519,224 @@ var init_sanitize = __esm({
378
519
  }
379
520
  });
380
521
 
522
+ // src/utils/metaRefresh.ts
523
+ function isAsciiWhitespace(ch) {
524
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
525
+ }
526
+ function trimAsciiWhitespace(value) {
527
+ let start = 0;
528
+ let end = value.length;
529
+ while (start < end && isAsciiWhitespace(value[start])) start++;
530
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
531
+ return value.slice(start, end);
532
+ }
533
+ function parseMetaRefreshContent(content) {
534
+ const raw = trimAsciiWhitespace(stripControlChars(content));
535
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
536
+ let i2 = 0;
537
+ while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
538
+ if (i2 === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
539
+ const delay = Number.parseInt(raw.slice(0, i2), 10);
540
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
541
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
542
+ if (i2 === raw.length) return { kind: "delay-only", delay };
543
+ if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
544
+ i2++;
545
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
546
+ const keyStart = i2;
547
+ while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
548
+ const key = raw.slice(keyStart, i2).toLowerCase();
549
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
550
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
551
+ if (raw[i2] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
552
+ i2++;
553
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
554
+ let destination;
555
+ const quote2 = raw[i2];
556
+ if (quote2 === '"' || quote2 === "'") {
557
+ const close = raw.indexOf(quote2, i2 + 1);
558
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
559
+ destination = raw.slice(i2 + 1, close);
560
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
561
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
562
+ } else {
563
+ destination = trimAsciiWhitespace(raw.slice(i2));
564
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
565
+ }
566
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
567
+ const sanitizedDestination = sanitizeUrl(destination);
568
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
569
+ return { kind: "allowed", delay, destination, sanitizedDestination };
570
+ }
571
+ function resolveMetaRefreshPolicy(attributes) {
572
+ let httpEquiv;
573
+ let content;
574
+ let duplicateName;
575
+ const seen = /* @__PURE__ */ new Set();
576
+ for (const [key, value] of attributes) {
577
+ const canonical = canonicalAttrName(key);
578
+ if (seen.has(canonical)) duplicateName = canonical;
579
+ seen.add(canonical);
580
+ if (canonical === "http-equiv") httpEquiv = value;
581
+ else if (canonical === "content") content = value;
582
+ }
583
+ if (duplicateName !== void 0) {
584
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
585
+ }
586
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
587
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
588
+ return { kind: "not-refresh" };
589
+ }
590
+ if (typeof content !== "string") return { kind: "not-refresh" };
591
+ return parseMetaRefreshContent(content);
592
+ }
593
+ function normalizeMetaAttributes(props) {
594
+ const out = /* @__PURE__ */ new Map();
595
+ const seen = /* @__PURE__ */ new Set();
596
+ for (const key of Object.keys(props)) {
597
+ const canonical = canonicalAttrName(key);
598
+ if (seen.has(canonical)) return null;
599
+ seen.add(canonical);
600
+ out.set(key, props[key]);
601
+ }
602
+ return out;
603
+ }
604
+ function isForbiddenMetaEntry(props) {
605
+ const normalized = normalizeMetaAttributes(props);
606
+ if (normalized === null) return true;
607
+ return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
608
+ }
609
+ var forbidden;
610
+ var init_metaRefresh = __esm({
611
+ "src/utils/metaRefresh.ts"() {
612
+ "use strict";
613
+ init_dev();
614
+ init_sanitize();
615
+ forbidden = (reason) => ({ kind: "forbidden", reason });
616
+ }
617
+ });
618
+
619
+ // src/utils/elementPolicy.ts
620
+ function canonicalTagName(tag) {
621
+ return canonicalAttrName(tag);
622
+ }
623
+ function isBlockedElement(tag) {
624
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
625
+ }
626
+ function isCodeTextElement(tag) {
627
+ return CODE_TEXT_ELEMENTS.has(canonicalTagName(tag));
628
+ }
629
+ function isHtmlNamespace(el) {
630
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
631
+ }
632
+ function metaSnapshot(el, canonical, value) {
633
+ const snapshot = /* @__PURE__ */ new Map();
634
+ const attrs = el.attributes;
635
+ for (let i2 = 0; i2 < attrs.length; i2++) {
636
+ const attr = attrs[i2];
637
+ if (attr.name === canonical) continue;
638
+ snapshot.set(attr.name, attr.value);
639
+ }
640
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
641
+ return snapshot;
642
+ }
643
+ function claimReactiveMeta(el) {
644
+ if (reactiveMetaElements.has(el)) return;
645
+ reactiveMetaElements.add(el);
646
+ if (el.getAttribute("content") === null) return;
647
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
648
+ el.removeAttribute("content");
649
+ }
650
+ }
651
+ function metaRefusal(el, name, value, origin) {
652
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
653
+ const canonical = canonicalAttrName(name);
654
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
655
+ if (origin === "reactive") claimReactiveMeta(el);
656
+ if (value === null) return ContextualRefusal.None;
657
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
658
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
659
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
660
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
661
+ return ContextualRefusal.None;
662
+ }
663
+ function animationRefusal(name, value) {
664
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
665
+ const target = stripControlChars(value);
666
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
667
+ }
668
+ function relHasStylesheet(rel) {
669
+ if (rel === null) return false;
670
+ const lower = canonicalAttrName(rel);
671
+ if (!lower.includes("stylesheet")) return false;
672
+ const tokens = lower.split(/[\t\n\f\r ]+/);
673
+ for (let i2 = 0; i2 < tokens.length; i2++) if (tokens[i2] === "stylesheet") return true;
674
+ return false;
675
+ }
676
+ function stylesheetRefusal(el, name, value, origin) {
677
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
678
+ const canonical = canonicalAttrName(name);
679
+ if (canonical === "href") {
680
+ if (origin === "static" || value === null) return ContextualRefusal.None;
681
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
682
+ runtimeHrefLinks.add(el);
683
+ return ContextualRefusal.None;
684
+ }
685
+ if (canonical === "rel") {
686
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
687
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
688
+ }
689
+ return ContextualRefusal.None;
690
+ }
691
+ function contextualAttributeRefusal(el, name, value, origin) {
692
+ const local = el.localName;
693
+ if (local === "meta") return metaRefusal(el, name, value, origin);
694
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
695
+ if (local === "script") {
696
+ if (origin === "static" || value === null) return ContextualRefusal.None;
697
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
698
+ }
699
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS) return animationRefusal(name, value);
700
+ return ContextualRefusal.None;
701
+ }
702
+ function settleAttributeProvenance(el, name, origin) {
703
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
704
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
705
+ else runtimeHrefLinks.delete(el);
706
+ }
707
+ function isForbiddenMetaSnapshot(attributes) {
708
+ return resolveMetaRefreshPolicy(attributes).kind === "forbidden";
709
+ }
710
+ var HTML_NS, SVG_NS, BLOCKED_ELEMENTS, CODE_TEXT_ELEMENTS, ContextualRefusal, SCRIPT_SOURCE_ATTRIBUTES, SVG_ANIMATION_ELEMENTS, reactiveMetaElements, runtimeHrefLinks;
711
+ var init_elementPolicy = __esm({
712
+ "src/utils/elementPolicy.ts"() {
713
+ "use strict";
714
+ init_metaRefresh();
715
+ init_sanitize();
716
+ HTML_NS = "http://www.w3.org/1999/xhtml";
717
+ SVG_NS = "http://www.w3.org/2000/svg";
718
+ BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
719
+ CODE_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
720
+ ContextualRefusal = {
721
+ None: 0,
722
+ /** The element would carry a refresh directive the shared policy forbids. */
723
+ MetaRefreshForbidden: 1,
724
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
725
+ MetaRefreshReactive: 2,
726
+ /** A runtime value may not choose which program a `<script>` runs. */
727
+ ScriptSource: 3,
728
+ /** An SVG animation may not target a URL, handler or document attribute. */
729
+ AnimationTarget: 4,
730
+ /** A runtime-written `href` may not become an applied stylesheet. */
731
+ StylesheetSource: 5
732
+ };
733
+ SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
734
+ SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
735
+ reactiveMetaElements = /* @__PURE__ */ new WeakSet();
736
+ runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
737
+ }
738
+ });
739
+
381
740
  // src/core/rendering/dispose.ts
382
741
  var dispose_exports = {};
383
742
  __export(dispose_exports, {
@@ -607,99 +966,22 @@ var init_ssr_context = __esm({
607
966
  }
608
967
  });
609
968
 
610
- // src/utils/metaRefresh.ts
611
- function isAsciiWhitespace(ch) {
612
- return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
613
- }
614
- function trimAsciiWhitespace(value) {
615
- let start = 0;
616
- let end = value.length;
617
- while (start < end && isAsciiWhitespace(value[start])) start++;
618
- while (end > start && isAsciiWhitespace(value[end - 1])) end--;
619
- return value.slice(start, end);
620
- }
621
- function parseMetaRefreshContent(content) {
622
- const raw = trimAsciiWhitespace(stripControlChars(content));
623
- if (raw === "") return forbidden("empty content");
624
- let i2 = 0;
625
- while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
626
- if (i2 === 0) return forbidden("missing or non-numeric delay");
627
- const delay = Number.parseInt(raw.slice(0, i2), 10);
628
- if (!Number.isFinite(delay)) return forbidden("unparseable delay");
629
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
630
- if (i2 === raw.length) return { kind: "delay-only", delay };
631
- if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden("unexpected text after delay");
632
- i2++;
633
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
634
- const keyStart = i2;
635
- while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
636
- const key = raw.slice(keyStart, i2).toLowerCase();
637
- if (key !== "url") return forbidden(`unsupported refresh key ${JSON.stringify(key)}`);
638
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
639
- if (raw[i2] !== "=") return forbidden("missing '=' after url");
640
- i2++;
641
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
642
- let destination;
643
- const quote2 = raw[i2];
644
- if (quote2 === '"' || quote2 === "'") {
645
- const close = raw.indexOf(quote2, i2 + 1);
646
- if (close === -1) return forbidden("unterminated quoted destination");
647
- destination = raw.slice(i2 + 1, close);
648
- const rest = trimAsciiWhitespace(raw.slice(close + 1));
649
- if (rest !== "") return forbidden("trailing text after quoted destination");
650
- } else {
651
- destination = trimAsciiWhitespace(raw.slice(i2));
652
- if (/[;,]/.test(destination)) return forbidden("multiple refresh assignments");
653
- }
654
- if (destination === "") return forbidden("empty destination");
655
- const sanitizedDestination = sanitizeUrl(destination);
656
- if (sanitizedDestination === "") return forbidden("destination uses a disallowed protocol");
657
- return { kind: "allowed", delay, destination, sanitizedDestination };
658
- }
659
- function resolveMetaRefreshPolicy(attributes) {
660
- let httpEquiv;
661
- let content;
662
- let duplicateName;
663
- const seen = /* @__PURE__ */ new Set();
664
- for (const [key, value] of attributes) {
665
- const canonical = canonicalAttrName(key);
666
- if (seen.has(canonical)) duplicateName = canonical;
667
- seen.add(canonical);
668
- if (canonical === "http-equiv") httpEquiv = value;
669
- else if (canonical === "content") content = value;
670
- }
671
- if (duplicateName !== void 0) {
672
- return forbidden(`duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}`);
673
- }
674
- if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
675
- if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
676
- return { kind: "not-refresh" };
677
- }
678
- if (typeof content !== "string") return { kind: "not-refresh" };
679
- return parseMetaRefreshContent(content);
680
- }
681
- function normalizeMetaAttributes(props) {
682
- const out = /* @__PURE__ */ new Map();
683
- const seen = /* @__PURE__ */ new Set();
684
- for (const key of Object.keys(props)) {
685
- const canonical = canonicalAttrName(key);
686
- if (seen.has(canonical)) return null;
687
- seen.add(canonical);
688
- out.set(key, props[key]);
689
- }
690
- return out;
969
+ // src/utils/guards.ts
970
+ function isUnsafeKey(key) {
971
+ return UNSAFE_KEYS.has(key);
691
972
  }
692
- function isForbiddenMetaEntry(props) {
693
- const normalized = normalizeMetaAttributes(props);
694
- if (normalized === null) return true;
695
- return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
973
+ function readOwnGlobal(key) {
974
+ if (typeof window === "undefined") return void 0;
975
+ if (!Object.hasOwn(window, key)) return void 0;
976
+ const value = window[key];
977
+ if (typeof Node !== "undefined" && value instanceof Node) return void 0;
978
+ return value;
696
979
  }
697
- var forbidden;
698
- var init_metaRefresh = __esm({
699
- "src/utils/metaRefresh.ts"() {
980
+ var UNSAFE_KEYS;
981
+ var init_guards = __esm({
982
+ "src/utils/guards.ts"() {
700
983
  "use strict";
701
- init_sanitize();
702
- forbidden = (reason) => ({ kind: "forbidden", reason });
984
+ UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
703
985
  }
704
986
  });
705
987
 
@@ -712,9 +994,7 @@ function isEmittableHeadAttrName(name) {
712
994
  return true;
713
995
  }
714
996
  function sanitizeHeadAttrValue(canonical, value) {
715
- if (!isPolicyAttribute(canonical)) return value;
716
- const safe = sanitizeAttributeString(canonical, value);
717
- return safe === "" ? null : safe;
997
+ return resolveAttributeValue(canonical, value);
718
998
  }
719
999
  function findDuplicateAttributeName(names) {
720
1000
  const seen = /* @__PURE__ */ new Set();
@@ -801,15 +1081,9 @@ __export(ssr_exports, {
801
1081
  suspenseSwapScript: () => suspenseSwapScript,
802
1082
  trustHTML: () => trustHTML
803
1083
  });
804
- function isPolicyAttr(lowerName) {
805
- return isPolicyAttribute(lowerName);
806
- }
807
1084
  function isForbiddenSsrAttr(lowerName) {
808
1085
  return isHtmlContentAttribute(lowerName);
809
1086
  }
810
- function sanitizeSsrAttributeValue(lowerName, value) {
811
- return sanitizeAttributeString(lowerName, value);
812
- }
813
1087
  function isSafeAttrName(name) {
814
1088
  return SAFE_ATTR_NAME.test(name);
815
1089
  }
@@ -823,6 +1097,63 @@ function ssrErrorComment(err) {
823
1097
  function safeCommentText(text2) {
824
1098
  return text2.replace(/-->/g, "--&gt;").replace(/--!>/g, "--!&gt;").replace(/<!--/g, "&lt;!--").replace(/--$/g, "--&#45;");
825
1099
  }
1100
+ function findNativeGetter(proto, name) {
1101
+ for (let p2 = proto; p2; p2 = Object.getPrototypeOf(p2)) {
1102
+ const get = Object.getOwnPropertyDescriptor(p2, name)?.get;
1103
+ if (get) return get;
1104
+ }
1105
+ return null;
1106
+ }
1107
+ function nativeAccessors() {
1108
+ const realm = Element;
1109
+ let accessors = nativeAccessorsByRealm.get(realm);
1110
+ if (!accessors) {
1111
+ accessors = {
1112
+ attributes: findNativeGetter(Element.prototype, "attributes"),
1113
+ tagName: findNativeGetter(Element.prototype, "tagName"),
1114
+ childNodes: findNativeGetter(Node.prototype, "childNodes"),
1115
+ getAttribute: Element.prototype.getAttribute
1116
+ };
1117
+ nativeAccessorsByRealm.set(realm, accessors);
1118
+ }
1119
+ return accessors;
1120
+ }
1121
+ function attributesOf(el) {
1122
+ const get = nativeAccessors().attributes;
1123
+ return get ? get.call(el) : el.attributes;
1124
+ }
1125
+ function tagNameOf(el) {
1126
+ const get = nativeAccessors().tagName;
1127
+ return get ? get.call(el) : el.tagName;
1128
+ }
1129
+ function childNodesOf(node) {
1130
+ const get = nativeAccessors().childNodes;
1131
+ return get ? get.call(node) : node.childNodes;
1132
+ }
1133
+ function getAttributeOf(el, name) {
1134
+ return nativeAccessors().getAttribute.call(el, name);
1135
+ }
1136
+ function serializeOpenTag(element, tag) {
1137
+ let html2 = `<${tag}`;
1138
+ const meta2 = tag === "meta" ? /* @__PURE__ */ new Map() : null;
1139
+ const attrs = attributesOf(element);
1140
+ for (let i2 = 0; i2 < attrs.length; i2++) {
1141
+ const attr = attrs[i2];
1142
+ const rawName = attr.name;
1143
+ if (!isSafeAttrName(rawName)) continue;
1144
+ if (isEventHandlerAttr(rawName)) continue;
1145
+ if (isForbiddenSsrAttr(rawName)) continue;
1146
+ const value = resolveAttributeValue(rawName.toLowerCase(), attr.value);
1147
+ if (value === null) continue;
1148
+ meta2?.set(rawName, value);
1149
+ html2 += ` ${rawName}="${escapeAttr(value)}"`;
1150
+ }
1151
+ if (meta2 && isForbiddenMetaSnapshot(meta2)) return null;
1152
+ if (!getAttributeOf(element, "data-sibu-hydrate")) {
1153
+ html2 += ` data-sibu-ssr="true"`;
1154
+ }
1155
+ return html2;
1156
+ }
826
1157
  function renderToString(element) {
827
1158
  if (element instanceof DocumentFragment) {
828
1159
  return Array.from(element.childNodes).map((child) => {
@@ -842,35 +1173,21 @@ function renderToString(element) {
842
1173
  if (!(element instanceof HTMLElement)) {
843
1174
  return escapeHtml(element.textContent || "");
844
1175
  }
845
- const tag = element.tagName.toLowerCase();
846
- if (tag === "script" || tag === "style") {
1176
+ const tag = tagNameOf(element).toLowerCase();
1177
+ if (isCodeTextElement(tag)) {
847
1178
  return DEV ? `<!--ssr:${tag}-stripped-->` : "";
848
1179
  }
849
1180
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
850
1181
  return DEV ? "<!--ssr:invalid-tag-->" : "";
851
1182
  }
852
- let html2 = `<${tag}`;
853
- for (const attr of Array.from(element.attributes)) {
854
- const rawName = attr.name;
855
- if (!isSafeAttrName(rawName)) continue;
856
- if (isEventHandlerAttr(rawName)) continue;
857
- if (isForbiddenSsrAttr(rawName)) continue;
858
- const lowerName = rawName.toLowerCase();
859
- let value = attr.value;
860
- if (isPolicyAttr(lowerName)) {
861
- value = sanitizeSsrAttributeValue(lowerName, value);
862
- if (!value) continue;
863
- }
864
- html2 += ` ${rawName}="${escapeAttr(value)}"`;
865
- }
866
- if (element.dataset && !element.dataset.sibuHydrate) {
867
- html2 += ` data-sibu-ssr="true"`;
868
- }
1183
+ const openTag = serializeOpenTag(element, tag);
1184
+ if (openTag === null) return DEV ? `<!--ssr:${tag}-stripped-->` : "";
1185
+ let html2 = openTag;
869
1186
  if (VOID_ELEMENTS.has(tag)) {
870
1187
  return `${html2} />`;
871
1188
  }
872
1189
  html2 += ">";
873
- for (const child of Array.from(element.childNodes)) {
1190
+ for (const child of Array.from(childNodesOf(element))) {
874
1191
  try {
875
1192
  html2 += renderToString(child);
876
1193
  } catch (err) {
@@ -1020,12 +1337,8 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
1020
1337
  if (!isSafeAttrName(rawKey)) continue;
1021
1338
  if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
1022
1339
  if (isForbiddenSsrAttr(rawKey)) continue;
1023
- const lowerKey = rawKey.toLowerCase();
1024
- let value = String(attrs[rawKey]);
1025
- if (isPolicyAttr(lowerKey)) {
1026
- value = sanitizeSsrAttributeValue(lowerKey, value);
1027
- if (!value) continue;
1028
- }
1340
+ const value = resolveAttributeValue(rawKey.toLowerCase(), String(attrs[rawKey]));
1341
+ if (value === null) continue;
1029
1342
  out.push(`${rawKey}="${escapeAttr(value)}"`);
1030
1343
  }
1031
1344
  return out.join(" ");
@@ -1088,8 +1401,8 @@ async function* renderToStream(element) {
1088
1401
  yield escapeHtml(element.textContent || "");
1089
1402
  return;
1090
1403
  }
1091
- const tag = element.tagName.toLowerCase();
1092
- if (tag === "script" || tag === "style") {
1404
+ const tag = tagNameOf(element).toLowerCase();
1405
+ if (isCodeTextElement(tag)) {
1093
1406
  if (DEV) yield `<!--ssr:${tag}-stripped-->`;
1094
1407
  return;
1095
1408
  }
@@ -1097,29 +1410,17 @@ async function* renderToStream(element) {
1097
1410
  if (DEV) yield "<!--ssr:invalid-tag-->";
1098
1411
  return;
1099
1412
  }
1100
- let openTag = `<${tag}`;
1101
- for (const attr of Array.from(element.attributes)) {
1102
- const rawName = attr.name;
1103
- if (!isSafeAttrName(rawName)) continue;
1104
- if (isEventHandlerAttr(rawName)) continue;
1105
- if (isForbiddenSsrAttr(rawName)) continue;
1106
- const lowerName = rawName.toLowerCase();
1107
- let value = attr.value;
1108
- if (isPolicyAttr(lowerName)) {
1109
- value = sanitizeSsrAttributeValue(lowerName, value);
1110
- if (!value) continue;
1111
- }
1112
- openTag += ` ${rawName}="${escapeAttr(value)}"`;
1113
- }
1114
- if (element.dataset && !element.dataset.sibuHydrate) {
1115
- openTag += ` data-sibu-ssr="true"`;
1413
+ const openTag = serializeOpenTag(element, tag);
1414
+ if (openTag === null) {
1415
+ if (DEV) yield `<!--ssr:${tag}-stripped-->`;
1416
+ return;
1116
1417
  }
1117
1418
  if (VOID_ELEMENTS.has(tag)) {
1118
1419
  yield `${openTag} />`;
1119
1420
  return;
1120
1421
  }
1121
1422
  yield `${openTag}>`;
1122
- for (const child of Array.from(element.childNodes)) {
1423
+ for (const child of Array.from(childNodesOf(element))) {
1123
1424
  try {
1124
1425
  yield* renderToStream(child);
1125
1426
  } catch (err) {
@@ -1300,8 +1601,7 @@ function deserializeState(validate) {
1300
1601
  "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
1301
1602
  );
1302
1603
  }
1303
- const w = window;
1304
- const raw = w[SSR_DATA_ATTR];
1604
+ const raw = readOwnGlobal(SSR_DATA_ATTR);
1305
1605
  if (raw === void 0) return void 0;
1306
1606
  if (validate && !validate(raw)) return void 0;
1307
1607
  return raw;
@@ -1312,13 +1612,15 @@ function escapeHtml(str) {
1312
1612
  function escapeAttr(str) {
1313
1613
  return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1314
1614
  }
1315
- var SAFE_ATTR_NAME, VOID_ELEMENTS, SAFE_ID, SAFE_SUSPENSE_ID, SSR_DATA_ATTR, DEFAULT_MAX_SSR_BYTES;
1615
+ var SAFE_ATTR_NAME, VOID_ELEMENTS, nativeAccessorsByRealm, SAFE_ID, SAFE_SUSPENSE_ID, SSR_DATA_ATTR, DEFAULT_MAX_SSR_BYTES;
1316
1616
  var init_ssr = __esm({
1317
1617
  "src/platform/ssr.ts"() {
1318
1618
  "use strict";
1319
1619
  init_dev();
1320
1620
  init_dispose();
1321
1621
  init_ssr_context();
1622
+ init_elementPolicy();
1623
+ init_guards();
1322
1624
  init_headEntry();
1323
1625
  init_metaRefresh();
1324
1626
  init_sanitize();
@@ -1339,6 +1641,7 @@ var init_ssr = __esm({
1339
1641
  "track",
1340
1642
  "wbr"
1341
1643
  ]);
1644
+ nativeAccessorsByRealm = /* @__PURE__ */ new WeakMap();
1342
1645
  SAFE_ID = /^[A-Za-z0-9_-]+$/;
1343
1646
  SAFE_SUSPENSE_ID = /^[A-Za-z0-9_-]+$/;
1344
1647
  SSR_DATA_ATTR = "__SIBU_SSR_DATA__";
@@ -1441,26 +1744,45 @@ init_sanitize();
1441
1744
 
1442
1745
  // src/utils/setSafeAttribute.ts
1443
1746
  init_dev();
1747
+ init_elementPolicy();
1444
1748
  init_sanitize();
1445
- var HTML_NS = "http://www.w3.org/1999/xhtml";
1446
- var SVG_NS = "http://www.w3.org/2000/svg";
1749
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
1750
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
1447
1751
  var XLINK_NS = "http://www.w3.org/1999/xlink";
1448
1752
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
1449
1753
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
1450
1754
  function isHtmlElement(el) {
1451
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
1755
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
1756
+ }
1757
+ function warnContextualRefusal(label2, name, refusal) {
1758
+ devWarnLazy(() => {
1759
+ const reasons = [
1760
+ "",
1761
+ 'the element would become a <meta http-equiv="refresh"> directive the shared refresh policy forbids (a destination outside the URL allowlist, or a directive too malformed to read unambiguously)',
1762
+ "a <meta> whose http-equiv or content is reactive may never carry a refresh directive: a browser schedules the navigation as soon as the directive is valid, and nothing can withdraw it when the state changes back",
1763
+ "a runtime value may not choose the program a <script> runs; name the script in static template source, or load it through Head({ script }) as an explicit trust decision",
1764
+ "an SVG animation may not target a URL, event-handler or nested-document attribute; its to / values would be written into that attribute without passing the URL policy",
1765
+ 'a runtime-chosen href may not become an applied <link rel="stylesheet">: a well-formed URL is not a trusted stylesheet. Name the stylesheet in static template source, or load a runtime-chosen one through Head({ link }) as an explicit trust decision'
1766
+ ];
1767
+ return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
1768
+ });
1452
1769
  }
1453
1770
  function setProp(el, key, val) {
1454
1771
  el[key] = val;
1455
1772
  }
1456
1773
  function namespaceFor(el, name) {
1457
- if (el.namespaceURI !== SVG_NS) return null;
1774
+ if (el.namespaceURI !== SVG_NS2) return null;
1458
1775
  const lower = name.toLowerCase();
1459
1776
  if (lower.startsWith("xlink:")) return XLINK_NS;
1460
1777
  if (lower.startsWith("xml:")) return XML_NS;
1461
1778
  return null;
1462
1779
  }
1463
1780
  function setSafeAttribute(el, name, value, options = {}) {
1781
+ const written = commitSafeAttribute(el, name, value, options);
1782
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
1783
+ return written;
1784
+ }
1785
+ function commitSafeAttribute(el, name, value, options) {
1464
1786
  const ns = namespaceFor(el, name);
1465
1787
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
1466
1788
  if (isEventHandlerAttr(name)) {
@@ -1473,6 +1795,14 @@ function setSafeAttribute(el, name, value, options = {}) {
1473
1795
  else el.removeAttribute(name);
1474
1796
  return false;
1475
1797
  }
1798
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
1799
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
1800
+ if (refusal !== ContextualRefusal.None) {
1801
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
1802
+ if (ns) el.removeAttributeNS(ns, localName);
1803
+ else el.removeAttribute(name);
1804
+ return false;
1805
+ }
1476
1806
  if (value == null) {
1477
1807
  if (ns) el.removeAttributeNS(ns, localName);
1478
1808
  else el.removeAttribute(name);
@@ -1516,7 +1846,12 @@ function setSafeAttribute(el, name, value, options = {}) {
1516
1846
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
1517
1847
  return true;
1518
1848
  }
1519
- const safe = sanitizeAttributeString(name, str, { element: el });
1849
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
1850
+ if (safe === null) {
1851
+ if (ns) el.removeAttributeNS(ns, localName);
1852
+ else el.removeAttribute(name);
1853
+ return true;
1854
+ }
1520
1855
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
1521
1856
  if (current === safe) return true;
1522
1857
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -2020,7 +2355,7 @@ function forEachSubscriber(signal2, visit) {
2020
2355
  }
2021
2356
 
2022
2357
  // src/reactivity/track.ts
2023
- var _runtimeVersion = true ? "4.7.0" : "dev";
2358
+ var _runtimeVersion = true ? "4.9.0" : "dev";
2024
2359
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
2025
2360
  function resolveReactiveApi() {
2026
2361
  const g2 = globalThis;
@@ -2091,7 +2426,7 @@ function bindAttribute(el, attr, getter) {
2091
2426
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
2092
2427
  return;
2093
2428
  }
2094
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
2429
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
2095
2430
  }
2096
2431
  return reactiveBinding2(commit, el);
2097
2432
  }
@@ -2271,13 +2606,10 @@ function bindChildNode(placeholder, getter) {
2271
2606
  }
2272
2607
 
2273
2608
  // src/core/rendering/tagFactory.ts
2609
+ init_elementPolicy();
2274
2610
  init_sanitize();
2275
2611
  init_dispose();
2276
- var SVG_NS2 = "http://www.w3.org/2000/svg";
2277
- var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
2278
- function isBlockedTag(tag) {
2279
- return BLOCKED_TAGS.has(tag.toLowerCase());
2280
- }
2612
+ var SVG_NS3 = "http://www.w3.org/2000/svg";
2281
2613
  var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
2282
2614
  "config",
2283
2615
  "location",
@@ -2322,11 +2654,12 @@ function applyStyle(el, style2) {
2322
2654
  }
2323
2655
  const next = [];
2324
2656
  for (const prop in value) {
2657
+ if (!Object.hasOwn(value, prop)) continue;
2325
2658
  const val = value[prop];
2326
2659
  if (val == null) continue;
2327
2660
  const name = toKebab(prop);
2328
2661
  next.push(name);
2329
- decl.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
2662
+ decl.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
2330
2663
  }
2331
2664
  for (let i2 = 0; i2 < written.length; i2++) {
2332
2665
  if (next.indexOf(written[i2]) === -1) decl.removeProperty(written[i2]);
@@ -2342,16 +2675,17 @@ function applyStyle(el, style2) {
2342
2675
  }
2343
2676
  const htmlEl = el;
2344
2677
  for (const prop in style2) {
2678
+ if (!Object.hasOwn(style2, prop)) continue;
2345
2679
  const val = style2[prop];
2346
2680
  const name = toKebab(prop);
2347
2681
  if (typeof val === "function") {
2348
2682
  const getter = val;
2349
2683
  const teardown = reactiveBinding2(() => {
2350
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
2684
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(getter()), { element: el }));
2351
2685
  }, el);
2352
2686
  registerDisposer(el, teardown);
2353
2687
  } else {
2354
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
2688
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
2355
2689
  }
2356
2690
  }
2357
2691
  }
@@ -2361,6 +2695,7 @@ function resolveClassValue(cls) {
2361
2695
  if (!cls) return "";
2362
2696
  let out = "";
2363
2697
  for (const name in cls) {
2698
+ if (!Object.hasOwn(cls, name)) continue;
2364
2699
  const val = cls[name];
2365
2700
  const active = typeof val === "function" ? val() : val;
2366
2701
  if (active) out = out ? `${out} ${name}` : name;
@@ -2385,6 +2720,7 @@ function applyClass(el, cls) {
2385
2720
  let hasReactive = false;
2386
2721
  let result = "";
2387
2722
  for (const name in obj) {
2723
+ if (!Object.hasOwn(obj, name)) continue;
2388
2724
  const val = obj[name];
2389
2725
  if (typeof val === "function") {
2390
2726
  hasReactive = true;
@@ -2396,6 +2732,7 @@ function applyClass(el, cls) {
2396
2732
  const update = () => {
2397
2733
  let r = "";
2398
2734
  for (const name in obj) {
2735
+ if (!Object.hasOwn(obj, name)) continue;
2399
2736
  const val = obj[name];
2400
2737
  const active = typeof val === "function" ? val() : val;
2401
2738
  if (active) r = r ? `${r} ${name}` : name;
@@ -2459,7 +2796,7 @@ function appendChildren(el, nodes) {
2459
2796
  }
2460
2797
  }
2461
2798
  function tagFactory(tag, ns) {
2462
- const blocked = isBlockedTag(tag);
2799
+ const blocked = isBlockedElement(tag);
2463
2800
  return (first, second) => {
2464
2801
  if (blocked) {
2465
2802
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
@@ -2522,6 +2859,7 @@ function tagFactory(tag, ns) {
2522
2859
  const pOn = props.on;
2523
2860
  if (pOn) {
2524
2861
  for (const ev in pOn) {
2862
+ if (!Object.hasOwn(pOn, ev)) continue;
2525
2863
  const handler = pOn[ev];
2526
2864
  if (typeof handler === "function") {
2527
2865
  el.addEventListener(ev, handler);
@@ -2548,6 +2886,7 @@ function tagFactory(tag, ns) {
2548
2886
  continue;
2549
2887
  // already handled above / below
2550
2888
  default: {
2889
+ if (!Object.hasOwn(props, key)) continue;
2551
2890
  const value = props[key];
2552
2891
  if (value == null) continue;
2553
2892
  if (isEventHandlerAttr(key)) continue;
@@ -2637,7 +2976,7 @@ var param = tagFactory("param");
2637
2976
  var picture = tagFactory("picture");
2638
2977
  var portal = tagFactory("portal");
2639
2978
  var source = tagFactory("source");
2640
- var svg = tagFactory("svg", SVG_NS2);
2979
+ var svg = tagFactory("svg", SVG_NS3);
2641
2980
  var math = tagFactory("math");
2642
2981
  var canvas = tagFactory("canvas");
2643
2982
  var noscript = tagFactory("noscript");
@@ -2678,26 +3017,26 @@ var base = tagFactory("base");
2678
3017
  var link = tagFactory("link");
2679
3018
  var meta = tagFactory("meta");
2680
3019
  var style = tagFactory("style");
2681
- var circle = tagFactory("circle", SVG_NS2);
2682
- var ellipse = tagFactory("ellipse", SVG_NS2);
2683
- var g = tagFactory("g", SVG_NS2);
2684
- var line = tagFactory("line", SVG_NS2);
2685
- var path = tagFactory("path", SVG_NS2);
2686
- var polygon = tagFactory("polygon", SVG_NS2);
2687
- var polyline = tagFactory("polyline", SVG_NS2);
2688
- var rect = tagFactory("rect", SVG_NS2);
2689
- var text = tagFactory("text", SVG_NS2);
2690
- var tspan = tagFactory("tspan", SVG_NS2);
2691
- var defs = tagFactory("defs", SVG_NS2);
2692
- var clipPath = tagFactory("clipPath", SVG_NS2);
2693
- var mask = tagFactory("mask", SVG_NS2);
2694
- var pattern = tagFactory("pattern", SVG_NS2);
2695
- var linearGradient = tagFactory("linearGradient", SVG_NS2);
2696
- var radialGradient = tagFactory("radialGradient", SVG_NS2);
2697
- var stop = tagFactory("stop", SVG_NS2);
2698
- var use = tagFactory("use", SVG_NS2);
2699
- var symbol = tagFactory("symbol", SVG_NS2);
2700
- var marker = tagFactory("marker", SVG_NS2);
3020
+ var circle = tagFactory("circle", SVG_NS3);
3021
+ var ellipse = tagFactory("ellipse", SVG_NS3);
3022
+ var g = tagFactory("g", SVG_NS3);
3023
+ var line = tagFactory("line", SVG_NS3);
3024
+ var path = tagFactory("path", SVG_NS3);
3025
+ var polygon = tagFactory("polygon", SVG_NS3);
3026
+ var polyline = tagFactory("polyline", SVG_NS3);
3027
+ var rect = tagFactory("rect", SVG_NS3);
3028
+ var text = tagFactory("text", SVG_NS3);
3029
+ var tspan = tagFactory("tspan", SVG_NS3);
3030
+ var defs = tagFactory("defs", SVG_NS3);
3031
+ var clipPath = tagFactory("clipPath", SVG_NS3);
3032
+ var mask = tagFactory("mask", SVG_NS3);
3033
+ var pattern = tagFactory("pattern", SVG_NS3);
3034
+ var linearGradient = tagFactory("linearGradient", SVG_NS3);
3035
+ var radialGradient = tagFactory("radialGradient", SVG_NS3);
3036
+ var stop = tagFactory("stop", SVG_NS3);
3037
+ var use = tagFactory("use", SVG_NS3);
3038
+ var symbol = tagFactory("symbol", SVG_NS3);
3039
+ var marker = tagFactory("marker", SVG_NS3);
2701
3040
  var center = tagFactory("center");
2702
3041
  var font = tagFactory("font");
2703
3042
  var marquee = tagFactory("marquee");
@@ -2880,6 +3219,7 @@ function getLocale() {
2880
3219
  }
2881
3220
  return clientLocale();
2882
3221
  }
3222
+ _i18n.getLocale ?? (_i18n.getLocale = getLocale);
2883
3223
  function registerTranslations(locale, messages) {
2884
3224
  const prepared = { ...messages };
2885
3225
  const merged = Object.assign(/* @__PURE__ */ Object.create(null), dictionaryFor(locale), prepared);
@@ -3078,9 +3418,14 @@ function normalizePathname(path2) {
3078
3418
  const trimmed = path2.length > 1 ? path2.replace(/\/+$/, "") : path2;
3079
3419
  return trimmed || "/";
3080
3420
  }
3081
- function normalizeQuery(query) {
3082
- const params = new URLSearchParams(query);
3083
- return [...params.entries()].sort(([a2], [b2]) => a2 < b2 ? -1 : a2 > b2 ? 1 : 0).map(([k, v]) => `${k}=${v}`).join("&");
3421
+ function parseQuery(query) {
3422
+ return Object.fromEntries(new URLSearchParams(query));
3423
+ }
3424
+ function paramsIdentity(params) {
3425
+ return JSON.stringify(Object.entries(params).sort(([a2], [b2]) => a2 < b2 ? -1 : a2 > b2 ? 1 : 0));
3426
+ }
3427
+ function queryIdentity(query) {
3428
+ return paramsIdentity(typeof query === "string" ? parseQuery(query) : query);
3084
3429
  }
3085
3430
  function isPathnameAncestor(target, current) {
3086
3431
  if (target === current) return true;
@@ -3781,7 +4126,7 @@ var _SibuRouter = class _SibuRouter {
3781
4126
  }
3782
4127
  createRouteContext(fullPath) {
3783
4128
  const { path: path2, query: queryString, hash } = splitRouteUrl(fullPath);
3784
- const query = Object.fromEntries(new URLSearchParams(queryString));
4129
+ const query = parseQuery(queryString);
3785
4130
  const match = this.matcher.match(path2 || "/");
3786
4131
  const params = match?.params || {};
3787
4132
  const meta2 = match?.route.meta || {};
@@ -3945,7 +4290,7 @@ var _SibuRouter = class _SibuRouter {
3945
4290
  }
3946
4291
  isSameRoute(from, to) {
3947
4292
  if (from.matched.length === 0 && to.matched.length > 0) return false;
3948
- return from.path === to.path && JSON.stringify(from.params) === JSON.stringify(to.params) && JSON.stringify(from.query) === JSON.stringify(to.query) && from.hash === to.hash;
4293
+ return from.path === to.path && paramsIdentity(from.params) === paramsIdentity(to.params) && queryIdentity(from.query) === queryIdentity(to.query) && from.hash === to.hash;
3949
4294
  }
3950
4295
  updateHistory(to, options) {
3951
4296
  const fullPath = this.options.base + to.path + (Object.keys(to.query).length ? `?${new URLSearchParams(to.query).toString()}` : "") + (to.hash ? `#${to.hash}` : "");
@@ -4455,8 +4800,7 @@ function KeepAliveRoute(options) {
4455
4800
  return;
4456
4801
  }
4457
4802
  if (!("component" in routeDef)) return;
4458
- const queryStr = Object.keys(route2.query).length > 0 ? `?${new URLSearchParams(route2.query).toString()}` : "";
4459
- const cacheKey = `${route2.path}${queryStr}${route2.hash ? `#${route2.hash}` : ""}`;
4803
+ const cacheKey = JSON.stringify([route2.path, queryIdentity(route2.query), route2.hash]);
4460
4804
  const shouldCache = !includeNames || routeDef.name != null && includeNames.includes(routeDef.name);
4461
4805
  if (cacheKey === currentKey && currentNode) return;
4462
4806
  if (!anchor.parentNode) return;
@@ -4558,7 +4902,7 @@ function RouterLink(props, children) {
4558
4902
  const href = kind === "unsafe" ? "#" : rawHref;
4559
4903
  const { path: hrefPathRaw, query: hrefQueryRaw, hash: hrefHash } = splitRouteUrl(href);
4560
4904
  const targetPath = normalizePathname(hrefPathRaw);
4561
- const targetQuery = normalizeQuery(hrefQueryRaw);
4905
+ const targetQuery = queryIdentity(hrefQueryRaw);
4562
4906
  const link2 = document.createElement("a");
4563
4907
  link2.href = href;
4564
4908
  if (target) {
@@ -4576,7 +4920,7 @@ function RouterLink(props, children) {
4576
4920
  const route2 = routeGetter();
4577
4921
  const currentPath = normalizePathname(route2.path);
4578
4922
  const isActive = kind === "internal" && isPathnameAncestor(targetPath, currentPath);
4579
- const isExactActive = kind === "internal" && currentPath === targetPath && normalizeQuery(route2.query) === targetQuery && route2.hash === hrefHash;
4923
+ const isExactActive = kind === "internal" && currentPath === targetPath && queryIdentity(route2.query) === targetQuery && route2.hash === hrefHash;
4580
4924
  const classes = [];
4581
4925
  if (isActive) {
4582
4926
  if (activeClass) classes.push(activeClass);
@@ -4593,15 +4937,7 @@ function RouterLink(props, children) {
4593
4937
  const lkey = key.toLowerCase();
4594
4938
  if (lkey === "href" || lkey[0] === "o" && lkey[1] === "n") return;
4595
4939
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4596
- const str = String(value);
4597
- if (isUrlAttribute(lkey)) {
4598
- const safe = sanitizeUrl(str);
4599
- if (safe) link2.setAttribute(key, safe);
4600
- } else if (lkey === "style") {
4601
- link2.setAttribute(key, sanitizeStyleAttribute(str, { element: link2 }));
4602
- } else {
4603
- link2.setAttribute(key, str);
4604
- }
4940
+ setSafeAttribute(link2, key, String(value), { syncValueProperty: false, label: "RouterLink" });
4605
4941
  }
4606
4942
  });
4607
4943
  if (content !== void 0) appendChildren(link2, content);
@@ -4900,14 +5236,7 @@ function createMemoryRouter(routes, _initialPath = "/") {
4900
5236
 
4901
5237
  // src/plugins/routerSSR.ts
4902
5238
  init_ssr();
4903
-
4904
- // src/utils/guards.ts
4905
- var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
4906
- function isUnsafeKey(key) {
4907
- return UNSAFE_KEYS.has(key);
4908
- }
4909
-
4910
- // src/plugins/routerSSR.ts
5239
+ init_guards();
4911
5240
  init_headEntry();
4912
5241
  init_sanitize();
4913
5242
  function safeDecode(raw) {
@@ -5140,8 +5469,7 @@ function serializeRouteState(state2, nonce) {
5140
5469
  return `<script${nonceAttr}>window.${SSR_ROUTE_STATE_KEY}=${json}</script>`;
5141
5470
  }
5142
5471
  function deserializeRouteState() {
5143
- if (typeof window === "undefined") return void 0;
5144
- return window[SSR_ROUTE_STATE_KEY];
5472
+ return readOwnGlobal(SSR_ROUTE_STATE_KEY);
5145
5473
  }
5146
5474
  function hydrateRouter(routes, options) {
5147
5475
  const serverState = deserializeRouteState();
@@ -5581,7 +5909,7 @@ var CDN_ENTRY_POINTS = {
5581
5909
  };
5582
5910
  var packageInfo = {
5583
5911
  name: "sibujs",
5584
- version: true ? "4.7.0" : "dev",
5912
+ version: true ? "4.9.0" : "dev",
5585
5913
  entryPoints: MODULE_ENTRY_POINTS,
5586
5914
  /**
5587
5915
  * Generate the package.json `exports` map. Module entries resolve to the
@@ -5726,7 +6054,7 @@ var env = {
5726
6054
  };
5727
6055
 
5728
6056
  // src/plugins/versioning.ts
5729
- var VERSION = true ? "4.7.0" : "dev";
6057
+ var VERSION = true ? "4.9.0" : "dev";
5730
6058
  var NUM = "(?:0|[1-9]\\d*)";
5731
6059
  var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
5732
6060
  var BUILD_ID = "[0-9a-zA-Z-]+";
@@ -6008,15 +6336,18 @@ function extractRangeTarget(range) {
6008
6336
  // src/plugins/startup.ts
6009
6337
  init_errors();
6010
6338
  init_ssr();
6339
+ init_sanitize();
6011
6340
  function preloadCritical(resources) {
6012
6341
  if (typeof document === "undefined") return;
6013
6342
  for (const resource of resources) {
6014
- const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(resource.href) : resource.href.replace(/["\\]/g, "\\$&");
6343
+ const href = sanitizeUrl(resource.href);
6344
+ if (!href) continue;
6345
+ const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(href) : href.replace(/["\\]/g, "\\$&");
6015
6346
  const existing = document.querySelector(`link[rel="preload"][href="${safeHref}"]`);
6016
6347
  if (existing) continue;
6017
6348
  const link2 = document.createElement("link");
6018
6349
  link2.rel = "preload";
6019
- link2.href = resource.href;
6350
+ link2.href = href;
6020
6351
  link2.setAttribute("as", resource.as);
6021
6352
  if (resource.type) {
6022
6353
  link2.type = resource.type;