webpack 5.108.3 → 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.
- package/lib/APIPlugin.js +48 -29
- package/lib/CacheFacade.js +8 -0
- package/lib/ChunkGroup.js +1 -0
- package/lib/CompatibilityPlugin.js +31 -29
- package/lib/Compilation.js +50 -8
- package/lib/ConstPlugin.js +11 -11
- package/lib/ContextModule.js +239 -6
- package/lib/ContextModuleFactory.js +139 -14
- package/lib/DefinePlugin.js +396 -101
- package/lib/Entrypoint.js +1 -1
- package/lib/ExportsInfoApiPlugin.js +2 -2
- package/lib/ExternalModule.js +107 -5
- package/lib/ExternalModuleFactoryPlugin.js +20 -1
- package/lib/FileSystemInfo.js +130 -74
- package/lib/FlagDependencyExportsPlugin.js +55 -8
- package/lib/HotModuleReplacementPlugin.js +18 -15
- package/lib/LazyBarrel.js +30 -3
- package/lib/Module.js +3 -0
- package/lib/ModuleFilenameHelpers.js +2 -2
- package/lib/ModuleTypeConstants.js +8 -1
- package/lib/MultiCompiler.js +13 -2
- package/lib/MultiWatching.js +1 -0
- package/lib/NodeStuffPlugin.js +51 -33
- package/lib/NormalModule.js +169 -405
- package/lib/NormalModuleFactory.js +76 -2
- package/lib/ProgressPlugin.js +211 -119
- package/lib/ProvidePlugin.js +2 -2
- package/lib/RuntimeGlobals.js +47 -0
- package/lib/RuntimePlugin.js +33 -0
- package/lib/RuntimeTemplate.js +304 -16
- package/lib/UseStrictPlugin.js +1 -1
- package/lib/WebpackIsIncludedPlugin.js +1 -1
- package/lib/WebpackOptionsApply.js +24 -19
- package/lib/asset/AssetGenerator.js +110 -28
- package/lib/asset/AssetModulesPlugin.js +92 -2
- package/lib/asset/WebManifestGenerator.js +164 -0
- package/lib/asset/WebManifestParser.js +130 -0
- package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
- package/lib/async-modules/isGeneratorLowered.js +26 -0
- package/lib/cache/PackFileCacheStrategy.js +9 -3
- package/lib/config/browserslistTargetHandler.js +40 -0
- package/lib/config/defaults.js +222 -32
- package/lib/config/normalization.js +26 -0
- package/lib/config/target.js +9 -1
- package/lib/css/CssGenerator.js +73 -73
- package/lib/css/CssLoadingRuntimeModule.js +5 -1
- package/lib/css/CssModulesPlugin.js +92 -53
- package/lib/css/CssParser.js +843 -302
- package/lib/css/syntax.js +1219 -483
- package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
- package/lib/dependencies/AMDPlugin.js +2 -2
- package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
- package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
- package/lib/dependencies/CommonJsExportsDependency.js +47 -2
- package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
- package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
- package/lib/dependencies/CommonJsPlugin.js +2 -2
- package/lib/dependencies/CommonJsRequireDependency.js +26 -1
- package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
- package/lib/dependencies/ContextDependencyHelpers.js +3 -3
- package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
- package/lib/dependencies/CssIcssExportDependency.js +190 -87
- package/lib/dependencies/CssUrlDependency.js +35 -4
- package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
- package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
- package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
- package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
- package/lib/dependencies/HarmonyImportDependency.js +12 -1
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +34 -107
- package/lib/dependencies/HarmonyImportGuard.js +192 -20
- package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
- package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
- package/lib/dependencies/HtmlEntryDependency.js +797 -172
- package/lib/dependencies/HtmlSourceDependency.js +26 -0
- package/lib/dependencies/ImportDependency.js +43 -1
- package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
- package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
- package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
- package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
- package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
- package/lib/dependencies/ImportMetaPlugin.js +532 -180
- package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
- package/lib/dependencies/ImportParserPlugin.js +87 -26
- package/lib/dependencies/ImportPhase.js +10 -8
- package/lib/dependencies/LoaderPlugin.js +5 -1
- package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
- package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
- package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
- package/lib/dependencies/SystemPlugin.js +2 -4
- package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
- package/lib/dependencies/URLDependency.js +141 -21
- package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
- package/lib/dependencies/WorkerDependency.js +86 -18
- package/lib/dependencies/WorkletDependency.js +278 -0
- package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
- package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
- package/lib/errors/LoaderLoadingError.js +20 -0
- package/lib/errors/ModuleParseError.js +39 -22
- package/lib/errors/NodeStuffInWebError.js +1 -1
- package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
- package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
- package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
- package/lib/hmr/lazyCompilationBackend.js +32 -8
- package/lib/html/HtmlGenerator.js +940 -91
- package/lib/html/HtmlModule.js +3 -2
- package/lib/html/HtmlModulesPlugin.js +625 -170
- package/lib/html/HtmlParser.js +812 -340
- package/lib/html/favicon.svg +1 -0
- package/lib/html/syntax.js +2116 -834
- package/lib/ids/IdHelpers.js +1 -1
- package/lib/index.js +6 -0
- package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
- package/lib/javascript/BasicEvaluatedExpression.js +366 -48
- package/lib/javascript/JavascriptGenerator.js +197 -49
- package/lib/javascript/JavascriptModulesPlugin.js +178 -24
- package/lib/javascript/JavascriptParser.js +711 -355
- package/lib/javascript/JavascriptParserHelpers.js +3 -6
- package/lib/javascript/syntax.js +4380 -0
- package/lib/library/ModuleLibraryPlugin.js +38 -1
- package/lib/loaders/LoaderRunner.js +714 -0
- package/lib/loaders/loadLoader.js +100 -0
- package/lib/node/ReadFileCompileAsyncWasmPlugin.js +19 -0
- package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
- package/lib/optimize/ConcatenatedModule.js +120 -16
- package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
- package/lib/optimize/InnerGraphPlugin.js +50 -30
- package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
- package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
- package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
- package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
- package/lib/prefetch/ResourceHintPlugin.js +663 -0
- package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
- package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
- package/lib/prefetch/parseResourceHintOptions.js +103 -0
- package/lib/rules/RuleSetCompiler.js +114 -0
- package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
- package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
- package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
- package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
- package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
- package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
- package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
- package/lib/schemes/HttpUriPlugin.js +40 -1
- package/lib/schemes/VirtualUrlPlugin.js +1 -2
- package/lib/serialization/FileMiddleware.js +29 -3
- package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
- package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
- package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
- package/lib/stats/StatsFactory.js +15 -8
- package/lib/url/URLParserPlugin.js +98 -19
- package/lib/util/AsyncQueue.js +122 -72
- package/lib/util/LocConverter.js +9 -8
- package/lib/util/SetHelpers.js +1 -1
- package/lib/util/SourceProcessor.js +41 -28
- package/lib/util/StackedMap.js +122 -48
- package/lib/util/binarySearchBounds.js +2 -2
- package/lib/util/concatenate.js +40 -14
- package/lib/util/createHooksRegistry.js +13 -5
- package/lib/util/dataURL.js +2 -2
- package/lib/util/deterministicGrouping.js +7 -5
- package/lib/util/formatSize.js +6 -2
- package/lib/util/globUtils.js +710 -0
- package/lib/util/identifier.js +23 -6
- package/lib/util/implicitTypeLoaderFallback.js +62 -0
- package/lib/util/internalSerializables.js +88 -71
- package/lib/util/magicComment.js +121 -25
- package/lib/util/numberHash.js +2 -2
- package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
- package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
- package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
- package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
- package/lib/web/FetchCompileWasmPlugin.js +14 -0
- package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
- package/lib/webpack.js +65 -0
- package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
- package/module.d.ts +21 -0
- package/package.json +14 -13
- package/schemas/WebpackOptions.check.js +1 -1
- package/schemas/WebpackOptions.json +759 -36
- package/schemas/plugins/HtmlParserOptions.check.js +1 -1
- package/schemas/plugins/ProgressPlugin.check.js +1 -1
- package/schemas/plugins/ProgressPlugin.json +17 -1
- package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
- package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
- package/schemas/plugins/container/ExternalsType.check.js +1 -1
- package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
- package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
- package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
- package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
- package/schemas/plugins/css/CssParserOptions.check.js +1 -1
- package/types.d.ts +1300 -325
package/lib/html/syntax.js
CHANGED
|
@@ -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
|
|
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 (
|
|
343
|
+
if (rangeEqualsLowerCase(input, start, end, "xmp")) return STATE_RAWTEXT;
|
|
343
344
|
return STATE_DATA;
|
|
344
345
|
case 5:
|
|
345
|
-
if (
|
|
346
|
-
if (
|
|
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 (
|
|
352
|
+
if (rangeEqualsLowerCase(input, start, end, "script")) {
|
|
350
353
|
return STATE_SCRIPT_DATA;
|
|
351
354
|
}
|
|
352
|
-
if (
|
|
355
|
+
if (rangeEqualsLowerCase(input, start, end, "iframe")) {
|
|
356
|
+
return STATE_RAWTEXT;
|
|
357
|
+
}
|
|
353
358
|
return STATE_DATA;
|
|
354
359
|
case 7:
|
|
355
|
-
if (
|
|
360
|
+
if (rangeEqualsLowerCase(input, start, end, "noembed")) {
|
|
361
|
+
return STATE_RAWTEXT;
|
|
362
|
+
}
|
|
356
363
|
return STATE_DATA;
|
|
357
364
|
case 8:
|
|
358
|
-
if (
|
|
359
|
-
|
|
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 (
|
|
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
|
|
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;
|
|
@@ -383,9 +394,9 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
383
394
|
let tagStart = pos;
|
|
384
395
|
let tagNameStart = -1;
|
|
385
396
|
let tagNameEnd = -1;
|
|
386
|
-
let
|
|
387
|
-
let
|
|
388
|
-
let
|
|
397
|
+
let attributeNameStart = -1;
|
|
398
|
+
let attributeNameEnd = -1;
|
|
399
|
+
let attributeValueStart = -1;
|
|
389
400
|
let attrQuoteType = QUOTE_NONE;
|
|
390
401
|
let commentStart = pos;
|
|
391
402
|
let lastOpenTagName = "";
|
|
@@ -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 `
|
|
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) => {
|
|
@@ -526,19 +549,19 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
526
549
|
// the callback is expected to do the same advance based on the
|
|
527
550
|
// reported `quoteType`.
|
|
528
551
|
let nextPos = attrQuoteType === QUOTE_NONE ? endPos : endPos + 1;
|
|
529
|
-
if (callbacks.attribute !== undefined &&
|
|
552
|
+
if (callbacks.attribute !== undefined && attributeNameStart !== -1) {
|
|
530
553
|
nextPos = callbacks.attribute(
|
|
531
554
|
input,
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
555
|
+
attributeNameStart,
|
|
556
|
+
attributeNameEnd,
|
|
557
|
+
attributeValueStart,
|
|
558
|
+
attributeValueStart === -1 ? -1 : endPos,
|
|
536
559
|
attrQuoteType
|
|
537
560
|
);
|
|
538
561
|
}
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
562
|
+
if (attributeNameStart !== -1) tagHasAttributes = true;
|
|
563
|
+
attributeNameStart = -1;
|
|
564
|
+
attributeValueStart = -1;
|
|
542
565
|
attrQuoteType = QUOTE_NONE;
|
|
543
566
|
return nextPos;
|
|
544
567
|
};
|
|
@@ -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 `
|
|
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
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
if (
|
|
633
|
-
|
|
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
|
|
|
@@ -806,14 +837,14 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
806
837
|
pos + 1,
|
|
807
838
|
"warning"
|
|
808
839
|
);
|
|
809
|
-
|
|
840
|
+
attributeNameStart = pos;
|
|
810
841
|
state = STATE_ATTRIBUTE_NAME;
|
|
811
842
|
pos++;
|
|
812
843
|
} else {
|
|
813
844
|
// Anything else
|
|
814
845
|
// Start a new attribute in the current tag token. Set that attribute name
|
|
815
846
|
// and value to the empty string. Reconsume in the attribute name state.
|
|
816
|
-
|
|
847
|
+
attributeNameStart = pos;
|
|
817
848
|
state = STATE_ATTRIBUTE_NAME;
|
|
818
849
|
// Reconsume
|
|
819
850
|
}
|
|
@@ -831,11 +862,11 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
831
862
|
// EOF
|
|
832
863
|
// Reconsume in the after attribute name state.
|
|
833
864
|
if (isSpace(cc) || cc === CC_SOLIDUS || cc === CC_GREATER_THAN) {
|
|
834
|
-
|
|
865
|
+
attributeNameEnd = pos;
|
|
835
866
|
state = STATE_AFTER_ATTRIBUTE_NAME;
|
|
836
867
|
// Reconsume
|
|
837
868
|
} else if (cc === CC_EQUALS) {
|
|
838
|
-
|
|
869
|
+
attributeNameEnd = pos;
|
|
839
870
|
state = STATE_BEFORE_ATTRIBUTE_VALUE;
|
|
840
871
|
pos++;
|
|
841
872
|
} else {
|
|
@@ -915,7 +946,7 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
915
946
|
// Anything else
|
|
916
947
|
// Start a new attribute in the current tag token.
|
|
917
948
|
emitAttribute(pos);
|
|
918
|
-
|
|
949
|
+
attributeNameStart = pos;
|
|
919
950
|
state = STATE_ATTRIBUTE_NAME;
|
|
920
951
|
// Reconsume
|
|
921
952
|
}
|
|
@@ -934,14 +965,14 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
934
965
|
} else if (cc === CC_QUOTATION_MARK) {
|
|
935
966
|
// U+0022 QUOTATION MARK (")
|
|
936
967
|
// Switch to the attribute value (double-quoted) state.
|
|
937
|
-
|
|
968
|
+
attributeValueStart = pos + 1;
|
|
938
969
|
attrQuoteType = QUOTE_DOUBLE;
|
|
939
970
|
state = STATE_ATTRIBUTE_VALUE_DOUBLE_QUOTED;
|
|
940
971
|
pos++;
|
|
941
972
|
} else if (cc === CC_APOSTROPHE) {
|
|
942
973
|
// U+0027 APOSTROPHE (')
|
|
943
974
|
// Switch to the attribute value (single-quoted) state.
|
|
944
|
-
|
|
975
|
+
attributeValueStart = pos + 1;
|
|
945
976
|
attrQuoteType = QUOTE_SINGLE;
|
|
946
977
|
state = STATE_ATTRIBUTE_VALUE_SINGLE_QUOTED;
|
|
947
978
|
pos++;
|
|
@@ -952,7 +983,7 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
952
983
|
// an empty value range pointing at the `>` so the open-tag offset range
|
|
953
984
|
// still includes the `>`.
|
|
954
985
|
reportError("missing-attribute-value", pos, pos + 1, "warning");
|
|
955
|
-
|
|
986
|
+
attributeValueStart = pos;
|
|
956
987
|
attrQuoteType = QUOTE_NONE;
|
|
957
988
|
pos = emitAttribute(pos);
|
|
958
989
|
if (input.charCodeAt(tagStart + 1) === CC_SOLIDUS) {
|
|
@@ -966,7 +997,7 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
966
997
|
} else {
|
|
967
998
|
// Anything else
|
|
968
999
|
// Reconsume in the attribute value (unquoted) state.
|
|
969
|
-
|
|
1000
|
+
attributeValueStart = pos;
|
|
970
1001
|
attrQuoteType = QUOTE_NONE;
|
|
971
1002
|
state = STATE_ATTRIBUTE_VALUE_UNQUOTED;
|
|
972
1003
|
// Reconsume
|
|
@@ -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
|
-
|
|
999
|
-
|
|
1000
|
-
if (
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
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
|
-
|
|
1035
|
-
|
|
1036
|
-
if (
|
|
1037
|
-
break;
|
|
1038
|
-
}
|
|
1039
|
-
pos++;
|
|
1069
|
+
if (nextSQuote < pos) {
|
|
1070
|
+
nextSQuote = input.indexOf("'", pos);
|
|
1071
|
+
if (nextSQuote === -1) nextSQuote = len;
|
|
1040
1072
|
}
|
|
1073
|
+
if (nextAmp < pos) {
|
|
1074
|
+
nextAmp = input.indexOf("&", pos);
|
|
1075
|
+
if (nextAmp === -1) nextAmp = len;
|
|
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
|
|
|
@@ -1301,7 +1342,20 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
1301
1342
|
if (cc === CC_NULL) {
|
|
1302
1343
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
1303
1344
|
}
|
|
1345
|
+
// Fast-forward ordinary comment text without re-entering the
|
|
1346
|
+
// state switch; stop on the significant code points above.
|
|
1304
1347
|
pos++;
|
|
1348
|
+
while (pos < len) {
|
|
1349
|
+
const c2 = input.charCodeAt(pos);
|
|
1350
|
+
if (
|
|
1351
|
+
c2 === CC_LESS_THAN ||
|
|
1352
|
+
c2 === CC_HYPHEN_MINUS ||
|
|
1353
|
+
c2 === CC_NULL
|
|
1354
|
+
) {
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
pos++;
|
|
1358
|
+
}
|
|
1305
1359
|
}
|
|
1306
1360
|
break;
|
|
1307
1361
|
|
|
@@ -1403,7 +1457,14 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
1403
1457
|
if (cc === CC_NULL) {
|
|
1404
1458
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
1405
1459
|
}
|
|
1460
|
+
// Fast-forward ordinary bogus-comment text without re-entering the
|
|
1461
|
+
// state switch; stop on the significant code points above.
|
|
1406
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
|
+
}
|
|
1407
1468
|
}
|
|
1408
1469
|
break;
|
|
1409
1470
|
|
|
@@ -2250,7 +2311,10 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2250
2311
|
} else {
|
|
2251
2312
|
// Anything else
|
|
2252
2313
|
// Emit the current input character as a character token.
|
|
2253
|
-
|
|
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;
|
|
2254
2318
|
}
|
|
2255
2319
|
break;
|
|
2256
2320
|
|
|
@@ -2319,15 +2383,23 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2319
2383
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2320
2384
|
pos++;
|
|
2321
2385
|
} else {
|
|
2322
|
-
// 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.
|
|
2323
2389
|
pos++;
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
if (
|
|
2327
|
-
break;
|
|
2328
|
-
}
|
|
2329
|
-
pos++;
|
|
2390
|
+
if (nextLt < pos) {
|
|
2391
|
+
nextLt = input.indexOf("<", pos);
|
|
2392
|
+
if (nextLt === -1) nextLt = len;
|
|
2330
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;
|
|
2401
|
+
}
|
|
2402
|
+
pos = Math.min(nextLt, nextAmp, nextNul);
|
|
2331
2403
|
}
|
|
2332
2404
|
break;
|
|
2333
2405
|
|
|
@@ -2382,7 +2454,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2382
2454
|
if (isSpace(cc)) {
|
|
2383
2455
|
tagNameEnd = pos;
|
|
2384
2456
|
if (
|
|
2385
|
-
|
|
2457
|
+
rangeEqualsLowerCase(
|
|
2458
|
+
input,
|
|
2459
|
+
tagNameStart,
|
|
2460
|
+
tagNameEnd,
|
|
2461
|
+
lastOpenTagName
|
|
2462
|
+
)
|
|
2386
2463
|
) {
|
|
2387
2464
|
flushText(tagStart);
|
|
2388
2465
|
state = STATE_BEFORE_ATTRIBUTE_NAME;
|
|
@@ -2398,7 +2475,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2398
2475
|
// "anything else" entry below.
|
|
2399
2476
|
tagNameEnd = pos;
|
|
2400
2477
|
if (
|
|
2401
|
-
|
|
2478
|
+
rangeEqualsLowerCase(
|
|
2479
|
+
input,
|
|
2480
|
+
tagNameStart,
|
|
2481
|
+
tagNameEnd,
|
|
2482
|
+
lastOpenTagName
|
|
2483
|
+
)
|
|
2402
2484
|
) {
|
|
2403
2485
|
flushText(tagStart);
|
|
2404
2486
|
state = STATE_SELF_CLOSING_START_TAG;
|
|
@@ -2414,7 +2496,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2414
2496
|
// per the "anything else" entry below.
|
|
2415
2497
|
tagNameEnd = pos;
|
|
2416
2498
|
if (
|
|
2417
|
-
|
|
2499
|
+
rangeEqualsLowerCase(
|
|
2500
|
+
input,
|
|
2501
|
+
tagNameStart,
|
|
2502
|
+
tagNameEnd,
|
|
2503
|
+
lastOpenTagName
|
|
2504
|
+
)
|
|
2418
2505
|
) {
|
|
2419
2506
|
flushText(tagStart);
|
|
2420
2507
|
state = STATE_DATA;
|
|
@@ -2454,13 +2541,19 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2454
2541
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2455
2542
|
pos++;
|
|
2456
2543
|
} else {
|
|
2457
|
-
// 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.
|
|
2458
2547
|
pos++;
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
if (
|
|
2462
|
-
|
|
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;
|
|
2463
2555
|
}
|
|
2556
|
+
pos = Math.min(nextLt, nextNul);
|
|
2464
2557
|
}
|
|
2465
2558
|
break;
|
|
2466
2559
|
|
|
@@ -2514,7 +2607,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2514
2607
|
if (isSpace(cc)) {
|
|
2515
2608
|
tagNameEnd = pos;
|
|
2516
2609
|
if (
|
|
2517
|
-
|
|
2610
|
+
rangeEqualsLowerCase(
|
|
2611
|
+
input,
|
|
2612
|
+
tagNameStart,
|
|
2613
|
+
tagNameEnd,
|
|
2614
|
+
lastOpenTagName
|
|
2615
|
+
)
|
|
2518
2616
|
) {
|
|
2519
2617
|
flushText(tagStart);
|
|
2520
2618
|
state = STATE_BEFORE_ATTRIBUTE_NAME;
|
|
@@ -2529,7 +2627,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2529
2627
|
// "anything else" entry below.
|
|
2530
2628
|
tagNameEnd = pos;
|
|
2531
2629
|
if (
|
|
2532
|
-
|
|
2630
|
+
rangeEqualsLowerCase(
|
|
2631
|
+
input,
|
|
2632
|
+
tagNameStart,
|
|
2633
|
+
tagNameEnd,
|
|
2634
|
+
lastOpenTagName
|
|
2635
|
+
)
|
|
2533
2636
|
) {
|
|
2534
2637
|
flushText(tagStart);
|
|
2535
2638
|
state = STATE_SELF_CLOSING_START_TAG;
|
|
@@ -2544,7 +2647,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2544
2647
|
// per the "anything else" entry below.
|
|
2545
2648
|
tagNameEnd = pos;
|
|
2546
2649
|
if (
|
|
2547
|
-
|
|
2650
|
+
rangeEqualsLowerCase(
|
|
2651
|
+
input,
|
|
2652
|
+
tagNameStart,
|
|
2653
|
+
tagNameEnd,
|
|
2654
|
+
lastOpenTagName
|
|
2655
|
+
)
|
|
2548
2656
|
) {
|
|
2549
2657
|
flushText(tagStart);
|
|
2550
2658
|
state = STATE_DATA;
|
|
@@ -2583,13 +2691,19 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2583
2691
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2584
2692
|
pos++;
|
|
2585
2693
|
} else {
|
|
2586
|
-
// 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.
|
|
2587
2697
|
pos++;
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
if (
|
|
2591
|
-
|
|
2698
|
+
if (nextLt < pos) {
|
|
2699
|
+
nextLt = input.indexOf("<", pos);
|
|
2700
|
+
if (nextLt === -1) nextLt = len;
|
|
2701
|
+
}
|
|
2702
|
+
if (nextNul < pos) {
|
|
2703
|
+
nextNul = input.indexOf("\0", pos);
|
|
2704
|
+
if (nextNul === -1) nextNul = len;
|
|
2592
2705
|
}
|
|
2706
|
+
pos = Math.min(nextLt, nextNul);
|
|
2593
2707
|
}
|
|
2594
2708
|
break;
|
|
2595
2709
|
|
|
@@ -2649,7 +2763,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2649
2763
|
if (isSpace(cc)) {
|
|
2650
2764
|
tagNameEnd = pos;
|
|
2651
2765
|
if (
|
|
2652
|
-
|
|
2766
|
+
rangeEqualsLowerCase(
|
|
2767
|
+
input,
|
|
2768
|
+
tagNameStart,
|
|
2769
|
+
tagNameEnd,
|
|
2770
|
+
lastOpenTagName
|
|
2771
|
+
)
|
|
2653
2772
|
) {
|
|
2654
2773
|
flushText(tagStart);
|
|
2655
2774
|
state = STATE_BEFORE_ATTRIBUTE_NAME;
|
|
@@ -2664,7 +2783,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2664
2783
|
// "anything else" entry below.
|
|
2665
2784
|
tagNameEnd = pos;
|
|
2666
2785
|
if (
|
|
2667
|
-
|
|
2786
|
+
rangeEqualsLowerCase(
|
|
2787
|
+
input,
|
|
2788
|
+
tagNameStart,
|
|
2789
|
+
tagNameEnd,
|
|
2790
|
+
lastOpenTagName
|
|
2791
|
+
)
|
|
2668
2792
|
) {
|
|
2669
2793
|
flushText(tagStart);
|
|
2670
2794
|
state = STATE_SELF_CLOSING_START_TAG;
|
|
@@ -2679,7 +2803,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2679
2803
|
// per the "anything else" entry below.
|
|
2680
2804
|
tagNameEnd = pos;
|
|
2681
2805
|
if (
|
|
2682
|
-
|
|
2806
|
+
rangeEqualsLowerCase(
|
|
2807
|
+
input,
|
|
2808
|
+
tagNameStart,
|
|
2809
|
+
tagNameEnd,
|
|
2810
|
+
lastOpenTagName
|
|
2811
|
+
)
|
|
2683
2812
|
) {
|
|
2684
2813
|
flushText(tagStart);
|
|
2685
2814
|
state = STATE_DATA;
|
|
@@ -2757,7 +2886,20 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2757
2886
|
if (cc === CC_NULL) {
|
|
2758
2887
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2759
2888
|
}
|
|
2889
|
+
// Fast-forward ordinary escaped script text without re-entering
|
|
2890
|
+
// the state switch; stop on the significant code points above.
|
|
2760
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
|
+
}
|
|
2761
2903
|
}
|
|
2762
2904
|
break;
|
|
2763
2905
|
|
|
@@ -2877,7 +3019,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2877
3019
|
if (isSpace(cc)) {
|
|
2878
3020
|
tagNameEnd = pos;
|
|
2879
3021
|
if (
|
|
2880
|
-
|
|
3022
|
+
rangeEqualsLowerCase(
|
|
3023
|
+
input,
|
|
3024
|
+
tagNameStart,
|
|
3025
|
+
tagNameEnd,
|
|
3026
|
+
lastOpenTagName
|
|
3027
|
+
)
|
|
2881
3028
|
) {
|
|
2882
3029
|
flushText(tagStart);
|
|
2883
3030
|
state = STATE_BEFORE_ATTRIBUTE_NAME;
|
|
@@ -2892,7 +3039,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2892
3039
|
// "anything else" entry below.
|
|
2893
3040
|
tagNameEnd = pos;
|
|
2894
3041
|
if (
|
|
2895
|
-
|
|
3042
|
+
rangeEqualsLowerCase(
|
|
3043
|
+
input,
|
|
3044
|
+
tagNameStart,
|
|
3045
|
+
tagNameEnd,
|
|
3046
|
+
lastOpenTagName
|
|
3047
|
+
)
|
|
2896
3048
|
) {
|
|
2897
3049
|
flushText(tagStart);
|
|
2898
3050
|
state = STATE_SELF_CLOSING_START_TAG;
|
|
@@ -2907,7 +3059,12 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2907
3059
|
// per the "anything else" entry below.
|
|
2908
3060
|
tagNameEnd = pos;
|
|
2909
3061
|
if (
|
|
2910
|
-
|
|
3062
|
+
rangeEqualsLowerCase(
|
|
3063
|
+
input,
|
|
3064
|
+
tagNameStart,
|
|
3065
|
+
tagNameEnd,
|
|
3066
|
+
lastOpenTagName
|
|
3067
|
+
)
|
|
2911
3068
|
) {
|
|
2912
3069
|
flushText(tagStart);
|
|
2913
3070
|
state = STATE_DATA;
|
|
@@ -2989,7 +3146,20 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
2989
3146
|
if (cc === CC_NULL) {
|
|
2990
3147
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2991
3148
|
}
|
|
3149
|
+
// Fast-forward ordinary double-escaped script text without re-entering
|
|
3150
|
+
// the state switch; stop on the significant code points above.
|
|
2992
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
|
+
}
|
|
2993
3163
|
}
|
|
2994
3164
|
break;
|
|
2995
3165
|
|
|
@@ -3175,25 +3345,25 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
3175
3345
|
pos + runLen < len && input.charCodeAt(pos + runLen) === CC_SEMICOLON;
|
|
3176
3346
|
namedEntityConsumed = 0;
|
|
3177
3347
|
let matchedWithSemicolon = false;
|
|
3178
|
-
//
|
|
3179
|
-
//
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
// Try with trailing `;` first if one is present after the run.
|
|
3185
|
-
if (
|
|
3186
|
-
n === runLen &&
|
|
3187
|
-
hasSemicolon &&
|
|
3188
|
-
HTML_ENTITIES[`${bare};`] !== undefined
|
|
3189
|
-
) {
|
|
3190
|
-
namedEntityConsumed = n + 1;
|
|
3348
|
+
// Try the full run with its trailing `;` first — the overwhelmingly
|
|
3349
|
+
// common case (`&`, ` `, …) then needs exactly one slice.
|
|
3350
|
+
if (hasSemicolon && runLen > 0) {
|
|
3351
|
+
const withSemicolon = input.slice(pos, pos + runLen + 1);
|
|
3352
|
+
if (HTML_ENTITIES[withSemicolon] !== undefined) {
|
|
3353
|
+
namedEntityConsumed = runLen + 1;
|
|
3191
3354
|
matchedWithSemicolon = true;
|
|
3192
|
-
break;
|
|
3193
3355
|
}
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3356
|
+
}
|
|
3357
|
+
if (namedEntityConsumed === 0) {
|
|
3358
|
+
// Slice the candidate run once; prefixes come from this short
|
|
3359
|
+
// string instead of re-slicing the input per length.
|
|
3360
|
+
const run = input.slice(pos, pos + runLen);
|
|
3361
|
+
for (let n = runLen; n > 0; n--) {
|
|
3362
|
+
const bare = n === runLen ? run : run.slice(0, n);
|
|
3363
|
+
if (HTML_ENTITIES[bare] !== undefined) {
|
|
3364
|
+
namedEntityConsumed = n;
|
|
3365
|
+
break;
|
|
3366
|
+
}
|
|
3197
3367
|
}
|
|
3198
3368
|
}
|
|
3199
3369
|
if (namedEntityConsumed > 0) {
|
|
@@ -3208,12 +3378,10 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
3208
3378
|
returnState === STATE_ATTRIBUTE_VALUE_DOUBLE_QUOTED ||
|
|
3209
3379
|
returnState === STATE_ATTRIBUTE_VALUE_SINGLE_QUOTED ||
|
|
3210
3380
|
returnState === STATE_ATTRIBUTE_VALUE_UNQUOTED;
|
|
3211
|
-
if (
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
)
|
|
3216
|
-
) {
|
|
3381
|
+
if (!(
|
|
3382
|
+
inAttribute &&
|
|
3383
|
+
(next === CC_EQUALS || isAsciiAlphanumeric(next))
|
|
3384
|
+
)) {
|
|
3217
3385
|
reportError(
|
|
3218
3386
|
"missing-semicolon-after-character-reference",
|
|
3219
3387
|
pos + namedEntityConsumed,
|
|
@@ -3470,11 +3638,11 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
3470
3638
|
// dropping the in-progress tag, we emit its offset range up to EOF.
|
|
3471
3639
|
reportError("eof-in-tag", len, len, "error");
|
|
3472
3640
|
// If we hit EOF mid-attribute-name, the name runs to EOF. Set
|
|
3473
|
-
//
|
|
3474
|
-
if (state === STATE_ATTRIBUTE_NAME &&
|
|
3475
|
-
|
|
3641
|
+
// attributeNameEnd here so the emitted attribute range is valid.
|
|
3642
|
+
if (state === STATE_ATTRIBUTE_NAME && attributeNameStart !== -1) {
|
|
3643
|
+
attributeNameEnd = len;
|
|
3476
3644
|
}
|
|
3477
|
-
if (
|
|
3645
|
+
if (attributeNameStart !== -1) emitAttribute(len);
|
|
3478
3646
|
// If we hit EOF before the tag-name end was recorded, the name runs
|
|
3479
3647
|
// to EOF. `tagNameEnd` may carry over from a previously emitted tag,
|
|
3480
3648
|
// so reset it whenever it's missing or stale (less than `tagNameStart`)
|
|
@@ -3607,15 +3775,6 @@ const decodeNumericReference = (code) => {
|
|
|
3607
3775
|
return String.fromCodePoint(code);
|
|
3608
3776
|
};
|
|
3609
3777
|
|
|
3610
|
-
// Match one of three forms (each with an optional trailing `;`):
|
|
3611
|
-
// `&#x<hex>` - hex numeric reference (requires the `x`/`X`).
|
|
3612
|
-
// `&#<dec>` - decimal numeric reference (digits only).
|
|
3613
|
-
// `&<name>` - named reference (letter followed by alphanumerics).
|
|
3614
|
-
// The three alternatives are kept separate so a decimal reference like
|
|
3615
|
-
// `Ab` doesn't greedily eat the trailing `b` as if it were hex.
|
|
3616
|
-
const CHARACTER_REFERENCE_REGEXP =
|
|
3617
|
-
/&(?:#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);?/g;
|
|
3618
|
-
|
|
3619
3778
|
/**
|
|
3620
3779
|
* Decode a single matched character reference.
|
|
3621
3780
|
* @param {string} match the matched reference text
|
|
@@ -3623,45 +3782,34 @@ const CHARACTER_REFERENCE_REGEXP =
|
|
|
3623
3782
|
* @param {boolean=} isAttribute true when the match came from an attribute value
|
|
3624
3783
|
* @returns {string} decoded text, or `match` itself when it stays literal
|
|
3625
3784
|
*/
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
return decodeNumericReference(Number.parseInt(body, isHex ? 16 : 10));
|
|
3641
|
-
}
|
|
3642
|
-
|
|
3643
|
-
// Named reference. Try the full captured name first, then
|
|
3644
|
-
// progressively shorter prefixes - this handles direct matches
|
|
3645
|
-
// like `&` as well as WHATWG longest-prefix semantics where
|
|
3646
|
-
// e.g. `¬pre;` decodes as `¬` (a legacy bare entity)
|
|
3647
|
-
// followed by `pre;` as literal text.
|
|
3648
|
-
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);
|
|
3649
3799
|
const matchEndsWithSemi = name.charCodeAt(name.length - 1) === 0x3b;
|
|
3650
3800
|
|
|
3651
3801
|
// Attribute-context guard: if the entity match didn't end with `;`
|
|
3652
3802
|
// and the next character in the source is `=` or ASCII
|
|
3653
3803
|
// alphanumeric, the WHATWG spec says to flush the literal text
|
|
3654
|
-
// rather than decode. The greedy
|
|
3804
|
+
// rather than decode. The greedy scan already absorbed any
|
|
3655
3805
|
// trailing alphanumerics, so the only candidate "next char" here
|
|
3656
3806
|
// is `=` (or any non-alphanumeric).
|
|
3657
3807
|
if (isAttribute && !matchEndsWithSemi && nextCharCode === 0x3d /* = */) {
|
|
3658
|
-
return
|
|
3808
|
+
return undefined;
|
|
3659
3809
|
}
|
|
3660
3810
|
|
|
3661
|
-
// Fast path: the
|
|
3662
|
-
// `<`, ` `,
|
|
3663
|
-
// full-length `name.slice(0, name.length)` the loop's first iteration
|
|
3664
|
-
// would allocate. No leftover, so the attribute guard never applies.
|
|
3811
|
+
// Fast path: the scan usually captures exactly one entity (`&`,
|
|
3812
|
+
// `<`, ` `, ...), so the whole `name` is the match.
|
|
3665
3813
|
if (name.length <= MAX_ENTITY_NAME_LEN) {
|
|
3666
3814
|
const full = HTML_ENTITIES[name];
|
|
3667
3815
|
if (full !== undefined) return full;
|
|
@@ -3680,23 +3828,21 @@ const decodeOneReference = (match, nextCharCode, isAttribute) => {
|
|
|
3680
3828
|
// Attribute-context longest-prefix guard: if the matched
|
|
3681
3829
|
// prefix doesn't end with `;` and the leftover starts with
|
|
3682
3830
|
// an alphanumeric character, leave literal per WHATWG.
|
|
3683
|
-
// (The regex greedy-consumes alphanumerics, so any leftover
|
|
3684
|
-
// within `name` is itself alphanumeric — we only need to
|
|
3685
|
-
// check non-empty leftover here; the `=` case is handled
|
|
3686
|
-
// above against the source character after the match.)
|
|
3687
3831
|
if (
|
|
3688
3832
|
isAttribute &&
|
|
3689
3833
|
i < name.length &&
|
|
3690
3834
|
prefix.charCodeAt(prefix.length - 1) !== 0x3b
|
|
3691
3835
|
) {
|
|
3692
|
-
return
|
|
3836
|
+
return undefined;
|
|
3693
3837
|
}
|
|
3694
3838
|
return HTML_ENTITIES[prefix] + name.slice(i);
|
|
3695
3839
|
}
|
|
3696
3840
|
}
|
|
3697
|
-
return
|
|
3841
|
+
return undefined;
|
|
3698
3842
|
};
|
|
3699
3843
|
|
|
3844
|
+
/** @typedef {{ text: string, map: number[] | undefined }} DecodedEntitiesWithMap */
|
|
3845
|
+
|
|
3700
3846
|
/**
|
|
3701
3847
|
* Decode HTML character references in a string. Handles all numeric
|
|
3702
3848
|
* references (with WHATWG remap of 0x00, surrogates, out-of-range, and the
|
|
@@ -3708,70 +3854,246 @@ const decodeOneReference = (match, nextCharCode, isAttribute) => {
|
|
|
3708
3854
|
* trailing `;` whose next character is `=` or ASCII alphanumeric is left
|
|
3709
3855
|
* undecoded, so e.g. `&=foo` stays literal in an attribute value but
|
|
3710
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
|
|
3711
3865
|
* @param {string} str the raw string from the token slice
|
|
3712
3866
|
* @param {boolean=} isAttribute true if `str` came from an attribute value
|
|
3713
3867
|
* @returns {string} decoded string
|
|
3714
3868
|
*/
|
|
3715
|
-
const decodeHtmlEntities = (str, isAttribute) => {
|
|
3716
|
-
if (!str.includes("&")) return str;
|
|
3717
|
-
|
|
3718
|
-
return str.replace(CHARACTER_REFERENCE_REGEXP, (match, offset, source) =>
|
|
3719
|
-
decodeOneReference(
|
|
3720
|
-
match,
|
|
3721
|
-
source.charCodeAt(offset + match.length),
|
|
3722
|
-
isAttribute
|
|
3723
|
-
)
|
|
3724
|
-
);
|
|
3725
|
-
};
|
|
3726
|
-
|
|
3727
3869
|
/**
|
|
3728
|
-
*
|
|
3729
|
-
*
|
|
3730
|
-
*
|
|
3731
|
-
*
|
|
3732
|
-
*
|
|
3733
|
-
|
|
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
|
+
/**
|
|
3734
3877
|
* @param {string} str the raw string from the token slice
|
|
3735
3878
|
* @param {boolean=} isAttribute true if `str` came from an attribute value
|
|
3736
|
-
* @
|
|
3879
|
+
* @param {boolean=} withMap also build the decoded-to-raw boundary map
|
|
3880
|
+
* @returns {string | DecodedEntitiesWithMap} decoded string (with map when `withMap`)
|
|
3737
3881
|
*/
|
|
3738
|
-
const
|
|
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 `Ab` 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;
|
|
3739
3894
|
/** @type {number[] | undefined} */
|
|
3740
3895
|
let map;
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
let
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
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 = "&";
|
|
4019
|
+
break;
|
|
4020
|
+
case CC_QUOTATION_MARK:
|
|
4021
|
+
rep = """;
|
|
4022
|
+
break;
|
|
4023
|
+
case CC_NO_BREAK_SPACE:
|
|
4024
|
+
rep = " ";
|
|
4025
|
+
break;
|
|
4026
|
+
case CC_LF:
|
|
4027
|
+
rep = " ";
|
|
4028
|
+
break;
|
|
4029
|
+
case CC_CR:
|
|
4030
|
+
rep = " ";
|
|
4031
|
+
break;
|
|
4032
|
+
default:
|
|
4033
|
+
continue;
|
|
3766
4034
|
}
|
|
4035
|
+
out += s.slice(last, i) + rep;
|
|
4036
|
+
last = i + 1;
|
|
3767
4037
|
}
|
|
3768
|
-
return
|
|
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 = "&";
|
|
4068
|
+
break;
|
|
4069
|
+
case CC_LESS_THAN:
|
|
4070
|
+
rep = "<";
|
|
4071
|
+
break;
|
|
4072
|
+
case CC_GREATER_THAN:
|
|
4073
|
+
rep = ">";
|
|
4074
|
+
break;
|
|
4075
|
+
case CC_NO_BREAK_SPACE:
|
|
4076
|
+
rep = " ";
|
|
4077
|
+
break;
|
|
4078
|
+
case CC_LF:
|
|
4079
|
+
rep = " ";
|
|
4080
|
+
break;
|
|
4081
|
+
case CC_CR:
|
|
4082
|
+
rep = " ";
|
|
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);
|
|
3769
4091
|
};
|
|
3770
4092
|
|
|
3771
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
|
|
3772
4094
|
|
|
3773
4095
|
// WHATWG HTML tree construction (https://html.spec.whatwg.org/multipage/parsing.html#tree-construction)
|
|
3774
|
-
// on top of
|
|
4096
|
+
// on top of tokenize. Scripting is always disabled (webpack is a build tool).
|
|
3775
4097
|
|
|
3776
4098
|
// Namespaces (mirrors swc_html_ast::Namespace)
|
|
3777
4099
|
const NS_HTML = 0;
|
|
@@ -3779,6 +4101,389 @@ const NS_MATHML = 1;
|
|
|
3779
4101
|
const NS_SVG = 2;
|
|
3780
4102
|
|
|
3781
4103
|
/**
|
|
4104
|
+
* AST node `type` discriminators. Numeric for the same reason as the CSS
|
|
4105
|
+
* `NodeType`: compact integer `===` dispatch on the tree-construction and
|
|
4106
|
+
* visitor-walk hot paths.
|
|
4107
|
+
* @type {{ Document: 1, DocumentFragment: 2, Element: 3, Text: 4, Comment: 5, Doctype: 6 }}
|
|
4108
|
+
*/
|
|
4109
|
+
const NodeType = {
|
|
4110
|
+
Document: 1,
|
|
4111
|
+
DocumentFragment: 2,
|
|
4112
|
+
Element: 3,
|
|
4113
|
+
Text: 4,
|
|
4114
|
+
Comment: 5,
|
|
4115
|
+
Doctype: 6
|
|
4116
|
+
};
|
|
4117
|
+
|
|
4118
|
+
/**
|
|
4119
|
+
* A contiguous run of attribute ids in the attribute columns — how a start-tag
|
|
4120
|
+
* token and an element refer to their attributes. `start` is the first id
|
|
4121
|
+
* (`count` 0 = none).
|
|
4122
|
+
* @typedef {{ start: number, count: number }} AttributeRun
|
|
4123
|
+
*/
|
|
4124
|
+
|
|
4125
|
+
// Shared frozen empty run for attributeless elements and synthesized tags.
|
|
4126
|
+
const EMPTY_ATTRS = /** @type {AttributeRun} */ (
|
|
4127
|
+
Object.freeze({ start: 0, count: 0 })
|
|
4128
|
+
);
|
|
4129
|
+
|
|
4130
|
+
// === Struct-of-arrays AST backend ===
|
|
4131
|
+
// One AST node = one integer id (`HtmlNodeRef`) indexing the parallel columns
|
|
4132
|
+
// below — no per-node object and no per-parent children array. Tree shape
|
|
4133
|
+
// lives in the four link columns (parent / firstChild / lastChild /
|
|
4134
|
+
// nextSibling); the only heap references are the string payload / attribute
|
|
4135
|
+
// name-and-value side arrays. Columns are module-level and reused
|
|
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;
|
|
4143
|
+
/** `NodeType` per node */
|
|
4144
|
+
let _nodeTypes = new Uint8Array(0);
|
|
4145
|
+
/** bits 0-1 namespace (`NS_*`), bit 2 self-closing (void element) */
|
|
4146
|
+
let _nodeFlags = new Uint8Array(0);
|
|
4147
|
+
let _nodeStarts = new Int32Array(0);
|
|
4148
|
+
let _nodeEnds = new Int32Array(0);
|
|
4149
|
+
/** end offset of an element's opening tag (after `>`) */
|
|
4150
|
+
let _nodeTagEnds = new Int32Array(0);
|
|
4151
|
+
/** end offset of an element's tag name */
|
|
4152
|
+
let _nodeNameEnds = new Int32Array(0);
|
|
4153
|
+
/** under `skip.text`, end offset of a raw-text element's body (`HtmlAstSkip`) */
|
|
4154
|
+
let _nodeContentEnds = new Int32Array(0);
|
|
4155
|
+
/** a `<template>`'s content DocumentFragment (0 = none) */
|
|
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);
|
|
4161
|
+
/** @type {string[]} tag name / text data / comment data / doctype name */
|
|
4162
|
+
const _nodeStrings = [];
|
|
4163
|
+
/** first attribute id of an element's contiguous run */
|
|
4164
|
+
let _nodeAttrStarts = new Int32Array(0);
|
|
4165
|
+
/** attribute count of an element's run */
|
|
4166
|
+
let _nodeAttrCounts = new Int32Array(0);
|
|
4167
|
+
// The single doctype node's public/system ids (a document inserts at most one
|
|
4168
|
+
// doctype node — later doctype tokens are ignored — so no column is needed).
|
|
4169
|
+
/** @type {string | null} */
|
|
4170
|
+
let _doctypePublicId = null;
|
|
4171
|
+
/** @type {string | null} */
|
|
4172
|
+
let _doctypeSystemId = null;
|
|
4173
|
+
|
|
4174
|
+
const NS_MASK = 3;
|
|
4175
|
+
const FLAG_SELF_CLOSING = 4;
|
|
4176
|
+
|
|
4177
|
+
/** @type {(el: HtmlNodeRef) => string} */
|
|
4178
|
+
const _tagNameOf = (el) => _nodeStrings[el];
|
|
4179
|
+
/** @type {(el: HtmlNodeRef) => number} */
|
|
4180
|
+
const _namespaceOf = (el) => _nodeFlags[el] & NS_MASK;
|
|
4181
|
+
|
|
4182
|
+
// === Attribute columns ===
|
|
4183
|
+
// One attribute = one integer id into these columns; an element (and a
|
|
4184
|
+
// start-tag token) holds a contiguous run. The value string is derived from
|
|
4185
|
+
// the source by offset on read — `_attrValues` carries an override only for
|
|
4186
|
+
// valueless attributes (`""`) and offset-less adoption-agency clones — and the
|
|
4187
|
+
// html5lib serializer name is derived from the adjusted name plus one flag
|
|
4188
|
+
// bit, so per attribute only the interned name pointer is retained.
|
|
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);
|
|
4195
|
+
/** bit 0: name has a `FOREIGN_ATTR_NS` serializer name (set on foreign adjust) */
|
|
4196
|
+
let _attrFlags = new Uint8Array(0);
|
|
4197
|
+
/** @type {string[]} lowercased (foreign-content: adjusted) attribute name */
|
|
4198
|
+
const _attrNames = [];
|
|
4199
|
+
/** @type {(string | null)[]} value override (null = slice the source by offset) */
|
|
4200
|
+
const _attrValues = [];
|
|
4201
|
+
/** source of the current parse, for by-offset attribute values */
|
|
4202
|
+
let _htmlSource = "";
|
|
4203
|
+
|
|
4204
|
+
/** @param {number} need minimum capacity */
|
|
4205
|
+
const _growAttrColumns = (need) => {
|
|
4206
|
+
let cap = _attrCapacity || 4096;
|
|
4207
|
+
while (cap < need) cap *= 2;
|
|
4208
|
+
const nameStart = new Int32Array(cap);
|
|
4209
|
+
nameStart.set(_attrNameStarts);
|
|
4210
|
+
_attrNameStarts = nameStart;
|
|
4211
|
+
const nameEnd = new Int32Array(cap);
|
|
4212
|
+
nameEnd.set(_attrNameEnds);
|
|
4213
|
+
_attrNameEnds = nameEnd;
|
|
4214
|
+
const valStart = new Int32Array(cap);
|
|
4215
|
+
valStart.set(_attrValueStarts);
|
|
4216
|
+
_attrValueStarts = valStart;
|
|
4217
|
+
const valEnd = new Int32Array(cap);
|
|
4218
|
+
valEnd.set(_attrValueEnds);
|
|
4219
|
+
_attrValueEnds = valEnd;
|
|
4220
|
+
const fl = new Uint8Array(cap);
|
|
4221
|
+
fl.set(_attrFlags);
|
|
4222
|
+
_attrFlags = fl;
|
|
4223
|
+
_attrCapacity = cap;
|
|
4224
|
+
};
|
|
4225
|
+
|
|
4226
|
+
/** @type {(name: string, value: string | null, nameStart: number, nameEnd: number, valueStart: number, valueEnd: number) => number} */
|
|
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;
|
|
4235
|
+
// Ids are sequential, so these indexed writes append (arrays stay packed).
|
|
4236
|
+
_attrNames[i] = name;
|
|
4237
|
+
_attrValues[i] = value;
|
|
4238
|
+
return i;
|
|
4239
|
+
};
|
|
4240
|
+
|
|
4241
|
+
/** @type {(i: number) => string} */
|
|
4242
|
+
const _attrValueOf = (i) => {
|
|
4243
|
+
const v = _attrValues[i];
|
|
4244
|
+
return v !== null
|
|
4245
|
+
? v
|
|
4246
|
+
: _htmlSource.slice(_attrValueStarts[i], _attrValueEnds[i]);
|
|
4247
|
+
};
|
|
4248
|
+
|
|
4249
|
+
// Linear name lookup in a run — attribute lists are short, a loop beats a Map.
|
|
4250
|
+
/** @type {(start: number, count: number, name: string) => number} */
|
|
4251
|
+
const _findAttr = (start, count, name) => {
|
|
4252
|
+
for (let i = start; i < start + count; i++) {
|
|
4253
|
+
if (_attrNames[i] === name) return i;
|
|
4254
|
+
}
|
|
4255
|
+
return 0;
|
|
4256
|
+
};
|
|
4257
|
+
|
|
4258
|
+
/** @type {(i: number) => number} exact copy of an attribute into a new id */
|
|
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]
|
|
4267
|
+
);
|
|
4268
|
+
_attrFlags[c] = _attrFlags[i];
|
|
4269
|
+
return c;
|
|
4270
|
+
};
|
|
4271
|
+
|
|
4272
|
+
// html5lib serializer name, derived: a `FOREIGN_ATTR_NS`-adjusted attribute is
|
|
4273
|
+
// flagged (its name is the table key), and a camelCase-adjusted name contains
|
|
4274
|
+
// an uppercase letter (unadjusted names are always lowercased), serializing as
|
|
4275
|
+
// itself. Everything else serializes as the plain name (undefined here).
|
|
4276
|
+
/** @type {(i: number) => string | undefined} */
|
|
4277
|
+
const _attrSerializedName = (i) => {
|
|
4278
|
+
if ((_attrFlags[i] & 1) !== 0) return FOREIGN_ATTR_NS[_attrNames[i]];
|
|
4279
|
+
const name = _attrNames[i];
|
|
4280
|
+
return /[A-Z]/.test(name) ? name : undefined;
|
|
4281
|
+
};
|
|
4282
|
+
|
|
4283
|
+
/** @param {number} need minimum capacity */
|
|
4284
|
+
const _growNodeColumns = (need) => {
|
|
4285
|
+
let cap = _nodeCapacity || 4096;
|
|
4286
|
+
while (cap < need) cap *= 2;
|
|
4287
|
+
const ty = new Uint8Array(cap);
|
|
4288
|
+
ty.set(_nodeTypes);
|
|
4289
|
+
_nodeTypes = ty;
|
|
4290
|
+
const fl = new Uint8Array(cap);
|
|
4291
|
+
fl.set(_nodeFlags);
|
|
4292
|
+
_nodeFlags = fl;
|
|
4293
|
+
const st = new Int32Array(cap);
|
|
4294
|
+
st.set(_nodeStarts);
|
|
4295
|
+
_nodeStarts = st;
|
|
4296
|
+
const en = new Int32Array(cap);
|
|
4297
|
+
en.set(_nodeEnds);
|
|
4298
|
+
_nodeEnds = en;
|
|
4299
|
+
const tagEnd = new Int32Array(cap);
|
|
4300
|
+
tagEnd.set(_nodeTagEnds);
|
|
4301
|
+
_nodeTagEnds = tagEnd;
|
|
4302
|
+
const nameEnd = new Int32Array(cap);
|
|
4303
|
+
nameEnd.set(_nodeNameEnds);
|
|
4304
|
+
_nodeNameEnds = nameEnd;
|
|
4305
|
+
const cEnd = new Int32Array(cap);
|
|
4306
|
+
cEnd.set(_nodeContentEnds);
|
|
4307
|
+
_nodeContentEnds = cEnd;
|
|
4308
|
+
const tc = new Int32Array(cap);
|
|
4309
|
+
tc.set(_nodeTemplateContents);
|
|
4310
|
+
_nodeTemplateContents = tc;
|
|
4311
|
+
const parent = new Int32Array(cap);
|
|
4312
|
+
parent.set(_nodeParents);
|
|
4313
|
+
_nodeParents = parent;
|
|
4314
|
+
const first = new Int32Array(cap);
|
|
4315
|
+
first.set(_nodeFirstChildren);
|
|
4316
|
+
_nodeFirstChildren = first;
|
|
4317
|
+
const last = new Int32Array(cap);
|
|
4318
|
+
last.set(_nodeLastChildren);
|
|
4319
|
+
_nodeLastChildren = last;
|
|
4320
|
+
const next = new Int32Array(cap);
|
|
4321
|
+
next.set(_nodeNextSiblings);
|
|
4322
|
+
_nodeNextSiblings = next;
|
|
4323
|
+
const aStart = new Int32Array(cap);
|
|
4324
|
+
aStart.set(_nodeAttrStarts);
|
|
4325
|
+
_nodeAttrStarts = aStart;
|
|
4326
|
+
const aCount = new Int32Array(cap);
|
|
4327
|
+
aCount.set(_nodeAttrCounts);
|
|
4328
|
+
_nodeAttrCounts = aCount;
|
|
4329
|
+
_nodeCapacity = cap;
|
|
4330
|
+
};
|
|
4331
|
+
|
|
4332
|
+
/** Start a new parse: invalidate all prior refs, release prior heap refs. */
|
|
4333
|
+
const _resetAstColumns = () => {
|
|
4334
|
+
_nodeCount = 0;
|
|
4335
|
+
_attrCount = 0;
|
|
4336
|
+
_nodeStrings.length = 0;
|
|
4337
|
+
_attrNames.length = 0;
|
|
4338
|
+
_attrValues.length = 0;
|
|
4339
|
+
// Keep id 0 ("no node" / "no attribute") occupied so writes stay packed.
|
|
4340
|
+
_nodeStrings.push("");
|
|
4341
|
+
_attrNames.push("");
|
|
4342
|
+
_attrValues.push(null);
|
|
4343
|
+
_doctypePublicId = null;
|
|
4344
|
+
_doctypeSystemId = null;
|
|
4345
|
+
};
|
|
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
|
+
|
|
4353
|
+
// Release the side arrays' heap references (strings, attribute names/values)
|
|
4354
|
+
// once a walk has consumed the tree, so the retained columns don't pin the
|
|
4355
|
+
// parsed source until the next parse.
|
|
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
|
+
}
|
|
4390
|
+
};
|
|
4391
|
+
|
|
4392
|
+
/** @type {(type: number, start: number, end: number) => HtmlNodeRef} */
|
|
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.
|
|
4413
|
+
return i;
|
|
4414
|
+
};
|
|
4415
|
+
|
|
4416
|
+
// Raw child append — no text merging, no `<template>` content redirect (the
|
|
4417
|
+
// tree builder layers those on top). `node` must be detached (`next` = 0).
|
|
4418
|
+
/** @type {(parent: HtmlNodeRef, node: HtmlNodeRef) => void} */
|
|
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;
|
|
4425
|
+
};
|
|
4426
|
+
|
|
4427
|
+
/** @type {(data: string, start: number, end: number) => HtmlNodeRef} */
|
|
4428
|
+
const _makeTextNode = (data, start, end) => {
|
|
4429
|
+
const i = _allocNode(NodeType.Text, start, end);
|
|
4430
|
+
_nodeStrings[i] = data;
|
|
4431
|
+
return i;
|
|
4432
|
+
};
|
|
4433
|
+
|
|
4434
|
+
/** @type {(data: string, start: number, end: number) => HtmlNodeRef} */
|
|
4435
|
+
const _makeCommentNode = (data, start, end) => {
|
|
4436
|
+
const i = _allocNode(NodeType.Comment, start, end);
|
|
4437
|
+
_nodeStrings[i] = data;
|
|
4438
|
+
return i;
|
|
4439
|
+
};
|
|
4440
|
+
|
|
4441
|
+
// Marker entry in the active-formatting-elements list (never a valid ref).
|
|
4442
|
+
const AFE_MARKER = -1;
|
|
4443
|
+
|
|
4444
|
+
// Clone of an element's attribute run: keep name/value (and the serializer
|
|
4445
|
+
// name flag) but drop source offsets so the consumer doesn't emit a duplicate
|
|
4446
|
+
// dependency for the reopened element's spans. Values are materialized since
|
|
4447
|
+
// the offsets are gone.
|
|
4448
|
+
const cloneAttrs = (/** @type {HtmlElement} */ el) => {
|
|
4449
|
+
const start = _nodeAttrStarts[el];
|
|
4450
|
+
const count = _nodeAttrCounts[el];
|
|
4451
|
+
const newStart = _attrCount + 1;
|
|
4452
|
+
for (let i = start; i < start + count; i++) {
|
|
4453
|
+
const c = _allocAttr(_attrNames[i], _attrValueOf(i), -1, -1, -1, -1);
|
|
4454
|
+
_attrFlags[c] = _attrFlags[i];
|
|
4455
|
+
}
|
|
4456
|
+
return { start: newStart, count };
|
|
4457
|
+
};
|
|
4458
|
+
|
|
4459
|
+
// Merge a repeated `<html>`/`<body>` tag's attributes into the element: only
|
|
4460
|
+
// names not already present are added (in source order after the existing
|
|
4461
|
+
// ones). Runs are contiguous, so any addition re-allocates the whole run; the
|
|
4462
|
+
// old slots are orphaned (at most once per repeated tag, rare).
|
|
4463
|
+
const mergeAttrs = (
|
|
4464
|
+
/** @type {HtmlElement} */ el,
|
|
4465
|
+
/** @type {AttributeRun} */ run
|
|
4466
|
+
) => {
|
|
4467
|
+
const start = _nodeAttrStarts[el];
|
|
4468
|
+
const count = _nodeAttrCounts[el];
|
|
4469
|
+
let extra = 0;
|
|
4470
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
4471
|
+
if (_findAttr(start, count, _attrNames[i]) === 0) extra++;
|
|
4472
|
+
}
|
|
4473
|
+
if (extra === 0) return;
|
|
4474
|
+
const newStart = _attrCount + 1;
|
|
4475
|
+
for (let i = start; i < start + count; i++) _copyAttr(i);
|
|
4476
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
4477
|
+
if (_findAttr(start, count, _attrNames[i]) === 0) _copyAttr(i);
|
|
4478
|
+
}
|
|
4479
|
+
_nodeAttrStarts[el] = newStart;
|
|
4480
|
+
_nodeAttrCounts[el] = count + extra;
|
|
4481
|
+
};
|
|
4482
|
+
|
|
4483
|
+
/**
|
|
4484
|
+
* A materialized attribute as returned by `A.attributes` (tests/tooling) —
|
|
4485
|
+
* the parser-facing representation is an id into the attribute columns, read
|
|
4486
|
+
* through the scalar `A.attr*` accessors.
|
|
3782
4487
|
* @typedef {object} HtmlAttribute
|
|
3783
4488
|
* @property {string} name lowercased (and, in foreign content, adjusted) attribute name
|
|
3784
4489
|
* @property {string} value
|
|
@@ -3790,60 +4495,27 @@ const NS_SVG = 2;
|
|
|
3790
4495
|
*/
|
|
3791
4496
|
|
|
3792
4497
|
/**
|
|
3793
|
-
*
|
|
3794
|
-
*
|
|
3795
|
-
*
|
|
3796
|
-
*
|
|
3797
|
-
* @
|
|
3798
|
-
* @property {HtmlNode[]} children
|
|
3799
|
-
* @property {boolean} selfClosing
|
|
3800
|
-
* @property {number} start
|
|
3801
|
-
* @property {number} end
|
|
3802
|
-
* @property {number} tagEnd end offset of the opening tag (after `>`)
|
|
3803
|
-
* @property {number} nameEnd end offset of the tag name
|
|
3804
|
-
* @property {HtmlDocumentFragment=} templateContent `<template>` content fragment
|
|
3805
|
-
*/
|
|
3806
|
-
|
|
3807
|
-
/**
|
|
3808
|
-
* @typedef {object} HtmlText
|
|
3809
|
-
* @property {typeof NodeType.Text} type
|
|
3810
|
-
* @property {string} data
|
|
3811
|
-
* @property {number} start
|
|
3812
|
-
* @property {number} end
|
|
4498
|
+
* A node reference into the struct-of-arrays AST: an integer id indexing the
|
|
4499
|
+
* parallel `_h*` columns. Read fields through the exported accessor `A`. Refs
|
|
4500
|
+
* are only valid until the next `parseHtml` call — the columns are reused
|
|
4501
|
+
* across parses — so consume a tree fully before parsing again.
|
|
4502
|
+
* @typedef {number} HtmlNodeRef
|
|
3813
4503
|
*/
|
|
3814
4504
|
|
|
3815
|
-
/**
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
*/
|
|
3822
|
-
|
|
3823
|
-
/**
|
|
3824
|
-
* @typedef {object} HtmlDoctype
|
|
3825
|
-
* @property {typeof NodeType.Doctype} type
|
|
3826
|
-
* @property {string} name
|
|
3827
|
-
* @property {string | null} publicId
|
|
3828
|
-
* @property {string | null} systemId
|
|
3829
|
-
* @property {number} start
|
|
3830
|
-
* @property {number} end
|
|
3831
|
-
*/
|
|
3832
|
-
|
|
3833
|
-
/**
|
|
3834
|
-
* @typedef {object} HtmlDocument
|
|
3835
|
-
* @property {typeof NodeType.Document} type
|
|
3836
|
-
* @property {HtmlNode[]} children
|
|
3837
|
-
*/
|
|
4505
|
+
/** @typedef {HtmlNodeRef} HtmlElement ref to an Element node */
|
|
4506
|
+
/** @typedef {HtmlNodeRef} HtmlText ref to a Text node */
|
|
4507
|
+
/** @typedef {HtmlNodeRef} HtmlComment ref to a Comment node */
|
|
4508
|
+
/** @typedef {HtmlNodeRef} HtmlDoctype ref to a Doctype node */
|
|
4509
|
+
/** @typedef {HtmlNodeRef} HtmlDocument ref to the Document node */
|
|
4510
|
+
/** @typedef {HtmlNodeRef} HtmlDocumentFragment ref to a DocumentFragment node */
|
|
4511
|
+
/** @typedef {HtmlNodeRef} HtmlNode */
|
|
3838
4512
|
|
|
3839
4513
|
/**
|
|
3840
|
-
*
|
|
3841
|
-
*
|
|
3842
|
-
* @
|
|
4514
|
+
* An attribute reference: an integer id into the attribute columns, read
|
|
4515
|
+
* through the `A.attr*` accessors. Same validity contract as `HtmlNodeRef`.
|
|
4516
|
+
* @typedef {number} HtmlAttributeRef
|
|
3843
4517
|
*/
|
|
3844
4518
|
|
|
3845
|
-
/** @typedef {HtmlElement | HtmlText | HtmlComment | HtmlDoctype} HtmlNode */
|
|
3846
|
-
|
|
3847
4519
|
/** @typedef {{ start: number, end: number, tagEnd: number, nameEnd: number }} TagPos */
|
|
3848
4520
|
|
|
3849
4521
|
// Tree-construction token `type` discriminators. Numeric for the same reason
|
|
@@ -3859,7 +4531,7 @@ const TOKEN_EOF = 6;
|
|
|
3859
4531
|
/** @typedef {{ type: typeof TOKEN_CHAR, data: string, start: number, end: number }} CharToken */
|
|
3860
4532
|
/** @typedef {{ type: typeof TOKEN_COMMENT, data: string, start: number, end: number }} CommentToken */
|
|
3861
4533
|
/** @typedef {{ type: typeof TOKEN_DOCTYPE, name: string, publicId: (string | null), systemId: (string | null), start: number, end: number }} DoctypeToken */
|
|
3862
|
-
/** @typedef {{ type: typeof TOKEN_START_TAG, name: string, attrs:
|
|
4534
|
+
/** @typedef {{ type: typeof TOKEN_START_TAG, name: string, attrs: AttributeRun, selfClosing: boolean, pos: TagPos, swallowNewline?: boolean }} StartTagToken */
|
|
3863
4535
|
/** @typedef {{ type: typeof TOKEN_END_TAG, name: string, pos: TagPos }} EndTagToken */
|
|
3864
4536
|
/** @typedef {{ type: typeof TOKEN_EOF }} EofToken */
|
|
3865
4537
|
|
|
@@ -3876,10 +4548,10 @@ const TOKEN_EOF = 6;
|
|
|
3876
4548
|
* stale values that those handlers never read. Tokens that must outlive the
|
|
3877
4549
|
* current callback (buffered table characters, synthesized re-dispatches) are
|
|
3878
4550
|
* copied into fresh plain objects instead.
|
|
3879
|
-
* @typedef {{ type: number, name: string, data: string, attrs:
|
|
4551
|
+
* @typedef {{ type: number, name: string, data: string, attrs: AttributeRun, selfClosing: boolean, start: number, end: number, publicId: (string | null), systemId: (string | null), swallowNewline: boolean, pos: TagPos }} MutableToken
|
|
3880
4552
|
*/
|
|
3881
4553
|
|
|
3882
|
-
/** @typedef {{ parent:
|
|
4554
|
+
/** @typedef {{ parent: HtmlNodeRef, beforeNode: HtmlNodeRef }} InsertionPlace `beforeNode` 0 = plain append */
|
|
3883
4555
|
|
|
3884
4556
|
// Insertion modes (§13.2.4.1). Numeric for the same reason as the token and
|
|
3885
4557
|
// `NodeType` enums: `runMode` dispatches on `mode` once per token.
|
|
@@ -4043,9 +4715,11 @@ const FORMATTING = new Set([
|
|
|
4043
4715
|
const HEADING = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
4044
4716
|
|
|
4045
4717
|
const isSpecial = (/** @type {HtmlElement} */ el) => {
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
if (
|
|
4718
|
+
const ns = _namespaceOf(el);
|
|
4719
|
+
const tag = _tagNameOf(el);
|
|
4720
|
+
if (ns === NS_HTML) return SPECIAL.has(tag);
|
|
4721
|
+
if (ns === NS_MATHML) return MATHML_SPECIAL.has(tag);
|
|
4722
|
+
if (ns === NS_SVG) return SVG_SPECIAL.has(tag.toLowerCase());
|
|
4049
4723
|
return false;
|
|
4050
4724
|
};
|
|
4051
4725
|
|
|
@@ -4493,14 +5167,13 @@ const FOREIGN_BREAKOUT = new Set([
|
|
|
4493
5167
|
]);
|
|
4494
5168
|
const FONT_BREAKOUT_ATTRS = new Set(["color", "face", "size"]);
|
|
4495
5169
|
|
|
4496
|
-
//
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
);
|
|
5170
|
+
// `<font color|face|size>` breaks out of foreign content (§13.2.6.5).
|
|
5171
|
+
const hasFontBreakoutAttr = (/** @type {AttributeRun} */ run) => {
|
|
5172
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
5173
|
+
if (FONT_BREAKOUT_ATTRS.has(_attrNames[i])) return true;
|
|
5174
|
+
}
|
|
5175
|
+
return false;
|
|
5176
|
+
};
|
|
4504
5177
|
|
|
4505
5178
|
/**
|
|
4506
5179
|
* Hash of the ASCII-lowercased `name` for the intern tables below; must stay
|
|
@@ -4518,33 +5191,58 @@ const hashLowerName = (name) => {
|
|
|
4518
5191
|
return h;
|
|
4519
5192
|
};
|
|
4520
5193
|
|
|
5194
|
+
// Text-run scan classes for the `skip.text` fast path: 2 = stop the fast
|
|
5195
|
+
// path (& / NUL / CR), 1 = ASCII whitespace, 0 = ordinary text.
|
|
5196
|
+
const _TEXT_SCAN_CLASS = new Uint8Array(128);
|
|
5197
|
+
_TEXT_SCAN_CLASS[0x09] = 1;
|
|
5198
|
+
_TEXT_SCAN_CLASS[0x0a] = 1;
|
|
5199
|
+
_TEXT_SCAN_CLASS[0x0c] = 1;
|
|
5200
|
+
_TEXT_SCAN_CLASS[0x20] = 1;
|
|
5201
|
+
_TEXT_SCAN_CLASS[0x26] = 2;
|
|
5202
|
+
_TEXT_SCAN_CLASS[0x00] = 2;
|
|
5203
|
+
_TEXT_SCAN_CLASS[0x0d] = 2;
|
|
5204
|
+
|
|
4521
5205
|
/**
|
|
4522
5206
|
* @param {Iterable<string>} names lowercase names to intern
|
|
4523
|
-
* @returns {
|
|
5207
|
+
* @returns {{ mask: number, hashes: Int32Array, values: (string | string[] | undefined)[] }} open-addressed intern table
|
|
4524
5208
|
*/
|
|
4525
5209
|
const buildNameInternTable = (names) => {
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
5210
|
+
// Open-addressed table (~25% load): the per-name probe is one or two array
|
|
5211
|
+
// reads instead of a `Map#get`, and the tables are built once at startup.
|
|
5212
|
+
const unique = [...new Set(names)];
|
|
5213
|
+
let size = 8;
|
|
5214
|
+
while (size < unique.length * 4) size <<= 1;
|
|
5215
|
+
const mask = size - 1;
|
|
5216
|
+
const hashes = new Int32Array(size);
|
|
5217
|
+
/** @type {(string | string[] | undefined)[]} */
|
|
5218
|
+
const values = Array.from({ length: size });
|
|
5219
|
+
for (const name of unique) {
|
|
4529
5220
|
const h = hashLowerName(name);
|
|
4530
|
-
|
|
5221
|
+
let slot = h & mask;
|
|
5222
|
+
while (values[slot] !== undefined && hashes[slot] !== h) {
|
|
5223
|
+
slot = (slot + 1) & mask;
|
|
5224
|
+
}
|
|
5225
|
+
const cur = values[slot];
|
|
4531
5226
|
if (cur === undefined) {
|
|
4532
|
-
|
|
5227
|
+
hashes[slot] = h;
|
|
5228
|
+
values[slot] = name;
|
|
4533
5229
|
} else if (typeof cur === "string") {
|
|
4534
|
-
|
|
4535
|
-
} else
|
|
5230
|
+
values[slot] = [cur, name];
|
|
5231
|
+
} else {
|
|
4536
5232
|
cur.push(name);
|
|
4537
5233
|
}
|
|
4538
5234
|
}
|
|
4539
|
-
return
|
|
5235
|
+
return { mask, hashes, values };
|
|
4540
5236
|
};
|
|
4541
5237
|
|
|
5238
|
+
/** @typedef {ReturnType<typeof buildNameInternTable>} NameInternTable */
|
|
5239
|
+
|
|
4542
5240
|
/**
|
|
4543
5241
|
* The lowercased name for `input[start..end)`, returning the shared interned
|
|
4544
5242
|
* string for known names — skipping the per-tag `slice().toLowerCase()`
|
|
4545
5243
|
* allocation, and making the tree builder's many Set/Map lookups and `===`
|
|
4546
5244
|
* comparisons on the name hit one string instance with a cached hash.
|
|
4547
|
-
* @param {
|
|
5245
|
+
* @param {NameInternTable} table intern table
|
|
4548
5246
|
* @param {string} input source text
|
|
4549
5247
|
* @param {number} start name start
|
|
4550
5248
|
* @param {number} end name end
|
|
@@ -4557,15 +5255,22 @@ const internLowerName = (table, input, start, end) => {
|
|
|
4557
5255
|
if (c >= 0x41 && c <= 0x5a) c += 0x20;
|
|
4558
5256
|
h = ((h << 5) - h + c) | 0;
|
|
4559
5257
|
}
|
|
4560
|
-
const
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
5258
|
+
const { mask, hashes, values } = table;
|
|
5259
|
+
let slot = h & mask;
|
|
5260
|
+
for (;;) {
|
|
5261
|
+
const hit = values[slot];
|
|
5262
|
+
if (hit === undefined) break;
|
|
5263
|
+
if (hashes[slot] === h) {
|
|
5264
|
+
if (typeof hit === "string") {
|
|
5265
|
+
if (rangeEqualsLowerCase(input, start, end, hit)) return hit;
|
|
5266
|
+
} else {
|
|
5267
|
+
for (let i = 0; i < hit.length; i++) {
|
|
5268
|
+
if (rangeEqualsLowerCase(input, start, end, hit[i])) return hit[i];
|
|
5269
|
+
}
|
|
4567
5270
|
}
|
|
5271
|
+
break;
|
|
4568
5272
|
}
|
|
5273
|
+
slot = (slot + 1) & mask;
|
|
4569
5274
|
}
|
|
4570
5275
|
// Unknown name (custom element, data-* attribute, non-ASCII, …).
|
|
4571
5276
|
return input.slice(start, end).toLowerCase();
|
|
@@ -4604,50 +5309,96 @@ const ATTR_NAME_INTERN = buildNameInternTable(
|
|
|
4604
5309
|
// Hoisted so the many `open.some(...)` "is there an open HTML <template>?"
|
|
4605
5310
|
// checks reuse one predicate instead of allocating an arrow per call.
|
|
4606
5311
|
const isHtmlTemplateEl = (/** @type {HtmlElement} */ e) =>
|
|
4607
|
-
e
|
|
5312
|
+
_tagNameOf(e) === "template" && _namespaceOf(e) === NS_HTML;
|
|
4608
5313
|
|
|
4609
|
-
//
|
|
4610
|
-
//
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
5314
|
+
// Raw text / escapable raw text elements (WHATWG §13.1.2) plus the other
|
|
5315
|
+
// RAWTEXT/RCDATA/PLAINTEXT-tokenized elements. Under `skip.text` their body's
|
|
5316
|
+
// end offset is recorded on the element (see `insertCharacters`) so a consumer
|
|
5317
|
+
// can read the raw content span without a `Text` node.
|
|
5318
|
+
const RAW_TEXT_ELEMENTS = new Set([
|
|
5319
|
+
"script",
|
|
5320
|
+
"style",
|
|
5321
|
+
"textarea",
|
|
5322
|
+
"title",
|
|
5323
|
+
"xmp",
|
|
5324
|
+
"iframe",
|
|
5325
|
+
"noembed",
|
|
5326
|
+
"noframes",
|
|
5327
|
+
"noscript",
|
|
5328
|
+
"plaintext"
|
|
5329
|
+
]);
|
|
5330
|
+
|
|
5331
|
+
/**
|
|
5332
|
+
* Shared empty skip set so the common (no-skip) call allocates nothing.
|
|
5333
|
+
* @type {HtmlAstSkip}
|
|
5334
|
+
*/
|
|
5335
|
+
const EMPTY_SKIP = Object.freeze({});
|
|
5336
|
+
|
|
5337
|
+
/**
|
|
5338
|
+
* Optional node kinds a consumer can drop from the AST for speed/memory. Each
|
|
5339
|
+
* is a pure output reduction — tree construction (and quirks detection) runs
|
|
5340
|
+
* unchanged, so element structure and offsets are identical either way.
|
|
5341
|
+
* @typedef {object} HtmlAstSkip
|
|
5342
|
+
* @property {boolean=} text drop every `Text` node. Raw-text element bodies (`<script>`/`<style>`/…) aren't emitted either — their content span is recorded as the element's `contentEnd` (see `RAW_TEXT_ELEMENTS`) so a consumer can read `[tagEnd, contentEnd]` by offset. For consumers that read text by offset (e.g. `HtmlParser`), never the html5lib serializer.
|
|
5343
|
+
* @property {boolean=} comments drop comment nodes entirely. Not for consumers that read comments (e.g. webpack magic comments).
|
|
5344
|
+
* @property {boolean=} doctype drop the doctype node; quirks-mode detection is unaffected.
|
|
5345
|
+
*/
|
|
4620
5346
|
|
|
4621
5347
|
/**
|
|
4622
|
-
* @
|
|
4623
|
-
* @
|
|
4624
|
-
* @
|
|
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
|
|
4625
5351
|
*/
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
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)
|
|
5357
|
+
* @returns {HtmlDocument} ref to the document node — read through `A`; valid until the next parse
|
|
5358
|
+
*/
|
|
5359
|
+
const parseHtml = (input, pos = 0, options = {}) => {
|
|
5360
|
+
const source = input;
|
|
5361
|
+
const { fragmentContext, skip = EMPTY_SKIP } = options;
|
|
5362
|
+
const skipText = skip.text === true;
|
|
5363
|
+
const skipComments = skip.comments === true;
|
|
5364
|
+
const skipDoctype = skip.doctype === true;
|
|
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] = "";
|
|
4629
5382
|
let mode = MODE_INITIAL;
|
|
4630
5383
|
// Mode to return to after MODE_TEXT / MODE_IN_TABLE_TEXT (0 = unset).
|
|
4631
5384
|
let originalMode = 0;
|
|
4632
5385
|
/** @type {HtmlElement[]} stack of open elements (bottom .. top) */
|
|
4633
5386
|
const open = [];
|
|
4634
|
-
/** @type {
|
|
5387
|
+
/** @type {HtmlNodeRef[]} active formatting elements (AFE_MARKER = marker) */
|
|
4635
5388
|
const afe = [];
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
/** @type {HtmlElement
|
|
4639
|
-
let
|
|
4640
|
-
/** @type {HtmlElement | null} */
|
|
4641
|
-
let form = null;
|
|
5389
|
+
/** @type {HtmlElement} 0 = none */
|
|
5390
|
+
let head = 0;
|
|
5391
|
+
/** @type {HtmlElement} 0 = none */
|
|
5392
|
+
let form = 0;
|
|
4642
5393
|
let framesetOk = true;
|
|
4643
|
-
|
|
4644
|
-
let
|
|
5394
|
+
// First attribute id of the tag currently being tokenized (0 = none).
|
|
5395
|
+
let pendAttrStart = 0;
|
|
4645
5396
|
let fosterParenting = false;
|
|
4646
5397
|
/** @type {number[]} */
|
|
4647
5398
|
const templateModes = [];
|
|
4648
5399
|
let quirks = false;
|
|
4649
|
-
/** @type {HtmlElement
|
|
4650
|
-
let fragment =
|
|
5400
|
+
/** @type {HtmlElement} fragment context element (0 = document parse) */
|
|
5401
|
+
let fragment = 0;
|
|
4651
5402
|
// End offset of the token currently being processed (for element `.end`).
|
|
4652
5403
|
let tokenEnd = 0;
|
|
4653
5404
|
|
|
@@ -4660,70 +5411,68 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4660
5411
|
const mkEl = (
|
|
4661
5412
|
/** @type {string} */ tagName,
|
|
4662
5413
|
/** @type {number} */ ns,
|
|
4663
|
-
/** @type {
|
|
5414
|
+
/** @type {AttributeRun} */ attrs,
|
|
4664
5415
|
/** @type {TagPos | null | undefined} */ pos
|
|
4665
|
-
) =>
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
name: a.name,
|
|
4688
|
-
value: a.value,
|
|
4689
|
-
serializedName: a.serializedName,
|
|
4690
|
-
nameStart: -1,
|
|
4691
|
-
nameEnd: -1,
|
|
4692
|
-
valueStart: -1,
|
|
4693
|
-
valueEnd: -1
|
|
4694
|
-
}));
|
|
5416
|
+
) => {
|
|
5417
|
+
const el = _allocNode(
|
|
5418
|
+
NodeType.Element,
|
|
5419
|
+
pos ? pos.start : 0,
|
|
5420
|
+
pos ? pos.end : 0
|
|
5421
|
+
);
|
|
5422
|
+
// Void HTML elements are marked self-closing and never receive children.
|
|
5423
|
+
_nodeFlags[el] =
|
|
5424
|
+
ns === NS_HTML && VOID.has(tagName) ? ns | FLAG_SELF_CLOSING : ns;
|
|
5425
|
+
_nodeStrings[el] = tagName;
|
|
5426
|
+
_nodeAttrStarts[el] = attrs.start;
|
|
5427
|
+
_nodeAttrCounts[el] = attrs.count;
|
|
5428
|
+
if (pos) {
|
|
5429
|
+
_nodeTagEnds[el] = pos.tagEnd;
|
|
5430
|
+
_nodeNameEnds[el] = pos.nameEnd;
|
|
5431
|
+
// End of a raw-text element's body under `skip.text` (defaults to the
|
|
5432
|
+
// body start, i.e. empty); lets consumers read `<script>`/`<style>`
|
|
5433
|
+
// content as [`tagEnd`, `contentEnd`] without a `Text` node.
|
|
5434
|
+
_nodeContentEnds[el] = pos.tagEnd;
|
|
5435
|
+
}
|
|
5436
|
+
return el;
|
|
5437
|
+
};
|
|
4695
5438
|
|
|
4696
5439
|
/**
|
|
4697
|
-
*
|
|
4698
|
-
*
|
|
5440
|
+
* A `<template>`'s children live in its content fragment; inserting into a
|
|
5441
|
+
* template really inserts there (the spec's "template contents" redirect).
|
|
5442
|
+
* @param {HtmlNodeRef} parent container
|
|
5443
|
+
* @returns {HtmlNodeRef} effective container to link children into
|
|
4699
5444
|
*/
|
|
4700
|
-
const
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
5445
|
+
const effParent = (parent) => {
|
|
5446
|
+
const tc = _nodeTemplateContents[parent];
|
|
5447
|
+
return tc !== 0 ? tc : parent;
|
|
5448
|
+
};
|
|
4704
5449
|
|
|
4705
5450
|
const appendTo = (
|
|
4706
|
-
/** @type {
|
|
4707
|
-
/** @type {
|
|
5451
|
+
/** @type {HtmlNodeRef} */ parent,
|
|
5452
|
+
/** @type {HtmlNodeRef} */ node
|
|
4708
5453
|
) => {
|
|
4709
|
-
const
|
|
4710
|
-
const last =
|
|
4711
|
-
if (
|
|
4712
|
-
|
|
4713
|
-
|
|
5454
|
+
const p = effParent(parent);
|
|
5455
|
+
const last = _nodeLastChildren[p];
|
|
5456
|
+
if (
|
|
5457
|
+
_nodeTypes[node] === NodeType.Text &&
|
|
5458
|
+
last !== 0 &&
|
|
5459
|
+
_nodeTypes[last] === NodeType.Text
|
|
5460
|
+
) {
|
|
5461
|
+
_nodeStrings[last] += _nodeStrings[node];
|
|
5462
|
+
_nodeEnds[last] = _nodeEnds[node];
|
|
4714
5463
|
return;
|
|
4715
5464
|
}
|
|
4716
|
-
|
|
5465
|
+
_appendChild(p, node);
|
|
4717
5466
|
};
|
|
4718
5467
|
|
|
4719
5468
|
// Reused result of `appropriatePlace` — consumed synchronously by
|
|
4720
5469
|
// `insertAtPlace` and never retained, so one shared object avoids an
|
|
4721
5470
|
// allocation per inserted node.
|
|
4722
5471
|
/** @type {InsertionPlace} */
|
|
4723
|
-
const sharedPlace = { parent: doc, beforeNode:
|
|
5472
|
+
const sharedPlace = { parent: doc, beforeNode: 0 };
|
|
4724
5473
|
const placeAt = (
|
|
4725
|
-
/** @type {
|
|
4726
|
-
/** @type {
|
|
5474
|
+
/** @type {HtmlNodeRef} */ parent,
|
|
5475
|
+
/** @type {HtmlNodeRef} */ beforeNode
|
|
4727
5476
|
) => {
|
|
4728
5477
|
sharedPlace.parent = parent;
|
|
4729
5478
|
sharedPlace.beforeNode = beforeNode;
|
|
@@ -4735,23 +5484,23 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4735
5484
|
const target = cur();
|
|
4736
5485
|
if (
|
|
4737
5486
|
fosterParenting &&
|
|
4738
|
-
TABLE_CONTEXT.has(target
|
|
4739
|
-
target
|
|
5487
|
+
TABLE_CONTEXT.has(_tagNameOf(target)) &&
|
|
5488
|
+
_namespaceOf(target) === NS_HTML
|
|
4740
5489
|
) {
|
|
4741
5490
|
// find last template / last table
|
|
4742
5491
|
let lastTemplate = -1;
|
|
4743
5492
|
let lastTable = -1;
|
|
4744
5493
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
4745
5494
|
if (
|
|
4746
|
-
open[i]
|
|
4747
|
-
open[i]
|
|
5495
|
+
_tagNameOf(open[i]) === "template" &&
|
|
5496
|
+
_namespaceOf(open[i]) === NS_HTML &&
|
|
4748
5497
|
lastTemplate === -1
|
|
4749
5498
|
) {
|
|
4750
5499
|
lastTemplate = i;
|
|
4751
5500
|
}
|
|
4752
5501
|
if (
|
|
4753
|
-
open[i]
|
|
4754
|
-
open[i]
|
|
5502
|
+
_tagNameOf(open[i]) === "table" &&
|
|
5503
|
+
_namespaceOf(open[i]) === NS_HTML &&
|
|
4755
5504
|
lastTable === -1
|
|
4756
5505
|
) {
|
|
4757
5506
|
lastTable = i;
|
|
@@ -4761,53 +5510,52 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4761
5510
|
lastTemplate !== -1 &&
|
|
4762
5511
|
(lastTable === -1 || lastTemplate > lastTable)
|
|
4763
5512
|
) {
|
|
4764
|
-
return placeAt(open[lastTemplate],
|
|
5513
|
+
return placeAt(open[lastTemplate], 0);
|
|
4765
5514
|
}
|
|
4766
5515
|
if (lastTable === -1) {
|
|
4767
|
-
return placeAt(open[0],
|
|
5516
|
+
return placeAt(open[0], 0);
|
|
4768
5517
|
}
|
|
4769
5518
|
const table = open[lastTable];
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
return placeAt(open[lastTable - 1], null);
|
|
5519
|
+
const tp = _nodeParents[table];
|
|
5520
|
+
if (tp !== 0) return placeAt(tp, table);
|
|
5521
|
+
return placeAt(open[lastTable - 1], 0);
|
|
4774
5522
|
}
|
|
4775
|
-
return placeAt(target,
|
|
4776
|
-
};
|
|
4777
|
-
|
|
4778
|
-
const findParent = (/** @type {HtmlNode} */ node) => {
|
|
4779
|
-
// search the whole document tree for the parent of node
|
|
4780
|
-
/** @type {(HtmlElement | HtmlDocument | HtmlDocumentFragment)[]} */
|
|
4781
|
-
const stack = [doc];
|
|
4782
|
-
while (stack.length) {
|
|
4783
|
-
const x =
|
|
4784
|
-
/** @type {HtmlElement | HtmlDocument | HtmlDocumentFragment} */ (
|
|
4785
|
-
stack.pop()
|
|
4786
|
-
);
|
|
4787
|
-
for (const k of childrenOf(x)) {
|
|
4788
|
-
if (k === node) return x;
|
|
4789
|
-
if (k.type === NodeType.Element) stack.push(k);
|
|
4790
|
-
}
|
|
4791
|
-
}
|
|
4792
|
-
return null;
|
|
5523
|
+
return placeAt(target, 0);
|
|
4793
5524
|
};
|
|
4794
5525
|
|
|
4795
5526
|
const insertAtPlace = (
|
|
4796
5527
|
/** @type {InsertionPlace} */ place,
|
|
4797
|
-
/** @type {
|
|
5528
|
+
/** @type {HtmlNodeRef} */ node
|
|
4798
5529
|
) => {
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
const
|
|
5530
|
+
const before = place.beforeNode;
|
|
5531
|
+
if (before !== 0) {
|
|
5532
|
+
const p = effParent(place.parent);
|
|
5533
|
+
// Find `before`'s previous sibling (insert-before is a rare foster/
|
|
5534
|
+
// adoption path, so the sibling scan stays off the hot path).
|
|
5535
|
+
let prev = 0;
|
|
5536
|
+
let c = _nodeFirstChildren[p];
|
|
5537
|
+
while (c !== 0 && c !== before) {
|
|
5538
|
+
prev = c;
|
|
5539
|
+
c = _nodeNextSiblings[c];
|
|
5540
|
+
}
|
|
5541
|
+
if (c === 0) {
|
|
5542
|
+
// `before` not under `parent` (not reachable from the spec paths).
|
|
5543
|
+
appendTo(place.parent, node);
|
|
5544
|
+
return;
|
|
5545
|
+
}
|
|
4802
5546
|
if (
|
|
4803
|
-
node
|
|
4804
|
-
|
|
4805
|
-
|
|
5547
|
+
_nodeTypes[node] === NodeType.Text &&
|
|
5548
|
+
prev !== 0 &&
|
|
5549
|
+
_nodeTypes[prev] === NodeType.Text
|
|
4806
5550
|
) {
|
|
4807
|
-
|
|
5551
|
+
// Before-node merge deliberately does not bump the sibling's `end`.
|
|
5552
|
+
_nodeStrings[prev] += _nodeStrings[node];
|
|
4808
5553
|
return;
|
|
4809
5554
|
}
|
|
4810
|
-
|
|
5555
|
+
_nodeParents[node] = p;
|
|
5556
|
+
_nodeNextSiblings[node] = before;
|
|
5557
|
+
if (prev === 0) _nodeFirstChildren[p] = node;
|
|
5558
|
+
else _nodeNextSiblings[prev] = node;
|
|
4811
5559
|
} else {
|
|
4812
5560
|
appendTo(place.parent, node);
|
|
4813
5561
|
}
|
|
@@ -4819,28 +5567,58 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4819
5567
|
/** @type {number} */ end
|
|
4820
5568
|
) => {
|
|
4821
5569
|
const place = appropriatePlace();
|
|
4822
|
-
if (place.parent
|
|
5570
|
+
if (_nodeTypes[place.parent] === NodeType.Document) return; // never insert text into document
|
|
5571
|
+
// `skip.text`: drop every `Text` node — construction already used the
|
|
5572
|
+
// decoded token, so removing the node never affects element structure.
|
|
5573
|
+
// For a raw-text element record the body end so a consumer reads the span
|
|
5574
|
+
// [`tagEnd`, `contentEnd`] without a `Text` node (see `HtmlParser`).
|
|
5575
|
+
// Namespace-agnostic: `HtmlParser` extracts `<script>`/`<style>` bodies in
|
|
5576
|
+
// foreign content (e.g. SVG `<style>`) too.
|
|
5577
|
+
if (skipText) {
|
|
5578
|
+
const p = place.parent;
|
|
5579
|
+
if (
|
|
5580
|
+
_nodeTypes[p] === NodeType.Element &&
|
|
5581
|
+
RAW_TEXT_ELEMENTS.has(_tagNameOf(p))
|
|
5582
|
+
) {
|
|
5583
|
+
_nodeContentEnds[p] = end;
|
|
5584
|
+
}
|
|
5585
|
+
return;
|
|
5586
|
+
}
|
|
4823
5587
|
// Inlined text insert: when the run merges into the adjacent text sibling
|
|
4824
5588
|
// (common with inline formatting) only the string is appended — no
|
|
4825
5589
|
// throwaway text node is allocated. Mirrors `insertAtPlace`/`appendTo`,
|
|
4826
5590
|
// including that the before-node merge does not bump `end`.
|
|
4827
|
-
const
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
5591
|
+
const p = effParent(place.parent);
|
|
5592
|
+
const before = place.beforeNode;
|
|
5593
|
+
if (before !== 0) {
|
|
5594
|
+
let prev = 0;
|
|
5595
|
+
let c = _nodeFirstChildren[p];
|
|
5596
|
+
while (c !== 0 && c !== before) {
|
|
5597
|
+
prev = c;
|
|
5598
|
+
c = _nodeNextSiblings[c];
|
|
5599
|
+
}
|
|
5600
|
+
if (c === 0) {
|
|
5601
|
+
// `before` not under `parent` (not reachable from the spec paths).
|
|
5602
|
+
_appendChild(p, _makeTextNode(data, start, end));
|
|
4833
5603
|
return;
|
|
4834
5604
|
}
|
|
4835
|
-
|
|
5605
|
+
if (prev !== 0 && _nodeTypes[prev] === NodeType.Text) {
|
|
5606
|
+
_nodeStrings[prev] += data;
|
|
5607
|
+
return;
|
|
5608
|
+
}
|
|
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;
|
|
4836
5614
|
} else {
|
|
4837
|
-
const last =
|
|
4838
|
-
if (last && last
|
|
4839
|
-
last
|
|
4840
|
-
last
|
|
5615
|
+
const last = _nodeLastChildren[p];
|
|
5616
|
+
if (last !== 0 && _nodeTypes[last] === NodeType.Text) {
|
|
5617
|
+
_nodeStrings[last] += data;
|
|
5618
|
+
_nodeEnds[last] = end;
|
|
4841
5619
|
return;
|
|
4842
5620
|
}
|
|
4843
|
-
|
|
5621
|
+
_appendChild(p, _makeTextNode(data, start, end));
|
|
4844
5622
|
}
|
|
4845
5623
|
};
|
|
4846
5624
|
|
|
@@ -4851,16 +5629,17 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4851
5629
|
* @param {InsertionPlace=} place explicit insertion place
|
|
4852
5630
|
*/
|
|
4853
5631
|
const insertComment = (data, start, end, place) => {
|
|
5632
|
+
if (skipComments) return;
|
|
4854
5633
|
const p = place || appropriatePlace();
|
|
4855
|
-
insertAtPlace(p,
|
|
5634
|
+
insertAtPlace(p, _makeCommentNode(data, start, end));
|
|
4856
5635
|
};
|
|
4857
5636
|
|
|
4858
5637
|
const insertHtmlElement = (
|
|
4859
5638
|
/** @type {string} */ tagName,
|
|
4860
|
-
/** @type {
|
|
5639
|
+
/** @type {AttributeRun} */ attrs,
|
|
4861
5640
|
/** @type {TagPos | null} */ pos
|
|
4862
5641
|
) => {
|
|
4863
|
-
const el = mkEl(tagName, NS_HTML,
|
|
5642
|
+
const el = mkEl(tagName, NS_HTML, attrs, pos);
|
|
4864
5643
|
const place = appropriatePlace();
|
|
4865
5644
|
insertAtPlace(place, el);
|
|
4866
5645
|
open.push(el);
|
|
@@ -4870,10 +5649,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4870
5649
|
const insertForeignElement = (
|
|
4871
5650
|
/** @type {string} */ tagName,
|
|
4872
5651
|
/** @type {number} */ ns,
|
|
4873
|
-
/** @type {
|
|
5652
|
+
/** @type {AttributeRun} */ attrs,
|
|
4874
5653
|
/** @type {TagPos | null} */ pos
|
|
4875
5654
|
) => {
|
|
4876
|
-
const el = mkEl(tagName, ns,
|
|
5655
|
+
const el = mkEl(tagName, ns, attrs, pos);
|
|
4877
5656
|
const place = appropriatePlace();
|
|
4878
5657
|
insertAtPlace(place, el);
|
|
4879
5658
|
open.push(el);
|
|
@@ -4882,10 +5661,12 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4882
5661
|
|
|
4883
5662
|
// ---- scopes ----
|
|
4884
5663
|
const isScopeBoundary = (/** @type {HtmlElement} */ el) => {
|
|
4885
|
-
if (el
|
|
4886
|
-
if (el
|
|
4887
|
-
|
|
4888
|
-
|
|
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());
|
|
4889
5670
|
}
|
|
4890
5671
|
return false;
|
|
4891
5672
|
};
|
|
@@ -4900,10 +5681,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4900
5681
|
/** @type {number} */ kind
|
|
4901
5682
|
) => {
|
|
4902
5683
|
if (isScopeBoundary(el)) return true;
|
|
4903
|
-
if (el
|
|
4904
|
-
if (kind === SCOPE_BUTTON) return el
|
|
5684
|
+
if (_namespaceOf(el) !== NS_HTML) return false;
|
|
5685
|
+
if (kind === SCOPE_BUTTON) return _tagNameOf(el) === "button";
|
|
4905
5686
|
if (kind === SCOPE_LIST_ITEM) {
|
|
4906
|
-
return el
|
|
5687
|
+
return _tagNameOf(el) === "ol" || _tagNameOf(el) === "ul";
|
|
4907
5688
|
}
|
|
4908
5689
|
return false;
|
|
4909
5690
|
};
|
|
@@ -4915,7 +5696,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4915
5696
|
) => {
|
|
4916
5697
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
4917
5698
|
const el = open[i];
|
|
4918
|
-
if (el
|
|
5699
|
+
if (_namespaceOf(el) === NS_HTML && _tagNameOf(el) === tagName) {
|
|
5700
|
+
return true;
|
|
5701
|
+
}
|
|
4919
5702
|
if (isBoundaryForKind(el, kind)) return false;
|
|
4920
5703
|
}
|
|
4921
5704
|
return false;
|
|
@@ -4939,9 +5722,11 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4939
5722
|
const set = typeof target === "string" ? null : target;
|
|
4940
5723
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
4941
5724
|
const el = open[i];
|
|
4942
|
-
if (el
|
|
4943
|
-
if (set ? set.has(el
|
|
4944
|
-
|
|
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;
|
|
4945
5730
|
}
|
|
4946
5731
|
}
|
|
4947
5732
|
return false;
|
|
@@ -4950,9 +5735,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4950
5735
|
while (open.length) {
|
|
4951
5736
|
const el = cur();
|
|
4952
5737
|
if (
|
|
4953
|
-
el
|
|
4954
|
-
IMPLIED.has(el
|
|
4955
|
-
el
|
|
5738
|
+
_namespaceOf(el) === NS_HTML &&
|
|
5739
|
+
IMPLIED.has(_tagNameOf(el)) &&
|
|
5740
|
+
_tagNameOf(el) !== except
|
|
4956
5741
|
) {
|
|
4957
5742
|
open.pop();
|
|
4958
5743
|
} else {
|
|
@@ -4963,7 +5748,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4963
5748
|
const generateImpliedEndTagsThorough = () => {
|
|
4964
5749
|
while (open.length) {
|
|
4965
5750
|
const el = cur();
|
|
4966
|
-
if (
|
|
5751
|
+
if (
|
|
5752
|
+
_namespaceOf(el) === NS_HTML &&
|
|
5753
|
+
IMPLIED_THOROUGH.has(_tagNameOf(el))
|
|
5754
|
+
) {
|
|
4967
5755
|
open.pop();
|
|
4968
5756
|
} else {
|
|
4969
5757
|
break;
|
|
@@ -4976,11 +5764,11 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4976
5764
|
let count = 0;
|
|
4977
5765
|
for (let i = afe.length - 1; i >= 0; i--) {
|
|
4978
5766
|
const e = afe[i];
|
|
4979
|
-
if (e
|
|
5767
|
+
if (e === AFE_MARKER) break;
|
|
4980
5768
|
if (
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
sameAttrs(
|
|
5769
|
+
_tagNameOf(e) === _tagNameOf(el) &&
|
|
5770
|
+
_namespaceOf(e) === _namespaceOf(el) &&
|
|
5771
|
+
sameAttrs(e, el)
|
|
4984
5772
|
) {
|
|
4985
5773
|
count++;
|
|
4986
5774
|
if (count === 3) {
|
|
@@ -4989,60 +5777,49 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4989
5777
|
}
|
|
4990
5778
|
}
|
|
4991
5779
|
}
|
|
4992
|
-
afe.push(
|
|
5780
|
+
afe.push(el);
|
|
4993
5781
|
};
|
|
4994
5782
|
const sameAttrs = (
|
|
4995
5783
|
/** @type {HtmlElement} */ a,
|
|
4996
5784
|
/** @type {HtmlElement} */ b
|
|
4997
5785
|
) => {
|
|
4998
|
-
const
|
|
4999
|
-
const
|
|
5000
|
-
|
|
5786
|
+
const aStart = _nodeAttrStarts[a];
|
|
5787
|
+
const aCount = _nodeAttrCounts[a];
|
|
5788
|
+
const bStart = _nodeAttrStarts[b];
|
|
5789
|
+
const bCount = _nodeAttrCounts[b];
|
|
5790
|
+
if (aCount !== bCount) return false;
|
|
5001
5791
|
// Names are unique (deduped), counts tiny — a nested scan beats a Map
|
|
5002
|
-
// here
|
|
5003
|
-
for (let i =
|
|
5004
|
-
const
|
|
5005
|
-
|
|
5006
|
-
for (let j = 0; j < aa.length; j++) {
|
|
5007
|
-
if (aa[j].name === x.name) {
|
|
5008
|
-
ok = aa[j].value === x.value;
|
|
5009
|
-
break;
|
|
5010
|
-
}
|
|
5011
|
-
}
|
|
5012
|
-
if (!ok) return false;
|
|
5792
|
+
// here on this formatting-element hot path.
|
|
5793
|
+
for (let i = bStart; i < bStart + bCount; i++) {
|
|
5794
|
+
const j = _findAttr(aStart, aCount, _attrNames[i]);
|
|
5795
|
+
if (j === 0 || _attrValueOf(j) !== _attrValueOf(i)) return false;
|
|
5013
5796
|
}
|
|
5014
5797
|
return true;
|
|
5015
5798
|
};
|
|
5016
|
-
const insertMarker = () => afe.push(
|
|
5799
|
+
const insertMarker = () => afe.push(AFE_MARKER);
|
|
5017
5800
|
const clearAfeToMarker = () => {
|
|
5018
5801
|
while (afe.length) {
|
|
5019
|
-
|
|
5020
|
-
if (e.marker) break;
|
|
5802
|
+
if (afe.pop() === AFE_MARKER) break;
|
|
5021
5803
|
}
|
|
5022
5804
|
};
|
|
5023
5805
|
const reconstructAfe = () => {
|
|
5024
5806
|
if (afe.length === 0) return;
|
|
5025
5807
|
let i = afe.length - 1;
|
|
5026
|
-
if (afe[i]
|
|
5808
|
+
if (afe[i] === AFE_MARKER || open.includes(afe[i])) return;
|
|
5027
5809
|
while (i > 0) {
|
|
5028
5810
|
i--;
|
|
5029
|
-
if (afe[i]
|
|
5811
|
+
if (afe[i] === AFE_MARKER || open.includes(afe[i])) {
|
|
5030
5812
|
i++;
|
|
5031
5813
|
break;
|
|
5032
5814
|
}
|
|
5033
5815
|
}
|
|
5034
5816
|
for (; i < afe.length; i++) {
|
|
5035
5817
|
const e = afe[i];
|
|
5036
|
-
const el = mkEl(
|
|
5037
|
-
afeEl(e).tagName,
|
|
5038
|
-
afeEl(e).namespace,
|
|
5039
|
-
cloneAttrs(afeEl(e).attributes),
|
|
5040
|
-
null
|
|
5041
|
-
);
|
|
5818
|
+
const el = mkEl(_tagNameOf(e), _namespaceOf(e), cloneAttrs(e), null);
|
|
5042
5819
|
const place = appropriatePlace();
|
|
5043
5820
|
insertAtPlace(place, el);
|
|
5044
5821
|
open.push(el);
|
|
5045
|
-
afe[i] =
|
|
5822
|
+
afe[i] = el;
|
|
5046
5823
|
}
|
|
5047
5824
|
};
|
|
5048
5825
|
|
|
@@ -5052,23 +5829,23 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5052
5829
|
// pop until a p has been popped
|
|
5053
5830
|
while (open.length) {
|
|
5054
5831
|
const el = /** @type {HtmlElement} */ (open.pop());
|
|
5055
|
-
el
|
|
5056
|
-
if (el
|
|
5832
|
+
_nodeEnds[el] = tokenEnd;
|
|
5833
|
+
if (_namespaceOf(el) === NS_HTML && _tagNameOf(el) === "p") break;
|
|
5057
5834
|
}
|
|
5058
5835
|
};
|
|
5059
5836
|
|
|
5060
5837
|
const popUntil = (/** @type {string} */ tagName) => {
|
|
5061
5838
|
while (open.length) {
|
|
5062
5839
|
const el = /** @type {HtmlElement} */ (open.pop());
|
|
5063
|
-
el
|
|
5064
|
-
if (el
|
|
5840
|
+
_nodeEnds[el] = tokenEnd;
|
|
5841
|
+
if (_namespaceOf(el) === NS_HTML && _tagNameOf(el) === tagName) break;
|
|
5065
5842
|
}
|
|
5066
5843
|
};
|
|
5067
5844
|
const popUntilOneOf = (/** @type {Set<string>} */ set) => {
|
|
5068
5845
|
while (open.length) {
|
|
5069
5846
|
const el = /** @type {HtmlElement} */ (open.pop());
|
|
5070
|
-
el
|
|
5071
|
-
if (el
|
|
5847
|
+
_nodeEnds[el] = tokenEnd;
|
|
5848
|
+
if (_namespaceOf(el) === NS_HTML && set.has(_tagNameOf(el))) break;
|
|
5072
5849
|
}
|
|
5073
5850
|
};
|
|
5074
5851
|
|
|
@@ -5081,8 +5858,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5081
5858
|
last = true;
|
|
5082
5859
|
if (fragment) node = fragment;
|
|
5083
5860
|
}
|
|
5084
|
-
const tn = node
|
|
5085
|
-
if (node
|
|
5861
|
+
const tn = _tagNameOf(node);
|
|
5862
|
+
if (_namespaceOf(node) === NS_HTML) {
|
|
5086
5863
|
if ((tn === "td" || tn === "th") && !last) {
|
|
5087
5864
|
mode = MODE_IN_CELL;
|
|
5088
5865
|
return;
|
|
@@ -5175,7 +5952,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5175
5952
|
const useForeign =
|
|
5176
5953
|
open.length > 0 &&
|
|
5177
5954
|
ac &&
|
|
5178
|
-
ac
|
|
5955
|
+
_namespaceOf(ac) !== NS_HTML &&
|
|
5179
5956
|
ty !== TOKEN_EOF &&
|
|
5180
5957
|
shouldUseForeignRules(ac, t);
|
|
5181
5958
|
if (useForeign) {
|
|
@@ -5189,7 +5966,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5189
5966
|
/** @type {HtmlElement} */ ac,
|
|
5190
5967
|
/** @type {Token} */ t
|
|
5191
5968
|
) => {
|
|
5192
|
-
if (ac
|
|
5969
|
+
if (_namespaceOf(ac) === NS_HTML) return false;
|
|
5193
5970
|
if (t.type === TOKEN_START_TAG) {
|
|
5194
5971
|
if (
|
|
5195
5972
|
mathmlTextIntegrationPoint(ac) &&
|
|
@@ -5199,8 +5976,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5199
5976
|
return false;
|
|
5200
5977
|
}
|
|
5201
5978
|
if (
|
|
5202
|
-
ac
|
|
5203
|
-
ac
|
|
5979
|
+
_namespaceOf(ac) === NS_MATHML &&
|
|
5980
|
+
_tagNameOf(ac) === "annotation-xml" &&
|
|
5204
5981
|
t.name === "svg"
|
|
5205
5982
|
) {
|
|
5206
5983
|
return false;
|
|
@@ -5219,20 +5996,27 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5219
5996
|
};
|
|
5220
5997
|
|
|
5221
5998
|
const mathmlTextIntegrationPoint = (/** @type {HtmlElement} */ el) =>
|
|
5222
|
-
el
|
|
5999
|
+
_namespaceOf(el) === NS_MATHML &&
|
|
6000
|
+
MATHML_TEXT_INTEGRATION.has(_tagNameOf(el));
|
|
5223
6001
|
const htmlIntegrationPoint = (/** @type {HtmlElement} */ el) => {
|
|
5224
|
-
if (el
|
|
5225
|
-
const enc =
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
) {
|
|
5231
|
-
|
|
6002
|
+
if (_namespaceOf(el) === NS_MATHML && _tagNameOf(el) === "annotation-xml") {
|
|
6003
|
+
const enc = _findAttr(
|
|
6004
|
+
_nodeAttrStarts[el],
|
|
6005
|
+
_nodeAttrCounts[el],
|
|
6006
|
+
"encoding"
|
|
6007
|
+
);
|
|
6008
|
+
if (enc !== 0) {
|
|
6009
|
+
const value = _attrValueOf(enc).toLowerCase();
|
|
6010
|
+
if (value === "text/html" || value === "application/xhtml+xml") {
|
|
6011
|
+
return true;
|
|
6012
|
+
}
|
|
5232
6013
|
}
|
|
5233
6014
|
return false;
|
|
5234
6015
|
}
|
|
5235
|
-
if (
|
|
6016
|
+
if (
|
|
6017
|
+
_namespaceOf(el) === NS_SVG &&
|
|
6018
|
+
SVG_SPECIAL.has(_tagNameOf(el).toLowerCase())
|
|
6019
|
+
) {
|
|
5236
6020
|
return true;
|
|
5237
6021
|
}
|
|
5238
6022
|
return false;
|
|
@@ -5240,37 +6024,28 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5240
6024
|
|
|
5241
6025
|
const adjustSvgTag = (/** @type {string} */ name) =>
|
|
5242
6026
|
/** @type {Record<string, string>} */ (SVG_TAG_ADJUST)[name] || name;
|
|
6027
|
+
// Adjust a foreign start tag's attribute run in place (the run is consumed
|
|
6028
|
+
// only by this tag's element): SVG camelCase names are rewritten, and
|
|
6029
|
+
// namespaced names get the serializer-name flag (see `_attrSerializedName`).
|
|
5243
6030
|
const adjustForeignAttrs = (
|
|
5244
|
-
/** @type {
|
|
6031
|
+
/** @type {AttributeRun} */ run,
|
|
5245
6032
|
/** @type {number} */ ns
|
|
5246
6033
|
) => {
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
// the serializer falls back to `name`. Avoids a new array + object per
|
|
5250
|
-
// attribute on every foreign element.
|
|
5251
|
-
let out = attrs;
|
|
5252
|
-
for (let i = 0; i < attrs.length; i++) {
|
|
5253
|
-
const a = attrs[i];
|
|
5254
|
-
let name = a.name;
|
|
5255
|
-
/** @type {string | undefined} */
|
|
5256
|
-
let serializedName;
|
|
6034
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
6035
|
+
const name = _attrNames[i];
|
|
5257
6036
|
if (
|
|
5258
6037
|
ns === NS_SVG &&
|
|
5259
6038
|
/** @type {Record<string, string>} */ (SVG_ATTR_ADJUST)[name]
|
|
5260
6039
|
) {
|
|
5261
|
-
|
|
5262
|
-
|
|
6040
|
+
_attrNames[i] = /** @type {Record<string, string>} */ (SVG_ATTR_ADJUST)[
|
|
6041
|
+
name
|
|
6042
|
+
];
|
|
5263
6043
|
}
|
|
5264
|
-
if (/** @type {Record<string, string>} */ (FOREIGN_ATTR_NS)[
|
|
5265
|
-
|
|
5266
|
-
FOREIGN_ATTR_NS
|
|
5267
|
-
)[a.name];
|
|
6044
|
+
if (/** @type {Record<string, string>} */ (FOREIGN_ATTR_NS)[name]) {
|
|
6045
|
+
_attrFlags[i] |= 1;
|
|
5268
6046
|
}
|
|
5269
|
-
if (name === a.name && serializedName === undefined) continue;
|
|
5270
|
-
if (out === attrs) out = [...attrs];
|
|
5271
|
-
out[i] = { ...a, name, serializedName };
|
|
5272
6047
|
}
|
|
5273
|
-
return
|
|
6048
|
+
return run;
|
|
5274
6049
|
};
|
|
5275
6050
|
|
|
5276
6051
|
const foreignContent = (/** @type {Token} */ t) => {
|
|
@@ -5287,17 +6062,16 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5287
6062
|
}
|
|
5288
6063
|
if (t.type === TOKEN_DOCTYPE) return;
|
|
5289
6064
|
if (t.type === TOKEN_START_TAG) {
|
|
5290
|
-
const acn = adjustedCurrent()
|
|
6065
|
+
const acn = _namespaceOf(adjustedCurrent());
|
|
5291
6066
|
if (
|
|
5292
6067
|
FOREIGN_BREAKOUT.has(t.name) ||
|
|
5293
|
-
(t.name === "font" &&
|
|
5294
|
-
t.attrs.some((a) => FONT_BREAKOUT_ATTRS.has(a.name)))
|
|
6068
|
+
(t.name === "font" && hasFontBreakoutAttr(t.attrs))
|
|
5295
6069
|
) {
|
|
5296
6070
|
// parse error; pop until integration point / html / mathml-text-integration
|
|
5297
6071
|
while (open.length > 1) {
|
|
5298
6072
|
const c = cur();
|
|
5299
6073
|
if (
|
|
5300
|
-
c
|
|
6074
|
+
_namespaceOf(c) === NS_HTML ||
|
|
5301
6075
|
mathmlTextIntegrationPoint(c) ||
|
|
5302
6076
|
htmlIntegrationPoint(c)
|
|
5303
6077
|
) {
|
|
@@ -5327,8 +6101,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5327
6101
|
if (t.type === TOKEN_END_TAG) {
|
|
5328
6102
|
if (
|
|
5329
6103
|
t.name === "script" &&
|
|
5330
|
-
cur()
|
|
5331
|
-
cur()
|
|
6104
|
+
_tagNameOf(cur()) === "script" &&
|
|
6105
|
+
_namespaceOf(cur()) === NS_SVG
|
|
5332
6106
|
) {
|
|
5333
6107
|
open.pop();
|
|
5334
6108
|
return;
|
|
@@ -5338,7 +6112,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5338
6112
|
if (t.name === "p" || t.name === "br") {
|
|
5339
6113
|
while (
|
|
5340
6114
|
open.length > 1 &&
|
|
5341
|
-
cur()
|
|
6115
|
+
_namespaceOf(cur()) !== NS_HTML &&
|
|
5342
6116
|
!mathmlTextIntegrationPoint(cur()) &&
|
|
5343
6117
|
!htmlIntegrationPoint(cur())
|
|
5344
6118
|
) {
|
|
@@ -5350,21 +6124,21 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5350
6124
|
// any other end tag
|
|
5351
6125
|
let i = open.length - 1;
|
|
5352
6126
|
let node = open[i];
|
|
5353
|
-
if (node.
|
|
6127
|
+
if (_tagNameOf(node).toLowerCase() !== t.name) {
|
|
5354
6128
|
/* parse error */
|
|
5355
6129
|
}
|
|
5356
6130
|
while (i >= 0) {
|
|
5357
6131
|
node = open[i];
|
|
5358
6132
|
if (i === 0) return;
|
|
5359
6133
|
if (
|
|
5360
|
-
node
|
|
5361
|
-
node.
|
|
6134
|
+
_namespaceOf(node) !== NS_HTML &&
|
|
6135
|
+
_tagNameOf(node).toLowerCase() === t.name
|
|
5362
6136
|
) {
|
|
5363
6137
|
while (open.length > i) open.pop();
|
|
5364
6138
|
return;
|
|
5365
6139
|
}
|
|
5366
6140
|
i--;
|
|
5367
|
-
if (open[i] && open[i]
|
|
6141
|
+
if (open[i] && _namespaceOf(open[i]) === NS_HTML) {
|
|
5368
6142
|
runMode(t);
|
|
5369
6143
|
return;
|
|
5370
6144
|
}
|
|
@@ -5380,9 +6154,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5380
6154
|
// step 1
|
|
5381
6155
|
const c = cur();
|
|
5382
6156
|
if (
|
|
5383
|
-
c
|
|
5384
|
-
c
|
|
5385
|
-
!afe.
|
|
6157
|
+
_namespaceOf(c) === NS_HTML &&
|
|
6158
|
+
_tagNameOf(c) === subject &&
|
|
6159
|
+
!afe.includes(c)
|
|
5386
6160
|
) {
|
|
5387
6161
|
open.pop();
|
|
5388
6162
|
return true;
|
|
@@ -5393,17 +6167,17 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5393
6167
|
// find formatting element
|
|
5394
6168
|
let fmtIdx = -1;
|
|
5395
6169
|
for (let i = afe.length - 1; i >= 0; i--) {
|
|
5396
|
-
if (afe[i]
|
|
6170
|
+
if (afe[i] === AFE_MARKER) break;
|
|
5397
6171
|
if (
|
|
5398
|
-
|
|
5399
|
-
|
|
6172
|
+
_tagNameOf(afe[i]) === subject &&
|
|
6173
|
+
_namespaceOf(afe[i]) === NS_HTML
|
|
5400
6174
|
) {
|
|
5401
6175
|
fmtIdx = i;
|
|
5402
6176
|
break;
|
|
5403
6177
|
}
|
|
5404
6178
|
}
|
|
5405
6179
|
if (fmtIdx === -1) return false; // act as any other end tag
|
|
5406
|
-
const fmt =
|
|
6180
|
+
const fmt = afe[fmtIdx];
|
|
5407
6181
|
const openIdx = open.indexOf(fmt);
|
|
5408
6182
|
if (openIdx === -1) {
|
|
5409
6183
|
afe.splice(fmtIdx, 1);
|
|
@@ -5435,10 +6209,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5435
6209
|
nodeIdx--;
|
|
5436
6210
|
node = open[nodeIdx];
|
|
5437
6211
|
if (node === fmt) break;
|
|
5438
|
-
|
|
5439
|
-
let nodeAfeIdx = afe.findIndex(
|
|
5440
|
-
(e) => !e.marker && e.element === nodeRef
|
|
5441
|
-
);
|
|
6212
|
+
let nodeAfeIdx = afe.indexOf(node);
|
|
5442
6213
|
if (inner > 3 && nodeAfeIdx !== -1) {
|
|
5443
6214
|
afe.splice(nodeAfeIdx, 1);
|
|
5444
6215
|
if (nodeAfeIdx < bookmark) bookmark--;
|
|
@@ -5450,12 +6221,12 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5450
6221
|
}
|
|
5451
6222
|
// create clone
|
|
5452
6223
|
const clone = mkEl(
|
|
5453
|
-
node
|
|
5454
|
-
node
|
|
5455
|
-
cloneAttrs(node
|
|
6224
|
+
_tagNameOf(node),
|
|
6225
|
+
_namespaceOf(node),
|
|
6226
|
+
cloneAttrs(node),
|
|
5456
6227
|
null
|
|
5457
6228
|
);
|
|
5458
|
-
afe[nodeAfeIdx] =
|
|
6229
|
+
afe[nodeAfeIdx] = clone;
|
|
5459
6230
|
open[nodeIdx] = clone;
|
|
5460
6231
|
node = clone;
|
|
5461
6232
|
if (lastNode === furthest) bookmark = nodeAfeIdx + 1;
|
|
@@ -5470,22 +6241,31 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5470
6241
|
insertAtPlace(place, lastNode);
|
|
5471
6242
|
// create element for fmt token, take children of furthest
|
|
5472
6243
|
const cloneFmt = mkEl(
|
|
5473
|
-
fmt
|
|
5474
|
-
fmt
|
|
5475
|
-
cloneAttrs(fmt
|
|
6244
|
+
_tagNameOf(fmt),
|
|
6245
|
+
_namespaceOf(fmt),
|
|
6246
|
+
cloneAttrs(fmt),
|
|
5476
6247
|
null
|
|
5477
6248
|
);
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
6249
|
+
// Take all direct children of `furthest` (a template's content fragment
|
|
6250
|
+
// deliberately stays put — mirrors childrenOf-less spec behavior here).
|
|
6251
|
+
let k = _nodeFirstChildren[furthest];
|
|
6252
|
+
_nodeFirstChildren[furthest] = 0;
|
|
6253
|
+
_nodeLastChildren[furthest] = 0;
|
|
6254
|
+
while (k !== 0) {
|
|
6255
|
+
const next = _nodeNextSiblings[k];
|
|
6256
|
+
_nodeNextSiblings[k] = 0;
|
|
6257
|
+
_nodeParents[k] = 0;
|
|
6258
|
+
appendTo(cloneFmt, k);
|
|
6259
|
+
k = next;
|
|
6260
|
+
}
|
|
5481
6261
|
appendTo(furthest, cloneFmt);
|
|
5482
6262
|
// remove fmt from afe, insert clone at bookmark
|
|
5483
|
-
const curFmtIdx = afe.
|
|
6263
|
+
const curFmtIdx = afe.indexOf(fmt);
|
|
5484
6264
|
if (curFmtIdx !== -1) {
|
|
5485
6265
|
afe.splice(curFmtIdx, 1);
|
|
5486
6266
|
if (curFmtIdx < bookmark) bookmark--;
|
|
5487
6267
|
}
|
|
5488
|
-
afe.splice(bookmark, 0,
|
|
6268
|
+
afe.splice(bookmark, 0, cloneFmt);
|
|
5489
6269
|
// remove fmt from open, insert clone below furthest
|
|
5490
6270
|
const ofi = open.indexOf(fmt);
|
|
5491
6271
|
if (ofi !== -1) open.splice(ofi, 1);
|
|
@@ -5499,8 +6279,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5499
6279
|
/** @type {HtmlElement} */ commonAncestor
|
|
5500
6280
|
) => {
|
|
5501
6281
|
if (
|
|
5502
|
-
TABLE_CONTEXT.has(commonAncestor
|
|
5503
|
-
commonAncestor
|
|
6282
|
+
TABLE_CONTEXT.has(_tagNameOf(commonAncestor)) &&
|
|
6283
|
+
_namespaceOf(commonAncestor) === NS_HTML
|
|
5504
6284
|
) {
|
|
5505
6285
|
// foster
|
|
5506
6286
|
// reuse appropriatePlace logic but rooted differently: emulate
|
|
@@ -5508,15 +6288,15 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5508
6288
|
let lastTable = -1;
|
|
5509
6289
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
5510
6290
|
if (
|
|
5511
|
-
open[i]
|
|
5512
|
-
open[i]
|
|
6291
|
+
_tagNameOf(open[i]) === "template" &&
|
|
6292
|
+
_namespaceOf(open[i]) === NS_HTML &&
|
|
5513
6293
|
lastTemplate === -1
|
|
5514
6294
|
) {
|
|
5515
6295
|
lastTemplate = i;
|
|
5516
6296
|
}
|
|
5517
6297
|
if (
|
|
5518
|
-
open[i]
|
|
5519
|
-
open[i]
|
|
6298
|
+
_tagNameOf(open[i]) === "table" &&
|
|
6299
|
+
_namespaceOf(open[i]) === NS_HTML &&
|
|
5520
6300
|
lastTable === -1
|
|
5521
6301
|
) {
|
|
5522
6302
|
lastTable = i;
|
|
@@ -5526,23 +6306,32 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5526
6306
|
lastTemplate !== -1 &&
|
|
5527
6307
|
(lastTable === -1 || lastTemplate > lastTable)
|
|
5528
6308
|
) {
|
|
5529
|
-
return { parent: open[lastTemplate], beforeNode:
|
|
6309
|
+
return { parent: open[lastTemplate], beforeNode: 0 };
|
|
5530
6310
|
}
|
|
5531
|
-
if (lastTable === -1) return { parent: open[0], beforeNode:
|
|
6311
|
+
if (lastTable === -1) return { parent: open[0], beforeNode: 0 };
|
|
5532
6312
|
const table = open[lastTable];
|
|
5533
|
-
const tp =
|
|
5534
|
-
if (tp) return { parent: tp, beforeNode: table };
|
|
5535
|
-
return { parent: open[lastTable - 1], beforeNode:
|
|
6313
|
+
const tp = _nodeParents[table];
|
|
6314
|
+
if (tp !== 0) return { parent: tp, beforeNode: table };
|
|
6315
|
+
return { parent: open[lastTable - 1], beforeNode: 0 };
|
|
5536
6316
|
}
|
|
5537
|
-
return { parent: commonAncestor, beforeNode:
|
|
6317
|
+
return { parent: commonAncestor, beforeNode: 0 };
|
|
5538
6318
|
};
|
|
5539
6319
|
|
|
5540
|
-
const detach = (/** @type {
|
|
5541
|
-
const p =
|
|
5542
|
-
if (
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
6320
|
+
const detach = (/** @type {HtmlNodeRef} */ node) => {
|
|
6321
|
+
const p = _nodeParents[node];
|
|
6322
|
+
if (p === 0) return;
|
|
6323
|
+
let prev = 0;
|
|
6324
|
+
let c = _nodeFirstChildren[p];
|
|
6325
|
+
while (c !== 0 && c !== node) {
|
|
6326
|
+
prev = c;
|
|
6327
|
+
c = _nodeNextSiblings[c];
|
|
6328
|
+
}
|
|
6329
|
+
if (c === 0) return;
|
|
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;
|
|
5546
6335
|
};
|
|
5547
6336
|
|
|
5548
6337
|
// ---------- insertion modes ----------
|
|
@@ -5700,18 +6489,20 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5700
6489
|
return;
|
|
5701
6490
|
}
|
|
5702
6491
|
if (t.type === TOKEN_COMMENT) {
|
|
5703
|
-
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode:
|
|
6492
|
+
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode: 0 });
|
|
5704
6493
|
return;
|
|
5705
6494
|
}
|
|
5706
6495
|
if (t.type === TOKEN_DOCTYPE) {
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
6496
|
+
// `skip.doctype` drops the node only; quirks detection below is unaffected.
|
|
6497
|
+
if (!skipDoctype) {
|
|
6498
|
+
const dt = _allocNode(NodeType.Doctype, t.start, t.end);
|
|
6499
|
+
_nodeStrings[dt] = t.name;
|
|
6500
|
+
// At most one doctype node is ever inserted (later doctype tokens
|
|
6501
|
+
// are ignored), so its ids live in two per-parse scalars.
|
|
6502
|
+
_doctypePublicId = t.publicId;
|
|
6503
|
+
_doctypeSystemId = t.systemId;
|
|
6504
|
+
_appendChild(doc, dt);
|
|
6505
|
+
}
|
|
5715
6506
|
quirks = isQuirky(t.name, t.publicId, t.systemId);
|
|
5716
6507
|
mode = MODE_BEFORE_HTML;
|
|
5717
6508
|
return;
|
|
@@ -5724,7 +6515,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5724
6515
|
modes.beforeHtml = (t) => {
|
|
5725
6516
|
if (t.type === TOKEN_DOCTYPE) return;
|
|
5726
6517
|
if (t.type === TOKEN_COMMENT) {
|
|
5727
|
-
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode:
|
|
6518
|
+
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode: 0 });
|
|
5728
6519
|
return;
|
|
5729
6520
|
}
|
|
5730
6521
|
if (t.type === TOKEN_CHAR) {
|
|
@@ -5734,7 +6525,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5734
6525
|
}
|
|
5735
6526
|
if (t.type === TOKEN_START_TAG && t.name === "html") {
|
|
5736
6527
|
const el = mkEl("html", NS_HTML, t.attrs, t.pos);
|
|
5737
|
-
doc
|
|
6528
|
+
_appendChild(doc, el);
|
|
5738
6529
|
open.push(el);
|
|
5739
6530
|
mode = MODE_BEFORE_HEAD;
|
|
5740
6531
|
return;
|
|
@@ -5742,8 +6533,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5742
6533
|
if (t.type === TOKEN_END_TAG && !HEAD_BODY_HTML_BR.has(t.name)) {
|
|
5743
6534
|
return;
|
|
5744
6535
|
}
|
|
5745
|
-
const el = mkEl("html", NS_HTML,
|
|
5746
|
-
doc
|
|
6536
|
+
const el = mkEl("html", NS_HTML, EMPTY_ATTRS, null);
|
|
6537
|
+
_appendChild(doc, el);
|
|
5747
6538
|
open.push(el);
|
|
5748
6539
|
mode = MODE_BEFORE_HEAD;
|
|
5749
6540
|
process(t);
|
|
@@ -5819,7 +6610,11 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5819
6610
|
mode = MODE_IN_TEMPLATE;
|
|
5820
6611
|
templateModes.push(MODE_IN_TEMPLATE);
|
|
5821
6612
|
const el = cur();
|
|
5822
|
-
|
|
6613
|
+
const fragment = _allocNode(NodeType.DocumentFragment, 0, 0);
|
|
6614
|
+
_nodeStrings[fragment] = "";
|
|
6615
|
+
// Parent link so the iterative walk can ascend out of the content.
|
|
6616
|
+
_nodeParents[fragment] = el;
|
|
6617
|
+
_nodeTemplateContents[el] = fragment;
|
|
5823
6618
|
return;
|
|
5824
6619
|
}
|
|
5825
6620
|
if (t.name === "head") return;
|
|
@@ -5887,7 +6682,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5887
6682
|
if (t.type === TOKEN_CHAR) {
|
|
5888
6683
|
const r = leadingWs(t, true);
|
|
5889
6684
|
if (!r) return;
|
|
5890
|
-
insertHtmlElement("body",
|
|
6685
|
+
insertHtmlElement("body", EMPTY_ATTRS, null);
|
|
5891
6686
|
mode = MODE_IN_BODY;
|
|
5892
6687
|
process(r);
|
|
5893
6688
|
return;
|
|
@@ -5924,7 +6719,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5924
6719
|
if (t.name === "template") return modes.inHead(t);
|
|
5925
6720
|
if (!BODY_HTML_BR.has(t.name)) return;
|
|
5926
6721
|
}
|
|
5927
|
-
insertHtmlElement("body",
|
|
6722
|
+
insertHtmlElement("body", EMPTY_ATTRS, null);
|
|
5928
6723
|
mode = MODE_IN_BODY;
|
|
5929
6724
|
process(t);
|
|
5930
6725
|
};
|
|
@@ -5962,10 +6757,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5962
6757
|
const anyOtherEndTag = (/** @type {string} */ name) => {
|
|
5963
6758
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
5964
6759
|
const node = open[i];
|
|
5965
|
-
if (node
|
|
6760
|
+
if (_namespaceOf(node) === NS_HTML && _tagNameOf(node) === name) {
|
|
5966
6761
|
generateImpliedEndTags(name);
|
|
5967
6762
|
while (open.length > i) {
|
|
5968
|
-
open[open.length - 1]
|
|
6763
|
+
_nodeEnds[open[open.length - 1]] = tokenEnd;
|
|
5969
6764
|
open.pop();
|
|
5970
6765
|
}
|
|
5971
6766
|
return;
|
|
@@ -5980,12 +6775,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5980
6775
|
if (open.some(isHtmlTemplateEl)) {
|
|
5981
6776
|
return;
|
|
5982
6777
|
}
|
|
5983
|
-
|
|
5984
|
-
for (const a of t.attrs) {
|
|
5985
|
-
if (!htmlEl.attributes.some((x) => x.name === a.name)) {
|
|
5986
|
-
htmlEl.attributes.push(a);
|
|
5987
|
-
}
|
|
5988
|
-
}
|
|
6778
|
+
mergeAttrs(open[0], t.attrs);
|
|
5989
6779
|
return;
|
|
5990
6780
|
}
|
|
5991
6781
|
if (HEAD_ELEMENTS.has(name)) {
|
|
@@ -5993,20 +6783,20 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5993
6783
|
}
|
|
5994
6784
|
if (name === "body") {
|
|
5995
6785
|
const second = open[1];
|
|
5996
|
-
if (
|
|
6786
|
+
if (
|
|
6787
|
+
!second ||
|
|
6788
|
+
_tagNameOf(second) !== "body" ||
|
|
6789
|
+
open.some(isHtmlTemplateEl)
|
|
6790
|
+
) {
|
|
5997
6791
|
return;
|
|
5998
6792
|
}
|
|
5999
6793
|
framesetOk = false;
|
|
6000
|
-
|
|
6001
|
-
if (!second.attributes.some((x) => x.name === a.name)) {
|
|
6002
|
-
second.attributes.push(a);
|
|
6003
|
-
}
|
|
6004
|
-
}
|
|
6794
|
+
mergeAttrs(second, t.attrs);
|
|
6005
6795
|
return;
|
|
6006
6796
|
}
|
|
6007
6797
|
if (name === "frameset") {
|
|
6008
6798
|
const second = open[1];
|
|
6009
|
-
if (!second || second
|
|
6799
|
+
if (!second || _tagNameOf(second) !== "body") return;
|
|
6010
6800
|
if (!framesetOk) return;
|
|
6011
6801
|
detach(second);
|
|
6012
6802
|
while (open.length > 1) open.pop();
|
|
@@ -6021,7 +6811,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6021
6811
|
}
|
|
6022
6812
|
if (HEADING.has(name)) {
|
|
6023
6813
|
closeIfPInButtonScope();
|
|
6024
|
-
if (cur()
|
|
6814
|
+
if (_namespaceOf(cur()) === NS_HTML && HEADING.has(_tagNameOf(cur()))) {
|
|
6815
|
+
open.pop();
|
|
6816
|
+
}
|
|
6025
6817
|
insertHtmlElement(name, t.attrs, t.pos);
|
|
6026
6818
|
return;
|
|
6027
6819
|
}
|
|
@@ -6047,14 +6839,17 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6047
6839
|
framesetOk = false;
|
|
6048
6840
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
6049
6841
|
const node = open[i];
|
|
6050
|
-
if (node
|
|
6842
|
+
if (_namespaceOf(node) === NS_HTML && _tagNameOf(node) === "li") {
|
|
6051
6843
|
generateImpliedEndTags("li");
|
|
6052
6844
|
popUntil("li");
|
|
6053
6845
|
break;
|
|
6054
6846
|
}
|
|
6055
6847
|
if (
|
|
6056
6848
|
isSpecial(node) &&
|
|
6057
|
-
!(
|
|
6849
|
+
!(
|
|
6850
|
+
_namespaceOf(node) === NS_HTML &&
|
|
6851
|
+
ADDRESS_DIV_P.has(_tagNameOf(node))
|
|
6852
|
+
)
|
|
6058
6853
|
) {
|
|
6059
6854
|
break;
|
|
6060
6855
|
}
|
|
@@ -6068,16 +6863,19 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6068
6863
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
6069
6864
|
const node = open[i];
|
|
6070
6865
|
if (
|
|
6071
|
-
node
|
|
6072
|
-
(node
|
|
6866
|
+
_namespaceOf(node) === NS_HTML &&
|
|
6867
|
+
(_tagNameOf(node) === "dd" || _tagNameOf(node) === "dt")
|
|
6073
6868
|
) {
|
|
6074
|
-
generateImpliedEndTags(node
|
|
6075
|
-
popUntil(node
|
|
6869
|
+
generateImpliedEndTags(_tagNameOf(node));
|
|
6870
|
+
popUntil(_tagNameOf(node));
|
|
6076
6871
|
break;
|
|
6077
6872
|
}
|
|
6078
6873
|
if (
|
|
6079
6874
|
isSpecial(node) &&
|
|
6080
|
-
!(
|
|
6875
|
+
!(
|
|
6876
|
+
_namespaceOf(node) === NS_HTML &&
|
|
6877
|
+
ADDRESS_DIV_P.has(_tagNameOf(node))
|
|
6878
|
+
)
|
|
6081
6879
|
) {
|
|
6082
6880
|
break;
|
|
6083
6881
|
}
|
|
@@ -6104,14 +6902,14 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6104
6902
|
if (name === "a") {
|
|
6105
6903
|
// if there's an <a> in afe after last marker
|
|
6106
6904
|
for (let i = afe.length - 1; i >= 0; i--) {
|
|
6107
|
-
if (afe[i]
|
|
6108
|
-
if (
|
|
6905
|
+
if (afe[i] === AFE_MARKER) break;
|
|
6906
|
+
if (_tagNameOf(afe[i]) === "a") {
|
|
6109
6907
|
adoptionAgency("a", t.pos);
|
|
6110
6908
|
const idx = afe.findIndex(
|
|
6111
|
-
(e) =>
|
|
6909
|
+
(e) => e !== AFE_MARKER && _tagNameOf(e) === "a"
|
|
6112
6910
|
);
|
|
6113
6911
|
if (idx !== -1) {
|
|
6114
|
-
const el =
|
|
6912
|
+
const el = afe[idx];
|
|
6115
6913
|
afe.splice(idx, 1);
|
|
6116
6914
|
const oi = open.indexOf(el);
|
|
6117
6915
|
if (oi !== -1) open.splice(oi, 1);
|
|
@@ -6171,16 +6969,18 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6171
6969
|
resetInsertionMode();
|
|
6172
6970
|
} else if (
|
|
6173
6971
|
fragment &&
|
|
6174
|
-
fragment
|
|
6175
|
-
fragment
|
|
6972
|
+
_namespaceOf(fragment) === NS_HTML &&
|
|
6973
|
+
_tagNameOf(fragment) === "select"
|
|
6176
6974
|
) {
|
|
6177
6975
|
return;
|
|
6178
6976
|
}
|
|
6179
6977
|
reconstructAfe();
|
|
6180
6978
|
insertHtmlElement("input", t.attrs, t.pos);
|
|
6181
6979
|
open.pop();
|
|
6182
|
-
const ty =
|
|
6183
|
-
if (
|
|
6980
|
+
const ty = _findAttr(t.attrs.start, t.attrs.count, "type");
|
|
6981
|
+
if (ty === 0 || _attrValueOf(ty).toLowerCase() !== "hidden") {
|
|
6982
|
+
framesetOk = false;
|
|
6983
|
+
}
|
|
6184
6984
|
return;
|
|
6185
6985
|
}
|
|
6186
6986
|
if (PARAM_SOURCE_TRACK.has(name)) {
|
|
@@ -6189,8 +6989,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6189
6989
|
return;
|
|
6190
6990
|
}
|
|
6191
6991
|
if (name === "hr") {
|
|
6192
|
-
if (cur()
|
|
6193
|
-
|
|
6992
|
+
if (_namespaceOf(cur()) === NS_HTML && _tagNameOf(cur()) === "option") {
|
|
6993
|
+
open.pop();
|
|
6994
|
+
}
|
|
6995
|
+
if (_namespaceOf(cur()) === NS_HTML && _tagNameOf(cur()) === "optgroup") {
|
|
6194
6996
|
open.pop();
|
|
6195
6997
|
}
|
|
6196
6998
|
closeIfPInButtonScope();
|
|
@@ -6239,11 +7041,13 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6239
7041
|
return;
|
|
6240
7042
|
}
|
|
6241
7043
|
if (name === "optgroup" || name === "option") {
|
|
6242
|
-
if (cur()
|
|
7044
|
+
if (_namespaceOf(cur()) === NS_HTML && _tagNameOf(cur()) === "option") {
|
|
7045
|
+
open.pop();
|
|
7046
|
+
}
|
|
6243
7047
|
if (
|
|
6244
7048
|
name === "optgroup" &&
|
|
6245
|
-
cur()
|
|
6246
|
-
cur()
|
|
7049
|
+
_namespaceOf(cur()) === NS_HTML &&
|
|
7050
|
+
_tagNameOf(cur()) === "optgroup"
|
|
6247
7051
|
) {
|
|
6248
7052
|
open.pop();
|
|
6249
7053
|
}
|
|
@@ -6283,20 +7087,13 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6283
7087
|
insertHtmlElement(name, t.attrs, t.pos);
|
|
6284
7088
|
};
|
|
6285
7089
|
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
for (let i =
|
|
6290
|
-
if (
|
|
6291
|
-
if (out === attrs) out = [...attrs];
|
|
6292
|
-
out[i] = {
|
|
6293
|
-
...attrs[i],
|
|
6294
|
-
name: "definitionURL",
|
|
6295
|
-
serializedName: "definitionURL"
|
|
6296
|
-
};
|
|
6297
|
-
}
|
|
7090
|
+
// Only `definitionurl` is rewritten (in place — the run is consumed only by
|
|
7091
|
+
// this tag's element); the camelCase name serializes as itself.
|
|
7092
|
+
const adjustMathmlAttrs = (/** @type {AttributeRun} */ run) => {
|
|
7093
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
7094
|
+
if (_attrNames[i] === "definitionurl") _attrNames[i] = "definitionURL";
|
|
6298
7095
|
}
|
|
6299
|
-
return
|
|
7096
|
+
return run;
|
|
6300
7097
|
};
|
|
6301
7098
|
|
|
6302
7099
|
const endTagInBody = (/** @type {EndTagToken} */ t) => {
|
|
@@ -6323,7 +7120,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6323
7120
|
if (name === "form") {
|
|
6324
7121
|
if (!open.some(isHtmlTemplateEl)) {
|
|
6325
7122
|
const node = form;
|
|
6326
|
-
form =
|
|
7123
|
+
form = 0;
|
|
6327
7124
|
if (!node || !inScopeEl(node)) return;
|
|
6328
7125
|
generateImpliedEndTags();
|
|
6329
7126
|
const idx = open.indexOf(node);
|
|
@@ -6420,7 +7217,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6420
7217
|
// current element: a non-matching name (e.g. a fragment context), or
|
|
6421
7218
|
// a name that ran straight to EOF with no delimiter (`</script` at
|
|
6422
7219
|
// EOF — the tokenizer still emits a partial tag there).
|
|
6423
|
-
if (
|
|
7220
|
+
if (
|
|
7221
|
+
cur() &&
|
|
7222
|
+
(t.name !== _tagNameOf(cur()) || t.pos.end === t.pos.nameEnd)
|
|
7223
|
+
) {
|
|
6424
7224
|
insertCharacters(
|
|
6425
7225
|
source.slice(t.pos.start, t.pos.end),
|
|
6426
7226
|
t.pos.start,
|
|
@@ -6428,7 +7228,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6428
7228
|
);
|
|
6429
7229
|
return;
|
|
6430
7230
|
}
|
|
6431
|
-
|
|
7231
|
+
// span the close tag, like every other explicit-close pop
|
|
7232
|
+
_nodeEnds[/** @type {HtmlElement} */ (open.pop())] = tokenEnd;
|
|
6432
7233
|
mode = originalMode;
|
|
6433
7234
|
}
|
|
6434
7235
|
};
|
|
@@ -6440,7 +7241,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6440
7241
|
modes.inTable = (t) => {
|
|
6441
7242
|
if (t.type === TOKEN_CHAR) {
|
|
6442
7243
|
const c = cur();
|
|
6443
|
-
if (TABLE_CONTEXT.has(c
|
|
7244
|
+
if (TABLE_CONTEXT.has(_tagNameOf(c)) && _namespaceOf(c) === NS_HTML) {
|
|
6444
7245
|
pendingTableChars = { list: [], hasNonWs: false };
|
|
6445
7246
|
originalMode = mode;
|
|
6446
7247
|
mode = MODE_IN_TABLE_TEXT;
|
|
@@ -6495,8 +7296,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6495
7296
|
return modes.inHead(t);
|
|
6496
7297
|
}
|
|
6497
7298
|
if (name === "input") {
|
|
6498
|
-
const ty =
|
|
6499
|
-
if (ty && ty.
|
|
7299
|
+
const ty = _findAttr(t.attrs.start, t.attrs.count, "type");
|
|
7300
|
+
if (ty !== 0 && _attrValueOf(ty).toLowerCase() === "hidden") {
|
|
6500
7301
|
insertHtmlElement("input", t.attrs, t.pos);
|
|
6501
7302
|
open.pop();
|
|
6502
7303
|
return;
|
|
@@ -6566,7 +7367,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6566
7367
|
const clearStackToTableContext = () => {
|
|
6567
7368
|
while (open.length) {
|
|
6568
7369
|
const c = cur();
|
|
6569
|
-
if (c
|
|
7370
|
+
if (_namespaceOf(c) === NS_HTML && CLEAR_TABLE.has(_tagNameOf(c))) {
|
|
6570
7371
|
break;
|
|
6571
7372
|
}
|
|
6572
7373
|
open.pop();
|
|
@@ -6575,7 +7376,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6575
7376
|
const clearStackToTableBodyContext = () => {
|
|
6576
7377
|
while (open.length) {
|
|
6577
7378
|
const c = cur();
|
|
6578
|
-
if (c
|
|
7379
|
+
if (_namespaceOf(c) === NS_HTML && CLEAR_TABLE_BODY.has(_tagNameOf(c))) {
|
|
6579
7380
|
break;
|
|
6580
7381
|
}
|
|
6581
7382
|
open.pop();
|
|
@@ -6584,7 +7385,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6584
7385
|
const clearStackToTableRowContext = () => {
|
|
6585
7386
|
while (open.length) {
|
|
6586
7387
|
const c = cur();
|
|
6587
|
-
if (c
|
|
7388
|
+
if (_namespaceOf(c) === NS_HTML && CLEAR_TABLE_ROW.has(_tagNameOf(c))) {
|
|
6588
7389
|
break;
|
|
6589
7390
|
}
|
|
6590
7391
|
open.pop();
|
|
@@ -6617,7 +7418,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6617
7418
|
if (t.type === TOKEN_CHAR) {
|
|
6618
7419
|
const r = leadingWs(t, true);
|
|
6619
7420
|
if (!r) return;
|
|
6620
|
-
if (cur()
|
|
7421
|
+
if (_tagNameOf(cur()) !== "colgroup") return;
|
|
6621
7422
|
open.pop();
|
|
6622
7423
|
mode = MODE_IN_TABLE;
|
|
6623
7424
|
process(r);
|
|
@@ -6635,7 +7436,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6635
7436
|
return;
|
|
6636
7437
|
}
|
|
6637
7438
|
if (t.type === TOKEN_END_TAG && t.name === "colgroup") {
|
|
6638
|
-
if (cur()
|
|
7439
|
+
if (_tagNameOf(cur()) !== "colgroup") return;
|
|
6639
7440
|
open.pop();
|
|
6640
7441
|
mode = MODE_IN_TABLE;
|
|
6641
7442
|
return;
|
|
@@ -6648,7 +7449,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6648
7449
|
return modes.inHead(t);
|
|
6649
7450
|
}
|
|
6650
7451
|
if (t.type === TOKEN_EOF) return modes.inBody(t);
|
|
6651
|
-
if (cur()
|
|
7452
|
+
if (_tagNameOf(cur()) !== "colgroup") return;
|
|
6652
7453
|
open.pop();
|
|
6653
7454
|
mode = MODE_IN_TABLE;
|
|
6654
7455
|
process(t);
|
|
@@ -6798,7 +7599,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6798
7599
|
if (t.type === TOKEN_COMMENT) {
|
|
6799
7600
|
insertComment(t.data, t.start, t.end, {
|
|
6800
7601
|
parent: open[0],
|
|
6801
|
-
beforeNode:
|
|
7602
|
+
beforeNode: 0
|
|
6802
7603
|
});
|
|
6803
7604
|
return;
|
|
6804
7605
|
}
|
|
@@ -6831,9 +7632,11 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6831
7632
|
return;
|
|
6832
7633
|
}
|
|
6833
7634
|
if (t.type === TOKEN_END_TAG && t.name === "frameset") {
|
|
6834
|
-
if (cur()
|
|
7635
|
+
if (_tagNameOf(cur()) === "html") return;
|
|
6835
7636
|
open.pop();
|
|
6836
|
-
if (!fragment && cur()
|
|
7637
|
+
if (!fragment && _tagNameOf(cur()) !== "frameset") {
|
|
7638
|
+
mode = MODE_AFTER_FRAMESET;
|
|
7639
|
+
}
|
|
6837
7640
|
return;
|
|
6838
7641
|
}
|
|
6839
7642
|
if (t.type === TOKEN_START_TAG && t.name === "frame") {
|
|
@@ -6869,7 +7672,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6869
7672
|
|
|
6870
7673
|
modes.afterAfterBody = (t) => {
|
|
6871
7674
|
if (t.type === TOKEN_COMMENT) {
|
|
6872
|
-
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode:
|
|
7675
|
+
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode: 0 });
|
|
6873
7676
|
return;
|
|
6874
7677
|
}
|
|
6875
7678
|
if (t.type === TOKEN_DOCTYPE) return modes.inBody(t);
|
|
@@ -6882,7 +7685,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6882
7685
|
|
|
6883
7686
|
modes.afterAfterFrameset = (t) => {
|
|
6884
7687
|
if (t.type === TOKEN_COMMENT) {
|
|
6885
|
-
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode:
|
|
7688
|
+
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode: 0 });
|
|
6886
7689
|
return;
|
|
6887
7690
|
}
|
|
6888
7691
|
if (t.type === TOKEN_DOCTYPE) return modes.inBody(t);
|
|
@@ -6904,9 +7707,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6904
7707
|
ctxNs = NS_MATHML;
|
|
6905
7708
|
ctxName = ctxName.slice(5);
|
|
6906
7709
|
}
|
|
6907
|
-
fragment = mkEl(ctxName, ctxNs,
|
|
6908
|
-
const htmlEl = mkEl("html", NS_HTML,
|
|
6909
|
-
doc
|
|
7710
|
+
fragment = mkEl(ctxName, ctxNs, EMPTY_ATTRS, null);
|
|
7711
|
+
const htmlEl = mkEl("html", NS_HTML, EMPTY_ATTRS, null);
|
|
7712
|
+
_appendChild(doc, htmlEl);
|
|
6910
7713
|
open.push(htmlEl);
|
|
6911
7714
|
if (ctxNs !== NS_HTML) {
|
|
6912
7715
|
mode = MODE_IN_BODY;
|
|
@@ -6932,7 +7735,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6932
7735
|
}
|
|
6933
7736
|
|
|
6934
7737
|
// ---------- tokenizer callbacks ----------
|
|
6935
|
-
const decode =
|
|
7738
|
+
const decode = decodeEntities;
|
|
6936
7739
|
|
|
6937
7740
|
let swallowNextNewline = false;
|
|
6938
7741
|
// Set by the tokenizer when it hits EOF mid-tag; such a partial tag is
|
|
@@ -6949,7 +7752,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6949
7752
|
type: TOKEN_EOF,
|
|
6950
7753
|
name: "",
|
|
6951
7754
|
data: "",
|
|
6952
|
-
attrs:
|
|
7755
|
+
attrs: { start: 0, count: 0 },
|
|
6953
7756
|
selfClosing: false,
|
|
6954
7757
|
start: 0,
|
|
6955
7758
|
end: 0,
|
|
@@ -6961,12 +7764,12 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6961
7764
|
const dispatch = () =>
|
|
6962
7765
|
process(/** @type {Token} */ (/** @type {unknown} */ (tok)));
|
|
6963
7766
|
|
|
6964
|
-
|
|
7767
|
+
tokenize(source, pos, {
|
|
6965
7768
|
isForeign: () => {
|
|
6966
7769
|
const ac = adjustedCurrent();
|
|
6967
|
-
return open.length > 0 && ac && ac
|
|
7770
|
+
return open.length > 0 && ac !== 0 && _namespaceOf(ac) !== NS_HTML;
|
|
6968
7771
|
},
|
|
6969
|
-
fragmentContext: fragment ? fragment
|
|
7772
|
+
fragmentContext: fragment ? _tagNameOf(fragment) : undefined,
|
|
6970
7773
|
parseError: (input, code) => {
|
|
6971
7774
|
if (code === "eof-in-tag") eofInTag = true;
|
|
6972
7775
|
},
|
|
@@ -6988,7 +7791,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6988
7791
|
// The tokenizer emits CDATA sections through this callback too.
|
|
6989
7792
|
if (input.startsWith("<![CDATA[", start)) {
|
|
6990
7793
|
const ac = adjustedCurrent();
|
|
6991
|
-
if (open.length > 0 && ac && ac
|
|
7794
|
+
if (open.length > 0 && ac && _namespaceOf(ac) !== NS_HTML) {
|
|
6992
7795
|
const innerEnd = input.endsWith("]]>", end) ? end - 3 : end;
|
|
6993
7796
|
const data = input.slice(start + 9, innerEnd).replace(/\r\n?/g, "\n");
|
|
6994
7797
|
if (data !== "") {
|
|
@@ -7030,17 +7833,42 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7030
7833
|
return end;
|
|
7031
7834
|
},
|
|
7032
7835
|
text: (input, start, end) => {
|
|
7836
|
+
// `skip.text` fast path: node dropped, so only whitespace-ness matters.
|
|
7837
|
+
// With no `&`/`\0`/`\r` (and no pending newline-swallow) the decoded value
|
|
7838
|
+
// equals the raw range — dispatch a canonical marker (`" "`/`"x"`) without
|
|
7839
|
+
// the slice + entity decode; anything trickier falls through.
|
|
7840
|
+
if (skipText && !swallowNextNewline) {
|
|
7841
|
+
let hasNonWs = false;
|
|
7842
|
+
let i = start;
|
|
7843
|
+
for (; i < end; i++) {
|
|
7844
|
+
const c = input.charCodeAt(i);
|
|
7845
|
+
// 2 = break (& / NUL / CR), 1 = whitespace, 0 = other.
|
|
7846
|
+
const cls = c < 128 ? _TEXT_SCAN_CLASS[c] : 0;
|
|
7847
|
+
if (cls === 2) break;
|
|
7848
|
+
if (cls === 0) hasNonWs = true;
|
|
7849
|
+
}
|
|
7850
|
+
if (i === end) {
|
|
7851
|
+
tok.type = TOKEN_CHAR;
|
|
7852
|
+
tok.data = hasNonWs ? "x" : " ";
|
|
7853
|
+
tok.start = start;
|
|
7854
|
+
tok.end = end;
|
|
7855
|
+
dispatch();
|
|
7856
|
+
return end;
|
|
7857
|
+
}
|
|
7858
|
+
}
|
|
7033
7859
|
const raw = input.slice(start, end);
|
|
7034
7860
|
// CR normalization only when a CR is actually present (common case has none).
|
|
7035
7861
|
const s = !raw.includes("\r") ? raw : raw.replace(/\r\n?/g, "\n");
|
|
7036
7862
|
const top = adjustedCurrent();
|
|
7037
7863
|
const rawMode =
|
|
7038
7864
|
mode === MODE_TEXT ||
|
|
7039
|
-
(top &&
|
|
7865
|
+
(top &&
|
|
7866
|
+
_namespaceOf(top) === NS_HTML &&
|
|
7867
|
+
_tagNameOf(top) === "plaintext");
|
|
7040
7868
|
const noDecode =
|
|
7041
7869
|
top &&
|
|
7042
|
-
top
|
|
7043
|
-
NO_DECODE_TEXT.has(top
|
|
7870
|
+
_namespaceOf(top) === NS_HTML &&
|
|
7871
|
+
NO_DECODE_TEXT.has(_tagNameOf(top)) &&
|
|
7044
7872
|
(mode === MODE_TEXT || mode === MODE_IN_BODY);
|
|
7045
7873
|
let data = noDecode ? s : decode(s, false);
|
|
7046
7874
|
// In RAWTEXT/RCDATA/script/PLAINTEXT, NULL becomes U+FFFD (tokenizer
|
|
@@ -7061,32 +7889,29 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7061
7889
|
},
|
|
7062
7890
|
attribute: (input, nameStart, nameEnd, valueStart, valueEnd, quoteType) => {
|
|
7063
7891
|
const name = internLowerName(ATTR_NAME_INTERN, input, nameStart, nameEnd);
|
|
7064
|
-
|
|
7065
|
-
// offsets must stay aligned with the source. Entity decoding for the
|
|
7066
|
-
// html5lib serializer happens there, not here.
|
|
7067
|
-
const value = valueStart !== -1 ? input.slice(valueStart, valueEnd) : "";
|
|
7892
|
+
if (pendAttrStart === 0) pendAttrStart = _attrCount + 1;
|
|
7068
7893
|
// Drop duplicate attribute names (per spec). Plain loop avoids a
|
|
7069
7894
|
// per-attribute closure allocation on this hot path.
|
|
7070
7895
|
let dup = false;
|
|
7071
|
-
for (let i =
|
|
7072
|
-
if (
|
|
7896
|
+
for (let i = pendAttrStart; i <= _attrCount; i++) {
|
|
7897
|
+
if (_attrNames[i] === name) {
|
|
7073
7898
|
dup = true;
|
|
7074
7899
|
break;
|
|
7075
7900
|
}
|
|
7076
7901
|
}
|
|
7077
7902
|
if (!dup) {
|
|
7078
|
-
//
|
|
7079
|
-
//
|
|
7080
|
-
//
|
|
7081
|
-
|
|
7903
|
+
// The raw (undecoded) value is read from the source by offset on
|
|
7904
|
+
// demand: consumers re-resolve requests from it and the offsets must
|
|
7905
|
+
// stay aligned with the source. Only a valueless attribute stores an
|
|
7906
|
+
// override ("").
|
|
7907
|
+
_allocAttr(
|
|
7082
7908
|
name,
|
|
7083
|
-
|
|
7084
|
-
serializedName: undefined,
|
|
7909
|
+
valueStart !== -1 ? null : "",
|
|
7085
7910
|
nameStart,
|
|
7086
7911
|
nameEnd,
|
|
7087
7912
|
valueStart,
|
|
7088
7913
|
valueEnd
|
|
7089
|
-
|
|
7914
|
+
);
|
|
7090
7915
|
}
|
|
7091
7916
|
if (valueStart === -1) return nameEnd;
|
|
7092
7917
|
return quoteType !== QUOTE_NONE ? valueEnd + 1 : valueEnd;
|
|
@@ -7095,25 +7920,20 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7095
7920
|
// A start tag the tokenizer only emitted because it hit EOF mid-tag
|
|
7096
7921
|
// is dropped, matching the spec's eof-in-tag handling.
|
|
7097
7922
|
if (eofInTag) {
|
|
7098
|
-
|
|
7923
|
+
// Any attribute slots already allocated for it are orphaned.
|
|
7924
|
+
pendAttrStart = 0;
|
|
7099
7925
|
return end;
|
|
7100
7926
|
}
|
|
7101
7927
|
const name = internLowerName(TAG_NAME_INTERN, input, nameStart, nameEnd);
|
|
7102
7928
|
if (name === "selectedcontent") sawSelectedContent = true;
|
|
7103
|
-
// Attributeless elements share the frozen EMPTY_ATTRS (no allocation);
|
|
7104
|
-
// the empty pendingAttrs buffer is reused for the next tag. `<html>`
|
|
7105
|
-
// and `<body>` keep their own mutable array since later duplicate tags
|
|
7106
|
-
// merge attributes into it.
|
|
7107
|
-
let attrs;
|
|
7108
|
-
if (pendingAttrs.length === 0 && name !== "html" && name !== "body") {
|
|
7109
|
-
attrs = EMPTY_ATTRS;
|
|
7110
|
-
} else {
|
|
7111
|
-
attrs = pendingAttrs;
|
|
7112
|
-
pendingAttrs = [];
|
|
7113
|
-
}
|
|
7114
7929
|
tok.type = TOKEN_START_TAG;
|
|
7115
7930
|
tok.name = name;
|
|
7116
|
-
|
|
7931
|
+
// The reused token carries the tag's attribute run; every consumer of
|
|
7932
|
+
// `t.attrs` runs synchronously within this dispatch.
|
|
7933
|
+
tok.attrs.start = pendAttrStart;
|
|
7934
|
+
tok.attrs.count =
|
|
7935
|
+
pendAttrStart === 0 ? 0 : _attrCount + 1 - pendAttrStart;
|
|
7936
|
+
pendAttrStart = 0;
|
|
7117
7937
|
tok.selfClosing = selfClosing;
|
|
7118
7938
|
tok.swallowNewline = false;
|
|
7119
7939
|
tok.pos.start = start;
|
|
@@ -7126,8 +7946,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7126
7946
|
},
|
|
7127
7947
|
closeTag: (input, start, end, nameStart, nameEnd) => {
|
|
7128
7948
|
const name = internLowerName(TAG_NAME_INTERN, input, nameStart, nameEnd);
|
|
7129
|
-
// End tags drop any parsed attributes
|
|
7130
|
-
|
|
7949
|
+
// End tags drop any parsed attributes (the slots are orphaned).
|
|
7950
|
+
pendAttrStart = 0;
|
|
7131
7951
|
tok.type = TOKEN_END_TAG;
|
|
7132
7952
|
tok.name = name;
|
|
7133
7953
|
tok.pos.start = start;
|
|
@@ -7141,63 +7961,73 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7141
7961
|
tok.type = TOKEN_EOF;
|
|
7142
7962
|
dispatch();
|
|
7143
7963
|
|
|
7144
|
-
if (sawSelectedContent) mirrorSelectedContent(doc,
|
|
7964
|
+
if (sawSelectedContent) mirrorSelectedContent(doc, 0);
|
|
7145
7965
|
|
|
7146
7966
|
return doc;
|
|
7147
7967
|
};
|
|
7148
7968
|
|
|
7149
7969
|
/**
|
|
7150
|
-
* Deep-clone a node, dropping source offsets
|
|
7151
|
-
* re-emit dependencies.
|
|
7152
|
-
* @param {
|
|
7153
|
-
* @returns {
|
|
7970
|
+
* Deep-clone a node into fresh ids, dropping attribute source offsets and the
|
|
7971
|
+
* raw-text body span so cloned content does not re-emit dependencies.
|
|
7972
|
+
* @param {HtmlNodeRef} node node
|
|
7973
|
+
* @returns {HtmlNodeRef} clone
|
|
7154
7974
|
*/
|
|
7155
7975
|
const cloneSubtree = (node) => {
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7976
|
+
const ty = _nodeTypes[node];
|
|
7977
|
+
const clone = _allocNode(ty, _nodeStarts[node], _nodeEnds[node]);
|
|
7978
|
+
_nodeStrings[clone] = _nodeStrings[node];
|
|
7979
|
+
if (ty !== NodeType.Element) return clone;
|
|
7980
|
+
_nodeFlags[clone] = _nodeFlags[node];
|
|
7981
|
+
const attrs = cloneAttrs(node);
|
|
7982
|
+
_nodeAttrStarts[clone] = attrs.start;
|
|
7983
|
+
_nodeAttrCounts[clone] = attrs.count;
|
|
7984
|
+
_nodeTagEnds[clone] = _nodeTagEnds[node];
|
|
7985
|
+
_nodeNameEnds[clone] = _nodeNameEnds[node];
|
|
7986
|
+
// Empty body span: the clone re-emits no raw-text dependency.
|
|
7987
|
+
_nodeContentEnds[clone] = _nodeTagEnds[node];
|
|
7988
|
+
for (let k = _nodeFirstChildren[node]; k !== 0; k = _nodeNextSiblings[k]) {
|
|
7989
|
+
_appendChild(clone, cloneSubtree(k));
|
|
7990
|
+
}
|
|
7991
|
+
// Clone `<template>` content into the clone's own fragment.
|
|
7992
|
+
const tc = _nodeTemplateContents[node];
|
|
7993
|
+
if (tc !== 0) {
|
|
7994
|
+
const fragment = _allocNode(NodeType.DocumentFragment, 0, 0);
|
|
7995
|
+
_nodeStrings[fragment] = "";
|
|
7996
|
+
// Parent link so the iterative walk can ascend out of the content.
|
|
7997
|
+
_nodeParents[fragment] = clone;
|
|
7998
|
+
_nodeTemplateContents[clone] = fragment;
|
|
7999
|
+
for (let k = _nodeFirstChildren[tc]; k !== 0; k = _nodeNextSiblings[k]) {
|
|
8000
|
+
_appendChild(fragment, cloneSubtree(k));
|
|
8001
|
+
}
|
|
8002
|
+
}
|
|
8003
|
+
return clone;
|
|
7177
8004
|
};
|
|
7178
8005
|
|
|
7179
8006
|
/**
|
|
7180
8007
|
* The selected option of a select: the last `<option selected>`, else the
|
|
7181
8008
|
* first option (scanning direct children and `<optgroup>` children).
|
|
7182
8009
|
* @param {HtmlElement} select select element
|
|
7183
|
-
* @returns {HtmlElement
|
|
8010
|
+
* @returns {HtmlElement} selected option (0 = none)
|
|
7184
8011
|
*/
|
|
7185
8012
|
const selectedOption = (select) => {
|
|
7186
8013
|
/** @type {HtmlElement[]} */
|
|
7187
8014
|
const options = [];
|
|
7188
8015
|
/** @param {HtmlElement} el element */
|
|
7189
8016
|
const collect = (el) => {
|
|
7190
|
-
for (
|
|
7191
|
-
if (c
|
|
7192
|
-
|
|
7193
|
-
|
|
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);
|
|
7194
8023
|
}
|
|
7195
8024
|
};
|
|
7196
8025
|
collect(select);
|
|
7197
|
-
if (options.length === 0) return
|
|
8026
|
+
if (options.length === 0) return 0;
|
|
7198
8027
|
for (let i = options.length - 1; i >= 0; i--) {
|
|
7199
|
-
|
|
7200
|
-
|
|
8028
|
+
const el = options[i];
|
|
8029
|
+
if (_findAttr(_nodeAttrStarts[el], _nodeAttrCounts[el], "selected") !== 0) {
|
|
8030
|
+
return el;
|
|
7201
8031
|
}
|
|
7202
8032
|
}
|
|
7203
8033
|
return options[0];
|
|
@@ -7206,25 +8036,39 @@ const selectedOption = (select) => {
|
|
|
7206
8036
|
/**
|
|
7207
8037
|
* Fill each `<selectedcontent>` with a clone of its `<select>`'s selected
|
|
7208
8038
|
* option subtree (the customizable-select mirroring behavior).
|
|
7209
|
-
* @param {
|
|
7210
|
-
* @param {HtmlElement
|
|
8039
|
+
* @param {HtmlNodeRef} node node
|
|
8040
|
+
* @param {HtmlElement} select nearest ancestor select (0 = none)
|
|
7211
8041
|
*/
|
|
7212
8042
|
const mirrorSelectedContent = (node, select) => {
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
8043
|
+
// A `<template>`'s children live in its content fragment.
|
|
8044
|
+
const tc = _nodeTemplateContents[node];
|
|
8045
|
+
const container = tc !== 0 ? tc : node;
|
|
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") {
|
|
7220
8053
|
mirrorSelectedContent(child, child);
|
|
7221
8054
|
} else if (
|
|
7222
|
-
select &&
|
|
7223
|
-
child
|
|
7224
|
-
child
|
|
8055
|
+
select !== 0 &&
|
|
8056
|
+
_namespaceOf(child) === NS_HTML &&
|
|
8057
|
+
_nodeStrings[child] === "selectedcontent"
|
|
7225
8058
|
) {
|
|
7226
8059
|
const option = selectedOption(select);
|
|
7227
|
-
if (option
|
|
8060
|
+
if (option !== 0) {
|
|
8061
|
+
// Replace the children with clones of the option's subtree.
|
|
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));
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
7228
8072
|
} else {
|
|
7229
8073
|
mirrorSelectedContent(child, select);
|
|
7230
8074
|
}
|
|
@@ -7257,37 +8101,20 @@ const parseDoctype = (/** @type {string} */ raw) => {
|
|
|
7257
8101
|
return { name, publicId, systemId };
|
|
7258
8102
|
};
|
|
7259
8103
|
|
|
7260
|
-
/**
|
|
7261
|
-
* AST node `type` discriminators. Numeric for the same reason as the CSS
|
|
7262
|
-
* `NodeType`: compact integer `===` dispatch on the tree-construction and
|
|
7263
|
-
* visitor-walk hot paths. Members are literal-typed so `node.type ===
|
|
7264
|
-
* NodeType.X` still narrows the `HtmlNode` union.
|
|
7265
|
-
* @type {{ Document: 1, DocumentFragment: 2, Element: 3, Text: 4, Comment: 5, Doctype: 6 }}
|
|
7266
|
-
*/
|
|
7267
|
-
const NodeType = {
|
|
7268
|
-
Document: 1,
|
|
7269
|
-
DocumentFragment: 2,
|
|
7270
|
-
Element: 3,
|
|
7271
|
-
Text: 4,
|
|
7272
|
-
Comment: 5,
|
|
7273
|
-
Doctype: 6
|
|
7274
|
-
};
|
|
7275
|
-
|
|
7276
8104
|
/** @typedef {HtmlNode | HtmlDocument | HtmlDocumentFragment} HtmlVisitableNode */
|
|
7277
8105
|
|
|
7278
8106
|
// HTML-typed views over the generic visitor machinery (`util/SourceProcessor`).
|
|
7279
8107
|
/**
|
|
7280
|
-
* @typedef {import("../util/SourceProcessor").
|
|
7281
|
-
* @typedef {import("../util/SourceProcessor").
|
|
7282
|
-
* @typedef {import("../util/SourceProcessor").
|
|
7283
|
-
* @typedef {import("../util/SourceProcessor").
|
|
7284
|
-
* @typedef {import("../util/SourceProcessor").CompiledVisitorMap<HtmlVisitableNode>} CompiledVisitorMap
|
|
8108
|
+
* @typedef {import("../util/SourceProcessor").VisitorFn<HtmlPath>} VisitorFn
|
|
8109
|
+
* @typedef {import("../util/SourceProcessor").VisitorBucket<HtmlPath>} VisitorBucket
|
|
8110
|
+
* @typedef {import("../util/SourceProcessor").VisitorMap<HtmlPath>} VisitorMap
|
|
8111
|
+
* @typedef {import("../util/SourceProcessor").CompiledVisitorMap<HtmlPath>} CompiledVisitorMap
|
|
7285
8112
|
*/
|
|
7286
8113
|
|
|
7287
8114
|
/**
|
|
7288
8115
|
* @typedef {object} HtmlProcessOptions
|
|
7289
|
-
* @property {string=} fragmentContext context element tag name for fragment parsing (see `
|
|
7290
|
-
* @property {
|
|
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
|
|
8117
|
+
* @property {HtmlAstSkip=} skip node kinds to omit from the AST for speed/memory (see `HtmlAstSkip`)
|
|
7291
8118
|
*/
|
|
7292
8119
|
|
|
7293
8120
|
/**
|
|
@@ -7299,54 +8126,97 @@ const NodeType = {
|
|
|
7299
8126
|
* @param {HtmlProcessOptions} options process options
|
|
7300
8127
|
*/
|
|
7301
8128
|
const grammar = (input, visitors, options) => {
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
8129
|
+
const root = parseHtml(input, 0, options);
|
|
8130
|
+
|
|
8131
|
+
// Iterative depth-first walk over the link columns: `firstChild`
|
|
8132
|
+
// / `nextSibling` descend, the `parent` column ascends, so arbitrarily deep
|
|
8133
|
+
// markup can't overflow the call stack (the old recursive walk died at
|
|
8134
|
+
// ~10⁵ nesting). A `<template>`'s content fragment is visited before the
|
|
8135
|
+
// element's children; ascending out of it continues with those children
|
|
8136
|
+
// (the fragment is never in a sibling chain, `_nodeNextSiblings` = 0).
|
|
8137
|
+
/**
|
|
8138
|
+
* Fire a node's `enter` visitors; true when the walk may descend.
|
|
8139
|
+
* @param {HtmlNodeRef} node node
|
|
8140
|
+
* @returns {boolean} false when a visitor called `skipChildren()`
|
|
8141
|
+
*/
|
|
8142
|
+
const enter = (node) => {
|
|
8143
|
+
const b = visitors[_nodeTypes[node]];
|
|
8144
|
+
if (b === undefined || b.enter.length === 0) return true;
|
|
8145
|
+
_walkSkip = false;
|
|
8146
|
+
_currentNode = node;
|
|
8147
|
+
const p = _nodeParents[node];
|
|
8148
|
+
_currentParent = p === 0 ? null : p;
|
|
8149
|
+
const e = b.enter;
|
|
8150
|
+
for (let i = 0; i < e.length; i++) e[i](A);
|
|
8151
|
+
const skip = _walkSkip;
|
|
8152
|
+
_walkSkip = false;
|
|
8153
|
+
return !skip;
|
|
7308
8154
|
};
|
|
7309
|
-
|
|
7310
8155
|
/**
|
|
7311
|
-
*
|
|
7312
|
-
*
|
|
7313
|
-
* @param {HtmlVisitableNode} node subtree root
|
|
7314
|
-
* @param {HtmlVisitableNode | null} parent enclosing node
|
|
8156
|
+
* Fire a node's `exit` visitors.
|
|
8157
|
+
* @param {HtmlNodeRef} node node
|
|
7315
8158
|
*/
|
|
7316
|
-
const
|
|
7317
|
-
const b = visitors[node
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
node.type === NodeType.Document ||
|
|
7336
|
-
node.type === NodeType.DocumentFragment
|
|
7337
|
-
) {
|
|
7338
|
-
const c = node.children;
|
|
7339
|
-
for (let i = 0; i < c.length; i++) walk(c[i], node);
|
|
7340
|
-
}
|
|
7341
|
-
// All other types are leaves.
|
|
8159
|
+
const exit = (node) => {
|
|
8160
|
+
const b = visitors[_nodeTypes[node]];
|
|
8161
|
+
if (b === undefined) return;
|
|
8162
|
+
_currentNode = node;
|
|
8163
|
+
const p = _nodeParents[node];
|
|
8164
|
+
_currentParent = p === 0 ? null : p;
|
|
8165
|
+
const x = b.exit;
|
|
8166
|
+
for (let i = 0; i < x.length; i++) x[i](A);
|
|
8167
|
+
};
|
|
8168
|
+
/**
|
|
8169
|
+
* First node to visit inside `node` (template content before children).
|
|
8170
|
+
* @param {HtmlNodeRef} node node
|
|
8171
|
+
* @returns {HtmlNodeRef} first inner node (0 = leaf)
|
|
8172
|
+
*/
|
|
8173
|
+
const firstInner = (node) => {
|
|
8174
|
+
const ty = _nodeTypes[node];
|
|
8175
|
+
if (ty === NodeType.Element) {
|
|
8176
|
+
const tc = _nodeTemplateContents[node];
|
|
8177
|
+
return tc !== 0 ? tc : _nodeFirstChildren[node];
|
|
7342
8178
|
}
|
|
7343
|
-
if (
|
|
7344
|
-
|
|
7345
|
-
for (let i = 0; i < x.length; i++) x[i](node, parent, visitorCtx);
|
|
8179
|
+
if (ty === NodeType.Document || ty === NodeType.DocumentFragment) {
|
|
8180
|
+
return _nodeFirstChildren[node];
|
|
7346
8181
|
}
|
|
8182
|
+
return 0;
|
|
7347
8183
|
};
|
|
7348
8184
|
|
|
7349
|
-
|
|
8185
|
+
let node = root;
|
|
8186
|
+
descend: for (;;) {
|
|
8187
|
+
if (enter(node)) {
|
|
8188
|
+
const inner = firstInner(node);
|
|
8189
|
+
if (inner !== 0) {
|
|
8190
|
+
node = inner;
|
|
8191
|
+
continue;
|
|
8192
|
+
}
|
|
8193
|
+
}
|
|
8194
|
+
// Leaf (or skipped): exit and move sideways / upwards.
|
|
8195
|
+
for (;;) {
|
|
8196
|
+
exit(node);
|
|
8197
|
+
if (node === root) break descend;
|
|
8198
|
+
const parent = _nodeParents[node];
|
|
8199
|
+
// Out of a template's content fragment: the element's children follow.
|
|
8200
|
+
if (_nodeTemplateContents[parent] === node) {
|
|
8201
|
+
const first = _nodeFirstChildren[parent];
|
|
8202
|
+
if (first !== 0) {
|
|
8203
|
+
node = first;
|
|
8204
|
+
continue descend;
|
|
8205
|
+
}
|
|
8206
|
+
} else {
|
|
8207
|
+
const sibling = _nodeNextSiblings[node];
|
|
8208
|
+
if (sibling !== 0) {
|
|
8209
|
+
node = sibling;
|
|
8210
|
+
continue descend;
|
|
8211
|
+
}
|
|
8212
|
+
}
|
|
8213
|
+
node = parent;
|
|
8214
|
+
}
|
|
8215
|
+
}
|
|
8216
|
+
|
|
8217
|
+
// The walk consumed the tree: release the side arrays' heap references so
|
|
8218
|
+
// the reused columns don't pin this parse's strings until the next parse.
|
|
8219
|
+
_releaseAstColumns();
|
|
7350
8220
|
};
|
|
7351
8221
|
|
|
7352
8222
|
/**
|
|
@@ -7354,14 +8224,16 @@ const grammar = (input, visitors, options) => {
|
|
|
7354
8224
|
* `grammar`. Babel-style usage:
|
|
7355
8225
|
*
|
|
7356
8226
|
* ```
|
|
7357
|
-
*
|
|
7358
|
-
* processor.process(source);
|
|
8227
|
+
* new SourceProcessor({ skip }).use({ [NodeType.Element]: (path) => {}, [NodeType.Comment]: { enter, exit } }).process(source);
|
|
7359
8228
|
* ```
|
|
7360
|
-
* @extends {GenericSourceProcessor<
|
|
8229
|
+
* @extends {GenericSourceProcessor<HtmlPath, HtmlProcessOptions>}
|
|
7361
8230
|
*/
|
|
7362
8231
|
class SourceProcessor extends GenericSourceProcessor {
|
|
7363
|
-
|
|
7364
|
-
|
|
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);
|
|
7365
8237
|
}
|
|
7366
8238
|
}
|
|
7367
8239
|
|
|
@@ -7377,12 +8249,14 @@ const SMALL_LETTER_W = "w".charCodeAt(0);
|
|
|
7377
8249
|
const SMALL_LETTER_X = "x".charCodeAt(0);
|
|
7378
8250
|
const SMALL_LETTER_H = "h".charCodeAt(0);
|
|
7379
8251
|
// (Don't use \s, to avoid matching non-breaking space)
|
|
8252
|
+
// Sticky so `collectCharacters` matches at an offset without slicing the
|
|
8253
|
+
// whole remaining input per call (which made srcset parsing quadratic).
|
|
7380
8254
|
// eslint-disable-next-line no-control-regex
|
|
7381
|
-
const LEADING_SPACES_REGEXP =
|
|
8255
|
+
const LEADING_SPACES_REGEXP = /[ \t\n\r\u000C]+/y;
|
|
7382
8256
|
// eslint-disable-next-line no-control-regex
|
|
7383
|
-
const LEADING_COMMAS_OR_SPACES_REGEXP =
|
|
8257
|
+
const LEADING_COMMAS_OR_SPACES_REGEXP = /[, \t\n\r\u000C]+/y;
|
|
7384
8258
|
// eslint-disable-next-line no-control-regex
|
|
7385
|
-
const LEADING_NOT_SPACES =
|
|
8259
|
+
const LEADING_NOT_SPACES = /[^ \t\n\r\u000C]+/y;
|
|
7386
8260
|
const TRAILING_COMMAS_REGEXP = /[,]+$/;
|
|
7387
8261
|
const NON_NEGATIVE_INTEGER_REGEXP = /^\d+$/;
|
|
7388
8262
|
// ( Positive or negative or unsigned integers or decimals, without or without exponents.
|
|
@@ -7405,8 +8279,8 @@ const parseSrcset = (input) => {
|
|
|
7405
8279
|
let url;
|
|
7406
8280
|
/** @type {string[]} */
|
|
7407
8281
|
let descriptors;
|
|
7408
|
-
/** @type {
|
|
7409
|
-
let
|
|
8282
|
+
/** @type {number} */
|
|
8283
|
+
let descriptorStart;
|
|
7410
8284
|
/** @type {string} */
|
|
7411
8285
|
let state;
|
|
7412
8286
|
/** @type {number} */
|
|
@@ -7420,16 +8294,15 @@ const parseSrcset = (input) => {
|
|
|
7420
8294
|
const candidates = [];
|
|
7421
8295
|
|
|
7422
8296
|
/**
|
|
7423
|
-
* @param {RegExp} regExp reg exp to collect characters
|
|
8297
|
+
* @param {RegExp} regExp sticky reg exp to collect characters
|
|
7424
8298
|
* @returns {string | undefined} characters
|
|
7425
8299
|
*/
|
|
7426
8300
|
function collectCharacters(regExp) {
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
const match = regExp.exec(input.slice(Math.max(0, position)));
|
|
8301
|
+
regExp.lastIndex = Math.max(0, position);
|
|
8302
|
+
const match = regExp.exec(input);
|
|
7430
8303
|
|
|
7431
8304
|
if (match) {
|
|
7432
|
-
[chars] = match;
|
|
8305
|
+
const [chars] = match;
|
|
7433
8306
|
position += chars.length;
|
|
7434
8307
|
|
|
7435
8308
|
return chars;
|
|
@@ -7555,12 +8428,13 @@ const parseSrcset = (input) => {
|
|
|
7555
8428
|
/**
|
|
7556
8429
|
* @returns {void}
|
|
7557
8430
|
*/
|
|
7558
|
-
function
|
|
8431
|
+
function tokenizeDescriptor() {
|
|
7559
8432
|
// 8.1. Descriptor tokenizer: Skip whitespace
|
|
7560
8433
|
collectCharacters(LEADING_SPACES_REGEXP);
|
|
7561
8434
|
|
|
7562
8435
|
// 8.2. Let current descriptor be the empty string.
|
|
7563
|
-
|
|
8436
|
+
// (Tracked as a start offset, `-1` = empty; sliced once per descriptor.)
|
|
8437
|
+
descriptorStart = -1;
|
|
7564
8438
|
|
|
7565
8439
|
// 8.3. Let state be in descriptor.
|
|
7566
8440
|
state = "in descriptor";
|
|
@@ -7582,9 +8456,9 @@ const parseSrcset = (input) => {
|
|
|
7582
8456
|
// descriptors and let current descriptor be the empty string.
|
|
7583
8457
|
// Set state to after descriptor.
|
|
7584
8458
|
if (isSpace(charCode)) {
|
|
7585
|
-
if (
|
|
7586
|
-
descriptors.push(
|
|
7587
|
-
|
|
8459
|
+
if (descriptorStart !== -1) {
|
|
8460
|
+
descriptors.push(input.slice(descriptorStart, position));
|
|
8461
|
+
descriptorStart = -1;
|
|
7588
8462
|
state = "after descriptor";
|
|
7589
8463
|
}
|
|
7590
8464
|
}
|
|
@@ -7595,8 +8469,8 @@ const parseSrcset = (input) => {
|
|
|
7595
8469
|
else if (charCode === COMMA) {
|
|
7596
8470
|
position += 1;
|
|
7597
8471
|
|
|
7598
|
-
if (
|
|
7599
|
-
descriptors.push(
|
|
8472
|
+
if (descriptorStart !== -1) {
|
|
8473
|
+
descriptors.push(input.slice(descriptorStart, position - 1));
|
|
7600
8474
|
}
|
|
7601
8475
|
|
|
7602
8476
|
parseDescriptors();
|
|
@@ -7606,15 +8480,15 @@ const parseSrcset = (input) => {
|
|
|
7606
8480
|
// U+0028 LEFT PARENTHESIS (()
|
|
7607
8481
|
// Append charCode to current descriptor. Set state to in parens.
|
|
7608
8482
|
else if (charCode === LEFT_PARENTHESIS) {
|
|
7609
|
-
|
|
8483
|
+
if (descriptorStart === -1) descriptorStart = position;
|
|
7610
8484
|
state = "in parens";
|
|
7611
8485
|
}
|
|
7612
8486
|
// EOF
|
|
7613
8487
|
// If current descriptor is not empty, append current descriptor to
|
|
7614
8488
|
// descriptors. Jump to the step labeled descriptor parser.
|
|
7615
8489
|
else if (Number.isNaN(charCode)) {
|
|
7616
|
-
if (
|
|
7617
|
-
descriptors.push(
|
|
8490
|
+
if (descriptorStart !== -1) {
|
|
8491
|
+
descriptors.push(input.slice(descriptorStart, position));
|
|
7618
8492
|
}
|
|
7619
8493
|
|
|
7620
8494
|
parseDescriptors();
|
|
@@ -7623,8 +8497,8 @@ const parseSrcset = (input) => {
|
|
|
7623
8497
|
|
|
7624
8498
|
// Anything else
|
|
7625
8499
|
// Append charCode to current descriptor.
|
|
7626
|
-
} else {
|
|
7627
|
-
|
|
8500
|
+
} else if (descriptorStart === -1) {
|
|
8501
|
+
descriptorStart = position;
|
|
7628
8502
|
}
|
|
7629
8503
|
}
|
|
7630
8504
|
// In parens
|
|
@@ -7632,22 +8506,18 @@ const parseSrcset = (input) => {
|
|
|
7632
8506
|
// U+0029 RIGHT PARENTHESIS ())
|
|
7633
8507
|
// Append charCode to current descriptor. Set state to in descriptor.
|
|
7634
8508
|
if (charCode === RIGHT_PARENTHESIS) {
|
|
7635
|
-
currentDescriptor += input.charAt(position);
|
|
7636
8509
|
state = "in descriptor";
|
|
7637
8510
|
}
|
|
7638
8511
|
// EOF
|
|
7639
8512
|
// Append current descriptor to descriptors. Jump to the step labeled
|
|
7640
8513
|
// descriptor parser.
|
|
7641
8514
|
else if (Number.isNaN(charCode)) {
|
|
7642
|
-
descriptors.push(
|
|
8515
|
+
descriptors.push(input.slice(descriptorStart, position));
|
|
7643
8516
|
parseDescriptors();
|
|
7644
8517
|
return;
|
|
7645
8518
|
}
|
|
7646
8519
|
// Anything else
|
|
7647
|
-
// Append charCode to current descriptor.
|
|
7648
|
-
else {
|
|
7649
|
-
currentDescriptor += input.charAt(position);
|
|
7650
|
-
}
|
|
8520
|
+
// Append charCode to current descriptor. (Covered by the tracked range.)
|
|
7651
8521
|
}
|
|
7652
8522
|
// After descriptor
|
|
7653
8523
|
else if (state === "after descriptor") {
|
|
@@ -7712,13 +8582,419 @@ const parseSrcset = (input) => {
|
|
|
7712
8582
|
}
|
|
7713
8583
|
// Otherwise, follow these sub steps:
|
|
7714
8584
|
else {
|
|
7715
|
-
|
|
8585
|
+
tokenizeDescriptor();
|
|
7716
8586
|
}
|
|
7717
8587
|
|
|
7718
8588
|
// 16. Return to the step labeled splitting loop.
|
|
7719
8589
|
}
|
|
7720
8590
|
};
|
|
7721
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
|
+
|
|
8732
|
+
// Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
|
|
8733
|
+
// `enter` dispatch, consumed by the walk.
|
|
8734
|
+
let _walkSkip = false;
|
|
8735
|
+
// The walk's current position (`A.node` / `A.parent` read these; module-level
|
|
8736
|
+
// so the accessor methods' defaults avoid self-referential `this` typing).
|
|
8737
|
+
/** @type {HtmlNodeRef} */
|
|
8738
|
+
let _currentNode = 0;
|
|
8739
|
+
/** @type {HtmlNodeRef | null} */
|
|
8740
|
+
let _currentParent = null;
|
|
8741
|
+
|
|
8742
|
+
/* eslint-disable jsdoc/require-template -- `A` below is the accessor const, not a type parameter */
|
|
8743
|
+
/**
|
|
8744
|
+
* The HTML path (Babel's `path` shape): the AST accessor with the walk's
|
|
8745
|
+
* current position on it — the single argument every visitor receives.
|
|
8746
|
+
* @typedef {typeof A} HtmlPath
|
|
8747
|
+
*/
|
|
8748
|
+
/* eslint-enable jsdoc/require-template */
|
|
8749
|
+
|
|
8750
|
+
// AST field-access seam (mirrors the CSS parser's `A`): every AST field a
|
|
8751
|
+
// consumer reads goes through one of these accessors, so the node
|
|
8752
|
+
// representation can change underneath without touching consumers. `n` is an
|
|
8753
|
+
// `HtmlNodeRef`; results are valid until the next `parseHtml` call.
|
|
8754
|
+
const A = {
|
|
8755
|
+
// === path position (rebound by the walk before every visitor call) ===
|
|
8756
|
+
/**
|
|
8757
|
+
* @returns {HtmlNodeRef} current node — only valid during a visitor callback
|
|
8758
|
+
*/
|
|
8759
|
+
get node() {
|
|
8760
|
+
return _currentNode;
|
|
8761
|
+
},
|
|
8762
|
+
/**
|
|
8763
|
+
* @returns {HtmlNodeRef | null} enclosing node (null = the document root)
|
|
8764
|
+
*/
|
|
8765
|
+
get parent() {
|
|
8766
|
+
return _currentParent;
|
|
8767
|
+
},
|
|
8768
|
+
/** Stop the walk descending into the current node (enter only). */
|
|
8769
|
+
skipChildren() {
|
|
8770
|
+
_walkSkip = true;
|
|
8771
|
+
},
|
|
8772
|
+
// === field reads — `n` defaults to the current node ===
|
|
8773
|
+
/**
|
|
8774
|
+
* @param {HtmlNodeRef=} n node
|
|
8775
|
+
* @returns {number} `NodeType`
|
|
8776
|
+
*/
|
|
8777
|
+
type(n = _currentNode) {
|
|
8778
|
+
return _nodeTypes[n];
|
|
8779
|
+
},
|
|
8780
|
+
/**
|
|
8781
|
+
* @param {HtmlNodeRef=} n node
|
|
8782
|
+
* @returns {number} start offset
|
|
8783
|
+
*/
|
|
8784
|
+
start(n = _currentNode) {
|
|
8785
|
+
return _nodeStarts[n];
|
|
8786
|
+
},
|
|
8787
|
+
/**
|
|
8788
|
+
* @param {HtmlNodeRef=} n node
|
|
8789
|
+
* @returns {number} end offset
|
|
8790
|
+
*/
|
|
8791
|
+
end(n = _currentNode) {
|
|
8792
|
+
return _nodeEnds[n];
|
|
8793
|
+
},
|
|
8794
|
+
/**
|
|
8795
|
+
* @param {HtmlElement=} n element
|
|
8796
|
+
* @returns {string} lowercased (foreign-content: adjusted) tag name
|
|
8797
|
+
*/
|
|
8798
|
+
tagName(n = _currentNode) {
|
|
8799
|
+
return _nodeStrings[n];
|
|
8800
|
+
},
|
|
8801
|
+
/**
|
|
8802
|
+
* @param {HtmlElement=} n element
|
|
8803
|
+
* @returns {number} `NS_*` namespace
|
|
8804
|
+
*/
|
|
8805
|
+
namespace(n = _currentNode) {
|
|
8806
|
+
return _nodeFlags[n] & NS_MASK;
|
|
8807
|
+
},
|
|
8808
|
+
/**
|
|
8809
|
+
* @param {HtmlElement=} n element
|
|
8810
|
+
* @returns {boolean} true for void elements
|
|
8811
|
+
*/
|
|
8812
|
+
selfClosing(n = _currentNode) {
|
|
8813
|
+
return (_nodeFlags[n] & FLAG_SELF_CLOSING) !== 0;
|
|
8814
|
+
},
|
|
8815
|
+
// materialized attribute list — test/tooling convenience, allocates; the
|
|
8816
|
+
// parser reads attributes through the scalar accessors below
|
|
8817
|
+
/**
|
|
8818
|
+
* @param {HtmlElement=} n element
|
|
8819
|
+
* @returns {HtmlAttribute[]} materialized attributes
|
|
8820
|
+
*/
|
|
8821
|
+
attributes(n = _currentNode) {
|
|
8822
|
+
const out = [];
|
|
8823
|
+
const start = _nodeAttrStarts[n];
|
|
8824
|
+
for (let i = start; i < start + _nodeAttrCounts[n]; i++) {
|
|
8825
|
+
out.push({
|
|
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]
|
|
8833
|
+
});
|
|
8834
|
+
}
|
|
8835
|
+
return out;
|
|
8836
|
+
},
|
|
8837
|
+
/**
|
|
8838
|
+
* @param {HtmlElement=} n element
|
|
8839
|
+
* @returns {number} attribute count
|
|
8840
|
+
*/
|
|
8841
|
+
attributeCount(n = _currentNode) {
|
|
8842
|
+
return _nodeAttrCounts[n];
|
|
8843
|
+
},
|
|
8844
|
+
/**
|
|
8845
|
+
* The i-th attribute of an element, as an id for the `attribute*` reads.
|
|
8846
|
+
* @param {number} i attribute index
|
|
8847
|
+
* @param {HtmlElement=} n element
|
|
8848
|
+
* @returns {HtmlAttributeRef} attribute ref
|
|
8849
|
+
*/
|
|
8850
|
+
attributeAt(i, n = _currentNode) {
|
|
8851
|
+
return _nodeAttrStarts[n] + i;
|
|
8852
|
+
},
|
|
8853
|
+
/**
|
|
8854
|
+
* Linear lookup by (lowercased) name.
|
|
8855
|
+
* @param {string} name attribute name
|
|
8856
|
+
* @param {HtmlElement=} n element
|
|
8857
|
+
* @returns {HtmlAttributeRef} attribute ref (0 = not present)
|
|
8858
|
+
*/
|
|
8859
|
+
findAttribute(name, n = _currentNode) {
|
|
8860
|
+
return _findAttr(_nodeAttrStarts[n], _nodeAttrCounts[n], name);
|
|
8861
|
+
},
|
|
8862
|
+
/**
|
|
8863
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8864
|
+
* @returns {string} lowercased (foreign-content: adjusted) attribute name
|
|
8865
|
+
*/
|
|
8866
|
+
attributeName(a) {
|
|
8867
|
+
return _attrNames[a];
|
|
8868
|
+
},
|
|
8869
|
+
/**
|
|
8870
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8871
|
+
* @returns {string} raw (undecoded) attribute value ("" when valueless)
|
|
8872
|
+
*/
|
|
8873
|
+
attributeValue(a) {
|
|
8874
|
+
return _attrValueOf(a);
|
|
8875
|
+
},
|
|
8876
|
+
/**
|
|
8877
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8878
|
+
* @returns {number} name start offset
|
|
8879
|
+
*/
|
|
8880
|
+
attributeNameStart(a) {
|
|
8881
|
+
return _attrNameStarts[a];
|
|
8882
|
+
},
|
|
8883
|
+
/**
|
|
8884
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8885
|
+
* @returns {number} name end offset
|
|
8886
|
+
*/
|
|
8887
|
+
attributeNameEnd(a) {
|
|
8888
|
+
return _attrNameEnds[a];
|
|
8889
|
+
},
|
|
8890
|
+
/**
|
|
8891
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8892
|
+
* @returns {number} value start offset (-1 when valueless or on adoption-agency clones)
|
|
8893
|
+
*/
|
|
8894
|
+
attributeValueStart(a) {
|
|
8895
|
+
return _attrValueStarts[a];
|
|
8896
|
+
},
|
|
8897
|
+
/**
|
|
8898
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8899
|
+
* @returns {number} value end offset
|
|
8900
|
+
*/
|
|
8901
|
+
attributeValueEnd(a) {
|
|
8902
|
+
return _attrValueEnds[a];
|
|
8903
|
+
},
|
|
8904
|
+
/**
|
|
8905
|
+
* @param {HtmlElement=} n element
|
|
8906
|
+
* @returns {number} end offset of the opening tag (after `>`)
|
|
8907
|
+
*/
|
|
8908
|
+
tagEnd(n = _currentNode) {
|
|
8909
|
+
return _nodeTagEnds[n];
|
|
8910
|
+
},
|
|
8911
|
+
/**
|
|
8912
|
+
* @param {HtmlElement=} n element
|
|
8913
|
+
* @returns {number} end offset of the tag name
|
|
8914
|
+
*/
|
|
8915
|
+
nameEnd(n = _currentNode) {
|
|
8916
|
+
return _nodeNameEnds[n];
|
|
8917
|
+
},
|
|
8918
|
+
/**
|
|
8919
|
+
* @param {HtmlElement=} n element
|
|
8920
|
+
* @returns {number} under `skip.text`, end offset of a raw-text element's body (`tagEnd` when empty)
|
|
8921
|
+
*/
|
|
8922
|
+
contentEnd(n = _currentNode) {
|
|
8923
|
+
return _nodeContentEnds[n];
|
|
8924
|
+
},
|
|
8925
|
+
/**
|
|
8926
|
+
* @param {HtmlElement=} n element
|
|
8927
|
+
* @returns {HtmlDocumentFragment} `<template>` content fragment (0 = none)
|
|
8928
|
+
*/
|
|
8929
|
+
templateContent(n = _currentNode) {
|
|
8930
|
+
return _nodeTemplateContents[n];
|
|
8931
|
+
},
|
|
8932
|
+
/**
|
|
8933
|
+
* @param {HtmlText | HtmlComment=} n text / comment node
|
|
8934
|
+
* @returns {string} decoded text / comment data
|
|
8935
|
+
*/
|
|
8936
|
+
data(n = _currentNode) {
|
|
8937
|
+
return _nodeStrings[n];
|
|
8938
|
+
},
|
|
8939
|
+
/**
|
|
8940
|
+
* @param {HtmlDoctype=} n doctype node
|
|
8941
|
+
* @returns {string} doctype name
|
|
8942
|
+
*/
|
|
8943
|
+
doctypeName(n = _currentNode) {
|
|
8944
|
+
return _nodeStrings[n];
|
|
8945
|
+
},
|
|
8946
|
+
// The doctype ids are per-parse scalars (a document has at most one
|
|
8947
|
+
// doctype node); the node parameter is accepted for call-shape uniformity.
|
|
8948
|
+
/**
|
|
8949
|
+
* @param {HtmlDoctype=} _n doctype node
|
|
8950
|
+
* @returns {string | null} doctype public id
|
|
8951
|
+
*/
|
|
8952
|
+
doctypePublicId(_n) {
|
|
8953
|
+
return _doctypePublicId;
|
|
8954
|
+
},
|
|
8955
|
+
/**
|
|
8956
|
+
* @param {HtmlDoctype=} _n doctype node
|
|
8957
|
+
* @returns {string | null} doctype system id
|
|
8958
|
+
*/
|
|
8959
|
+
doctypeSystemId(_n) {
|
|
8960
|
+
return _doctypeSystemId;
|
|
8961
|
+
},
|
|
8962
|
+
// === tree links (0 = none) ===
|
|
8963
|
+
/**
|
|
8964
|
+
* @param {HtmlNodeRef=} n node
|
|
8965
|
+
* @returns {HtmlNodeRef} first child
|
|
8966
|
+
*/
|
|
8967
|
+
firstChild(n = _currentNode) {
|
|
8968
|
+
return _nodeFirstChildren[n];
|
|
8969
|
+
},
|
|
8970
|
+
/**
|
|
8971
|
+
* @param {HtmlNodeRef=} n node
|
|
8972
|
+
* @returns {HtmlNodeRef} next sibling
|
|
8973
|
+
*/
|
|
8974
|
+
nextSibling(n = _currentNode) {
|
|
8975
|
+
return _nodeNextSiblings[n];
|
|
8976
|
+
},
|
|
8977
|
+
/**
|
|
8978
|
+
* @param {HtmlNodeRef=} n node
|
|
8979
|
+
* @returns {HtmlNodeRef} parent node (a `<template>`'s content links to its fragment)
|
|
8980
|
+
*/
|
|
8981
|
+
parentOf(n = _currentNode) {
|
|
8982
|
+
return _nodeParents[n];
|
|
8983
|
+
},
|
|
8984
|
+
/**
|
|
8985
|
+
* @param {HtmlNodeRef=} n node
|
|
8986
|
+
* @returns {HtmlNodeRef[]} materialized child list — test/tooling convenience, allocates
|
|
8987
|
+
*/
|
|
8988
|
+
children(n = _currentNode) {
|
|
8989
|
+
const out = [];
|
|
8990
|
+
for (let c = _nodeFirstChildren[n]; c !== 0; c = _nodeNextSiblings[c]) {
|
|
8991
|
+
out.push(c);
|
|
8992
|
+
}
|
|
8993
|
+
return out;
|
|
8994
|
+
}
|
|
8995
|
+
};
|
|
8996
|
+
|
|
8997
|
+
module.exports.A = A;
|
|
7722
8998
|
module.exports.NS_HTML = NS_HTML;
|
|
7723
8999
|
module.exports.NS_MATHML = NS_MATHML;
|
|
7724
9000
|
module.exports.NS_SVG = NS_SVG;
|
|
@@ -7726,13 +9002,19 @@ module.exports.NodeType = NodeType;
|
|
|
7726
9002
|
module.exports.QUOTE_DOUBLE = QUOTE_DOUBLE;
|
|
7727
9003
|
module.exports.QUOTE_NONE = QUOTE_NONE;
|
|
7728
9004
|
module.exports.QUOTE_SINGLE = QUOTE_SINGLE;
|
|
7729
|
-
module.exports.SVG_TAG_ADJUST = SVG_TAG_ADJUST;
|
|
7730
|
-
module.exports.SourceProcessor = SourceProcessor;
|
|
7731
9005
|
// Exposed so HtmlParser can map user-configured (lowercased) tag names to
|
|
7732
9006
|
// the adjusted camelCase names the AST carries for foreign content.
|
|
7733
|
-
module.exports.
|
|
7734
|
-
module.exports.
|
|
7735
|
-
module.exports.
|
|
7736
|
-
module.exports.
|
|
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;
|
|
7737
9019
|
module.exports.parseSrcset = parseSrcset;
|
|
7738
|
-
module.exports.
|
|
9020
|
+
module.exports.tokenize = tokenize;
|