webpack 5.108.4 → 5.109.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 (186) hide show
  1. package/lib/APIPlugin.js +48 -29
  2. package/lib/CacheFacade.js +8 -0
  3. package/lib/ChunkGroup.js +1 -0
  4. package/lib/CompatibilityPlugin.js +31 -29
  5. package/lib/Compilation.js +49 -4
  6. package/lib/ConstPlugin.js +11 -11
  7. package/lib/ContextModule.js +239 -6
  8. package/lib/ContextModuleFactory.js +139 -14
  9. package/lib/DefinePlugin.js +396 -101
  10. package/lib/Entrypoint.js +1 -1
  11. package/lib/ExportsInfoApiPlugin.js +2 -2
  12. package/lib/ExternalModule.js +107 -5
  13. package/lib/ExternalModuleFactoryPlugin.js +20 -1
  14. package/lib/FileSystemInfo.js +130 -74
  15. package/lib/HotModuleReplacementPlugin.js +18 -15
  16. package/lib/Module.js +3 -0
  17. package/lib/ModuleFilenameHelpers.js +2 -2
  18. package/lib/ModuleTypeConstants.js +8 -1
  19. package/lib/MultiCompiler.js +13 -2
  20. package/lib/MultiWatching.js +1 -0
  21. package/lib/NodeStuffPlugin.js +51 -33
  22. package/lib/NormalModule.js +169 -401
  23. package/lib/NormalModuleFactory.js +76 -2
  24. package/lib/ProgressPlugin.js +211 -119
  25. package/lib/ProvidePlugin.js +2 -2
  26. package/lib/RuntimeGlobals.js +47 -0
  27. package/lib/RuntimePlugin.js +33 -0
  28. package/lib/RuntimeTemplate.js +304 -16
  29. package/lib/UseStrictPlugin.js +1 -1
  30. package/lib/WebpackIsIncludedPlugin.js +1 -1
  31. package/lib/WebpackOptionsApply.js +24 -19
  32. package/lib/asset/AssetGenerator.js +110 -28
  33. package/lib/asset/AssetModulesPlugin.js +92 -2
  34. package/lib/asset/WebManifestGenerator.js +164 -0
  35. package/lib/asset/WebManifestParser.js +130 -0
  36. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  37. package/lib/async-modules/isGeneratorLowered.js +26 -0
  38. package/lib/cache/PackFileCacheStrategy.js +9 -3
  39. package/lib/config/browserslistTargetHandler.js +40 -0
  40. package/lib/config/defaults.js +219 -29
  41. package/lib/config/normalization.js +26 -0
  42. package/lib/config/target.js +9 -1
  43. package/lib/css/CssGenerator.js +40 -31
  44. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  45. package/lib/css/CssModulesPlugin.js +13 -0
  46. package/lib/css/CssParser.js +594 -263
  47. package/lib/css/syntax.js +875 -462
  48. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  49. package/lib/dependencies/AMDPlugin.js +2 -2
  50. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  51. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  52. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  53. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  54. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  55. package/lib/dependencies/CommonJsPlugin.js +2 -2
  56. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  57. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  58. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  59. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  60. package/lib/dependencies/CssIcssExportDependency.js +162 -71
  61. package/lib/dependencies/CssUrlDependency.js +35 -4
  62. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  63. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  64. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  65. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  66. package/lib/dependencies/HarmonyImportDependency.js +12 -1
  67. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
  68. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  69. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  70. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  71. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  72. package/lib/dependencies/ImportDependency.js +43 -1
  73. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  74. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  75. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  76. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  77. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  78. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  79. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  80. package/lib/dependencies/ImportParserPlugin.js +80 -17
  81. package/lib/dependencies/ImportPhase.js +10 -8
  82. package/lib/dependencies/LoaderPlugin.js +5 -1
  83. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  84. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  85. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  86. package/lib/dependencies/SystemPlugin.js +2 -4
  87. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  88. package/lib/dependencies/URLDependency.js +141 -21
  89. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  90. package/lib/dependencies/WorkerDependency.js +86 -18
  91. package/lib/dependencies/WorkletDependency.js +278 -0
  92. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  93. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  94. package/lib/errors/LoaderLoadingError.js +20 -0
  95. package/lib/errors/ModuleParseError.js +39 -22
  96. package/lib/errors/NodeStuffInWebError.js +1 -1
  97. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  98. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  99. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  100. package/lib/hmr/lazyCompilationBackend.js +32 -8
  101. package/lib/html/HtmlGenerator.js +887 -15
  102. package/lib/html/HtmlModule.js +2 -2
  103. package/lib/html/HtmlModulesPlugin.js +610 -166
  104. package/lib/html/HtmlParser.js +603 -247
  105. package/lib/html/favicon.svg +1 -0
  106. package/lib/html/syntax.js +1264 -661
  107. package/lib/ids/IdHelpers.js +1 -1
  108. package/lib/index.js +6 -0
  109. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  110. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  111. package/lib/javascript/JavascriptGenerator.js +197 -49
  112. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  113. package/lib/javascript/JavascriptParser.js +701 -355
  114. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  115. package/lib/javascript/syntax.js +4380 -0
  116. package/lib/library/ModuleLibraryPlugin.js +38 -1
  117. package/lib/loaders/LoaderRunner.js +714 -0
  118. package/lib/loaders/loadLoader.js +100 -0
  119. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +19 -0
  120. package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
  121. package/lib/optimize/ConcatenatedModule.js +120 -16
  122. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  123. package/lib/optimize/InnerGraphPlugin.js +50 -30
  124. package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
  125. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  126. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  127. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  128. package/lib/prefetch/ResourceHintPlugin.js +663 -0
  129. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  130. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  131. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  132. package/lib/rules/RuleSetCompiler.js +114 -0
  133. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  134. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  135. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  136. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  137. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  138. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  139. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  140. package/lib/schemes/HttpUriPlugin.js +40 -1
  141. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  142. package/lib/serialization/FileMiddleware.js +29 -3
  143. package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
  144. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  145. package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
  146. package/lib/stats/StatsFactory.js +15 -8
  147. package/lib/url/URLParserPlugin.js +66 -13
  148. package/lib/util/AsyncQueue.js +122 -72
  149. package/lib/util/SetHelpers.js +1 -1
  150. package/lib/util/StackedMap.js +122 -48
  151. package/lib/util/binarySearchBounds.js +2 -2
  152. package/lib/util/concatenate.js +40 -14
  153. package/lib/util/createHooksRegistry.js +13 -5
  154. package/lib/util/dataURL.js +2 -2
  155. package/lib/util/deterministicGrouping.js +7 -5
  156. package/lib/util/formatSize.js +6 -2
  157. package/lib/util/globUtils.js +710 -0
  158. package/lib/util/identifier.js +23 -6
  159. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  160. package/lib/util/internalSerializables.js +88 -71
  161. package/lib/util/magicComment.js +121 -25
  162. package/lib/util/numberHash.js +2 -2
  163. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  164. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  165. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  166. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  167. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  168. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  169. package/lib/webpack.js +65 -0
  170. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  171. package/module.d.ts +21 -0
  172. package/package.json +13 -12
  173. package/schemas/WebpackOptions.check.js +1 -1
  174. package/schemas/WebpackOptions.json +759 -36
  175. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  176. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  177. package/schemas/plugins/ProgressPlugin.json +17 -1
  178. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  179. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  180. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  181. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  182. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  183. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  184. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  185. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  186. package/types.d.ts +1213 -96
@@ -147,6 +147,7 @@ const CC_QUESTION_MARK = 0x3f;
147
147
  const CC_LEFT_SQUARE_BRACKET = 0x5b;
148
148
  const CC_RIGHT_SQUARE_BRACKET = 0x5d;
149
149
  const CC_GRAVE_ACCENT = 0x60;
150
+ const CC_NO_BREAK_SPACE = 0xa0;
150
151
 
151
152
  const QUOTE_DOUBLE = 1;
152
153
  const QUOTE_SINGLE = 2;
@@ -317,7 +318,7 @@ const getContentModeForTag = (name) => {
317
318
  * @param {string} lit lowercase ASCII literal
318
319
  * @returns {boolean} true if the range equals `lit` ignoring ASCII case
319
320
  */
320
- const rangeEqualsLower = (input, start, end, lit) => {
321
+ const rangeEqualsLowerCase = (input, start, end, lit) => {
321
322
  if (end - start !== lit.length) return false;
322
323
  for (let i = 0; i < lit.length; i++) {
323
324
  let c = input.charCodeAt(start + i);
@@ -339,27 +340,37 @@ const rangeEqualsLower = (input, start, end, lit) => {
339
340
  const getContentModeForRange = (input, start, end) => {
340
341
  switch (end - start) {
341
342
  case 3:
342
- if (rangeEqualsLower(input, start, end, "xmp")) return STATE_RAWTEXT;
343
+ if (rangeEqualsLowerCase(input, start, end, "xmp")) return STATE_RAWTEXT;
343
344
  return STATE_DATA;
344
345
  case 5:
345
- if (rangeEqualsLower(input, start, end, "title")) return STATE_RCDATA;
346
- if (rangeEqualsLower(input, start, end, "style")) return STATE_RAWTEXT;
346
+ if (rangeEqualsLowerCase(input, start, end, "title")) return STATE_RCDATA;
347
+ if (rangeEqualsLowerCase(input, start, end, "style")) {
348
+ return STATE_RAWTEXT;
349
+ }
347
350
  return STATE_DATA;
348
351
  case 6:
349
- if (rangeEqualsLower(input, start, end, "script")) {
352
+ if (rangeEqualsLowerCase(input, start, end, "script")) {
350
353
  return STATE_SCRIPT_DATA;
351
354
  }
352
- if (rangeEqualsLower(input, start, end, "iframe")) return STATE_RAWTEXT;
355
+ if (rangeEqualsLowerCase(input, start, end, "iframe")) {
356
+ return STATE_RAWTEXT;
357
+ }
353
358
  return STATE_DATA;
354
359
  case 7:
355
- if (rangeEqualsLower(input, start, end, "noembed")) return STATE_RAWTEXT;
360
+ if (rangeEqualsLowerCase(input, start, end, "noembed")) {
361
+ return STATE_RAWTEXT;
362
+ }
356
363
  return STATE_DATA;
357
364
  case 8:
358
- if (rangeEqualsLower(input, start, end, "textarea")) return STATE_RCDATA;
359
- if (rangeEqualsLower(input, start, end, "noframes")) return STATE_RAWTEXT;
365
+ if (rangeEqualsLowerCase(input, start, end, "textarea")) {
366
+ return STATE_RCDATA;
367
+ }
368
+ if (rangeEqualsLowerCase(input, start, end, "noframes")) {
369
+ return STATE_RAWTEXT;
370
+ }
360
371
  return STATE_DATA;
361
372
  case 9:
362
- if (rangeEqualsLower(input, start, end, "plaintext")) {
373
+ if (rangeEqualsLowerCase(input, start, end, "plaintext")) {
363
374
  return STATE_PLAINTEXT;
364
375
  }
365
376
  return STATE_DATA;
@@ -374,7 +385,7 @@ const getContentModeForRange = (input, start, end) => {
374
385
  * @param {HtmlTokenCallbacks} callbacks callbacks
375
386
  * @returns {number} final position
376
387
  */
377
- const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
388
+ const tokenize = (input, pos = 0, callbacks = {}) => {
378
389
  const len = input.length;
379
390
  let state = STATE_DATA;
380
391
  let returnState = STATE_DATA;
@@ -406,6 +417,18 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
406
417
  // Tracks whether the current tag has parsed any attributes — used to
407
418
  // fire the `end-tag-with-attributes` parse error when an end tag emits.
408
419
  let tagHasAttributes = false;
420
+ // Memoized next occurrence of `<` / `&` / NUL for the text-run
421
+ // fast-forwards: native `indexOf` beats a per-char JS loop, and `pos` only
422
+ // moves forward, so each memo is refreshed at most once per occurrence
423
+ // (`len` = no further occurrence).
424
+ let nextLt = -1;
425
+ let nextAmp = -1;
426
+ let nextNul = -1;
427
+ // Same memo scheme for the closing quote of quoted attribute values (long
428
+ // values: data: URIs, srcset, inline style). Memoized so a value with many
429
+ // `&` references doesn't re-run the quote scan per reference.
430
+ let nextDQuote = -1;
431
+ let nextSQuote = -1;
409
432
 
410
433
  /**
411
434
  * Reports a tokenizer parse error to the consumer. The offset range and
@@ -429,7 +452,7 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
429
452
  * the accumulated `charRefCode`, if any. Used both inline (when the
430
453
  * reference is terminated by a real next character) and at EOF (when the
431
454
  * reference runs to the end of input). The scanner only flags the error —
432
- * the spec's U+FFFD / Windows-1252 substitution is done by `decodeHtmlEntities`.
455
+ * the spec's U+FFFD / Windows-1252 substitution is done by `decodeEntities`.
433
456
  * @param {number} endPos offset just past the reference
434
457
  */
435
458
  const validateNumericReference = (endPos) => {
@@ -600,7 +623,7 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
600
623
  // All WHATWG tokenizer states handled. Deliberately omitted parse errors
601
624
  // (need state this offset scanner lacks): duplicate-attribute,
602
625
  // cdata-in-html-content, `*-in-input-stream`. Reference substitution is
603
- // left to `decodeHtmlEntities`.
626
+ // left to `decodeEntities`.
604
627
  switch (state) {
605
628
  // https://html.spec.whatwg.org/multipage/parsing.html#data-state
606
629
  case STATE_DATA:
@@ -625,15 +648,23 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
625
648
  pos++;
626
649
  } else {
627
650
  // Fast-forward over the run of ordinary text without re-entering
628
- // the per-state switch; stop on the next significant code point.
651
+ // the per-state switch — batches only the data state's "Anything
652
+ // else — emit the current input character" arc; the stops are
653
+ // exactly the code points with their own arcs (`<`, `&`, NUL).
629
654
  pos++;
630
- while (pos < len) {
631
- const c2 = input.charCodeAt(pos);
632
- if (c2 === CC_LESS_THAN || c2 === CC_AMPERSAND || c2 === CC_NULL) {
633
- break;
634
- }
635
- pos++;
655
+ if (nextLt < pos) {
656
+ nextLt = input.indexOf("<", pos);
657
+ if (nextLt === -1) nextLt = len;
658
+ }
659
+ if (nextAmp < pos) {
660
+ nextAmp = input.indexOf("&", pos);
661
+ if (nextAmp === -1) nextAmp = len;
662
+ }
663
+ if (nextNul < pos) {
664
+ nextNul = input.indexOf("\0", pos);
665
+ if (nextNul === -1) nextNul = len;
636
666
  }
667
+ pos = Math.min(nextLt, nextAmp, nextNul);
637
668
  }
638
669
  break;
639
670
 
@@ -993,19 +1024,22 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
993
1024
  reportError("unexpected-null-character", pos, pos + 1, "warning");
994
1025
  pos++;
995
1026
  } else {
996
- // Fast-forward over the ordinary run of the quoted value.
1027
+ // Fast-forward over the ordinary run of the quoted value with the
1028
+ // same memoized native-scan scheme as the data state.
997
1029
  pos++;
998
- while (pos < len) {
999
- const c2 = input.charCodeAt(pos);
1000
- if (
1001
- c2 === CC_QUOTATION_MARK ||
1002
- c2 === CC_AMPERSAND ||
1003
- c2 === CC_NULL
1004
- ) {
1005
- break;
1006
- }
1007
- pos++;
1030
+ if (nextDQuote < pos) {
1031
+ nextDQuote = input.indexOf('"', pos);
1032
+ if (nextDQuote === -1) nextDQuote = len;
1033
+ }
1034
+ if (nextAmp < pos) {
1035
+ nextAmp = input.indexOf("&", pos);
1036
+ if (nextAmp === -1) nextAmp = len;
1008
1037
  }
1038
+ if (nextNul < pos) {
1039
+ nextNul = input.indexOf("\0", pos);
1040
+ if (nextNul === -1) nextNul = len;
1041
+ }
1042
+ pos = Math.min(nextDQuote, nextAmp, nextNul);
1009
1043
  }
1010
1044
  break;
1011
1045
 
@@ -1029,15 +1063,22 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
1029
1063
  reportError("unexpected-null-character", pos, pos + 1, "warning");
1030
1064
  pos++;
1031
1065
  } else {
1032
- // Fast-forward over the ordinary run of the quoted value.
1066
+ // Fast-forward over the ordinary run of the quoted value with the
1067
+ // same memoized native-scan scheme as the data state.
1033
1068
  pos++;
1034
- while (pos < len) {
1035
- const c2 = input.charCodeAt(pos);
1036
- if (c2 === CC_APOSTROPHE || c2 === CC_AMPERSAND || c2 === CC_NULL) {
1037
- break;
1038
- }
1039
- pos++;
1069
+ if (nextSQuote < pos) {
1070
+ nextSQuote = input.indexOf("'", pos);
1071
+ if (nextSQuote === -1) nextSQuote = len;
1072
+ }
1073
+ if (nextAmp < pos) {
1074
+ nextAmp = input.indexOf("&", pos);
1075
+ if (nextAmp === -1) nextAmp = len;
1040
1076
  }
1077
+ if (nextNul < pos) {
1078
+ nextNul = input.indexOf("\0", pos);
1079
+ if (nextNul === -1) nextNul = len;
1080
+ }
1081
+ pos = Math.min(nextSQuote, nextAmp, nextNul);
1041
1082
  }
1042
1083
  break;
1043
1084
 
@@ -1416,7 +1457,14 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
1416
1457
  if (cc === CC_NULL) {
1417
1458
  reportError("unexpected-null-character", pos, pos + 1, "warning");
1418
1459
  }
1460
+ // Fast-forward ordinary bogus-comment text without re-entering the
1461
+ // state switch; stop on the significant code points above.
1419
1462
  pos++;
1463
+ while (pos < len) {
1464
+ const c2 = input.charCodeAt(pos);
1465
+ if (c2 === CC_GREATER_THAN || c2 === CC_NULL) break;
1466
+ pos++;
1467
+ }
1420
1468
  }
1421
1469
  break;
1422
1470
 
@@ -2263,7 +2311,10 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2263
2311
  } else {
2264
2312
  // Anything else
2265
2313
  // Emit the current input character as a character token.
2266
- pos++;
2314
+ // Fast-forward to the next `]` (the only code point with its own
2315
+ // arc) in one native scan.
2316
+ pos = input.indexOf("]", pos + 1);
2317
+ if (pos === -1) pos = len;
2267
2318
  }
2268
2319
  break;
2269
2320
 
@@ -2332,15 +2383,23 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2332
2383
  reportError("unexpected-null-character", pos, pos + 1, "warning");
2333
2384
  pos++;
2334
2385
  } else {
2335
- // Fast-forward over ordinary RCDATA text.
2386
+ // Fast-forward over ordinary RCDATA text (same memoized
2387
+ // `indexOf` scheme as the data state) — batches only this
2388
+ // state's "anything else" arc; `&` / `<` / NUL keep their own.
2336
2389
  pos++;
2337
- while (pos < len) {
2338
- const c2 = input.charCodeAt(pos);
2339
- if (c2 === CC_AMPERSAND || c2 === CC_LESS_THAN || c2 === CC_NULL) {
2340
- break;
2341
- }
2342
- pos++;
2390
+ if (nextLt < pos) {
2391
+ nextLt = input.indexOf("<", pos);
2392
+ if (nextLt === -1) nextLt = len;
2393
+ }
2394
+ if (nextAmp < pos) {
2395
+ nextAmp = input.indexOf("&", pos);
2396
+ if (nextAmp === -1) nextAmp = len;
2397
+ }
2398
+ if (nextNul < pos) {
2399
+ nextNul = input.indexOf("\0", pos);
2400
+ if (nextNul === -1) nextNul = len;
2343
2401
  }
2402
+ pos = Math.min(nextLt, nextAmp, nextNul);
2344
2403
  }
2345
2404
  break;
2346
2405
 
@@ -2395,7 +2454,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2395
2454
  if (isSpace(cc)) {
2396
2455
  tagNameEnd = pos;
2397
2456
  if (
2398
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2457
+ rangeEqualsLowerCase(
2458
+ input,
2459
+ tagNameStart,
2460
+ tagNameEnd,
2461
+ lastOpenTagName
2462
+ )
2399
2463
  ) {
2400
2464
  flushText(tagStart);
2401
2465
  state = STATE_BEFORE_ATTRIBUTE_NAME;
@@ -2411,7 +2475,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2411
2475
  // "anything else" entry below.
2412
2476
  tagNameEnd = pos;
2413
2477
  if (
2414
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2478
+ rangeEqualsLowerCase(
2479
+ input,
2480
+ tagNameStart,
2481
+ tagNameEnd,
2482
+ lastOpenTagName
2483
+ )
2415
2484
  ) {
2416
2485
  flushText(tagStart);
2417
2486
  state = STATE_SELF_CLOSING_START_TAG;
@@ -2427,7 +2496,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2427
2496
  // per the "anything else" entry below.
2428
2497
  tagNameEnd = pos;
2429
2498
  if (
2430
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2499
+ rangeEqualsLowerCase(
2500
+ input,
2501
+ tagNameStart,
2502
+ tagNameEnd,
2503
+ lastOpenTagName
2504
+ )
2431
2505
  ) {
2432
2506
  flushText(tagStart);
2433
2507
  state = STATE_DATA;
@@ -2467,13 +2541,19 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2467
2541
  reportError("unexpected-null-character", pos, pos + 1, "warning");
2468
2542
  pos++;
2469
2543
  } else {
2470
- // Fast-forward over ordinary RAWTEXT text.
2544
+ // Fast-forward over ordinary RAWTEXT text (same memoized
2545
+ // `indexOf` scheme as the data state) — batches only this
2546
+ // state's "anything else" arc; `<` / NUL keep their own.
2471
2547
  pos++;
2472
- while (pos < len) {
2473
- const c2 = input.charCodeAt(pos);
2474
- if (c2 === CC_LESS_THAN || c2 === CC_NULL) break;
2475
- pos++;
2548
+ if (nextLt < pos) {
2549
+ nextLt = input.indexOf("<", pos);
2550
+ if (nextLt === -1) nextLt = len;
2551
+ }
2552
+ if (nextNul < pos) {
2553
+ nextNul = input.indexOf("\0", pos);
2554
+ if (nextNul === -1) nextNul = len;
2476
2555
  }
2556
+ pos = Math.min(nextLt, nextNul);
2477
2557
  }
2478
2558
  break;
2479
2559
 
@@ -2527,7 +2607,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2527
2607
  if (isSpace(cc)) {
2528
2608
  tagNameEnd = pos;
2529
2609
  if (
2530
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2610
+ rangeEqualsLowerCase(
2611
+ input,
2612
+ tagNameStart,
2613
+ tagNameEnd,
2614
+ lastOpenTagName
2615
+ )
2531
2616
  ) {
2532
2617
  flushText(tagStart);
2533
2618
  state = STATE_BEFORE_ATTRIBUTE_NAME;
@@ -2542,7 +2627,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2542
2627
  // "anything else" entry below.
2543
2628
  tagNameEnd = pos;
2544
2629
  if (
2545
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2630
+ rangeEqualsLowerCase(
2631
+ input,
2632
+ tagNameStart,
2633
+ tagNameEnd,
2634
+ lastOpenTagName
2635
+ )
2546
2636
  ) {
2547
2637
  flushText(tagStart);
2548
2638
  state = STATE_SELF_CLOSING_START_TAG;
@@ -2557,7 +2647,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2557
2647
  // per the "anything else" entry below.
2558
2648
  tagNameEnd = pos;
2559
2649
  if (
2560
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2650
+ rangeEqualsLowerCase(
2651
+ input,
2652
+ tagNameStart,
2653
+ tagNameEnd,
2654
+ lastOpenTagName
2655
+ )
2561
2656
  ) {
2562
2657
  flushText(tagStart);
2563
2658
  state = STATE_DATA;
@@ -2596,13 +2691,19 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2596
2691
  reportError("unexpected-null-character", pos, pos + 1, "warning");
2597
2692
  pos++;
2598
2693
  } else {
2599
- // Fast-forward over ordinary script-data text.
2694
+ // Fast-forward over ordinary script-data text (same memoized
2695
+ // `indexOf` scheme as the data state) — batches only this
2696
+ // state's "anything else" arc; `<` / NUL keep their own.
2600
2697
  pos++;
2601
- while (pos < len) {
2602
- const c2 = input.charCodeAt(pos);
2603
- if (c2 === CC_LESS_THAN || c2 === CC_NULL) break;
2604
- pos++;
2698
+ if (nextLt < pos) {
2699
+ nextLt = input.indexOf("<", pos);
2700
+ if (nextLt === -1) nextLt = len;
2605
2701
  }
2702
+ if (nextNul < pos) {
2703
+ nextNul = input.indexOf("\0", pos);
2704
+ if (nextNul === -1) nextNul = len;
2705
+ }
2706
+ pos = Math.min(nextLt, nextNul);
2606
2707
  }
2607
2708
  break;
2608
2709
 
@@ -2662,7 +2763,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2662
2763
  if (isSpace(cc)) {
2663
2764
  tagNameEnd = pos;
2664
2765
  if (
2665
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2766
+ rangeEqualsLowerCase(
2767
+ input,
2768
+ tagNameStart,
2769
+ tagNameEnd,
2770
+ lastOpenTagName
2771
+ )
2666
2772
  ) {
2667
2773
  flushText(tagStart);
2668
2774
  state = STATE_BEFORE_ATTRIBUTE_NAME;
@@ -2677,7 +2783,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2677
2783
  // "anything else" entry below.
2678
2784
  tagNameEnd = pos;
2679
2785
  if (
2680
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2786
+ rangeEqualsLowerCase(
2787
+ input,
2788
+ tagNameStart,
2789
+ tagNameEnd,
2790
+ lastOpenTagName
2791
+ )
2681
2792
  ) {
2682
2793
  flushText(tagStart);
2683
2794
  state = STATE_SELF_CLOSING_START_TAG;
@@ -2692,7 +2803,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2692
2803
  // per the "anything else" entry below.
2693
2804
  tagNameEnd = pos;
2694
2805
  if (
2695
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
2806
+ rangeEqualsLowerCase(
2807
+ input,
2808
+ tagNameStart,
2809
+ tagNameEnd,
2810
+ lastOpenTagName
2811
+ )
2696
2812
  ) {
2697
2813
  flushText(tagStart);
2698
2814
  state = STATE_DATA;
@@ -2770,7 +2886,20 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2770
2886
  if (cc === CC_NULL) {
2771
2887
  reportError("unexpected-null-character", pos, pos + 1, "warning");
2772
2888
  }
2889
+ // Fast-forward ordinary escaped script text without re-entering
2890
+ // the state switch; stop on the significant code points above.
2773
2891
  pos++;
2892
+ while (pos < len) {
2893
+ const c2 = input.charCodeAt(pos);
2894
+ if (
2895
+ c2 === CC_HYPHEN_MINUS ||
2896
+ c2 === CC_LESS_THAN ||
2897
+ c2 === CC_NULL
2898
+ ) {
2899
+ break;
2900
+ }
2901
+ pos++;
2902
+ }
2774
2903
  }
2775
2904
  break;
2776
2905
 
@@ -2890,7 +3019,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2890
3019
  if (isSpace(cc)) {
2891
3020
  tagNameEnd = pos;
2892
3021
  if (
2893
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
3022
+ rangeEqualsLowerCase(
3023
+ input,
3024
+ tagNameStart,
3025
+ tagNameEnd,
3026
+ lastOpenTagName
3027
+ )
2894
3028
  ) {
2895
3029
  flushText(tagStart);
2896
3030
  state = STATE_BEFORE_ATTRIBUTE_NAME;
@@ -2905,7 +3039,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2905
3039
  // "anything else" entry below.
2906
3040
  tagNameEnd = pos;
2907
3041
  if (
2908
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
3042
+ rangeEqualsLowerCase(
3043
+ input,
3044
+ tagNameStart,
3045
+ tagNameEnd,
3046
+ lastOpenTagName
3047
+ )
2909
3048
  ) {
2910
3049
  flushText(tagStart);
2911
3050
  state = STATE_SELF_CLOSING_START_TAG;
@@ -2920,7 +3059,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
2920
3059
  // per the "anything else" entry below.
2921
3060
  tagNameEnd = pos;
2922
3061
  if (
2923
- rangeEqualsLower(input, tagNameStart, tagNameEnd, lastOpenTagName)
3062
+ rangeEqualsLowerCase(
3063
+ input,
3064
+ tagNameStart,
3065
+ tagNameEnd,
3066
+ lastOpenTagName
3067
+ )
2924
3068
  ) {
2925
3069
  flushText(tagStart);
2926
3070
  state = STATE_DATA;
@@ -3002,7 +3146,20 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
3002
3146
  if (cc === CC_NULL) {
3003
3147
  reportError("unexpected-null-character", pos, pos + 1, "warning");
3004
3148
  }
3149
+ // Fast-forward ordinary double-escaped script text without re-entering
3150
+ // the state switch; stop on the significant code points above.
3005
3151
  pos++;
3152
+ while (pos < len) {
3153
+ const c2 = input.charCodeAt(pos);
3154
+ if (
3155
+ c2 === CC_HYPHEN_MINUS ||
3156
+ c2 === CC_LESS_THAN ||
3157
+ c2 === CC_NULL
3158
+ ) {
3159
+ break;
3160
+ }
3161
+ pos++;
3162
+ }
3006
3163
  }
3007
3164
  break;
3008
3165
 
@@ -3618,15 +3775,6 @@ const decodeNumericReference = (code) => {
3618
3775
  return String.fromCodePoint(code);
3619
3776
  };
3620
3777
 
3621
- // Match one of three forms (each with an optional trailing `;`):
3622
- // `&#x<hex>` - hex numeric reference (requires the `x`/`X`).
3623
- // `&#<dec>` - decimal numeric reference (digits only).
3624
- // `&<name>` - named reference (letter followed by alphanumerics).
3625
- // The three alternatives are kept separate so a decimal reference like
3626
- // `&#65b` doesn't greedily eat the trailing `b` as if it were hex.
3627
- const CHARACTER_REFERENCE_REGEXP =
3628
- /&(?:#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);?/g;
3629
-
3630
3778
  /**
3631
3779
  * Decode a single matched character reference.
3632
3780
  * @param {string} match the matched reference text
@@ -3634,45 +3782,34 @@ const CHARACTER_REFERENCE_REGEXP =
3634
3782
  * @param {boolean=} isAttribute true when the match came from an attribute value
3635
3783
  * @returns {string} decoded text, or `match` itself when it stays literal
3636
3784
  */
3637
- const decodeOneReference = (match, nextCharCode, isAttribute) => {
3638
- // Numeric reference: &#65; or &#x41;
3639
- if (match.charCodeAt(1) === 0x23 /* # */) {
3640
- const lastChar = match.charAt(match.length - 1);
3641
- const isHex = match.charCodeAt(2) === 0x78 || match.charCodeAt(2) === 0x58;
3642
- const body = isHex
3643
- ? lastChar === ";"
3644
- ? match.slice(3, -1)
3645
- : match.slice(3)
3646
- : lastChar === ";"
3647
- ? match.slice(2, -1)
3648
- : match.slice(2);
3649
- // The regex above guarantees at least one digit in `body`,
3650
- // so `parseInt` always returns a finite number here.
3651
- return decodeNumericReference(Number.parseInt(body, isHex ? 16 : 10));
3652
- }
3653
-
3654
- // Named reference. Try the full captured name first, then
3655
- // progressively shorter prefixes - this handles direct matches
3656
- // like `&amp;` as well as WHATWG longest-prefix semantics where
3657
- // e.g. `&notpre;` decodes as `&not` (a legacy bare entity)
3658
- // followed by `pre;` as literal text.
3659
- const name = match.slice(1);
3785
+ /**
3786
+ * Decode a named character reference spanning `[start, end)` of `str` (the
3787
+ * `&` at `start`, the optional `;` included). Slices the name once; WHATWG
3788
+ * longest-prefix and consumed-as-part-of-an-attribute semantics as before.
3789
+ * @param {string} str the raw string
3790
+ * @param {number} start offset of the reference's `&`
3791
+ * @param {number} end offset just past the reference (past the `;` if present)
3792
+ * @param {number} nextCharCode char code following the reference (NaN at the end)
3793
+ * @param {boolean=} isAttribute true when the reference came from an attribute value
3794
+ * @returns {string | undefined} decoded text, or `undefined` when it stays literal
3795
+ */
3796
+ const decodeNamedReference = (str, start, end, nextCharCode, isAttribute) => {
3797
+ // Mirrors the old `match.slice(1)`: the name keeps its trailing `;`.
3798
+ const name = str.slice(start + 1, end);
3660
3799
  const matchEndsWithSemi = name.charCodeAt(name.length - 1) === 0x3b;
3661
3800
 
3662
3801
  // Attribute-context guard: if the entity match didn't end with `;`
3663
3802
  // and the next character in the source is `=` or ASCII
3664
3803
  // alphanumeric, the WHATWG spec says to flush the literal text
3665
- // rather than decode. The greedy regex already absorbed any
3804
+ // rather than decode. The greedy scan already absorbed any
3666
3805
  // trailing alphanumerics, so the only candidate "next char" here
3667
3806
  // is `=` (or any non-alphanumeric).
3668
3807
  if (isAttribute && !matchEndsWithSemi && nextCharCode === 0x3d /* = */) {
3669
- return match;
3808
+ return undefined;
3670
3809
  }
3671
3810
 
3672
- // Fast path: the regex usually captures exactly one entity (`&amp;`,
3673
- // `&lt;`, `&nbsp;`, …), so the whole `name` is the match — avoid the
3674
- // full-length `name.slice(0, name.length)` the loop's first iteration
3675
- // would allocate. No leftover, so the attribute guard never applies.
3811
+ // Fast path: the scan usually captures exactly one entity (`&amp;`,
3812
+ // `&lt;`, `&nbsp;`, ...), so the whole `name` is the match.
3676
3813
  if (name.length <= MAX_ENTITY_NAME_LEN) {
3677
3814
  const full = HTML_ENTITIES[name];
3678
3815
  if (full !== undefined) return full;
@@ -3691,31 +3828,20 @@ const decodeOneReference = (match, nextCharCode, isAttribute) => {
3691
3828
  // Attribute-context longest-prefix guard: if the matched
3692
3829
  // prefix doesn't end with `;` and the leftover starts with
3693
3830
  // an alphanumeric character, leave literal per WHATWG.
3694
- // (The regex greedy-consumes alphanumerics, so any leftover
3695
- // within `name` is itself alphanumeric — we only need to
3696
- // check non-empty leftover here; the `=` case is handled
3697
- // above against the source character after the match.)
3698
3831
  if (
3699
3832
  isAttribute &&
3700
3833
  i < name.length &&
3701
3834
  prefix.charCodeAt(prefix.length - 1) !== 0x3b
3702
3835
  ) {
3703
- return match;
3836
+ return undefined;
3704
3837
  }
3705
3838
  return HTML_ENTITIES[prefix] + name.slice(i);
3706
3839
  }
3707
3840
  }
3708
- return match;
3841
+ return undefined;
3709
3842
  };
3710
3843
 
3711
- // Hoisted `replace` callbacks (one per `isAttribute` mode) — no closure
3712
- // per decode call, and the callback stays monomorphic.
3713
- /** @type {(match: string, offset: number, source: string) => string} */
3714
- const _decodeReferenceInText = (match, offset, source) =>
3715
- decodeOneReference(match, source.charCodeAt(offset + match.length), false);
3716
- /** @type {(match: string, offset: number, source: string) => string} */
3717
- const _decodeReferenceInAttribute = (match, offset, source) =>
3718
- decodeOneReference(match, source.charCodeAt(offset + match.length), true);
3844
+ /** @typedef {{ text: string, map: number[] | undefined }} DecodedEntitiesWithMap */
3719
3845
 
3720
3846
  /**
3721
3847
  * Decode HTML character references in a string. Handles all numeric
@@ -3728,67 +3854,246 @@ const _decodeReferenceInAttribute = (match, offset, source) =>
3728
3854
  * trailing `;` whose next character is `=` or ASCII alphanumeric is left
3729
3855
  * undecoded, so e.g. `&amp=foo` stays literal in an attribute value but
3730
3856
  * decodes to `&=foo` in text.
3857
+ *
3858
+ * With `withMap` the result also carries a boundary map from the decoded
3859
+ * string back to raw offsets, so spans computed on the decoded text (e.g.
3860
+ * srcset candidate URLs) can be translated to source ranges. `map[i]` is the
3861
+ * raw offset of decoded boundary `i` (`0..text.length`); boundaries inside a
3862
+ * reference's decoded text map to the reference start. `map` is `undefined`
3863
+ * when nothing was decoded (then `text === str`).
3864
+ * @overload
3731
3865
  * @param {string} str the raw string from the token slice
3732
3866
  * @param {boolean=} isAttribute true if `str` came from an attribute value
3733
3867
  * @returns {string} decoded string
3734
3868
  */
3735
- const decodeHtmlEntities = (str, isAttribute) => {
3736
- if (!str.includes("&")) return str;
3737
-
3738
- return str.replace(
3739
- CHARACTER_REFERENCE_REGEXP,
3740
- isAttribute ? _decodeReferenceInAttribute : _decodeReferenceInText
3741
- );
3742
- };
3743
-
3744
3869
  /**
3745
- * Like `decodeHtmlEntities`, but also returns a boundary map from the
3746
- * decoded string back to raw offsets, so spans computed on the decoded
3747
- * text (e.g. srcset candidate URLs) can be translated to source ranges.
3748
- * `map[i]` is the raw offset of decoded boundary `i` (`0..text.length`);
3749
- * boundaries inside a reference's decoded text map to the reference start.
3750
- * `map` is `undefined` when nothing was decoded (then `text === str`).
3870
+ * @overload
3871
+ * @param {string} str the raw string from the token slice
3872
+ * @param {boolean | undefined} isAttribute true if `str` came from an attribute value
3873
+ * @param {true} withMap also build the decoded-to-raw boundary map
3874
+ * @returns {DecodedEntitiesWithMap} decoded text and offset map
3875
+ */
3876
+ /**
3751
3877
  * @param {string} str the raw string from the token slice
3752
3878
  * @param {boolean=} isAttribute true if `str` came from an attribute value
3753
- * @returns {{ text: string, map: number[] | undefined }} decoded text and offset map
3879
+ * @param {boolean=} withMap also build the decoded-to-raw boundary map
3880
+ * @returns {string | DecodedEntitiesWithMap} decoded string (with map when `withMap`)
3754
3881
  */
3755
- const decodeHtmlEntitiesWithMap = (str, isAttribute) => {
3882
+ const decodeEntities = (str, isAttribute, withMap) => {
3883
+ // Hand-rolled scan of one of three reference forms, each with an optional
3884
+ // trailing `;` — `&#x<hex>+` / `&#<dec>+` / `&<alpha><alnum>*` (kept separate
3885
+ // so `&#65b` doesn't eat the `b` as hex): entity-dense text pays no regex
3886
+ // machinery or per-match object, and a string whose references all stay
3887
+ // literal is returned unchanged without rebuilding. Map bookkeeping is
3888
+ // gated on `withMap` at the rare per-decoded-reference points, so the
3889
+ // dominant no-map mode pays nothing for the unified implementation.
3890
+ let amp = str.indexOf("&");
3891
+ if (amp === -1) return withMap ? { text: str, map: undefined } : str;
3892
+ let out = "";
3893
+ let last = 0;
3756
3894
  /** @type {number[] | undefined} */
3757
3895
  let map;
3758
- if (str.includes("&")) {
3759
- CHARACTER_REFERENCE_REGEXP.lastIndex = 0;
3760
- let text = "";
3761
- let last = 0;
3762
- /** @type {RegExpExecArray | null} */
3763
- let m;
3764
- while ((m = CHARACTER_REFERENCE_REGEXP.exec(str)) !== null) {
3765
- const match = m[0];
3766
- const decoded = decodeOneReference(
3767
- match,
3768
- str.charCodeAt(m.index + match.length),
3769
- isAttribute
3770
- );
3771
- if (decoded === match) continue;
3772
- if (map === undefined) map = [];
3773
- for (let r = last; r < m.index; r++) map.push(r);
3774
- text += str.slice(last, m.index);
3775
- for (let i = 0; i < decoded.length; i++) map.push(m.index);
3776
- text += decoded;
3777
- last = m.index + match.length;
3778
- }
3779
- if (map !== undefined) {
3780
- for (let r = last; r < str.length; r++) map.push(r);
3781
- map.push(str.length);
3782
- return { text: text + str.slice(last), map };
3896
+ do {
3897
+ // `end` stays 0 when the `&` doesn't start a well-formed reference; a
3898
+ // numeric reference's code point accumulates during the digit scan
3899
+ // (saturating above the code-point range — every overflow decodes the
3900
+ // same replacement) so it needs no slice at all.
3901
+ let end = 0;
3902
+ let code = -1;
3903
+ let cc = str.charCodeAt(amp + 1);
3904
+ if (cc === 0x23 /* # */) {
3905
+ cc = str.charCodeAt(amp + 2);
3906
+ let p;
3907
+ if (cc === 0x78 /* x */ || cc === 0x58 /* X */) {
3908
+ code = 0;
3909
+ p = amp + 3;
3910
+ while (isAsciiHexDigit((cc = str.charCodeAt(p)))) {
3911
+ code = code * 16 + (cc <= 0x39 ? cc - 0x30 : (cc | 0x20) - 0x57);
3912
+ if (code > 0x10ffff) code = 0x110000;
3913
+ p++;
3914
+ }
3915
+ if (p > amp + 3) end = p;
3916
+ } else {
3917
+ code = 0;
3918
+ p = amp + 2;
3919
+ while (isAsciiDigit((cc = str.charCodeAt(p)))) {
3920
+ code = code * 10 + (cc - 0x30);
3921
+ if (code > 0x10ffff) code = 0x110000;
3922
+ p++;
3923
+ }
3924
+ if (p > amp + 2) end = p;
3925
+ }
3926
+ } else if (isAsciiAlpha(cc)) {
3927
+ code = -1;
3928
+ let p = amp + 2;
3929
+ while (isAsciiAlphanumeric(str.charCodeAt(p))) p++;
3930
+ end = p;
3931
+ }
3932
+ if (end !== 0) {
3933
+ if (str.charCodeAt(end) === 0x3b /* ; */) end++;
3934
+ // Numeric references always decode; named ones may stay literal.
3935
+ const decoded =
3936
+ code >= 0
3937
+ ? decodeNumericReference(code)
3938
+ : decodeNamedReference(
3939
+ str,
3940
+ amp,
3941
+ end,
3942
+ str.charCodeAt(end),
3943
+ isAttribute
3944
+ );
3945
+ if (decoded !== undefined) {
3946
+ if (withMap) map = _mapReference(map, last, amp, decoded.length);
3947
+ out += str.slice(last, amp);
3948
+ out += decoded;
3949
+ last = end;
3950
+ }
3951
+ amp = str.indexOf("&", end);
3952
+ } else {
3953
+ amp = str.indexOf("&", amp + 1);
3954
+ }
3955
+ } while (amp !== -1);
3956
+ if (last === 0) return withMap ? { text: str, map: undefined } : str;
3957
+ const text = out + str.slice(last);
3958
+ return withMap
3959
+ ? _finishMap(text, /** @type {number[]} */ (map), last, str.length)
3960
+ : text;
3961
+ };
3962
+
3963
+ // Map-mode bookkeeping for `decodeEntities`, kept out of the scanner body so
3964
+ // the dominant no-map mode stays small enough to inline.
3965
+ /**
3966
+ * @param {number[] | undefined} map boundary map so far
3967
+ * @param {number} last raw offset the literal run starts at
3968
+ * @param {number} amp raw offset of the decoded reference
3969
+ * @param {number} decodedLength length of the decoded text
3970
+ * @returns {number[]} the map, with the literal run's and reference's boundaries appended
3971
+ */
3972
+ const _mapReference = (map, last, amp, decodedLength) => {
3973
+ if (map === undefined) map = [];
3974
+ for (let r = last; r < amp; r++) map.push(r);
3975
+ for (let i = 0; i < decodedLength; i++) map.push(amp);
3976
+ return map;
3977
+ };
3978
+
3979
+ /**
3980
+ * @param {string} text decoded text
3981
+ * @param {number[]} map boundary map so far
3982
+ * @param {number} last raw offset of the trailing literal run
3983
+ * @param {number} rawLength raw string length
3984
+ * @returns {DecodedEntitiesWithMap} the finished result
3985
+ */
3986
+ const _finishMap = (text, map, last, rawLength) => {
3987
+ for (let r = last; r < rawLength; r++) map.push(r);
3988
+ map.push(rawLength);
3989
+ return { text, map };
3990
+ };
3991
+
3992
+ /**
3993
+ * Escape a string per the WHATWG "escape a string" algorithm
3994
+ * (https://html.spec.whatwg.org/multipage/parsing.html#escapingString) in
3995
+ * attribute mode: `&`, U+00A0 and `"`. CR/LF are additionally encoded as
3996
+ * numeric references so the result stays single-line (usable in a `data:`
3997
+ * URI). Single pass, returns the input unchanged when nothing needs escaping.
3998
+ * @param {string} s string to escape
3999
+ * @returns {string} HTML attribute-safe string
4000
+ */
4001
+ const escapeAttribute = (s) => {
4002
+ // fast path: native indexOf scans beat any per-character loop
4003
+ if (
4004
+ !s.includes("&") &&
4005
+ !s.includes('"') &&
4006
+ !s.includes("\u00A0") &&
4007
+ !s.includes("\n") &&
4008
+ !s.includes("\r")
4009
+ ) {
4010
+ return s;
4011
+ }
4012
+ let out = "";
4013
+ let last = 0;
4014
+ for (let i = 0; i < s.length; i++) {
4015
+ let rep;
4016
+ switch (s.charCodeAt(i)) {
4017
+ case CC_AMPERSAND:
4018
+ rep = "&amp;";
4019
+ break;
4020
+ case CC_QUOTATION_MARK:
4021
+ rep = "&quot;";
4022
+ break;
4023
+ case CC_NO_BREAK_SPACE:
4024
+ rep = "&nbsp;";
4025
+ break;
4026
+ case CC_LF:
4027
+ rep = "&#10;";
4028
+ break;
4029
+ case CC_CR:
4030
+ rep = "&#13;";
4031
+ break;
4032
+ default:
4033
+ continue;
3783
4034
  }
4035
+ out += s.slice(last, i) + rep;
4036
+ last = i + 1;
3784
4037
  }
3785
- return { text: str, map };
4038
+ return out + s.slice(last);
4039
+ };
4040
+
4041
+ /**
4042
+ * Escape a string per the WHATWG "escape a string" algorithm in text mode:
4043
+ * `&`, U+00A0, `<` and `>`. CR/LF are additionally encoded as numeric
4044
+ * references so the result stays single-line (usable in a `data:` URI).
4045
+ * Single pass, returns the input unchanged when nothing needs escaping.
4046
+ * @param {string} s string to escape
4047
+ * @returns {string} HTML text-content-safe string
4048
+ */
4049
+ const escapeText = (s) => {
4050
+ // fast path: native indexOf scans beat any per-character loop
4051
+ if (
4052
+ !s.includes("&") &&
4053
+ !s.includes("<") &&
4054
+ !s.includes(">") &&
4055
+ !s.includes("\u00A0") &&
4056
+ !s.includes("\n") &&
4057
+ !s.includes("\r")
4058
+ ) {
4059
+ return s;
4060
+ }
4061
+ let out = "";
4062
+ let last = 0;
4063
+ for (let i = 0; i < s.length; i++) {
4064
+ let rep;
4065
+ switch (s.charCodeAt(i)) {
4066
+ case CC_AMPERSAND:
4067
+ rep = "&amp;";
4068
+ break;
4069
+ case CC_LESS_THAN:
4070
+ rep = "&lt;";
4071
+ break;
4072
+ case CC_GREATER_THAN:
4073
+ rep = "&gt;";
4074
+ break;
4075
+ case CC_NO_BREAK_SPACE:
4076
+ rep = "&nbsp;";
4077
+ break;
4078
+ case CC_LF:
4079
+ rep = "&#10;";
4080
+ break;
4081
+ case CC_CR:
4082
+ rep = "&#13;";
4083
+ break;
4084
+ default:
4085
+ continue;
4086
+ }
4087
+ out += s.slice(last, i) + rep;
4088
+ last = i + 1;
4089
+ }
4090
+ return out + s.slice(last);
3786
4091
  };
3787
4092
 
3788
4093
  // cspell:ignore advasoft altglyph altglyphdef altglyphitem animatecolor animatemotion animatetransform arcrole aswedit attributename attributetype basefrequency baseprofile bgsound calcmode clippathunits definitionurl diffuseconstant fedropshadow filterunits glyphref gradienttransform gradientunits hotjava hotmetal kernelmatrix kernelunitlength keypoints keysplines keytimes limitingconeangle malignmark markerheight markerwidth maskcontentunits maskunits metrius mglyph mtext numoctaves pathlength patterncontentunits patterntransform patternunits pointsatx pointsaty pointsatz preservealpha primitiveunits refx refy repeatcount repeatdur requiredextensions requiredfeatures selectedcontent silmaril softquad specularconstant specularexponent startoffset stddeviation stitchtiles surfacescale systemlanguage tablevalues targetx targety textlength viewbox viewtarget webtechs xchannelselector ychannelselector megamorphic attributeless rowspan imagesizes novalidate maxlength
3789
4094
 
3790
4095
  // WHATWG HTML tree construction (https://html.spec.whatwg.org/multipage/parsing.html#tree-construction)
3791
- // on top of walkHtmlTokens. Scripting is always disabled (webpack is a build tool).
4096
+ // on top of tokenize. Scripting is always disabled (webpack is a build tool).
3792
4097
 
3793
4098
  // Namespaces (mirrors swc_html_ast::Namespace)
3794
4099
  const NS_HTML = 0;
@@ -3828,136 +4133,139 @@ const EMPTY_ATTRS = /** @type {AttributeRun} */ (
3828
4133
  // lives in the four link columns (parent / firstChild / lastChild /
3829
4134
  // nextSibling); the only heap references are the string payload / attribute
3830
4135
  // name-and-value side arrays. Columns are module-level and reused
3831
- // across parses (grown, never shrunk — the CSS parser's `_soa*` strategy), so
3832
- // a steady-state parse allocates almost nothing per node; consumers must fully
3833
- // read a tree before the next `buildHtmlAst` call. Id 0 is reserved as
3834
- // "no node" so the link columns can use 0 as null.
3835
- let _hCap = 0;
3836
- let _hN = 0;
4136
+ // across parses (grown, shrunk on release only past `_COLUMN_SHRINK_CAPACITY` —
4137
+ // the CSS parser's `_soa*` strategy), so a steady-state parse allocates almost
4138
+ // nothing per node; consumers must fully read a tree before the next
4139
+ // `parseHtml` call. Id 0 is reserved as "no node" so the link columns can use
4140
+ // 0 as null.
4141
+ let _nodeCapacity = 0;
4142
+ let _nodeCount = 0;
3837
4143
  /** `NodeType` per node */
3838
- let _hTy = new Uint8Array(0);
4144
+ let _nodeTypes = new Uint8Array(0);
3839
4145
  /** bits 0-1 namespace (`NS_*`), bit 2 self-closing (void element) */
3840
- let _hFl = new Uint8Array(0);
3841
- let _hSt = new Int32Array(0);
3842
- let _hEn = new Int32Array(0);
4146
+ let _nodeFlags = new Uint8Array(0);
4147
+ let _nodeStarts = new Int32Array(0);
4148
+ let _nodeEnds = new Int32Array(0);
3843
4149
  /** end offset of an element's opening tag (after `>`) */
3844
- let _hTagEnd = new Int32Array(0);
4150
+ let _nodeTagEnds = new Int32Array(0);
3845
4151
  /** end offset of an element's tag name */
3846
- let _hNameEnd = new Int32Array(0);
4152
+ let _nodeNameEnds = new Int32Array(0);
3847
4153
  /** under `skip.text`, end offset of a raw-text element's body (`HtmlAstSkip`) */
3848
- let _hCEnd = new Int32Array(0);
4154
+ let _nodeContentEnds = new Int32Array(0);
3849
4155
  /** a `<template>`'s content DocumentFragment (0 = none) */
3850
- let _hTc = new Int32Array(0);
3851
- let _hParent = new Int32Array(0);
3852
- let _hFirst = new Int32Array(0);
3853
- let _hLast = new Int32Array(0);
3854
- let _hNext = new Int32Array(0);
4156
+ let _nodeTemplateContents = new Int32Array(0);
4157
+ let _nodeParents = new Int32Array(0);
4158
+ let _nodeFirstChildren = new Int32Array(0);
4159
+ let _nodeLastChildren = new Int32Array(0);
4160
+ let _nodeNextSiblings = new Int32Array(0);
3855
4161
  /** @type {string[]} tag name / text data / comment data / doctype name */
3856
- const _hStr = [];
4162
+ const _nodeStrings = [];
3857
4163
  /** first attribute id of an element's contiguous run */
3858
- let _hAStart = new Int32Array(0);
4164
+ let _nodeAttrStarts = new Int32Array(0);
3859
4165
  /** attribute count of an element's run */
3860
- let _hACount = new Int32Array(0);
4166
+ let _nodeAttrCounts = new Int32Array(0);
3861
4167
  // The single doctype node's public/system ids (a document inserts at most one
3862
4168
  // doctype node — later doctype tokens are ignored — so no column is needed).
3863
4169
  /** @type {string | null} */
3864
- let _hDocPub = null;
4170
+ let _doctypePublicId = null;
3865
4171
  /** @type {string | null} */
3866
- let _hDocSys = null;
4172
+ let _doctypeSystemId = null;
3867
4173
 
3868
4174
  const NS_MASK = 3;
3869
4175
  const FLAG_SELF_CLOSING = 4;
3870
4176
 
3871
4177
  /** @type {(el: HtmlNodeRef) => string} */
3872
- const _tag = (el) => _hStr[el];
4178
+ const _tagNameOf = (el) => _nodeStrings[el];
3873
4179
  /** @type {(el: HtmlNodeRef) => number} */
3874
- const _ns = (el) => _hFl[el] & NS_MASK;
4180
+ const _namespaceOf = (el) => _nodeFlags[el] & NS_MASK;
3875
4181
 
3876
4182
  // === Attribute columns ===
3877
4183
  // One attribute = one integer id into these columns; an element (and a
3878
4184
  // start-tag token) holds a contiguous run. The value string is derived from
3879
- // the source by offset on read — `_aVal` carries an override only for
4185
+ // the source by offset on read — `_attrValues` carries an override only for
3880
4186
  // valueless attributes (`""`) and offset-less adoption-agency clones — and the
3881
4187
  // html5lib serializer name is derived from the adjusted name plus one flag
3882
4188
  // bit, so per attribute only the interned name pointer is retained.
3883
- let _aCap = 0;
3884
- let _aN = 0;
3885
- let _aNameStart = new Int32Array(0);
3886
- let _aNameEnd = new Int32Array(0);
3887
- let _aValStart = new Int32Array(0);
3888
- let _aValEnd = new Int32Array(0);
4189
+ let _attrCapacity = 0;
4190
+ let _attrCount = 0;
4191
+ let _attrNameStarts = new Int32Array(0);
4192
+ let _attrNameEnds = new Int32Array(0);
4193
+ let _attrValueStarts = new Int32Array(0);
4194
+ let _attrValueEnds = new Int32Array(0);
3889
4195
  /** bit 0: name has a `FOREIGN_ATTR_NS` serializer name (set on foreign adjust) */
3890
- let _aFl = new Uint8Array(0);
4196
+ let _attrFlags = new Uint8Array(0);
3891
4197
  /** @type {string[]} lowercased (foreign-content: adjusted) attribute name */
3892
- const _aName = [];
4198
+ const _attrNames = [];
3893
4199
  /** @type {(string | null)[]} value override (null = slice the source by offset) */
3894
- const _aVal = [];
4200
+ const _attrValues = [];
3895
4201
  /** source of the current parse, for by-offset attribute values */
3896
- let _hSrc = "";
4202
+ let _htmlSource = "";
3897
4203
 
3898
4204
  /** @param {number} need minimum capacity */
3899
- const _aGrow = (need) => {
3900
- let cap = _aCap || 4096;
4205
+ const _growAttrColumns = (need) => {
4206
+ let cap = _attrCapacity || 4096;
3901
4207
  while (cap < need) cap *= 2;
3902
4208
  const nameStart = new Int32Array(cap);
3903
- nameStart.set(_aNameStart);
3904
- _aNameStart = nameStart;
4209
+ nameStart.set(_attrNameStarts);
4210
+ _attrNameStarts = nameStart;
3905
4211
  const nameEnd = new Int32Array(cap);
3906
- nameEnd.set(_aNameEnd);
3907
- _aNameEnd = nameEnd;
4212
+ nameEnd.set(_attrNameEnds);
4213
+ _attrNameEnds = nameEnd;
3908
4214
  const valStart = new Int32Array(cap);
3909
- valStart.set(_aValStart);
3910
- _aValStart = valStart;
4215
+ valStart.set(_attrValueStarts);
4216
+ _attrValueStarts = valStart;
3911
4217
  const valEnd = new Int32Array(cap);
3912
- valEnd.set(_aValEnd);
3913
- _aValEnd = valEnd;
4218
+ valEnd.set(_attrValueEnds);
4219
+ _attrValueEnds = valEnd;
3914
4220
  const fl = new Uint8Array(cap);
3915
- fl.set(_aFl);
3916
- _aFl = fl;
3917
- _aCap = cap;
4221
+ fl.set(_attrFlags);
4222
+ _attrFlags = fl;
4223
+ _attrCapacity = cap;
3918
4224
  };
3919
4225
 
3920
4226
  /** @type {(name: string, value: string | null, nameStart: number, nameEnd: number, valueStart: number, valueEnd: number) => number} */
3921
- const _aAlloc = (name, value, nameStart, nameEnd, valueStart, valueEnd) => {
3922
- const i = ++_aN;
3923
- if (i >= _aCap) _aGrow(i + 1);
3924
- _aNameStart[i] = nameStart;
3925
- _aNameEnd[i] = nameEnd;
3926
- _aValStart[i] = valueStart;
3927
- _aValEnd[i] = valueEnd;
3928
- _aFl[i] = 0;
4227
+ const _allocAttr = (name, value, nameStart, nameEnd, valueStart, valueEnd) => {
4228
+ const i = ++_attrCount;
4229
+ if (i >= _attrCapacity) _growAttrColumns(i + 1);
4230
+ _attrNameStarts[i] = nameStart;
4231
+ _attrNameEnds[i] = nameEnd;
4232
+ _attrValueStarts[i] = valueStart;
4233
+ _attrValueEnds[i] = valueEnd;
4234
+ _attrFlags[i] = 0;
3929
4235
  // Ids are sequential, so these indexed writes append (arrays stay packed).
3930
- _aName[i] = name;
3931
- _aVal[i] = value;
4236
+ _attrNames[i] = name;
4237
+ _attrValues[i] = value;
3932
4238
  return i;
3933
4239
  };
3934
4240
 
3935
4241
  /** @type {(i: number) => string} */
3936
- const _aValueOf = (i) => {
3937
- const v = _aVal[i];
3938
- return v !== null ? v : _hSrc.slice(_aValStart[i], _aValEnd[i]);
4242
+ const _attrValueOf = (i) => {
4243
+ const v = _attrValues[i];
4244
+ return v !== null
4245
+ ? v
4246
+ : _htmlSource.slice(_attrValueStarts[i], _attrValueEnds[i]);
3939
4247
  };
3940
4248
 
3941
4249
  // Linear name lookup in a run — attribute lists are short, a loop beats a Map.
3942
4250
  /** @type {(start: number, count: number, name: string) => number} */
3943
- const _aFind = (start, count, name) => {
4251
+ const _findAttr = (start, count, name) => {
3944
4252
  for (let i = start; i < start + count; i++) {
3945
- if (_aName[i] === name) return i;
4253
+ if (_attrNames[i] === name) return i;
3946
4254
  }
3947
4255
  return 0;
3948
4256
  };
3949
4257
 
3950
4258
  /** @type {(i: number) => number} exact copy of an attribute into a new id */
3951
- const _aCopy = (i) => {
3952
- const c = _aAlloc(
3953
- _aName[i],
3954
- _aVal[i],
3955
- _aNameStart[i],
3956
- _aNameEnd[i],
3957
- _aValStart[i],
3958
- _aValEnd[i]
4259
+ const _copyAttr = (i) => {
4260
+ const c = _allocAttr(
4261
+ _attrNames[i],
4262
+ _attrValues[i],
4263
+ _attrNameStarts[i],
4264
+ _attrNameEnds[i],
4265
+ _attrValueStarts[i],
4266
+ _attrValueEnds[i]
3959
4267
  );
3960
- _aFl[c] = _aFl[i];
4268
+ _attrFlags[c] = _attrFlags[i];
3961
4269
  return c;
3962
4270
  };
3963
4271
 
@@ -3966,135 +4274,167 @@ const _aCopy = (i) => {
3966
4274
  // an uppercase letter (unadjusted names are always lowercased), serializing as
3967
4275
  // itself. Everything else serializes as the plain name (undefined here).
3968
4276
  /** @type {(i: number) => string | undefined} */
3969
- const _aSerializedName = (i) => {
3970
- if ((_aFl[i] & 1) !== 0) return FOREIGN_ATTR_NS[_aName[i]];
3971
- const name = _aName[i];
4277
+ const _attrSerializedName = (i) => {
4278
+ if ((_attrFlags[i] & 1) !== 0) return FOREIGN_ATTR_NS[_attrNames[i]];
4279
+ const name = _attrNames[i];
3972
4280
  return /[A-Z]/.test(name) ? name : undefined;
3973
4281
  };
3974
4282
 
3975
4283
  /** @param {number} need minimum capacity */
3976
- const _hGrow = (need) => {
3977
- let cap = _hCap || 4096;
4284
+ const _growNodeColumns = (need) => {
4285
+ let cap = _nodeCapacity || 4096;
3978
4286
  while (cap < need) cap *= 2;
3979
4287
  const ty = new Uint8Array(cap);
3980
- ty.set(_hTy);
3981
- _hTy = ty;
4288
+ ty.set(_nodeTypes);
4289
+ _nodeTypes = ty;
3982
4290
  const fl = new Uint8Array(cap);
3983
- fl.set(_hFl);
3984
- _hFl = fl;
4291
+ fl.set(_nodeFlags);
4292
+ _nodeFlags = fl;
3985
4293
  const st = new Int32Array(cap);
3986
- st.set(_hSt);
3987
- _hSt = st;
4294
+ st.set(_nodeStarts);
4295
+ _nodeStarts = st;
3988
4296
  const en = new Int32Array(cap);
3989
- en.set(_hEn);
3990
- _hEn = en;
4297
+ en.set(_nodeEnds);
4298
+ _nodeEnds = en;
3991
4299
  const tagEnd = new Int32Array(cap);
3992
- tagEnd.set(_hTagEnd);
3993
- _hTagEnd = tagEnd;
4300
+ tagEnd.set(_nodeTagEnds);
4301
+ _nodeTagEnds = tagEnd;
3994
4302
  const nameEnd = new Int32Array(cap);
3995
- nameEnd.set(_hNameEnd);
3996
- _hNameEnd = nameEnd;
4303
+ nameEnd.set(_nodeNameEnds);
4304
+ _nodeNameEnds = nameEnd;
3997
4305
  const cEnd = new Int32Array(cap);
3998
- cEnd.set(_hCEnd);
3999
- _hCEnd = cEnd;
4306
+ cEnd.set(_nodeContentEnds);
4307
+ _nodeContentEnds = cEnd;
4000
4308
  const tc = new Int32Array(cap);
4001
- tc.set(_hTc);
4002
- _hTc = tc;
4309
+ tc.set(_nodeTemplateContents);
4310
+ _nodeTemplateContents = tc;
4003
4311
  const parent = new Int32Array(cap);
4004
- parent.set(_hParent);
4005
- _hParent = parent;
4312
+ parent.set(_nodeParents);
4313
+ _nodeParents = parent;
4006
4314
  const first = new Int32Array(cap);
4007
- first.set(_hFirst);
4008
- _hFirst = first;
4315
+ first.set(_nodeFirstChildren);
4316
+ _nodeFirstChildren = first;
4009
4317
  const last = new Int32Array(cap);
4010
- last.set(_hLast);
4011
- _hLast = last;
4318
+ last.set(_nodeLastChildren);
4319
+ _nodeLastChildren = last;
4012
4320
  const next = new Int32Array(cap);
4013
- next.set(_hNext);
4014
- _hNext = next;
4321
+ next.set(_nodeNextSiblings);
4322
+ _nodeNextSiblings = next;
4015
4323
  const aStart = new Int32Array(cap);
4016
- aStart.set(_hAStart);
4017
- _hAStart = aStart;
4324
+ aStart.set(_nodeAttrStarts);
4325
+ _nodeAttrStarts = aStart;
4018
4326
  const aCount = new Int32Array(cap);
4019
- aCount.set(_hACount);
4020
- _hACount = aCount;
4021
- _hCap = cap;
4327
+ aCount.set(_nodeAttrCounts);
4328
+ _nodeAttrCounts = aCount;
4329
+ _nodeCapacity = cap;
4022
4330
  };
4023
4331
 
4024
4332
  /** Start a new parse: invalidate all prior refs, release prior heap refs. */
4025
- const _hReset = () => {
4026
- _hN = 0;
4027
- _aN = 0;
4028
- _hStr.length = 0;
4029
- _aName.length = 0;
4030
- _aVal.length = 0;
4333
+ const _resetAstColumns = () => {
4334
+ _nodeCount = 0;
4335
+ _attrCount = 0;
4336
+ _nodeStrings.length = 0;
4337
+ _attrNames.length = 0;
4338
+ _attrValues.length = 0;
4031
4339
  // Keep id 0 ("no node" / "no attribute") occupied so writes stay packed.
4032
- _hStr.push("");
4033
- _aName.push("");
4034
- _aVal.push(null);
4035
- _hDocPub = null;
4036
- _hDocSys = null;
4340
+ _nodeStrings.push("");
4341
+ _attrNames.push("");
4342
+ _attrValues.push(null);
4343
+ _doctypePublicId = null;
4344
+ _doctypeSystemId = null;
4037
4345
  };
4038
4346
 
4347
+ // The typed-array columns grow to the largest document ever parsed; above this
4348
+ // capacity they are re-shrunk on release so one pathological file can't pin
4349
+ // tens of MB at module level for the process lifetime (~50 bytes/node,
4350
+ // ~17 bytes/attribute of capacity across the columns).
4351
+ const _COLUMN_SHRINK_CAPACITY = 65536;
4352
+
4039
4353
  // Release the side arrays' heap references (strings, attribute names/values)
4040
4354
  // once a walk has consumed the tree, so the retained columns don't pin the
4041
4355
  // parsed source until the next parse.
4042
- const _hRelease = () => {
4043
- _hN = 0;
4044
- _aN = 0;
4045
- _hStr.length = 0;
4046
- _aName.length = 0;
4047
- _aVal.length = 0;
4048
- _hSrc = "";
4049
- _hDocPub = null;
4050
- _hDocSys = null;
4356
+ const _releaseAstColumns = () => {
4357
+ _nodeCount = 0;
4358
+ _attrCount = 0;
4359
+ _nodeStrings.length = 0;
4360
+ _attrNames.length = 0;
4361
+ _attrValues.length = 0;
4362
+ _htmlSource = "";
4363
+ _doctypePublicId = null;
4364
+ _doctypeSystemId = null;
4365
+ if (_nodeCapacity > _COLUMN_SHRINK_CAPACITY) {
4366
+ _nodeCapacity = 0;
4367
+ _nodeTypes = new Uint8Array(0);
4368
+ _nodeFlags = new Uint8Array(0);
4369
+ _nodeStarts = new Int32Array(0);
4370
+ _nodeEnds = new Int32Array(0);
4371
+ _nodeTagEnds = new Int32Array(0);
4372
+ _nodeNameEnds = new Int32Array(0);
4373
+ _nodeContentEnds = new Int32Array(0);
4374
+ _nodeTemplateContents = new Int32Array(0);
4375
+ _nodeParents = new Int32Array(0);
4376
+ _nodeFirstChildren = new Int32Array(0);
4377
+ _nodeLastChildren = new Int32Array(0);
4378
+ _nodeNextSiblings = new Int32Array(0);
4379
+ _nodeAttrStarts = new Int32Array(0);
4380
+ _nodeAttrCounts = new Int32Array(0);
4381
+ }
4382
+ if (_attrCapacity > _COLUMN_SHRINK_CAPACITY) {
4383
+ _attrCapacity = 0;
4384
+ _attrNameStarts = new Int32Array(0);
4385
+ _attrNameEnds = new Int32Array(0);
4386
+ _attrValueStarts = new Int32Array(0);
4387
+ _attrValueEnds = new Int32Array(0);
4388
+ _attrFlags = new Uint8Array(0);
4389
+ }
4051
4390
  };
4052
4391
 
4053
4392
  /** @type {(type: number, start: number, end: number) => HtmlNodeRef} */
4054
- const _hAlloc = (type, start, end) => {
4055
- const i = ++_hN;
4056
- if (i >= _hCap) _hGrow(i + 1);
4057
- _hTy[i] = type;
4058
- _hFl[i] = 0;
4059
- _hSt[i] = start;
4060
- _hEn[i] = end;
4061
- _hTagEnd[i] = 0;
4062
- _hNameEnd[i] = 0;
4063
- _hCEnd[i] = 0;
4064
- _hTc[i] = 0;
4065
- _hParent[i] = 0;
4066
- _hFirst[i] = 0;
4067
- _hLast[i] = 0;
4068
- _hNext[i] = 0;
4069
- _hAStart[i] = 0;
4070
- _hACount[i] = 0;
4071
- // Ids are sequential, so this indexed write appends (array stays packed).
4072
- _hStr[i] = "";
4393
+ const _allocNode = (type, start, end) => {
4394
+ const i = ++_nodeCount;
4395
+ if (i >= _nodeCapacity) _growNodeColumns(i + 1);
4396
+ _nodeTypes[i] = type;
4397
+ _nodeFlags[i] = 0;
4398
+ _nodeStarts[i] = start;
4399
+ _nodeEnds[i] = end;
4400
+ _nodeTagEnds[i] = 0;
4401
+ _nodeNameEnds[i] = 0;
4402
+ _nodeContentEnds[i] = 0;
4403
+ _nodeTemplateContents[i] = 0;
4404
+ _nodeParents[i] = 0;
4405
+ _nodeFirstChildren[i] = 0;
4406
+ _nodeLastChildren[i] = 0;
4407
+ _nodeNextSiblings[i] = 0;
4408
+ _nodeAttrStarts[i] = 0;
4409
+ _nodeAttrCounts[i] = 0;
4410
+ // `_nodeStrings[i]` is written by every caller right after this returns
4411
+ // (tag name / text / comment data; "" at the few string-less sites), as a
4412
+ // packed sequential append — no placeholder double-write here.
4073
4413
  return i;
4074
4414
  };
4075
4415
 
4076
4416
  // Raw child append — no text merging, no `<template>` content redirect (the
4077
4417
  // tree builder layers those on top). `node` must be detached (`next` = 0).
4078
4418
  /** @type {(parent: HtmlNodeRef, node: HtmlNodeRef) => void} */
4079
- const _hAppend = (parent, node) => {
4080
- _hParent[node] = parent;
4081
- const last = _hLast[parent];
4082
- if (last === 0) _hFirst[parent] = node;
4083
- else _hNext[last] = node;
4084
- _hLast[parent] = node;
4419
+ const _appendChild = (parent, node) => {
4420
+ _nodeParents[node] = parent;
4421
+ const last = _nodeLastChildren[parent];
4422
+ if (last === 0) _nodeFirstChildren[parent] = node;
4423
+ else _nodeNextSiblings[last] = node;
4424
+ _nodeLastChildren[parent] = node;
4085
4425
  };
4086
4426
 
4087
4427
  /** @type {(data: string, start: number, end: number) => HtmlNodeRef} */
4088
- const _mkText = (data, start, end) => {
4089
- const i = _hAlloc(NodeType.Text, start, end);
4090
- _hStr[i] = data;
4428
+ const _makeTextNode = (data, start, end) => {
4429
+ const i = _allocNode(NodeType.Text, start, end);
4430
+ _nodeStrings[i] = data;
4091
4431
  return i;
4092
4432
  };
4093
4433
 
4094
4434
  /** @type {(data: string, start: number, end: number) => HtmlNodeRef} */
4095
- const _mkComment = (data, start, end) => {
4096
- const i = _hAlloc(NodeType.Comment, start, end);
4097
- _hStr[i] = data;
4435
+ const _makeCommentNode = (data, start, end) => {
4436
+ const i = _allocNode(NodeType.Comment, start, end);
4437
+ _nodeStrings[i] = data;
4098
4438
  return i;
4099
4439
  };
4100
4440
 
@@ -4106,12 +4446,12 @@ const AFE_MARKER = -1;
4106
4446
  // dependency for the reopened element's spans. Values are materialized since
4107
4447
  // the offsets are gone.
4108
4448
  const cloneAttrs = (/** @type {HtmlElement} */ el) => {
4109
- const start = _hAStart[el];
4110
- const count = _hACount[el];
4111
- const newStart = _aN + 1;
4449
+ const start = _nodeAttrStarts[el];
4450
+ const count = _nodeAttrCounts[el];
4451
+ const newStart = _attrCount + 1;
4112
4452
  for (let i = start; i < start + count; i++) {
4113
- const c = _aAlloc(_aName[i], _aValueOf(i), -1, -1, -1, -1);
4114
- _aFl[c] = _aFl[i];
4453
+ const c = _allocAttr(_attrNames[i], _attrValueOf(i), -1, -1, -1, -1);
4454
+ _attrFlags[c] = _attrFlags[i];
4115
4455
  }
4116
4456
  return { start: newStart, count };
4117
4457
  };
@@ -4124,20 +4464,20 @@ const mergeAttrs = (
4124
4464
  /** @type {HtmlElement} */ el,
4125
4465
  /** @type {AttributeRun} */ run
4126
4466
  ) => {
4127
- const start = _hAStart[el];
4128
- const count = _hACount[el];
4467
+ const start = _nodeAttrStarts[el];
4468
+ const count = _nodeAttrCounts[el];
4129
4469
  let extra = 0;
4130
4470
  for (let i = run.start; i < run.start + run.count; i++) {
4131
- if (_aFind(start, count, _aName[i]) === 0) extra++;
4471
+ if (_findAttr(start, count, _attrNames[i]) === 0) extra++;
4132
4472
  }
4133
4473
  if (extra === 0) return;
4134
- const newStart = _aN + 1;
4135
- for (let i = start; i < start + count; i++) _aCopy(i);
4474
+ const newStart = _attrCount + 1;
4475
+ for (let i = start; i < start + count; i++) _copyAttr(i);
4136
4476
  for (let i = run.start; i < run.start + run.count; i++) {
4137
- if (_aFind(start, count, _aName[i]) === 0) _aCopy(i);
4477
+ if (_findAttr(start, count, _attrNames[i]) === 0) _copyAttr(i);
4138
4478
  }
4139
- _hAStart[el] = newStart;
4140
- _hACount[el] = count + extra;
4479
+ _nodeAttrStarts[el] = newStart;
4480
+ _nodeAttrCounts[el] = count + extra;
4141
4481
  };
4142
4482
 
4143
4483
  /**
@@ -4157,7 +4497,7 @@ const mergeAttrs = (
4157
4497
  /**
4158
4498
  * A node reference into the struct-of-arrays AST: an integer id indexing the
4159
4499
  * parallel `_h*` columns. Read fields through the exported accessor `A`. Refs
4160
- * are only valid until the next `buildHtmlAst` call — the columns are reused
4500
+ * are only valid until the next `parseHtml` call — the columns are reused
4161
4501
  * across parses — so consume a tree fully before parsing again.
4162
4502
  * @typedef {number} HtmlNodeRef
4163
4503
  */
@@ -4375,8 +4715,8 @@ const FORMATTING = new Set([
4375
4715
  const HEADING = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
4376
4716
 
4377
4717
  const isSpecial = (/** @type {HtmlElement} */ el) => {
4378
- const ns = _ns(el);
4379
- const tag = _tag(el);
4718
+ const ns = _namespaceOf(el);
4719
+ const tag = _tagNameOf(el);
4380
4720
  if (ns === NS_HTML) return SPECIAL.has(tag);
4381
4721
  if (ns === NS_MATHML) return MATHML_SPECIAL.has(tag);
4382
4722
  if (ns === NS_SVG) return SVG_SPECIAL.has(tag.toLowerCase());
@@ -4830,7 +5170,7 @@ const FONT_BREAKOUT_ATTRS = new Set(["color", "face", "size"]);
4830
5170
  // `<font color|face|size>` breaks out of foreign content (§13.2.6.5).
4831
5171
  const hasFontBreakoutAttr = (/** @type {AttributeRun} */ run) => {
4832
5172
  for (let i = run.start; i < run.start + run.count; i++) {
4833
- if (FONT_BREAKOUT_ATTRS.has(_aName[i])) return true;
5173
+ if (FONT_BREAKOUT_ATTRS.has(_attrNames[i])) return true;
4834
5174
  }
4835
5175
  return false;
4836
5176
  };
@@ -4922,10 +5262,10 @@ const internLowerName = (table, input, start, end) => {
4922
5262
  if (hit === undefined) break;
4923
5263
  if (hashes[slot] === h) {
4924
5264
  if (typeof hit === "string") {
4925
- if (rangeEqualsLower(input, start, end, hit)) return hit;
5265
+ if (rangeEqualsLowerCase(input, start, end, hit)) return hit;
4926
5266
  } else {
4927
5267
  for (let i = 0; i < hit.length; i++) {
4928
- if (rangeEqualsLower(input, start, end, hit[i])) return hit[i];
5268
+ if (rangeEqualsLowerCase(input, start, end, hit[i])) return hit[i];
4929
5269
  }
4930
5270
  }
4931
5271
  break;
@@ -4969,7 +5309,7 @@ const ATTR_NAME_INTERN = buildNameInternTable(
4969
5309
  // Hoisted so the many `open.some(...)` "is there an open HTML <template>?"
4970
5310
  // checks reuse one predicate instead of allocating an arrow per call.
4971
5311
  const isHtmlTemplateEl = (/** @type {HtmlElement} */ e) =>
4972
- _tag(e) === "template" && _ns(e) === NS_HTML;
5312
+ _tagNameOf(e) === "template" && _namespaceOf(e) === NS_HTML;
4973
5313
 
4974
5314
  // Raw text / escapable raw text elements (WHATWG §13.1.2) plus the other
4975
5315
  // RAWTEXT/RCDATA/PLAINTEXT-tokenized elements. Under `skip.text` their body's
@@ -4988,7 +5328,10 @@ const RAW_TEXT_ELEMENTS = new Set([
4988
5328
  "plaintext"
4989
5329
  ]);
4990
5330
 
4991
- /** Shared empty skip set so the common (no-skip) call allocates nothing. */
5331
+ /**
5332
+ * Shared empty skip set so the common (no-skip) call allocates nothing.
5333
+ * @type {HtmlAstSkip}
5334
+ */
4992
5335
  const EMPTY_SKIP = Object.freeze({});
4993
5336
 
4994
5337
  /**
@@ -5002,18 +5345,40 @@ const EMPTY_SKIP = Object.freeze({});
5002
5345
  */
5003
5346
 
5004
5347
  /**
5005
- * @param {string} source HTML source
5006
- * @param {string=} fragmentContext context element name for fragment parsing (e.g. `td`, `svg path`); omit for a full document
5007
- * @param {HtmlAstSkip=} skip node kinds to omit from the AST (see `HtmlAstSkip`); omit to build the full tree
5348
+ * @typedef {object} HtmlParseOptions
5349
+ * @property {string=} fragmentContext context element name for fragment parsing (e.g. `td`, `svg path`); omit for a full document
5350
+ * @property {HtmlAstSkip=} skip node kinds to omit from the AST (see `HtmlAstSkip`); omit to build the full tree
5351
+ */
5352
+
5353
+ /**
5354
+ * @param {string} input HTML source
5355
+ * @param {number=} pos start byte offset (string input only; default `0`)
5356
+ * @param {HtmlParseOptions=} options parse options (fragment context, AST skips)
5008
5357
  * @returns {HtmlDocument} ref to the document node — read through `A`; valid until the next parse
5009
5358
  */
5010
- const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5359
+ const parseHtml = (input, pos = 0, options = {}) => {
5360
+ const source = input;
5361
+ const { fragmentContext, skip = EMPTY_SKIP } = options;
5011
5362
  const skipText = skip.text === true;
5012
5363
  const skipComments = skip.comments === true;
5013
5364
  const skipDoctype = skip.doctype === true;
5014
- _hReset();
5015
- _hSrc = source;
5016
- const doc = _hAlloc(NodeType.Document, 0, 0);
5365
+ _resetAstColumns();
5366
+ // Pre-size the columns from the input length: a document big enough to
5367
+ // exceed `_COLUMN_SHRINK_CAPACITY` re-enters with released (empty) columns,
5368
+ // and growing through the doubling cascade re-allocates and copies every
5369
+ // column several times per parse. ~12 bytes/node and ~50 bytes/attribute
5370
+ // are the densest realistic HTML, so `len/12` (`len/48`) reaches the final
5371
+ // capacity in one allocation; a sparser document over-allocates bounded
5372
+ // scratch that the post-walk release frees, and the doubling growth remains
5373
+ // as the safety net when the estimate is short.
5374
+ const _sizeEstimate = (input.length / 12) | 0;
5375
+ if (_sizeEstimate > _nodeCapacity) _growNodeColumns(_sizeEstimate);
5376
+ if (_sizeEstimate > _attrCapacity << 2) {
5377
+ _growAttrColumns(_sizeEstimate >> 2);
5378
+ }
5379
+ _htmlSource = source;
5380
+ const doc = _allocNode(NodeType.Document, 0, 0);
5381
+ _nodeStrings[doc] = "";
5017
5382
  let mode = MODE_INITIAL;
5018
5383
  // Mode to return to after MODE_TEXT / MODE_IN_TABLE_TEXT (0 = unset).
5019
5384
  let originalMode = 0;
@@ -5049,24 +5414,24 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5049
5414
  /** @type {AttributeRun} */ attrs,
5050
5415
  /** @type {TagPos | null | undefined} */ pos
5051
5416
  ) => {
5052
- const el = _hAlloc(
5417
+ const el = _allocNode(
5053
5418
  NodeType.Element,
5054
5419
  pos ? pos.start : 0,
5055
5420
  pos ? pos.end : 0
5056
5421
  );
5057
5422
  // Void HTML elements are marked self-closing and never receive children.
5058
- _hFl[el] =
5423
+ _nodeFlags[el] =
5059
5424
  ns === NS_HTML && VOID.has(tagName) ? ns | FLAG_SELF_CLOSING : ns;
5060
- _hStr[el] = tagName;
5061
- _hAStart[el] = attrs.start;
5062
- _hACount[el] = attrs.count;
5425
+ _nodeStrings[el] = tagName;
5426
+ _nodeAttrStarts[el] = attrs.start;
5427
+ _nodeAttrCounts[el] = attrs.count;
5063
5428
  if (pos) {
5064
- _hTagEnd[el] = pos.tagEnd;
5065
- _hNameEnd[el] = pos.nameEnd;
5429
+ _nodeTagEnds[el] = pos.tagEnd;
5430
+ _nodeNameEnds[el] = pos.nameEnd;
5066
5431
  // End of a raw-text element's body under `skip.text` (defaults to the
5067
5432
  // body start, i.e. empty); lets consumers read `<script>`/`<style>`
5068
5433
  // content as [`tagEnd`, `contentEnd`] without a `Text` node.
5069
- _hCEnd[el] = pos.tagEnd;
5434
+ _nodeContentEnds[el] = pos.tagEnd;
5070
5435
  }
5071
5436
  return el;
5072
5437
  };
@@ -5078,7 +5443,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5078
5443
  * @returns {HtmlNodeRef} effective container to link children into
5079
5444
  */
5080
5445
  const effParent = (parent) => {
5081
- const tc = _hTc[parent];
5446
+ const tc = _nodeTemplateContents[parent];
5082
5447
  return tc !== 0 ? tc : parent;
5083
5448
  };
5084
5449
 
@@ -5087,17 +5452,17 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5087
5452
  /** @type {HtmlNodeRef} */ node
5088
5453
  ) => {
5089
5454
  const p = effParent(parent);
5090
- const last = _hLast[p];
5455
+ const last = _nodeLastChildren[p];
5091
5456
  if (
5092
- _hTy[node] === NodeType.Text &&
5457
+ _nodeTypes[node] === NodeType.Text &&
5093
5458
  last !== 0 &&
5094
- _hTy[last] === NodeType.Text
5459
+ _nodeTypes[last] === NodeType.Text
5095
5460
  ) {
5096
- _hStr[last] += _hStr[node];
5097
- _hEn[last] = _hEn[node];
5461
+ _nodeStrings[last] += _nodeStrings[node];
5462
+ _nodeEnds[last] = _nodeEnds[node];
5098
5463
  return;
5099
5464
  }
5100
- _hAppend(p, node);
5465
+ _appendChild(p, node);
5101
5466
  };
5102
5467
 
5103
5468
  // Reused result of `appropriatePlace` — consumed synchronously by
@@ -5119,23 +5484,23 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5119
5484
  const target = cur();
5120
5485
  if (
5121
5486
  fosterParenting &&
5122
- TABLE_CONTEXT.has(_tag(target)) &&
5123
- _ns(target) === NS_HTML
5487
+ TABLE_CONTEXT.has(_tagNameOf(target)) &&
5488
+ _namespaceOf(target) === NS_HTML
5124
5489
  ) {
5125
5490
  // find last template / last table
5126
5491
  let lastTemplate = -1;
5127
5492
  let lastTable = -1;
5128
5493
  for (let i = open.length - 1; i >= 0; i--) {
5129
5494
  if (
5130
- _tag(open[i]) === "template" &&
5131
- _ns(open[i]) === NS_HTML &&
5495
+ _tagNameOf(open[i]) === "template" &&
5496
+ _namespaceOf(open[i]) === NS_HTML &&
5132
5497
  lastTemplate === -1
5133
5498
  ) {
5134
5499
  lastTemplate = i;
5135
5500
  }
5136
5501
  if (
5137
- _tag(open[i]) === "table" &&
5138
- _ns(open[i]) === NS_HTML &&
5502
+ _tagNameOf(open[i]) === "table" &&
5503
+ _namespaceOf(open[i]) === NS_HTML &&
5139
5504
  lastTable === -1
5140
5505
  ) {
5141
5506
  lastTable = i;
@@ -5151,7 +5516,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5151
5516
  return placeAt(open[0], 0);
5152
5517
  }
5153
5518
  const table = open[lastTable];
5154
- const tp = _hParent[table];
5519
+ const tp = _nodeParents[table];
5155
5520
  if (tp !== 0) return placeAt(tp, table);
5156
5521
  return placeAt(open[lastTable - 1], 0);
5157
5522
  }
@@ -5168,10 +5533,10 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5168
5533
  // Find `before`'s previous sibling (insert-before is a rare foster/
5169
5534
  // adoption path, so the sibling scan stays off the hot path).
5170
5535
  let prev = 0;
5171
- let c = _hFirst[p];
5536
+ let c = _nodeFirstChildren[p];
5172
5537
  while (c !== 0 && c !== before) {
5173
5538
  prev = c;
5174
- c = _hNext[c];
5539
+ c = _nodeNextSiblings[c];
5175
5540
  }
5176
5541
  if (c === 0) {
5177
5542
  // `before` not under `parent` (not reachable from the spec paths).
@@ -5179,18 +5544,18 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5179
5544
  return;
5180
5545
  }
5181
5546
  if (
5182
- _hTy[node] === NodeType.Text &&
5547
+ _nodeTypes[node] === NodeType.Text &&
5183
5548
  prev !== 0 &&
5184
- _hTy[prev] === NodeType.Text
5549
+ _nodeTypes[prev] === NodeType.Text
5185
5550
  ) {
5186
5551
  // Before-node merge deliberately does not bump the sibling's `end`.
5187
- _hStr[prev] += _hStr[node];
5552
+ _nodeStrings[prev] += _nodeStrings[node];
5188
5553
  return;
5189
5554
  }
5190
- _hParent[node] = p;
5191
- _hNext[node] = before;
5192
- if (prev === 0) _hFirst[p] = node;
5193
- else _hNext[prev] = node;
5555
+ _nodeParents[node] = p;
5556
+ _nodeNextSiblings[node] = before;
5557
+ if (prev === 0) _nodeFirstChildren[p] = node;
5558
+ else _nodeNextSiblings[prev] = node;
5194
5559
  } else {
5195
5560
  appendTo(place.parent, node);
5196
5561
  }
@@ -5202,7 +5567,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5202
5567
  /** @type {number} */ end
5203
5568
  ) => {
5204
5569
  const place = appropriatePlace();
5205
- if (_hTy[place.parent] === NodeType.Document) return; // never insert text into document
5570
+ if (_nodeTypes[place.parent] === NodeType.Document) return; // never insert text into document
5206
5571
  // `skip.text`: drop every `Text` node — construction already used the
5207
5572
  // decoded token, so removing the node never affects element structure.
5208
5573
  // For a raw-text element record the body end so a consumer reads the span
@@ -5211,8 +5576,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5211
5576
  // foreign content (e.g. SVG `<style>`) too.
5212
5577
  if (skipText) {
5213
5578
  const p = place.parent;
5214
- if (_hTy[p] === NodeType.Element && RAW_TEXT_ELEMENTS.has(_tag(p))) {
5215
- _hCEnd[p] = end;
5579
+ if (
5580
+ _nodeTypes[p] === NodeType.Element &&
5581
+ RAW_TEXT_ELEMENTS.has(_tagNameOf(p))
5582
+ ) {
5583
+ _nodeContentEnds[p] = end;
5216
5584
  }
5217
5585
  return;
5218
5586
  }
@@ -5224,33 +5592,33 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5224
5592
  const before = place.beforeNode;
5225
5593
  if (before !== 0) {
5226
5594
  let prev = 0;
5227
- let c = _hFirst[p];
5595
+ let c = _nodeFirstChildren[p];
5228
5596
  while (c !== 0 && c !== before) {
5229
5597
  prev = c;
5230
- c = _hNext[c];
5598
+ c = _nodeNextSiblings[c];
5231
5599
  }
5232
5600
  if (c === 0) {
5233
5601
  // `before` not under `parent` (not reachable from the spec paths).
5234
- _hAppend(p, _mkText(data, start, end));
5602
+ _appendChild(p, _makeTextNode(data, start, end));
5235
5603
  return;
5236
5604
  }
5237
- if (prev !== 0 && _hTy[prev] === NodeType.Text) {
5238
- _hStr[prev] += data;
5605
+ if (prev !== 0 && _nodeTypes[prev] === NodeType.Text) {
5606
+ _nodeStrings[prev] += data;
5239
5607
  return;
5240
5608
  }
5241
- const node = _mkText(data, start, end);
5242
- _hParent[node] = p;
5243
- _hNext[node] = before;
5244
- if (prev === 0) _hFirst[p] = node;
5245
- else _hNext[prev] = node;
5609
+ const node = _makeTextNode(data, start, end);
5610
+ _nodeParents[node] = p;
5611
+ _nodeNextSiblings[node] = before;
5612
+ if (prev === 0) _nodeFirstChildren[p] = node;
5613
+ else _nodeNextSiblings[prev] = node;
5246
5614
  } else {
5247
- const last = _hLast[p];
5248
- if (last !== 0 && _hTy[last] === NodeType.Text) {
5249
- _hStr[last] += data;
5250
- _hEn[last] = end;
5615
+ const last = _nodeLastChildren[p];
5616
+ if (last !== 0 && _nodeTypes[last] === NodeType.Text) {
5617
+ _nodeStrings[last] += data;
5618
+ _nodeEnds[last] = end;
5251
5619
  return;
5252
5620
  }
5253
- _hAppend(p, _mkText(data, start, end));
5621
+ _appendChild(p, _makeTextNode(data, start, end));
5254
5622
  }
5255
5623
  };
5256
5624
 
@@ -5263,7 +5631,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5263
5631
  const insertComment = (data, start, end, place) => {
5264
5632
  if (skipComments) return;
5265
5633
  const p = place || appropriatePlace();
5266
- insertAtPlace(p, _mkComment(data, start, end));
5634
+ insertAtPlace(p, _makeCommentNode(data, start, end));
5267
5635
  };
5268
5636
 
5269
5637
  const insertHtmlElement = (
@@ -5293,10 +5661,12 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5293
5661
 
5294
5662
  // ---- scopes ----
5295
5663
  const isScopeBoundary = (/** @type {HtmlElement} */ el) => {
5296
- if (_ns(el) === NS_HTML) return HTML_SCOPE.has(_tag(el));
5297
- if (_ns(el) === NS_MATHML) return MATHML_SPECIAL.has(_tag(el));
5298
- if (_ns(el) === NS_SVG) {
5299
- return SVG_SPECIAL.has(_tag(el).toLowerCase());
5664
+ if (_namespaceOf(el) === NS_HTML) return HTML_SCOPE.has(_tagNameOf(el));
5665
+ if (_namespaceOf(el) === NS_MATHML) {
5666
+ return MATHML_SPECIAL.has(_tagNameOf(el));
5667
+ }
5668
+ if (_namespaceOf(el) === NS_SVG) {
5669
+ return SVG_SPECIAL.has(_tagNameOf(el).toLowerCase());
5300
5670
  }
5301
5671
  return false;
5302
5672
  };
@@ -5311,10 +5681,10 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5311
5681
  /** @type {number} */ kind
5312
5682
  ) => {
5313
5683
  if (isScopeBoundary(el)) return true;
5314
- if (_ns(el) !== NS_HTML) return false;
5315
- if (kind === SCOPE_BUTTON) return _tag(el) === "button";
5684
+ if (_namespaceOf(el) !== NS_HTML) return false;
5685
+ if (kind === SCOPE_BUTTON) return _tagNameOf(el) === "button";
5316
5686
  if (kind === SCOPE_LIST_ITEM) {
5317
- return _tag(el) === "ol" || _tag(el) === "ul";
5687
+ return _tagNameOf(el) === "ol" || _tagNameOf(el) === "ul";
5318
5688
  }
5319
5689
  return false;
5320
5690
  };
@@ -5326,7 +5696,9 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5326
5696
  ) => {
5327
5697
  for (let i = open.length - 1; i >= 0; i--) {
5328
5698
  const el = open[i];
5329
- if (_ns(el) === NS_HTML && _tag(el) === tagName) return true;
5699
+ if (_namespaceOf(el) === NS_HTML && _tagNameOf(el) === tagName) {
5700
+ return true;
5701
+ }
5330
5702
  if (isBoundaryForKind(el, kind)) return false;
5331
5703
  }
5332
5704
  return false;
@@ -5350,9 +5722,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5350
5722
  const set = typeof target === "string" ? null : target;
5351
5723
  for (let i = open.length - 1; i >= 0; i--) {
5352
5724
  const el = open[i];
5353
- if (_ns(el) === NS_HTML) {
5354
- if (set ? set.has(_tag(el)) : _tag(el) === target) return true;
5355
- if (TABLE_SCOPE_STOP.has(_tag(el))) return false;
5725
+ if (_namespaceOf(el) === NS_HTML) {
5726
+ if (set ? set.has(_tagNameOf(el)) : _tagNameOf(el) === target) {
5727
+ return true;
5728
+ }
5729
+ if (TABLE_SCOPE_STOP.has(_tagNameOf(el))) return false;
5356
5730
  }
5357
5731
  }
5358
5732
  return false;
@@ -5360,7 +5734,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5360
5734
  const generateImpliedEndTags = (except = "") => {
5361
5735
  while (open.length) {
5362
5736
  const el = cur();
5363
- if (_ns(el) === NS_HTML && IMPLIED.has(_tag(el)) && _tag(el) !== except) {
5737
+ if (
5738
+ _namespaceOf(el) === NS_HTML &&
5739
+ IMPLIED.has(_tagNameOf(el)) &&
5740
+ _tagNameOf(el) !== except
5741
+ ) {
5364
5742
  open.pop();
5365
5743
  } else {
5366
5744
  break;
@@ -5370,7 +5748,10 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5370
5748
  const generateImpliedEndTagsThorough = () => {
5371
5749
  while (open.length) {
5372
5750
  const el = cur();
5373
- if (_ns(el) === NS_HTML && IMPLIED_THOROUGH.has(_tag(el))) {
5751
+ if (
5752
+ _namespaceOf(el) === NS_HTML &&
5753
+ IMPLIED_THOROUGH.has(_tagNameOf(el))
5754
+ ) {
5374
5755
  open.pop();
5375
5756
  } else {
5376
5757
  break;
@@ -5384,7 +5765,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5384
5765
  for (let i = afe.length - 1; i >= 0; i--) {
5385
5766
  const e = afe[i];
5386
5767
  if (e === AFE_MARKER) break;
5387
- if (_tag(e) === _tag(el) && _ns(e) === _ns(el) && sameAttrs(e, el)) {
5768
+ if (
5769
+ _tagNameOf(e) === _tagNameOf(el) &&
5770
+ _namespaceOf(e) === _namespaceOf(el) &&
5771
+ sameAttrs(e, el)
5772
+ ) {
5388
5773
  count++;
5389
5774
  if (count === 3) {
5390
5775
  afe.splice(i, 1);
@@ -5398,16 +5783,16 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5398
5783
  /** @type {HtmlElement} */ a,
5399
5784
  /** @type {HtmlElement} */ b
5400
5785
  ) => {
5401
- const aStart = _hAStart[a];
5402
- const aCount = _hACount[a];
5403
- const bStart = _hAStart[b];
5404
- const bCount = _hACount[b];
5786
+ const aStart = _nodeAttrStarts[a];
5787
+ const aCount = _nodeAttrCounts[a];
5788
+ const bStart = _nodeAttrStarts[b];
5789
+ const bCount = _nodeAttrCounts[b];
5405
5790
  if (aCount !== bCount) return false;
5406
5791
  // Names are unique (deduped), counts tiny — a nested scan beats a Map
5407
5792
  // here on this formatting-element hot path.
5408
5793
  for (let i = bStart; i < bStart + bCount; i++) {
5409
- const j = _aFind(aStart, aCount, _aName[i]);
5410
- if (j === 0 || _aValueOf(j) !== _aValueOf(i)) return false;
5794
+ const j = _findAttr(aStart, aCount, _attrNames[i]);
5795
+ if (j === 0 || _attrValueOf(j) !== _attrValueOf(i)) return false;
5411
5796
  }
5412
5797
  return true;
5413
5798
  };
@@ -5430,7 +5815,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5430
5815
  }
5431
5816
  for (; i < afe.length; i++) {
5432
5817
  const e = afe[i];
5433
- const el = mkEl(_tag(e), _ns(e), cloneAttrs(e), null);
5818
+ const el = mkEl(_tagNameOf(e), _namespaceOf(e), cloneAttrs(e), null);
5434
5819
  const place = appropriatePlace();
5435
5820
  insertAtPlace(place, el);
5436
5821
  open.push(el);
@@ -5444,23 +5829,23 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5444
5829
  // pop until a p has been popped
5445
5830
  while (open.length) {
5446
5831
  const el = /** @type {HtmlElement} */ (open.pop());
5447
- _hEn[el] = tokenEnd;
5448
- if (_ns(el) === NS_HTML && _tag(el) === "p") break;
5832
+ _nodeEnds[el] = tokenEnd;
5833
+ if (_namespaceOf(el) === NS_HTML && _tagNameOf(el) === "p") break;
5449
5834
  }
5450
5835
  };
5451
5836
 
5452
5837
  const popUntil = (/** @type {string} */ tagName) => {
5453
5838
  while (open.length) {
5454
5839
  const el = /** @type {HtmlElement} */ (open.pop());
5455
- _hEn[el] = tokenEnd;
5456
- if (_ns(el) === NS_HTML && _tag(el) === tagName) break;
5840
+ _nodeEnds[el] = tokenEnd;
5841
+ if (_namespaceOf(el) === NS_HTML && _tagNameOf(el) === tagName) break;
5457
5842
  }
5458
5843
  };
5459
5844
  const popUntilOneOf = (/** @type {Set<string>} */ set) => {
5460
5845
  while (open.length) {
5461
5846
  const el = /** @type {HtmlElement} */ (open.pop());
5462
- _hEn[el] = tokenEnd;
5463
- if (_ns(el) === NS_HTML && set.has(_tag(el))) break;
5847
+ _nodeEnds[el] = tokenEnd;
5848
+ if (_namespaceOf(el) === NS_HTML && set.has(_tagNameOf(el))) break;
5464
5849
  }
5465
5850
  };
5466
5851
 
@@ -5473,8 +5858,8 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5473
5858
  last = true;
5474
5859
  if (fragment) node = fragment;
5475
5860
  }
5476
- const tn = _tag(node);
5477
- if (_ns(node) === NS_HTML) {
5861
+ const tn = _tagNameOf(node);
5862
+ if (_namespaceOf(node) === NS_HTML) {
5478
5863
  if ((tn === "td" || tn === "th") && !last) {
5479
5864
  mode = MODE_IN_CELL;
5480
5865
  return;
@@ -5567,7 +5952,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5567
5952
  const useForeign =
5568
5953
  open.length > 0 &&
5569
5954
  ac &&
5570
- _ns(ac) !== NS_HTML &&
5955
+ _namespaceOf(ac) !== NS_HTML &&
5571
5956
  ty !== TOKEN_EOF &&
5572
5957
  shouldUseForeignRules(ac, t);
5573
5958
  if (useForeign) {
@@ -5581,7 +5966,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5581
5966
  /** @type {HtmlElement} */ ac,
5582
5967
  /** @type {Token} */ t
5583
5968
  ) => {
5584
- if (_ns(ac) === NS_HTML) return false;
5969
+ if (_namespaceOf(ac) === NS_HTML) return false;
5585
5970
  if (t.type === TOKEN_START_TAG) {
5586
5971
  if (
5587
5972
  mathmlTextIntegrationPoint(ac) &&
@@ -5591,8 +5976,8 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5591
5976
  return false;
5592
5977
  }
5593
5978
  if (
5594
- _ns(ac) === NS_MATHML &&
5595
- _tag(ac) === "annotation-xml" &&
5979
+ _namespaceOf(ac) === NS_MATHML &&
5980
+ _tagNameOf(ac) === "annotation-xml" &&
5596
5981
  t.name === "svg"
5597
5982
  ) {
5598
5983
  return false;
@@ -5611,19 +5996,27 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5611
5996
  };
5612
5997
 
5613
5998
  const mathmlTextIntegrationPoint = (/** @type {HtmlElement} */ el) =>
5614
- _ns(el) === NS_MATHML && MATHML_TEXT_INTEGRATION.has(_tag(el));
5999
+ _namespaceOf(el) === NS_MATHML &&
6000
+ MATHML_TEXT_INTEGRATION.has(_tagNameOf(el));
5615
6001
  const htmlIntegrationPoint = (/** @type {HtmlElement} */ el) => {
5616
- if (_ns(el) === NS_MATHML && _tag(el) === "annotation-xml") {
5617
- const enc = _aFind(_hAStart[el], _hACount[el], "encoding");
6002
+ if (_namespaceOf(el) === NS_MATHML && _tagNameOf(el) === "annotation-xml") {
6003
+ const enc = _findAttr(
6004
+ _nodeAttrStarts[el],
6005
+ _nodeAttrCounts[el],
6006
+ "encoding"
6007
+ );
5618
6008
  if (enc !== 0) {
5619
- const value = _aValueOf(enc).toLowerCase();
6009
+ const value = _attrValueOf(enc).toLowerCase();
5620
6010
  if (value === "text/html" || value === "application/xhtml+xml") {
5621
6011
  return true;
5622
6012
  }
5623
6013
  }
5624
6014
  return false;
5625
6015
  }
5626
- if (_ns(el) === NS_SVG && SVG_SPECIAL.has(_tag(el).toLowerCase())) {
6016
+ if (
6017
+ _namespaceOf(el) === NS_SVG &&
6018
+ SVG_SPECIAL.has(_tagNameOf(el).toLowerCase())
6019
+ ) {
5627
6020
  return true;
5628
6021
  }
5629
6022
  return false;
@@ -5633,23 +6026,23 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5633
6026
  /** @type {Record<string, string>} */ (SVG_TAG_ADJUST)[name] || name;
5634
6027
  // Adjust a foreign start tag's attribute run in place (the run is consumed
5635
6028
  // only by this tag's element): SVG camelCase names are rewritten, and
5636
- // namespaced names get the serializer-name flag (see `_aSerializedName`).
6029
+ // namespaced names get the serializer-name flag (see `_attrSerializedName`).
5637
6030
  const adjustForeignAttrs = (
5638
6031
  /** @type {AttributeRun} */ run,
5639
6032
  /** @type {number} */ ns
5640
6033
  ) => {
5641
6034
  for (let i = run.start; i < run.start + run.count; i++) {
5642
- const name = _aName[i];
6035
+ const name = _attrNames[i];
5643
6036
  if (
5644
6037
  ns === NS_SVG &&
5645
6038
  /** @type {Record<string, string>} */ (SVG_ATTR_ADJUST)[name]
5646
6039
  ) {
5647
- _aName[i] = /** @type {Record<string, string>} */ (SVG_ATTR_ADJUST)[
6040
+ _attrNames[i] = /** @type {Record<string, string>} */ (SVG_ATTR_ADJUST)[
5648
6041
  name
5649
6042
  ];
5650
6043
  }
5651
6044
  if (/** @type {Record<string, string>} */ (FOREIGN_ATTR_NS)[name]) {
5652
- _aFl[i] |= 1;
6045
+ _attrFlags[i] |= 1;
5653
6046
  }
5654
6047
  }
5655
6048
  return run;
@@ -5669,7 +6062,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5669
6062
  }
5670
6063
  if (t.type === TOKEN_DOCTYPE) return;
5671
6064
  if (t.type === TOKEN_START_TAG) {
5672
- const acn = _ns(adjustedCurrent());
6065
+ const acn = _namespaceOf(adjustedCurrent());
5673
6066
  if (
5674
6067
  FOREIGN_BREAKOUT.has(t.name) ||
5675
6068
  (t.name === "font" && hasFontBreakoutAttr(t.attrs))
@@ -5678,7 +6071,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5678
6071
  while (open.length > 1) {
5679
6072
  const c = cur();
5680
6073
  if (
5681
- _ns(c) === NS_HTML ||
6074
+ _namespaceOf(c) === NS_HTML ||
5682
6075
  mathmlTextIntegrationPoint(c) ||
5683
6076
  htmlIntegrationPoint(c)
5684
6077
  ) {
@@ -5708,8 +6101,8 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5708
6101
  if (t.type === TOKEN_END_TAG) {
5709
6102
  if (
5710
6103
  t.name === "script" &&
5711
- _tag(cur()) === "script" &&
5712
- _ns(cur()) === NS_SVG
6104
+ _tagNameOf(cur()) === "script" &&
6105
+ _namespaceOf(cur()) === NS_SVG
5713
6106
  ) {
5714
6107
  open.pop();
5715
6108
  return;
@@ -5719,7 +6112,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5719
6112
  if (t.name === "p" || t.name === "br") {
5720
6113
  while (
5721
6114
  open.length > 1 &&
5722
- _ns(cur()) !== NS_HTML &&
6115
+ _namespaceOf(cur()) !== NS_HTML &&
5723
6116
  !mathmlTextIntegrationPoint(cur()) &&
5724
6117
  !htmlIntegrationPoint(cur())
5725
6118
  ) {
@@ -5731,18 +6124,21 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5731
6124
  // any other end tag
5732
6125
  let i = open.length - 1;
5733
6126
  let node = open[i];
5734
- if (_tag(node).toLowerCase() !== t.name) {
6127
+ if (_tagNameOf(node).toLowerCase() !== t.name) {
5735
6128
  /* parse error */
5736
6129
  }
5737
6130
  while (i >= 0) {
5738
6131
  node = open[i];
5739
6132
  if (i === 0) return;
5740
- if (_ns(node) !== NS_HTML && _tag(node).toLowerCase() === t.name) {
6133
+ if (
6134
+ _namespaceOf(node) !== NS_HTML &&
6135
+ _tagNameOf(node).toLowerCase() === t.name
6136
+ ) {
5741
6137
  while (open.length > i) open.pop();
5742
6138
  return;
5743
6139
  }
5744
6140
  i--;
5745
- if (open[i] && _ns(open[i]) === NS_HTML) {
6141
+ if (open[i] && _namespaceOf(open[i]) === NS_HTML) {
5746
6142
  runMode(t);
5747
6143
  return;
5748
6144
  }
@@ -5757,7 +6153,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5757
6153
  ) => {
5758
6154
  // step 1
5759
6155
  const c = cur();
5760
- if (_ns(c) === NS_HTML && _tag(c) === subject && !afe.includes(c)) {
6156
+ if (
6157
+ _namespaceOf(c) === NS_HTML &&
6158
+ _tagNameOf(c) === subject &&
6159
+ !afe.includes(c)
6160
+ ) {
5761
6161
  open.pop();
5762
6162
  return true;
5763
6163
  }
@@ -5768,7 +6168,10 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5768
6168
  let fmtIdx = -1;
5769
6169
  for (let i = afe.length - 1; i >= 0; i--) {
5770
6170
  if (afe[i] === AFE_MARKER) break;
5771
- if (_tag(afe[i]) === subject && _ns(afe[i]) === NS_HTML) {
6171
+ if (
6172
+ _tagNameOf(afe[i]) === subject &&
6173
+ _namespaceOf(afe[i]) === NS_HTML
6174
+ ) {
5772
6175
  fmtIdx = i;
5773
6176
  break;
5774
6177
  }
@@ -5817,7 +6220,12 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5817
6220
  continue;
5818
6221
  }
5819
6222
  // create clone
5820
- const clone = mkEl(_tag(node), _ns(node), cloneAttrs(node), null);
6223
+ const clone = mkEl(
6224
+ _tagNameOf(node),
6225
+ _namespaceOf(node),
6226
+ cloneAttrs(node),
6227
+ null
6228
+ );
5821
6229
  afe[nodeAfeIdx] = clone;
5822
6230
  open[nodeIdx] = clone;
5823
6231
  node = clone;
@@ -5832,16 +6240,21 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5832
6240
  const place = placeForCommonAncestor(commonAncestor);
5833
6241
  insertAtPlace(place, lastNode);
5834
6242
  // create element for fmt token, take children of furthest
5835
- const cloneFmt = mkEl(_tag(fmt), _ns(fmt), cloneAttrs(fmt), null);
6243
+ const cloneFmt = mkEl(
6244
+ _tagNameOf(fmt),
6245
+ _namespaceOf(fmt),
6246
+ cloneAttrs(fmt),
6247
+ null
6248
+ );
5836
6249
  // Take all direct children of `furthest` (a template's content fragment
5837
6250
  // deliberately stays put — mirrors childrenOf-less spec behavior here).
5838
- let k = _hFirst[furthest];
5839
- _hFirst[furthest] = 0;
5840
- _hLast[furthest] = 0;
6251
+ let k = _nodeFirstChildren[furthest];
6252
+ _nodeFirstChildren[furthest] = 0;
6253
+ _nodeLastChildren[furthest] = 0;
5841
6254
  while (k !== 0) {
5842
- const next = _hNext[k];
5843
- _hNext[k] = 0;
5844
- _hParent[k] = 0;
6255
+ const next = _nodeNextSiblings[k];
6256
+ _nodeNextSiblings[k] = 0;
6257
+ _nodeParents[k] = 0;
5845
6258
  appendTo(cloneFmt, k);
5846
6259
  k = next;
5847
6260
  }
@@ -5866,8 +6279,8 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5866
6279
  /** @type {HtmlElement} */ commonAncestor
5867
6280
  ) => {
5868
6281
  if (
5869
- TABLE_CONTEXT.has(_tag(commonAncestor)) &&
5870
- _ns(commonAncestor) === NS_HTML
6282
+ TABLE_CONTEXT.has(_tagNameOf(commonAncestor)) &&
6283
+ _namespaceOf(commonAncestor) === NS_HTML
5871
6284
  ) {
5872
6285
  // foster
5873
6286
  // reuse appropriatePlace logic but rooted differently: emulate
@@ -5875,15 +6288,15 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5875
6288
  let lastTable = -1;
5876
6289
  for (let i = open.length - 1; i >= 0; i--) {
5877
6290
  if (
5878
- _tag(open[i]) === "template" &&
5879
- _ns(open[i]) === NS_HTML &&
6291
+ _tagNameOf(open[i]) === "template" &&
6292
+ _namespaceOf(open[i]) === NS_HTML &&
5880
6293
  lastTemplate === -1
5881
6294
  ) {
5882
6295
  lastTemplate = i;
5883
6296
  }
5884
6297
  if (
5885
- _tag(open[i]) === "table" &&
5886
- _ns(open[i]) === NS_HTML &&
6298
+ _tagNameOf(open[i]) === "table" &&
6299
+ _namespaceOf(open[i]) === NS_HTML &&
5887
6300
  lastTable === -1
5888
6301
  ) {
5889
6302
  lastTable = i;
@@ -5897,7 +6310,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5897
6310
  }
5898
6311
  if (lastTable === -1) return { parent: open[0], beforeNode: 0 };
5899
6312
  const table = open[lastTable];
5900
- const tp = _hParent[table];
6313
+ const tp = _nodeParents[table];
5901
6314
  if (tp !== 0) return { parent: tp, beforeNode: table };
5902
6315
  return { parent: open[lastTable - 1], beforeNode: 0 };
5903
6316
  }
@@ -5905,20 +6318,20 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
5905
6318
  };
5906
6319
 
5907
6320
  const detach = (/** @type {HtmlNodeRef} */ node) => {
5908
- const p = _hParent[node];
6321
+ const p = _nodeParents[node];
5909
6322
  if (p === 0) return;
5910
6323
  let prev = 0;
5911
- let c = _hFirst[p];
6324
+ let c = _nodeFirstChildren[p];
5912
6325
  while (c !== 0 && c !== node) {
5913
6326
  prev = c;
5914
- c = _hNext[c];
6327
+ c = _nodeNextSiblings[c];
5915
6328
  }
5916
6329
  if (c === 0) return;
5917
- if (prev === 0) _hFirst[p] = _hNext[node];
5918
- else _hNext[prev] = _hNext[node];
5919
- if (_hLast[p] === node) _hLast[p] = prev;
5920
- _hNext[node] = 0;
5921
- _hParent[node] = 0;
6330
+ if (prev === 0) _nodeFirstChildren[p] = _nodeNextSiblings[node];
6331
+ else _nodeNextSiblings[prev] = _nodeNextSiblings[node];
6332
+ if (_nodeLastChildren[p] === node) _nodeLastChildren[p] = prev;
6333
+ _nodeNextSiblings[node] = 0;
6334
+ _nodeParents[node] = 0;
5922
6335
  };
5923
6336
 
5924
6337
  // ---------- insertion modes ----------
@@ -6082,13 +6495,13 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6082
6495
  if (t.type === TOKEN_DOCTYPE) {
6083
6496
  // `skip.doctype` drops the node only; quirks detection below is unaffected.
6084
6497
  if (!skipDoctype) {
6085
- const dt = _hAlloc(NodeType.Doctype, t.start, t.end);
6086
- _hStr[dt] = t.name;
6498
+ const dt = _allocNode(NodeType.Doctype, t.start, t.end);
6499
+ _nodeStrings[dt] = t.name;
6087
6500
  // At most one doctype node is ever inserted (later doctype tokens
6088
6501
  // are ignored), so its ids live in two per-parse scalars.
6089
- _hDocPub = t.publicId;
6090
- _hDocSys = t.systemId;
6091
- _hAppend(doc, dt);
6502
+ _doctypePublicId = t.publicId;
6503
+ _doctypeSystemId = t.systemId;
6504
+ _appendChild(doc, dt);
6092
6505
  }
6093
6506
  quirks = isQuirky(t.name, t.publicId, t.systemId);
6094
6507
  mode = MODE_BEFORE_HTML;
@@ -6112,7 +6525,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6112
6525
  }
6113
6526
  if (t.type === TOKEN_START_TAG && t.name === "html") {
6114
6527
  const el = mkEl("html", NS_HTML, t.attrs, t.pos);
6115
- _hAppend(doc, el);
6528
+ _appendChild(doc, el);
6116
6529
  open.push(el);
6117
6530
  mode = MODE_BEFORE_HEAD;
6118
6531
  return;
@@ -6121,7 +6534,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6121
6534
  return;
6122
6535
  }
6123
6536
  const el = mkEl("html", NS_HTML, EMPTY_ATTRS, null);
6124
- _hAppend(doc, el);
6537
+ _appendChild(doc, el);
6125
6538
  open.push(el);
6126
6539
  mode = MODE_BEFORE_HEAD;
6127
6540
  process(t);
@@ -6197,10 +6610,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6197
6610
  mode = MODE_IN_TEMPLATE;
6198
6611
  templateModes.push(MODE_IN_TEMPLATE);
6199
6612
  const el = cur();
6200
- const fragment = _hAlloc(NodeType.DocumentFragment, 0, 0);
6613
+ const fragment = _allocNode(NodeType.DocumentFragment, 0, 0);
6614
+ _nodeStrings[fragment] = "";
6201
6615
  // Parent link so the iterative walk can ascend out of the content.
6202
- _hParent[fragment] = el;
6203
- _hTc[el] = fragment;
6616
+ _nodeParents[fragment] = el;
6617
+ _nodeTemplateContents[el] = fragment;
6204
6618
  return;
6205
6619
  }
6206
6620
  if (t.name === "head") return;
@@ -6343,10 +6757,10 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6343
6757
  const anyOtherEndTag = (/** @type {string} */ name) => {
6344
6758
  for (let i = open.length - 1; i >= 0; i--) {
6345
6759
  const node = open[i];
6346
- if (_ns(node) === NS_HTML && _tag(node) === name) {
6760
+ if (_namespaceOf(node) === NS_HTML && _tagNameOf(node) === name) {
6347
6761
  generateImpliedEndTags(name);
6348
6762
  while (open.length > i) {
6349
- _hEn[open[open.length - 1]] = tokenEnd;
6763
+ _nodeEnds[open[open.length - 1]] = tokenEnd;
6350
6764
  open.pop();
6351
6765
  }
6352
6766
  return;
@@ -6369,7 +6783,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6369
6783
  }
6370
6784
  if (name === "body") {
6371
6785
  const second = open[1];
6372
- if (!second || _tag(second) !== "body" || open.some(isHtmlTemplateEl)) {
6786
+ if (
6787
+ !second ||
6788
+ _tagNameOf(second) !== "body" ||
6789
+ open.some(isHtmlTemplateEl)
6790
+ ) {
6373
6791
  return;
6374
6792
  }
6375
6793
  framesetOk = false;
@@ -6378,7 +6796,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6378
6796
  }
6379
6797
  if (name === "frameset") {
6380
6798
  const second = open[1];
6381
- if (!second || _tag(second) !== "body") return;
6799
+ if (!second || _tagNameOf(second) !== "body") return;
6382
6800
  if (!framesetOk) return;
6383
6801
  detach(second);
6384
6802
  while (open.length > 1) open.pop();
@@ -6393,7 +6811,9 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6393
6811
  }
6394
6812
  if (HEADING.has(name)) {
6395
6813
  closeIfPInButtonScope();
6396
- if (_ns(cur()) === NS_HTML && HEADING.has(_tag(cur()))) open.pop();
6814
+ if (_namespaceOf(cur()) === NS_HTML && HEADING.has(_tagNameOf(cur()))) {
6815
+ open.pop();
6816
+ }
6397
6817
  insertHtmlElement(name, t.attrs, t.pos);
6398
6818
  return;
6399
6819
  }
@@ -6419,14 +6839,17 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6419
6839
  framesetOk = false;
6420
6840
  for (let i = open.length - 1; i >= 0; i--) {
6421
6841
  const node = open[i];
6422
- if (_ns(node) === NS_HTML && _tag(node) === "li") {
6842
+ if (_namespaceOf(node) === NS_HTML && _tagNameOf(node) === "li") {
6423
6843
  generateImpliedEndTags("li");
6424
6844
  popUntil("li");
6425
6845
  break;
6426
6846
  }
6427
6847
  if (
6428
6848
  isSpecial(node) &&
6429
- !(_ns(node) === NS_HTML && ADDRESS_DIV_P.has(_tag(node)))
6849
+ !(
6850
+ _namespaceOf(node) === NS_HTML &&
6851
+ ADDRESS_DIV_P.has(_tagNameOf(node))
6852
+ )
6430
6853
  ) {
6431
6854
  break;
6432
6855
  }
@@ -6440,16 +6863,19 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6440
6863
  for (let i = open.length - 1; i >= 0; i--) {
6441
6864
  const node = open[i];
6442
6865
  if (
6443
- _ns(node) === NS_HTML &&
6444
- (_tag(node) === "dd" || _tag(node) === "dt")
6866
+ _namespaceOf(node) === NS_HTML &&
6867
+ (_tagNameOf(node) === "dd" || _tagNameOf(node) === "dt")
6445
6868
  ) {
6446
- generateImpliedEndTags(_tag(node));
6447
- popUntil(_tag(node));
6869
+ generateImpliedEndTags(_tagNameOf(node));
6870
+ popUntil(_tagNameOf(node));
6448
6871
  break;
6449
6872
  }
6450
6873
  if (
6451
6874
  isSpecial(node) &&
6452
- !(_ns(node) === NS_HTML && ADDRESS_DIV_P.has(_tag(node)))
6875
+ !(
6876
+ _namespaceOf(node) === NS_HTML &&
6877
+ ADDRESS_DIV_P.has(_tagNameOf(node))
6878
+ )
6453
6879
  ) {
6454
6880
  break;
6455
6881
  }
@@ -6477,9 +6903,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6477
6903
  // if there's an <a> in afe after last marker
6478
6904
  for (let i = afe.length - 1; i >= 0; i--) {
6479
6905
  if (afe[i] === AFE_MARKER) break;
6480
- if (_tag(afe[i]) === "a") {
6906
+ if (_tagNameOf(afe[i]) === "a") {
6481
6907
  adoptionAgency("a", t.pos);
6482
- const idx = afe.findIndex((e) => e !== AFE_MARKER && _tag(e) === "a");
6908
+ const idx = afe.findIndex(
6909
+ (e) => e !== AFE_MARKER && _tagNameOf(e) === "a"
6910
+ );
6483
6911
  if (idx !== -1) {
6484
6912
  const el = afe[idx];
6485
6913
  afe.splice(idx, 1);
@@ -6541,16 +6969,16 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6541
6969
  resetInsertionMode();
6542
6970
  } else if (
6543
6971
  fragment &&
6544
- _ns(fragment) === NS_HTML &&
6545
- _tag(fragment) === "select"
6972
+ _namespaceOf(fragment) === NS_HTML &&
6973
+ _tagNameOf(fragment) === "select"
6546
6974
  ) {
6547
6975
  return;
6548
6976
  }
6549
6977
  reconstructAfe();
6550
6978
  insertHtmlElement("input", t.attrs, t.pos);
6551
6979
  open.pop();
6552
- const ty = _aFind(t.attrs.start, t.attrs.count, "type");
6553
- if (ty === 0 || _aValueOf(ty).toLowerCase() !== "hidden") {
6980
+ const ty = _findAttr(t.attrs.start, t.attrs.count, "type");
6981
+ if (ty === 0 || _attrValueOf(ty).toLowerCase() !== "hidden") {
6554
6982
  framesetOk = false;
6555
6983
  }
6556
6984
  return;
@@ -6561,8 +6989,10 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6561
6989
  return;
6562
6990
  }
6563
6991
  if (name === "hr") {
6564
- if (_ns(cur()) === NS_HTML && _tag(cur()) === "option") open.pop();
6565
- if (_ns(cur()) === NS_HTML && _tag(cur()) === "optgroup") {
6992
+ if (_namespaceOf(cur()) === NS_HTML && _tagNameOf(cur()) === "option") {
6993
+ open.pop();
6994
+ }
6995
+ if (_namespaceOf(cur()) === NS_HTML && _tagNameOf(cur()) === "optgroup") {
6566
6996
  open.pop();
6567
6997
  }
6568
6998
  closeIfPInButtonScope();
@@ -6611,11 +7041,13 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6611
7041
  return;
6612
7042
  }
6613
7043
  if (name === "optgroup" || name === "option") {
6614
- if (_ns(cur()) === NS_HTML && _tag(cur()) === "option") open.pop();
7044
+ if (_namespaceOf(cur()) === NS_HTML && _tagNameOf(cur()) === "option") {
7045
+ open.pop();
7046
+ }
6615
7047
  if (
6616
7048
  name === "optgroup" &&
6617
- _ns(cur()) === NS_HTML &&
6618
- _tag(cur()) === "optgroup"
7049
+ _namespaceOf(cur()) === NS_HTML &&
7050
+ _tagNameOf(cur()) === "optgroup"
6619
7051
  ) {
6620
7052
  open.pop();
6621
7053
  }
@@ -6659,7 +7091,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6659
7091
  // this tag's element); the camelCase name serializes as itself.
6660
7092
  const adjustMathmlAttrs = (/** @type {AttributeRun} */ run) => {
6661
7093
  for (let i = run.start; i < run.start + run.count; i++) {
6662
- if (_aName[i] === "definitionurl") _aName[i] = "definitionURL";
7094
+ if (_attrNames[i] === "definitionurl") _attrNames[i] = "definitionURL";
6663
7095
  }
6664
7096
  return run;
6665
7097
  };
@@ -6785,7 +7217,10 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6785
7217
  // current element: a non-matching name (e.g. a fragment context), or
6786
7218
  // a name that ran straight to EOF with no delimiter (`</script` at
6787
7219
  // EOF — the tokenizer still emits a partial tag there).
6788
- if (cur() && (t.name !== _tag(cur()) || t.pos.end === t.pos.nameEnd)) {
7220
+ if (
7221
+ cur() &&
7222
+ (t.name !== _tagNameOf(cur()) || t.pos.end === t.pos.nameEnd)
7223
+ ) {
6789
7224
  insertCharacters(
6790
7225
  source.slice(t.pos.start, t.pos.end),
6791
7226
  t.pos.start,
@@ -6793,7 +7228,8 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6793
7228
  );
6794
7229
  return;
6795
7230
  }
6796
- open.pop();
7231
+ // span the close tag, like every other explicit-close pop
7232
+ _nodeEnds[/** @type {HtmlElement} */ (open.pop())] = tokenEnd;
6797
7233
  mode = originalMode;
6798
7234
  }
6799
7235
  };
@@ -6805,7 +7241,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6805
7241
  modes.inTable = (t) => {
6806
7242
  if (t.type === TOKEN_CHAR) {
6807
7243
  const c = cur();
6808
- if (TABLE_CONTEXT.has(_tag(c)) && _ns(c) === NS_HTML) {
7244
+ if (TABLE_CONTEXT.has(_tagNameOf(c)) && _namespaceOf(c) === NS_HTML) {
6809
7245
  pendingTableChars = { list: [], hasNonWs: false };
6810
7246
  originalMode = mode;
6811
7247
  mode = MODE_IN_TABLE_TEXT;
@@ -6860,8 +7296,8 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6860
7296
  return modes.inHead(t);
6861
7297
  }
6862
7298
  if (name === "input") {
6863
- const ty = _aFind(t.attrs.start, t.attrs.count, "type");
6864
- if (ty !== 0 && _aValueOf(ty).toLowerCase() === "hidden") {
7299
+ const ty = _findAttr(t.attrs.start, t.attrs.count, "type");
7300
+ if (ty !== 0 && _attrValueOf(ty).toLowerCase() === "hidden") {
6865
7301
  insertHtmlElement("input", t.attrs, t.pos);
6866
7302
  open.pop();
6867
7303
  return;
@@ -6931,7 +7367,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6931
7367
  const clearStackToTableContext = () => {
6932
7368
  while (open.length) {
6933
7369
  const c = cur();
6934
- if (_ns(c) === NS_HTML && CLEAR_TABLE.has(_tag(c))) {
7370
+ if (_namespaceOf(c) === NS_HTML && CLEAR_TABLE.has(_tagNameOf(c))) {
6935
7371
  break;
6936
7372
  }
6937
7373
  open.pop();
@@ -6940,7 +7376,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6940
7376
  const clearStackToTableBodyContext = () => {
6941
7377
  while (open.length) {
6942
7378
  const c = cur();
6943
- if (_ns(c) === NS_HTML && CLEAR_TABLE_BODY.has(_tag(c))) {
7379
+ if (_namespaceOf(c) === NS_HTML && CLEAR_TABLE_BODY.has(_tagNameOf(c))) {
6944
7380
  break;
6945
7381
  }
6946
7382
  open.pop();
@@ -6949,7 +7385,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6949
7385
  const clearStackToTableRowContext = () => {
6950
7386
  while (open.length) {
6951
7387
  const c = cur();
6952
- if (_ns(c) === NS_HTML && CLEAR_TABLE_ROW.has(_tag(c))) {
7388
+ if (_namespaceOf(c) === NS_HTML && CLEAR_TABLE_ROW.has(_tagNameOf(c))) {
6953
7389
  break;
6954
7390
  }
6955
7391
  open.pop();
@@ -6982,7 +7418,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
6982
7418
  if (t.type === TOKEN_CHAR) {
6983
7419
  const r = leadingWs(t, true);
6984
7420
  if (!r) return;
6985
- if (_tag(cur()) !== "colgroup") return;
7421
+ if (_tagNameOf(cur()) !== "colgroup") return;
6986
7422
  open.pop();
6987
7423
  mode = MODE_IN_TABLE;
6988
7424
  process(r);
@@ -7000,7 +7436,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7000
7436
  return;
7001
7437
  }
7002
7438
  if (t.type === TOKEN_END_TAG && t.name === "colgroup") {
7003
- if (_tag(cur()) !== "colgroup") return;
7439
+ if (_tagNameOf(cur()) !== "colgroup") return;
7004
7440
  open.pop();
7005
7441
  mode = MODE_IN_TABLE;
7006
7442
  return;
@@ -7013,7 +7449,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7013
7449
  return modes.inHead(t);
7014
7450
  }
7015
7451
  if (t.type === TOKEN_EOF) return modes.inBody(t);
7016
- if (_tag(cur()) !== "colgroup") return;
7452
+ if (_tagNameOf(cur()) !== "colgroup") return;
7017
7453
  open.pop();
7018
7454
  mode = MODE_IN_TABLE;
7019
7455
  process(t);
@@ -7196,9 +7632,11 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7196
7632
  return;
7197
7633
  }
7198
7634
  if (t.type === TOKEN_END_TAG && t.name === "frameset") {
7199
- if (_tag(cur()) === "html") return;
7635
+ if (_tagNameOf(cur()) === "html") return;
7200
7636
  open.pop();
7201
- if (!fragment && _tag(cur()) !== "frameset") mode = MODE_AFTER_FRAMESET;
7637
+ if (!fragment && _tagNameOf(cur()) !== "frameset") {
7638
+ mode = MODE_AFTER_FRAMESET;
7639
+ }
7202
7640
  return;
7203
7641
  }
7204
7642
  if (t.type === TOKEN_START_TAG && t.name === "frame") {
@@ -7271,7 +7709,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7271
7709
  }
7272
7710
  fragment = mkEl(ctxName, ctxNs, EMPTY_ATTRS, null);
7273
7711
  const htmlEl = mkEl("html", NS_HTML, EMPTY_ATTRS, null);
7274
- _hAppend(doc, htmlEl);
7712
+ _appendChild(doc, htmlEl);
7275
7713
  open.push(htmlEl);
7276
7714
  if (ctxNs !== NS_HTML) {
7277
7715
  mode = MODE_IN_BODY;
@@ -7297,7 +7735,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7297
7735
  }
7298
7736
 
7299
7737
  // ---------- tokenizer callbacks ----------
7300
- const decode = decodeHtmlEntities;
7738
+ const decode = decodeEntities;
7301
7739
 
7302
7740
  let swallowNextNewline = false;
7303
7741
  // Set by the tokenizer when it hits EOF mid-tag; such a partial tag is
@@ -7326,12 +7764,12 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7326
7764
  const dispatch = () =>
7327
7765
  process(/** @type {Token} */ (/** @type {unknown} */ (tok)));
7328
7766
 
7329
- walkHtmlTokens(source, 0, {
7767
+ tokenize(source, pos, {
7330
7768
  isForeign: () => {
7331
7769
  const ac = adjustedCurrent();
7332
- return open.length > 0 && ac !== 0 && _ns(ac) !== NS_HTML;
7770
+ return open.length > 0 && ac !== 0 && _namespaceOf(ac) !== NS_HTML;
7333
7771
  },
7334
- fragmentContext: fragment ? _tag(fragment) : undefined,
7772
+ fragmentContext: fragment ? _tagNameOf(fragment) : undefined,
7335
7773
  parseError: (input, code) => {
7336
7774
  if (code === "eof-in-tag") eofInTag = true;
7337
7775
  },
@@ -7353,7 +7791,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7353
7791
  // The tokenizer emits CDATA sections through this callback too.
7354
7792
  if (input.startsWith("<![CDATA[", start)) {
7355
7793
  const ac = adjustedCurrent();
7356
- if (open.length > 0 && ac && _ns(ac) !== NS_HTML) {
7794
+ if (open.length > 0 && ac && _namespaceOf(ac) !== NS_HTML) {
7357
7795
  const innerEnd = input.endsWith("]]>", end) ? end - 3 : end;
7358
7796
  const data = input.slice(start + 9, innerEnd).replace(/\r\n?/g, "\n");
7359
7797
  if (data !== "") {
@@ -7424,11 +7862,13 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7424
7862
  const top = adjustedCurrent();
7425
7863
  const rawMode =
7426
7864
  mode === MODE_TEXT ||
7427
- (top && _ns(top) === NS_HTML && _tag(top) === "plaintext");
7865
+ (top &&
7866
+ _namespaceOf(top) === NS_HTML &&
7867
+ _tagNameOf(top) === "plaintext");
7428
7868
  const noDecode =
7429
7869
  top &&
7430
- _ns(top) === NS_HTML &&
7431
- NO_DECODE_TEXT.has(_tag(top)) &&
7870
+ _namespaceOf(top) === NS_HTML &&
7871
+ NO_DECODE_TEXT.has(_tagNameOf(top)) &&
7432
7872
  (mode === MODE_TEXT || mode === MODE_IN_BODY);
7433
7873
  let data = noDecode ? s : decode(s, false);
7434
7874
  // In RAWTEXT/RCDATA/script/PLAINTEXT, NULL becomes U+FFFD (tokenizer
@@ -7449,12 +7889,12 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7449
7889
  },
7450
7890
  attribute: (input, nameStart, nameEnd, valueStart, valueEnd, quoteType) => {
7451
7891
  const name = internLowerName(ATTR_NAME_INTERN, input, nameStart, nameEnd);
7452
- if (pendAttrStart === 0) pendAttrStart = _aN + 1;
7892
+ if (pendAttrStart === 0) pendAttrStart = _attrCount + 1;
7453
7893
  // Drop duplicate attribute names (per spec). Plain loop avoids a
7454
7894
  // per-attribute closure allocation on this hot path.
7455
7895
  let dup = false;
7456
- for (let i = pendAttrStart; i <= _aN; i++) {
7457
- if (_aName[i] === name) {
7896
+ for (let i = pendAttrStart; i <= _attrCount; i++) {
7897
+ if (_attrNames[i] === name) {
7458
7898
  dup = true;
7459
7899
  break;
7460
7900
  }
@@ -7464,7 +7904,7 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7464
7904
  // demand: consumers re-resolve requests from it and the offsets must
7465
7905
  // stay aligned with the source. Only a valueless attribute stores an
7466
7906
  // override ("").
7467
- _aAlloc(
7907
+ _allocAttr(
7468
7908
  name,
7469
7909
  valueStart !== -1 ? null : "",
7470
7910
  nameStart,
@@ -7491,7 +7931,8 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7491
7931
  // The reused token carries the tag's attribute run; every consumer of
7492
7932
  // `t.attrs` runs synchronously within this dispatch.
7493
7933
  tok.attrs.start = pendAttrStart;
7494
- tok.attrs.count = pendAttrStart === 0 ? 0 : _aN + 1 - pendAttrStart;
7934
+ tok.attrs.count =
7935
+ pendAttrStart === 0 ? 0 : _attrCount + 1 - pendAttrStart;
7495
7936
  pendAttrStart = 0;
7496
7937
  tok.selfClosing = selfClosing;
7497
7938
  tok.swallowNewline = false;
@@ -7532,30 +7973,31 @@ const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
7532
7973
  * @returns {HtmlNodeRef} clone
7533
7974
  */
7534
7975
  const cloneSubtree = (node) => {
7535
- const ty = _hTy[node];
7536
- const clone = _hAlloc(ty, _hSt[node], _hEn[node]);
7537
- _hStr[clone] = _hStr[node];
7976
+ const ty = _nodeTypes[node];
7977
+ const clone = _allocNode(ty, _nodeStarts[node], _nodeEnds[node]);
7978
+ _nodeStrings[clone] = _nodeStrings[node];
7538
7979
  if (ty !== NodeType.Element) return clone;
7539
- _hFl[clone] = _hFl[node];
7980
+ _nodeFlags[clone] = _nodeFlags[node];
7540
7981
  const attrs = cloneAttrs(node);
7541
- _hAStart[clone] = attrs.start;
7542
- _hACount[clone] = attrs.count;
7543
- _hTagEnd[clone] = _hTagEnd[node];
7544
- _hNameEnd[clone] = _hNameEnd[node];
7982
+ _nodeAttrStarts[clone] = attrs.start;
7983
+ _nodeAttrCounts[clone] = attrs.count;
7984
+ _nodeTagEnds[clone] = _nodeTagEnds[node];
7985
+ _nodeNameEnds[clone] = _nodeNameEnds[node];
7545
7986
  // Empty body span: the clone re-emits no raw-text dependency.
7546
- _hCEnd[clone] = _hTagEnd[node];
7547
- for (let k = _hFirst[node]; k !== 0; k = _hNext[k]) {
7548
- _hAppend(clone, cloneSubtree(k));
7987
+ _nodeContentEnds[clone] = _nodeTagEnds[node];
7988
+ for (let k = _nodeFirstChildren[node]; k !== 0; k = _nodeNextSiblings[k]) {
7989
+ _appendChild(clone, cloneSubtree(k));
7549
7990
  }
7550
7991
  // Clone `<template>` content into the clone's own fragment.
7551
- const tc = _hTc[node];
7992
+ const tc = _nodeTemplateContents[node];
7552
7993
  if (tc !== 0) {
7553
- const fragment = _hAlloc(NodeType.DocumentFragment, 0, 0);
7994
+ const fragment = _allocNode(NodeType.DocumentFragment, 0, 0);
7995
+ _nodeStrings[fragment] = "";
7554
7996
  // Parent link so the iterative walk can ascend out of the content.
7555
- _hParent[fragment] = clone;
7556
- _hTc[clone] = fragment;
7557
- for (let k = _hFirst[tc]; k !== 0; k = _hNext[k]) {
7558
- _hAppend(fragment, cloneSubtree(k));
7997
+ _nodeParents[fragment] = clone;
7998
+ _nodeTemplateContents[clone] = fragment;
7999
+ for (let k = _nodeFirstChildren[tc]; k !== 0; k = _nodeNextSiblings[k]) {
8000
+ _appendChild(fragment, cloneSubtree(k));
7559
8001
  }
7560
8002
  }
7561
8003
  return clone;
@@ -7572,17 +8014,19 @@ const selectedOption = (select) => {
7572
8014
  const options = [];
7573
8015
  /** @param {HtmlElement} el element */
7574
8016
  const collect = (el) => {
7575
- for (let c = _hFirst[el]; c !== 0; c = _hNext[c]) {
7576
- if (_hTy[c] !== NodeType.Element || _ns(c) !== NS_HTML) continue;
7577
- if (_hStr[c] === "option") options.push(c);
7578
- else if (_hStr[c] === "optgroup") collect(c);
8017
+ for (let c = _nodeFirstChildren[el]; c !== 0; c = _nodeNextSiblings[c]) {
8018
+ if (_nodeTypes[c] !== NodeType.Element || _namespaceOf(c) !== NS_HTML) {
8019
+ continue;
8020
+ }
8021
+ if (_nodeStrings[c] === "option") options.push(c);
8022
+ else if (_nodeStrings[c] === "optgroup") collect(c);
7579
8023
  }
7580
8024
  };
7581
8025
  collect(select);
7582
8026
  if (options.length === 0) return 0;
7583
8027
  for (let i = options.length - 1; i >= 0; i--) {
7584
8028
  const el = options[i];
7585
- if (_aFind(_hAStart[el], _hACount[el], "selected") !== 0) {
8029
+ if (_findAttr(_nodeAttrStarts[el], _nodeAttrCounts[el], "selected") !== 0) {
7586
8030
  return el;
7587
8031
  }
7588
8032
  }
@@ -7597,24 +8041,32 @@ const selectedOption = (select) => {
7597
8041
  */
7598
8042
  const mirrorSelectedContent = (node, select) => {
7599
8043
  // A `<template>`'s children live in its content fragment.
7600
- const tc = _hTc[node];
8044
+ const tc = _nodeTemplateContents[node];
7601
8045
  const container = tc !== 0 ? tc : node;
7602
- for (let child = _hFirst[container]; child !== 0; child = _hNext[child]) {
7603
- if (_hTy[child] !== NodeType.Element) continue;
7604
- if (_ns(child) === NS_HTML && _hStr[child] === "select") {
8046
+ for (
8047
+ let child = _nodeFirstChildren[container];
8048
+ child !== 0;
8049
+ child = _nodeNextSiblings[child]
8050
+ ) {
8051
+ if (_nodeTypes[child] !== NodeType.Element) continue;
8052
+ if (_namespaceOf(child) === NS_HTML && _nodeStrings[child] === "select") {
7605
8053
  mirrorSelectedContent(child, child);
7606
8054
  } else if (
7607
8055
  select !== 0 &&
7608
- _ns(child) === NS_HTML &&
7609
- _hStr[child] === "selectedcontent"
8056
+ _namespaceOf(child) === NS_HTML &&
8057
+ _nodeStrings[child] === "selectedcontent"
7610
8058
  ) {
7611
8059
  const option = selectedOption(select);
7612
8060
  if (option !== 0) {
7613
8061
  // Replace the children with clones of the option's subtree.
7614
- _hFirst[child] = 0;
7615
- _hLast[child] = 0;
7616
- for (let k = _hFirst[option]; k !== 0; k = _hNext[k]) {
7617
- _hAppend(child, cloneSubtree(k));
8062
+ _nodeFirstChildren[child] = 0;
8063
+ _nodeLastChildren[child] = 0;
8064
+ for (
8065
+ let k = _nodeFirstChildren[option];
8066
+ k !== 0;
8067
+ k = _nodeNextSiblings[k]
8068
+ ) {
8069
+ _appendChild(child, cloneSubtree(k));
7618
8070
  }
7619
8071
  }
7620
8072
  } else {
@@ -7661,7 +8113,7 @@ const parseDoctype = (/** @type {string} */ raw) => {
7661
8113
 
7662
8114
  /**
7663
8115
  * @typedef {object} HtmlProcessOptions
7664
- * @property {string=} fragmentContext context element tag name for fragment parsing (see `buildHtmlAst`); the HTML analog of the CSS parser's `as` parse-mode option
8116
+ * @property {string=} fragmentContext context element tag name for fragment parsing (see `parseHtml`); the HTML analog of the CSS parser's `as` parse-mode option
7665
8117
  * @property {HtmlAstSkip=} skip node kinds to omit from the AST for speed/memory (see `HtmlAstSkip`)
7666
8118
  */
7667
8119
 
@@ -7674,26 +8126,26 @@ const parseDoctype = (/** @type {string} */ raw) => {
7674
8126
  * @param {HtmlProcessOptions} options process options
7675
8127
  */
7676
8128
  const grammar = (input, visitors, options) => {
7677
- const root = buildHtmlAst(input, options.fragmentContext, options.skip);
8129
+ const root = parseHtml(input, 0, options);
7678
8130
 
7679
8131
  // Iterative depth-first walk over the link columns: `firstChild`
7680
8132
  // / `nextSibling` descend, the `parent` column ascends, so arbitrarily deep
7681
8133
  // markup can't overflow the call stack (the old recursive walk died at
7682
8134
  // ~10⁵ nesting). A `<template>`'s content fragment is visited before the
7683
8135
  // element's children; ascending out of it continues with those children
7684
- // (the fragment is never in a sibling chain, `_hNext` = 0).
8136
+ // (the fragment is never in a sibling chain, `_nodeNextSiblings` = 0).
7685
8137
  /**
7686
8138
  * Fire a node's `enter` visitors; true when the walk may descend.
7687
8139
  * @param {HtmlNodeRef} node node
7688
8140
  * @returns {boolean} false when a visitor called `skipChildren()`
7689
8141
  */
7690
8142
  const enter = (node) => {
7691
- const b = visitors[_hTy[node]];
8143
+ const b = visitors[_nodeTypes[node]];
7692
8144
  if (b === undefined || b.enter.length === 0) return true;
7693
8145
  _walkSkip = false;
7694
- _curNode = node;
7695
- const p = _hParent[node];
7696
- _curParent = p === 0 ? null : p;
8146
+ _currentNode = node;
8147
+ const p = _nodeParents[node];
8148
+ _currentParent = p === 0 ? null : p;
7697
8149
  const e = b.enter;
7698
8150
  for (let i = 0; i < e.length; i++) e[i](A);
7699
8151
  const skip = _walkSkip;
@@ -7705,11 +8157,11 @@ const grammar = (input, visitors, options) => {
7705
8157
  * @param {HtmlNodeRef} node node
7706
8158
  */
7707
8159
  const exit = (node) => {
7708
- const b = visitors[_hTy[node]];
8160
+ const b = visitors[_nodeTypes[node]];
7709
8161
  if (b === undefined) return;
7710
- _curNode = node;
7711
- const p = _hParent[node];
7712
- _curParent = p === 0 ? null : p;
8162
+ _currentNode = node;
8163
+ const p = _nodeParents[node];
8164
+ _currentParent = p === 0 ? null : p;
7713
8165
  const x = b.exit;
7714
8166
  for (let i = 0; i < x.length; i++) x[i](A);
7715
8167
  };
@@ -7719,13 +8171,13 @@ const grammar = (input, visitors, options) => {
7719
8171
  * @returns {HtmlNodeRef} first inner node (0 = leaf)
7720
8172
  */
7721
8173
  const firstInner = (node) => {
7722
- const ty = _hTy[node];
8174
+ const ty = _nodeTypes[node];
7723
8175
  if (ty === NodeType.Element) {
7724
- const tc = _hTc[node];
7725
- return tc !== 0 ? tc : _hFirst[node];
8176
+ const tc = _nodeTemplateContents[node];
8177
+ return tc !== 0 ? tc : _nodeFirstChildren[node];
7726
8178
  }
7727
8179
  if (ty === NodeType.Document || ty === NodeType.DocumentFragment) {
7728
- return _hFirst[node];
8180
+ return _nodeFirstChildren[node];
7729
8181
  }
7730
8182
  return 0;
7731
8183
  };
@@ -7743,16 +8195,16 @@ const grammar = (input, visitors, options) => {
7743
8195
  for (;;) {
7744
8196
  exit(node);
7745
8197
  if (node === root) break descend;
7746
- const parent = _hParent[node];
8198
+ const parent = _nodeParents[node];
7747
8199
  // Out of a template's content fragment: the element's children follow.
7748
- if (_hTc[parent] === node) {
7749
- const first = _hFirst[parent];
8200
+ if (_nodeTemplateContents[parent] === node) {
8201
+ const first = _nodeFirstChildren[parent];
7750
8202
  if (first !== 0) {
7751
8203
  node = first;
7752
8204
  continue descend;
7753
8205
  }
7754
8206
  } else {
7755
- const sibling = _hNext[node];
8207
+ const sibling = _nodeNextSiblings[node];
7756
8208
  if (sibling !== 0) {
7757
8209
  node = sibling;
7758
8210
  continue descend;
@@ -7764,7 +8216,7 @@ const grammar = (input, visitors, options) => {
7764
8216
 
7765
8217
  // The walk consumed the tree: release the side arrays' heap references so
7766
8218
  // the reused columns don't pin this parse's strings until the next parse.
7767
- _hRelease();
8219
+ _releaseAstColumns();
7768
8220
  };
7769
8221
 
7770
8222
  /**
@@ -7772,14 +8224,16 @@ const grammar = (input, visitors, options) => {
7772
8224
  * `grammar`. Babel-style usage:
7773
8225
  *
7774
8226
  * ```
7775
- * processor.use({ [NodeType.Element]: (path) => {}, [NodeType.Comment]: { enter, exit } });
7776
- * processor.process(source);
8227
+ * new SourceProcessor({ skip }).use({ [NodeType.Element]: (path) => {}, [NodeType.Comment]: { enter, exit } }).process(source);
7777
8228
  * ```
7778
8229
  * @extends {GenericSourceProcessor<HtmlPath, HtmlProcessOptions>}
7779
8230
  */
7780
8231
  class SourceProcessor extends GenericSourceProcessor {
7781
- constructor() {
7782
- super(grammar);
8232
+ /**
8233
+ * @param {HtmlProcessOptions=} options default process options (`skip`, `fragmentContext`) merged under each `process` call's own options
8234
+ */
8235
+ constructor(options) {
8236
+ super(grammar, options);
7783
8237
  }
7784
8238
  }
7785
8239
 
@@ -7974,7 +8428,7 @@ const parseSrcset = (input) => {
7974
8428
  /**
7975
8429
  * @returns {void}
7976
8430
  */
7977
- function tokenize() {
8431
+ function tokenizeDescriptor() {
7978
8432
  // 8.1. Descriptor tokenizer: Skip whitespace
7979
8433
  collectCharacters(LEADING_SPACES_REGEXP);
7980
8434
 
@@ -8128,22 +8582,162 @@ const parseSrcset = (input) => {
8128
8582
  }
8129
8583
  // Otherwise, follow these sub steps:
8130
8584
  else {
8131
- tokenize();
8585
+ tokenizeDescriptor();
8132
8586
  }
8133
8587
 
8134
8588
  // 16. Return to the step labeled splitting loop.
8135
8589
  }
8136
8590
  };
8137
8591
 
8592
+ // The spec's "URL-potentially-surrounded-by-spaces" cleanup: C0 controls,
8593
+ // DEL..C1 and U+00A0 are stripped from the value after the whitespace trim.
8594
+ // eslint-disable-next-line no-control-regex
8595
+ const IGNORE_CHARS_REGEXP = /[\u0000-\u001F\u007F-\u009F\u00A0]/g;
8596
+
8597
+ /**
8598
+ * Parse a `src`-like URL attribute value: trim ASCII whitespace / U+00A0 from
8599
+ * both ends (offsets preserved for source rewriting), then strip ignorable
8600
+ * control characters. Throws when nothing remains.
8601
+ * @param {string} input attribute value
8602
+ * @returns {ParsedSource[]} parsed src
8603
+ */
8604
+ const parseSrc = (input) => {
8605
+ const len = input.length;
8606
+ if (len === 0) throw new Error("Must be non-empty");
8607
+
8608
+ let start = 0;
8609
+ let end = len;
8610
+
8611
+ while (start < end) {
8612
+ const code = input.charCodeAt(start);
8613
+ if (code > 32 && code !== 160) break;
8614
+ start++;
8615
+ }
8616
+
8617
+ if (start === end) throw new Error("Must be non-empty");
8618
+
8619
+ while (end > start) {
8620
+ const code = input.charCodeAt(end - 1);
8621
+ if (code > 32 && code !== 160) break;
8622
+ end--;
8623
+ }
8624
+
8625
+ let value = input.slice(start, end);
8626
+
8627
+ if (IGNORE_CHARS_REGEXP.test(value)) {
8628
+ value = value.replace(IGNORE_CHARS_REGEXP, "");
8629
+ if (value.length === 0) throw new Error("Must be non-empty");
8630
+ }
8631
+
8632
+ return [[value, start, end]];
8633
+ };
8634
+
8635
+ /**
8636
+ * Extracts the `icon-uri` value of an `msapplication-task` meta content
8637
+ * (`name=…;action-uri=…;icon-uri=…`) — the other parts are page URLs, not assets.
8638
+ * @param {string} input input
8639
+ * @returns {ParsedSource[]} parsed icon-uri
8640
+ */
8641
+ const parseMsapplicationTask = (input) => {
8642
+ const len = input.length;
8643
+ let pos = 0;
8644
+ while (pos < len) {
8645
+ let sep = input.indexOf(";", pos);
8646
+ if (sep === -1) sep = len;
8647
+ const eq = input.indexOf("=", pos);
8648
+ if (eq !== -1 && eq < sep) {
8649
+ const key = input.slice(pos, eq).trim().toLowerCase();
8650
+ if (key === "icon-uri") {
8651
+ let start = eq + 1;
8652
+ let end = sep;
8653
+ while (start < end && isSpace(input.charCodeAt(start))) {
8654
+ start++;
8655
+ }
8656
+ while (end > start && isSpace(input.charCodeAt(end - 1))) {
8657
+ end--;
8658
+ }
8659
+ if (start === end) return [];
8660
+ return [[input.slice(start, end), start, end]];
8661
+ }
8662
+ }
8663
+ pos = sep + 1;
8664
+ }
8665
+ return [];
8666
+ };
8667
+
8668
+ // CSS syntax is loaded on first `parseCssUrls` call, not at module load —
8669
+ // `HtmlGenerator` deliberately defers it the same way, and most documents
8670
+ // never carry a URL-bearing SVG presentation attribute.
8671
+ /** @type {typeof import("../css/syntax") | undefined} */
8672
+ let _cssSyntax;
8673
+
8674
+ /**
8675
+ * Extracts `url(...)` references from a CSS value (an SVG presentation
8676
+ * attribute). Reuses webpack's CSS lexer so quoting/escaping match the CSS
8677
+ * spec; returns the `parseSrc` shape so the shared emit path maps and rewrites
8678
+ * the spans. Unquoted `url(path)` rewrites the content span; quoted
8679
+ * `url("path")` rewrites the inner string span (quotes preserved).
8680
+ * @param {string} input attribute value (a CSS component-value list)
8681
+ * @returns {ParsedSource[]} url references
8682
+ */
8683
+ const parseCssUrls = (input) => {
8684
+ const {
8685
+ TT_EOF,
8686
+ TT_FUNCTION,
8687
+ TT_STRING,
8688
+ TT_URL,
8689
+ TT_WHITESPACE,
8690
+ TokenStream,
8691
+ equalsLowerCase
8692
+ } = _cssSyntax || (_cssSyntax = require("../css/syntax"));
8693
+ const ts = new TokenStream(input);
8694
+ /** @type {ParsedSource[]} */
8695
+ const result = [];
8696
+ for (;;) {
8697
+ const t = ts.consume();
8698
+ if (t.type === TT_EOF) break;
8699
+ if (t.type === TT_URL) {
8700
+ if (t.contentEnd > t.contentStart) {
8701
+ result.push([
8702
+ input.slice(t.contentStart, t.contentEnd),
8703
+ t.contentStart,
8704
+ t.contentEnd
8705
+ ]);
8706
+ }
8707
+ } else if (
8708
+ t.type === TT_FUNCTION &&
8709
+ equalsLowerCase(input.slice(t.start, t.end - 1), "url")
8710
+ ) {
8711
+ let s = ts.consume();
8712
+ while (s.type === TT_WHITESPACE) s = ts.consume();
8713
+ if (s.type === TT_STRING) {
8714
+ const quote = input.charCodeAt(s.start);
8715
+ const innerStart = s.start + 1;
8716
+ // Drop the closing quote, unless the string is unterminated at EOF.
8717
+ const innerEnd =
8718
+ input.charCodeAt(s.end - 1) === quote ? s.end - 1 : s.end;
8719
+ if (innerEnd > innerStart) {
8720
+ result.push([
8721
+ input.slice(innerStart, innerEnd),
8722
+ innerStart,
8723
+ innerEnd
8724
+ ]);
8725
+ }
8726
+ }
8727
+ }
8728
+ }
8729
+ return result;
8730
+ };
8731
+
8138
8732
  // Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
8139
8733
  // `enter` dispatch, consumed by the walk.
8140
8734
  let _walkSkip = false;
8141
8735
  // The walk's current position (`A.node` / `A.parent` read these; module-level
8142
8736
  // so the accessor methods' defaults avoid self-referential `this` typing).
8143
8737
  /** @type {HtmlNodeRef} */
8144
- let _curNode = 0;
8738
+ let _currentNode = 0;
8145
8739
  /** @type {HtmlNodeRef | null} */
8146
- let _curParent = null;
8740
+ let _currentParent = null;
8147
8741
 
8148
8742
  /* eslint-disable jsdoc/require-template -- `A` below is the accessor const, not a type parameter */
8149
8743
  /**
@@ -8156,20 +8750,20 @@ let _curParent = null;
8156
8750
  // AST field-access seam (mirrors the CSS parser's `A`): every AST field a
8157
8751
  // consumer reads goes through one of these accessors, so the node
8158
8752
  // representation can change underneath without touching consumers. `n` is an
8159
- // `HtmlNodeRef`; results are valid until the next `buildHtmlAst` call.
8753
+ // `HtmlNodeRef`; results are valid until the next `parseHtml` call.
8160
8754
  const A = {
8161
8755
  // === path position (rebound by the walk before every visitor call) ===
8162
8756
  /**
8163
8757
  * @returns {HtmlNodeRef} current node — only valid during a visitor callback
8164
8758
  */
8165
8759
  get node() {
8166
- return _curNode;
8760
+ return _currentNode;
8167
8761
  },
8168
8762
  /**
8169
8763
  * @returns {HtmlNodeRef | null} enclosing node (null = the document root)
8170
8764
  */
8171
8765
  get parent() {
8172
- return _curParent;
8766
+ return _currentParent;
8173
8767
  },
8174
8768
  /** Stop the walk descending into the current node (enter only). */
8175
8769
  skipChildren() {
@@ -8180,43 +8774,43 @@ const A = {
8180
8774
  * @param {HtmlNodeRef=} n node
8181
8775
  * @returns {number} `NodeType`
8182
8776
  */
8183
- type(n = _curNode) {
8184
- return _hTy[n];
8777
+ type(n = _currentNode) {
8778
+ return _nodeTypes[n];
8185
8779
  },
8186
8780
  /**
8187
8781
  * @param {HtmlNodeRef=} n node
8188
8782
  * @returns {number} start offset
8189
8783
  */
8190
- start(n = _curNode) {
8191
- return _hSt[n];
8784
+ start(n = _currentNode) {
8785
+ return _nodeStarts[n];
8192
8786
  },
8193
8787
  /**
8194
8788
  * @param {HtmlNodeRef=} n node
8195
8789
  * @returns {number} end offset
8196
8790
  */
8197
- end(n = _curNode) {
8198
- return _hEn[n];
8791
+ end(n = _currentNode) {
8792
+ return _nodeEnds[n];
8199
8793
  },
8200
8794
  /**
8201
8795
  * @param {HtmlElement=} n element
8202
8796
  * @returns {string} lowercased (foreign-content: adjusted) tag name
8203
8797
  */
8204
- tagName(n = _curNode) {
8205
- return _hStr[n];
8798
+ tagName(n = _currentNode) {
8799
+ return _nodeStrings[n];
8206
8800
  },
8207
8801
  /**
8208
8802
  * @param {HtmlElement=} n element
8209
8803
  * @returns {number} `NS_*` namespace
8210
8804
  */
8211
- namespace(n = _curNode) {
8212
- return _hFl[n] & NS_MASK;
8805
+ namespace(n = _currentNode) {
8806
+ return _nodeFlags[n] & NS_MASK;
8213
8807
  },
8214
8808
  /**
8215
8809
  * @param {HtmlElement=} n element
8216
8810
  * @returns {boolean} true for void elements
8217
8811
  */
8218
- selfClosing(n = _curNode) {
8219
- return (_hFl[n] & FLAG_SELF_CLOSING) !== 0;
8812
+ selfClosing(n = _currentNode) {
8813
+ return (_nodeFlags[n] & FLAG_SELF_CLOSING) !== 0;
8220
8814
  },
8221
8815
  // materialized attribute list — test/tooling convenience, allocates; the
8222
8816
  // parser reads attributes through the scalar accessors below
@@ -8224,18 +8818,18 @@ const A = {
8224
8818
  * @param {HtmlElement=} n element
8225
8819
  * @returns {HtmlAttribute[]} materialized attributes
8226
8820
  */
8227
- attributes(n = _curNode) {
8821
+ attributes(n = _currentNode) {
8228
8822
  const out = [];
8229
- const start = _hAStart[n];
8230
- for (let i = start; i < start + _hACount[n]; i++) {
8823
+ const start = _nodeAttrStarts[n];
8824
+ for (let i = start; i < start + _nodeAttrCounts[n]; i++) {
8231
8825
  out.push({
8232
- name: _aName[i],
8233
- value: _aValueOf(i),
8234
- serializedName: _aSerializedName(i),
8235
- nameStart: _aNameStart[i],
8236
- nameEnd: _aNameEnd[i],
8237
- valueStart: _aValStart[i],
8238
- valueEnd: _aValEnd[i]
8826
+ name: _attrNames[i],
8827
+ value: _attrValueOf(i),
8828
+ serializedName: _attrSerializedName(i),
8829
+ nameStart: _attrNameStarts[i],
8830
+ nameEnd: _attrNameEnds[i],
8831
+ valueStart: _attrValueStarts[i],
8832
+ valueEnd: _attrValueEnds[i]
8239
8833
  });
8240
8834
  }
8241
8835
  return out;
@@ -8244,8 +8838,8 @@ const A = {
8244
8838
  * @param {HtmlElement=} n element
8245
8839
  * @returns {number} attribute count
8246
8840
  */
8247
- attributeCount(n = _curNode) {
8248
- return _hACount[n];
8841
+ attributeCount(n = _currentNode) {
8842
+ return _nodeAttrCounts[n];
8249
8843
  },
8250
8844
  /**
8251
8845
  * The i-th attribute of an element, as an id for the `attribute*` reads.
@@ -8253,8 +8847,8 @@ const A = {
8253
8847
  * @param {HtmlElement=} n element
8254
8848
  * @returns {HtmlAttributeRef} attribute ref
8255
8849
  */
8256
- attributeAt(i, n = _curNode) {
8257
- return _hAStart[n] + i;
8850
+ attributeAt(i, n = _currentNode) {
8851
+ return _nodeAttrStarts[n] + i;
8258
8852
  },
8259
8853
  /**
8260
8854
  * Linear lookup by (lowercased) name.
@@ -8262,92 +8856,92 @@ const A = {
8262
8856
  * @param {HtmlElement=} n element
8263
8857
  * @returns {HtmlAttributeRef} attribute ref (0 = not present)
8264
8858
  */
8265
- findAttribute(name, n = _curNode) {
8266
- return _aFind(_hAStart[n], _hACount[n], name);
8859
+ findAttribute(name, n = _currentNode) {
8860
+ return _findAttr(_nodeAttrStarts[n], _nodeAttrCounts[n], name);
8267
8861
  },
8268
8862
  /**
8269
8863
  * @param {HtmlAttributeRef} a attribute ref
8270
8864
  * @returns {string} lowercased (foreign-content: adjusted) attribute name
8271
8865
  */
8272
8866
  attributeName(a) {
8273
- return _aName[a];
8867
+ return _attrNames[a];
8274
8868
  },
8275
8869
  /**
8276
8870
  * @param {HtmlAttributeRef} a attribute ref
8277
8871
  * @returns {string} raw (undecoded) attribute value ("" when valueless)
8278
8872
  */
8279
8873
  attributeValue(a) {
8280
- return _aValueOf(a);
8874
+ return _attrValueOf(a);
8281
8875
  },
8282
8876
  /**
8283
8877
  * @param {HtmlAttributeRef} a attribute ref
8284
8878
  * @returns {number} name start offset
8285
8879
  */
8286
8880
  attributeNameStart(a) {
8287
- return _aNameStart[a];
8881
+ return _attrNameStarts[a];
8288
8882
  },
8289
8883
  /**
8290
8884
  * @param {HtmlAttributeRef} a attribute ref
8291
8885
  * @returns {number} name end offset
8292
8886
  */
8293
8887
  attributeNameEnd(a) {
8294
- return _aNameEnd[a];
8888
+ return _attrNameEnds[a];
8295
8889
  },
8296
8890
  /**
8297
8891
  * @param {HtmlAttributeRef} a attribute ref
8298
8892
  * @returns {number} value start offset (-1 when valueless or on adoption-agency clones)
8299
8893
  */
8300
8894
  attributeValueStart(a) {
8301
- return _aValStart[a];
8895
+ return _attrValueStarts[a];
8302
8896
  },
8303
8897
  /**
8304
8898
  * @param {HtmlAttributeRef} a attribute ref
8305
8899
  * @returns {number} value end offset
8306
8900
  */
8307
8901
  attributeValueEnd(a) {
8308
- return _aValEnd[a];
8902
+ return _attrValueEnds[a];
8309
8903
  },
8310
8904
  /**
8311
8905
  * @param {HtmlElement=} n element
8312
8906
  * @returns {number} end offset of the opening tag (after `>`)
8313
8907
  */
8314
- tagEnd(n = _curNode) {
8315
- return _hTagEnd[n];
8908
+ tagEnd(n = _currentNode) {
8909
+ return _nodeTagEnds[n];
8316
8910
  },
8317
8911
  /**
8318
8912
  * @param {HtmlElement=} n element
8319
8913
  * @returns {number} end offset of the tag name
8320
8914
  */
8321
- nameEnd(n = _curNode) {
8322
- return _hNameEnd[n];
8915
+ nameEnd(n = _currentNode) {
8916
+ return _nodeNameEnds[n];
8323
8917
  },
8324
8918
  /**
8325
8919
  * @param {HtmlElement=} n element
8326
8920
  * @returns {number} under `skip.text`, end offset of a raw-text element's body (`tagEnd` when empty)
8327
8921
  */
8328
- contentEnd(n = _curNode) {
8329
- return _hCEnd[n];
8922
+ contentEnd(n = _currentNode) {
8923
+ return _nodeContentEnds[n];
8330
8924
  },
8331
8925
  /**
8332
8926
  * @param {HtmlElement=} n element
8333
8927
  * @returns {HtmlDocumentFragment} `<template>` content fragment (0 = none)
8334
8928
  */
8335
- templateContent(n = _curNode) {
8336
- return _hTc[n];
8929
+ templateContent(n = _currentNode) {
8930
+ return _nodeTemplateContents[n];
8337
8931
  },
8338
8932
  /**
8339
8933
  * @param {HtmlText | HtmlComment=} n text / comment node
8340
8934
  * @returns {string} decoded text / comment data
8341
8935
  */
8342
- data(n = _curNode) {
8343
- return _hStr[n];
8936
+ data(n = _currentNode) {
8937
+ return _nodeStrings[n];
8344
8938
  },
8345
8939
  /**
8346
8940
  * @param {HtmlDoctype=} n doctype node
8347
8941
  * @returns {string} doctype name
8348
8942
  */
8349
- doctypeName(n = _curNode) {
8350
- return _hStr[n];
8943
+ doctypeName(n = _currentNode) {
8944
+ return _nodeStrings[n];
8351
8945
  },
8352
8946
  // The doctype ids are per-parse scalars (a document has at most one
8353
8947
  // doctype node); the node parameter is accepted for call-shape uniformity.
@@ -8356,44 +8950,46 @@ const A = {
8356
8950
  * @returns {string | null} doctype public id
8357
8951
  */
8358
8952
  doctypePublicId(_n) {
8359
- return _hDocPub;
8953
+ return _doctypePublicId;
8360
8954
  },
8361
8955
  /**
8362
8956
  * @param {HtmlDoctype=} _n doctype node
8363
8957
  * @returns {string | null} doctype system id
8364
8958
  */
8365
8959
  doctypeSystemId(_n) {
8366
- return _hDocSys;
8960
+ return _doctypeSystemId;
8367
8961
  },
8368
8962
  // === tree links (0 = none) ===
8369
8963
  /**
8370
8964
  * @param {HtmlNodeRef=} n node
8371
8965
  * @returns {HtmlNodeRef} first child
8372
8966
  */
8373
- firstChild(n = _curNode) {
8374
- return _hFirst[n];
8967
+ firstChild(n = _currentNode) {
8968
+ return _nodeFirstChildren[n];
8375
8969
  },
8376
8970
  /**
8377
8971
  * @param {HtmlNodeRef=} n node
8378
8972
  * @returns {HtmlNodeRef} next sibling
8379
8973
  */
8380
- nextSibling(n = _curNode) {
8381
- return _hNext[n];
8974
+ nextSibling(n = _currentNode) {
8975
+ return _nodeNextSiblings[n];
8382
8976
  },
8383
8977
  /**
8384
8978
  * @param {HtmlNodeRef=} n node
8385
8979
  * @returns {HtmlNodeRef} parent node (a `<template>`'s content links to its fragment)
8386
8980
  */
8387
- parentOf(n = _curNode) {
8388
- return _hParent[n];
8981
+ parentOf(n = _currentNode) {
8982
+ return _nodeParents[n];
8389
8983
  },
8390
8984
  /**
8391
8985
  * @param {HtmlNodeRef=} n node
8392
8986
  * @returns {HtmlNodeRef[]} materialized child list — test/tooling convenience, allocates
8393
8987
  */
8394
- children(n = _curNode) {
8988
+ children(n = _currentNode) {
8395
8989
  const out = [];
8396
- for (let c = _hFirst[n]; c !== 0; c = _hNext[c]) out.push(c);
8990
+ for (let c = _nodeFirstChildren[n]; c !== 0; c = _nodeNextSiblings[c]) {
8991
+ out.push(c);
8992
+ }
8397
8993
  return out;
8398
8994
  }
8399
8995
  };
@@ -8406,12 +9002,19 @@ module.exports.NodeType = NodeType;
8406
9002
  module.exports.QUOTE_DOUBLE = QUOTE_DOUBLE;
8407
9003
  module.exports.QUOTE_NONE = QUOTE_NONE;
8408
9004
  module.exports.QUOTE_SINGLE = QUOTE_SINGLE;
8409
- module.exports.SVG_TAG_ADJUST = SVG_TAG_ADJUST;
8410
- module.exports.SourceProcessor = SourceProcessor;
8411
9005
  // Exposed so HtmlParser can map user-configured (lowercased) tag names to
8412
9006
  // the adjusted camelCase names the AST carries for foreign content.
8413
- module.exports.buildHtmlAst = buildHtmlAst;
8414
- module.exports.decodeHtmlEntities = decodeHtmlEntities;
8415
- module.exports.decodeHtmlEntitiesWithMap = decodeHtmlEntitiesWithMap;
9007
+ module.exports.SVG_TAG_ADJUST = SVG_TAG_ADJUST;
9008
+ module.exports.SourceProcessor = SourceProcessor;
9009
+ module.exports.decodeEntities = decodeEntities;
9010
+ module.exports.escapeAttribute = escapeAttribute;
9011
+ module.exports.escapeText = escapeText;
9012
+ // WHATWG "ASCII whitespace" (tab / LF / FF / CR / space) — the tokenizer's
9013
+ // whitespace class, exported under the spec's name.
9014
+ module.exports.isAsciiWhitespace = isSpace;
9015
+ module.exports.parseCssUrls = parseCssUrls;
9016
+ module.exports.parseHtml = parseHtml;
9017
+ module.exports.parseMsapplicationTask = parseMsapplicationTask;
9018
+ module.exports.parseSrc = parseSrc;
8416
9019
  module.exports.parseSrcset = parseSrcset;
8417
- module.exports.walkHtmlTokens = walkHtmlTokens;
9020
+ module.exports.tokenize = tokenize;