webpack 5.108.2 → 5.108.4
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/Compilation.js +1 -4
- package/lib/FlagDependencyExportsPlugin.js +55 -8
- package/lib/LazyBarrel.js +30 -3
- package/lib/NormalModule.js +8 -12
- package/lib/config/defaults.js +3 -3
- package/lib/css/CssGenerator.js +33 -42
- package/lib/css/CssModulesPlugin.js +79 -53
- package/lib/css/CssParser.js +699 -449
- package/lib/css/syntax.js +1146 -360
- package/lib/dependencies/CssIcssExportDependency.js +28 -16
- package/lib/dependencies/HarmonyAcceptDependency.js +8 -16
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +23 -96
- package/lib/dependencies/HarmonyImportGuard.js +192 -20
- package/lib/dependencies/ImportParserPlugin.js +7 -9
- package/lib/html/HtmlGenerator.js +53 -76
- package/lib/html/HtmlModule.js +1 -0
- package/lib/html/HtmlModulesPlugin.js +24 -13
- package/lib/html/HtmlParser.js +240 -124
- package/lib/html/syntax.js +1362 -683
- package/lib/javascript/JavascriptParser.js +10 -0
- package/lib/url/URLParserPlugin.js +33 -7
- package/lib/util/LocConverter.js +9 -8
- package/lib/util/SourceProcessor.js +41 -28
- package/package.json +3 -3
- package/types.d.ts +79 -221
package/lib/html/syntax.js
CHANGED
|
@@ -383,9 +383,9 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
383
383
|
let tagStart = pos;
|
|
384
384
|
let tagNameStart = -1;
|
|
385
385
|
let tagNameEnd = -1;
|
|
386
|
-
let
|
|
387
|
-
let
|
|
388
|
-
let
|
|
386
|
+
let attributeNameStart = -1;
|
|
387
|
+
let attributeNameEnd = -1;
|
|
388
|
+
let attributeValueStart = -1;
|
|
389
389
|
let attrQuoteType = QUOTE_NONE;
|
|
390
390
|
let commentStart = pos;
|
|
391
391
|
let lastOpenTagName = "";
|
|
@@ -526,19 +526,19 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
526
526
|
// the callback is expected to do the same advance based on the
|
|
527
527
|
// reported `quoteType`.
|
|
528
528
|
let nextPos = attrQuoteType === QUOTE_NONE ? endPos : endPos + 1;
|
|
529
|
-
if (callbacks.attribute !== undefined &&
|
|
529
|
+
if (callbacks.attribute !== undefined && attributeNameStart !== -1) {
|
|
530
530
|
nextPos = callbacks.attribute(
|
|
531
531
|
input,
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
532
|
+
attributeNameStart,
|
|
533
|
+
attributeNameEnd,
|
|
534
|
+
attributeValueStart,
|
|
535
|
+
attributeValueStart === -1 ? -1 : endPos,
|
|
536
536
|
attrQuoteType
|
|
537
537
|
);
|
|
538
538
|
}
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
539
|
+
if (attributeNameStart !== -1) tagHasAttributes = true;
|
|
540
|
+
attributeNameStart = -1;
|
|
541
|
+
attributeValueStart = -1;
|
|
542
542
|
attrQuoteType = QUOTE_NONE;
|
|
543
543
|
return nextPos;
|
|
544
544
|
};
|
|
@@ -806,14 +806,14 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
806
806
|
pos + 1,
|
|
807
807
|
"warning"
|
|
808
808
|
);
|
|
809
|
-
|
|
809
|
+
attributeNameStart = pos;
|
|
810
810
|
state = STATE_ATTRIBUTE_NAME;
|
|
811
811
|
pos++;
|
|
812
812
|
} else {
|
|
813
813
|
// Anything else
|
|
814
814
|
// Start a new attribute in the current tag token. Set that attribute name
|
|
815
815
|
// and value to the empty string. Reconsume in the attribute name state.
|
|
816
|
-
|
|
816
|
+
attributeNameStart = pos;
|
|
817
817
|
state = STATE_ATTRIBUTE_NAME;
|
|
818
818
|
// Reconsume
|
|
819
819
|
}
|
|
@@ -831,11 +831,11 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
831
831
|
// EOF
|
|
832
832
|
// Reconsume in the after attribute name state.
|
|
833
833
|
if (isSpace(cc) || cc === CC_SOLIDUS || cc === CC_GREATER_THAN) {
|
|
834
|
-
|
|
834
|
+
attributeNameEnd = pos;
|
|
835
835
|
state = STATE_AFTER_ATTRIBUTE_NAME;
|
|
836
836
|
// Reconsume
|
|
837
837
|
} else if (cc === CC_EQUALS) {
|
|
838
|
-
|
|
838
|
+
attributeNameEnd = pos;
|
|
839
839
|
state = STATE_BEFORE_ATTRIBUTE_VALUE;
|
|
840
840
|
pos++;
|
|
841
841
|
} else {
|
|
@@ -915,7 +915,7 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
915
915
|
// Anything else
|
|
916
916
|
// Start a new attribute in the current tag token.
|
|
917
917
|
emitAttribute(pos);
|
|
918
|
-
|
|
918
|
+
attributeNameStart = pos;
|
|
919
919
|
state = STATE_ATTRIBUTE_NAME;
|
|
920
920
|
// Reconsume
|
|
921
921
|
}
|
|
@@ -934,14 +934,14 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
934
934
|
} else if (cc === CC_QUOTATION_MARK) {
|
|
935
935
|
// U+0022 QUOTATION MARK (")
|
|
936
936
|
// Switch to the attribute value (double-quoted) state.
|
|
937
|
-
|
|
937
|
+
attributeValueStart = pos + 1;
|
|
938
938
|
attrQuoteType = QUOTE_DOUBLE;
|
|
939
939
|
state = STATE_ATTRIBUTE_VALUE_DOUBLE_QUOTED;
|
|
940
940
|
pos++;
|
|
941
941
|
} else if (cc === CC_APOSTROPHE) {
|
|
942
942
|
// U+0027 APOSTROPHE (')
|
|
943
943
|
// Switch to the attribute value (single-quoted) state.
|
|
944
|
-
|
|
944
|
+
attributeValueStart = pos + 1;
|
|
945
945
|
attrQuoteType = QUOTE_SINGLE;
|
|
946
946
|
state = STATE_ATTRIBUTE_VALUE_SINGLE_QUOTED;
|
|
947
947
|
pos++;
|
|
@@ -952,7 +952,7 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
952
952
|
// an empty value range pointing at the `>` so the open-tag offset range
|
|
953
953
|
// still includes the `>`.
|
|
954
954
|
reportError("missing-attribute-value", pos, pos + 1, "warning");
|
|
955
|
-
|
|
955
|
+
attributeValueStart = pos;
|
|
956
956
|
attrQuoteType = QUOTE_NONE;
|
|
957
957
|
pos = emitAttribute(pos);
|
|
958
958
|
if (input.charCodeAt(tagStart + 1) === CC_SOLIDUS) {
|
|
@@ -966,7 +966,7 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
966
966
|
} else {
|
|
967
967
|
// Anything else
|
|
968
968
|
// Reconsume in the attribute value (unquoted) state.
|
|
969
|
-
|
|
969
|
+
attributeValueStart = pos;
|
|
970
970
|
attrQuoteType = QUOTE_NONE;
|
|
971
971
|
state = STATE_ATTRIBUTE_VALUE_UNQUOTED;
|
|
972
972
|
// Reconsume
|
|
@@ -1301,7 +1301,20 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
1301
1301
|
if (cc === CC_NULL) {
|
|
1302
1302
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
1303
1303
|
}
|
|
1304
|
+
// Fast-forward ordinary comment text without re-entering the
|
|
1305
|
+
// state switch; stop on the significant code points above.
|
|
1304
1306
|
pos++;
|
|
1307
|
+
while (pos < len) {
|
|
1308
|
+
const c2 = input.charCodeAt(pos);
|
|
1309
|
+
if (
|
|
1310
|
+
c2 === CC_LESS_THAN ||
|
|
1311
|
+
c2 === CC_HYPHEN_MINUS ||
|
|
1312
|
+
c2 === CC_NULL
|
|
1313
|
+
) {
|
|
1314
|
+
break;
|
|
1315
|
+
}
|
|
1316
|
+
pos++;
|
|
1317
|
+
}
|
|
1305
1318
|
}
|
|
1306
1319
|
break;
|
|
1307
1320
|
|
|
@@ -3175,25 +3188,25 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
3175
3188
|
pos + runLen < len && input.charCodeAt(pos + runLen) === CC_SEMICOLON;
|
|
3176
3189
|
namedEntityConsumed = 0;
|
|
3177
3190
|
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;
|
|
3191
|
+
// Try the full run with its trailing `;` first — the overwhelmingly
|
|
3192
|
+
// common case (`&`, ` `, …) then needs exactly one slice.
|
|
3193
|
+
if (hasSemicolon && runLen > 0) {
|
|
3194
|
+
const withSemicolon = input.slice(pos, pos + runLen + 1);
|
|
3195
|
+
if (HTML_ENTITIES[withSemicolon] !== undefined) {
|
|
3196
|
+
namedEntityConsumed = runLen + 1;
|
|
3191
3197
|
matchedWithSemicolon = true;
|
|
3192
|
-
break;
|
|
3193
3198
|
}
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3199
|
+
}
|
|
3200
|
+
if (namedEntityConsumed === 0) {
|
|
3201
|
+
// Slice the candidate run once; prefixes come from this short
|
|
3202
|
+
// string instead of re-slicing the input per length.
|
|
3203
|
+
const run = input.slice(pos, pos + runLen);
|
|
3204
|
+
for (let n = runLen; n > 0; n--) {
|
|
3205
|
+
const bare = n === runLen ? run : run.slice(0, n);
|
|
3206
|
+
if (HTML_ENTITIES[bare] !== undefined) {
|
|
3207
|
+
namedEntityConsumed = n;
|
|
3208
|
+
break;
|
|
3209
|
+
}
|
|
3197
3210
|
}
|
|
3198
3211
|
}
|
|
3199
3212
|
if (namedEntityConsumed > 0) {
|
|
@@ -3208,12 +3221,10 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
3208
3221
|
returnState === STATE_ATTRIBUTE_VALUE_DOUBLE_QUOTED ||
|
|
3209
3222
|
returnState === STATE_ATTRIBUTE_VALUE_SINGLE_QUOTED ||
|
|
3210
3223
|
returnState === STATE_ATTRIBUTE_VALUE_UNQUOTED;
|
|
3211
|
-
if (
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
)
|
|
3216
|
-
) {
|
|
3224
|
+
if (!(
|
|
3225
|
+
inAttribute &&
|
|
3226
|
+
(next === CC_EQUALS || isAsciiAlphanumeric(next))
|
|
3227
|
+
)) {
|
|
3217
3228
|
reportError(
|
|
3218
3229
|
"missing-semicolon-after-character-reference",
|
|
3219
3230
|
pos + namedEntityConsumed,
|
|
@@ -3470,11 +3481,11 @@ const walkHtmlTokens = (input, pos = 0, callbacks = {}) => {
|
|
|
3470
3481
|
// dropping the in-progress tag, we emit its offset range up to EOF.
|
|
3471
3482
|
reportError("eof-in-tag", len, len, "error");
|
|
3472
3483
|
// If we hit EOF mid-attribute-name, the name runs to EOF. Set
|
|
3473
|
-
//
|
|
3474
|
-
if (state === STATE_ATTRIBUTE_NAME &&
|
|
3475
|
-
|
|
3484
|
+
// attributeNameEnd here so the emitted attribute range is valid.
|
|
3485
|
+
if (state === STATE_ATTRIBUTE_NAME && attributeNameStart !== -1) {
|
|
3486
|
+
attributeNameEnd = len;
|
|
3476
3487
|
}
|
|
3477
|
-
if (
|
|
3488
|
+
if (attributeNameStart !== -1) emitAttribute(len);
|
|
3478
3489
|
// If we hit EOF before the tag-name end was recorded, the name runs
|
|
3479
3490
|
// to EOF. `tagNameEnd` may carry over from a previously emitted tag,
|
|
3480
3491
|
// so reset it whenever it's missing or stale (less than `tagNameStart`)
|
|
@@ -3697,6 +3708,15 @@ const decodeOneReference = (match, nextCharCode, isAttribute) => {
|
|
|
3697
3708
|
return match;
|
|
3698
3709
|
};
|
|
3699
3710
|
|
|
3711
|
+
// Hoisted `replace` callbacks (one per `isAttribute` mode) — no closure
|
|
3712
|
+
// per decode call, and the callback stays monomorphic.
|
|
3713
|
+
/** @type {(match: string, offset: number, source: string) => string} */
|
|
3714
|
+
const _decodeReferenceInText = (match, offset, source) =>
|
|
3715
|
+
decodeOneReference(match, source.charCodeAt(offset + match.length), false);
|
|
3716
|
+
/** @type {(match: string, offset: number, source: string) => string} */
|
|
3717
|
+
const _decodeReferenceInAttribute = (match, offset, source) =>
|
|
3718
|
+
decodeOneReference(match, source.charCodeAt(offset + match.length), true);
|
|
3719
|
+
|
|
3700
3720
|
/**
|
|
3701
3721
|
* Decode HTML character references in a string. Handles all numeric
|
|
3702
3722
|
* references (with WHATWG remap of 0x00, surrogates, out-of-range, and the
|
|
@@ -3715,12 +3735,9 @@ const decodeOneReference = (match, nextCharCode, isAttribute) => {
|
|
|
3715
3735
|
const decodeHtmlEntities = (str, isAttribute) => {
|
|
3716
3736
|
if (!str.includes("&")) return str;
|
|
3717
3737
|
|
|
3718
|
-
return str.replace(
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
source.charCodeAt(offset + match.length),
|
|
3722
|
-
isAttribute
|
|
3723
|
-
)
|
|
3738
|
+
return str.replace(
|
|
3739
|
+
CHARACTER_REFERENCE_REGEXP,
|
|
3740
|
+
isAttribute ? _decodeReferenceInAttribute : _decodeReferenceInText
|
|
3724
3741
|
);
|
|
3725
3742
|
};
|
|
3726
3743
|
|
|
@@ -3779,6 +3796,354 @@ const NS_MATHML = 1;
|
|
|
3779
3796
|
const NS_SVG = 2;
|
|
3780
3797
|
|
|
3781
3798
|
/**
|
|
3799
|
+
* AST node `type` discriminators. Numeric for the same reason as the CSS
|
|
3800
|
+
* `NodeType`: compact integer `===` dispatch on the tree-construction and
|
|
3801
|
+
* visitor-walk hot paths.
|
|
3802
|
+
* @type {{ Document: 1, DocumentFragment: 2, Element: 3, Text: 4, Comment: 5, Doctype: 6 }}
|
|
3803
|
+
*/
|
|
3804
|
+
const NodeType = {
|
|
3805
|
+
Document: 1,
|
|
3806
|
+
DocumentFragment: 2,
|
|
3807
|
+
Element: 3,
|
|
3808
|
+
Text: 4,
|
|
3809
|
+
Comment: 5,
|
|
3810
|
+
Doctype: 6
|
|
3811
|
+
};
|
|
3812
|
+
|
|
3813
|
+
/**
|
|
3814
|
+
* A contiguous run of attribute ids in the attribute columns — how a start-tag
|
|
3815
|
+
* token and an element refer to their attributes. `start` is the first id
|
|
3816
|
+
* (`count` 0 = none).
|
|
3817
|
+
* @typedef {{ start: number, count: number }} AttributeRun
|
|
3818
|
+
*/
|
|
3819
|
+
|
|
3820
|
+
// Shared frozen empty run for attributeless elements and synthesized tags.
|
|
3821
|
+
const EMPTY_ATTRS = /** @type {AttributeRun} */ (
|
|
3822
|
+
Object.freeze({ start: 0, count: 0 })
|
|
3823
|
+
);
|
|
3824
|
+
|
|
3825
|
+
// === Struct-of-arrays AST backend ===
|
|
3826
|
+
// One AST node = one integer id (`HtmlNodeRef`) indexing the parallel columns
|
|
3827
|
+
// below — no per-node object and no per-parent children array. Tree shape
|
|
3828
|
+
// lives in the four link columns (parent / firstChild / lastChild /
|
|
3829
|
+
// nextSibling); the only heap references are the string payload / attribute
|
|
3830
|
+
// name-and-value side arrays. Columns are module-level and reused
|
|
3831
|
+
// across parses (grown, never shrunk — the CSS parser's `_soa*` strategy), so
|
|
3832
|
+
// a steady-state parse allocates almost nothing per node; consumers must fully
|
|
3833
|
+
// read a tree before the next `buildHtmlAst` call. Id 0 is reserved as
|
|
3834
|
+
// "no node" so the link columns can use 0 as null.
|
|
3835
|
+
let _hCap = 0;
|
|
3836
|
+
let _hN = 0;
|
|
3837
|
+
/** `NodeType` per node */
|
|
3838
|
+
let _hTy = new Uint8Array(0);
|
|
3839
|
+
/** bits 0-1 namespace (`NS_*`), bit 2 self-closing (void element) */
|
|
3840
|
+
let _hFl = new Uint8Array(0);
|
|
3841
|
+
let _hSt = new Int32Array(0);
|
|
3842
|
+
let _hEn = new Int32Array(0);
|
|
3843
|
+
/** end offset of an element's opening tag (after `>`) */
|
|
3844
|
+
let _hTagEnd = new Int32Array(0);
|
|
3845
|
+
/** end offset of an element's tag name */
|
|
3846
|
+
let _hNameEnd = new Int32Array(0);
|
|
3847
|
+
/** under `skip.text`, end offset of a raw-text element's body (`HtmlAstSkip`) */
|
|
3848
|
+
let _hCEnd = new Int32Array(0);
|
|
3849
|
+
/** a `<template>`'s content DocumentFragment (0 = none) */
|
|
3850
|
+
let _hTc = new Int32Array(0);
|
|
3851
|
+
let _hParent = new Int32Array(0);
|
|
3852
|
+
let _hFirst = new Int32Array(0);
|
|
3853
|
+
let _hLast = new Int32Array(0);
|
|
3854
|
+
let _hNext = new Int32Array(0);
|
|
3855
|
+
/** @type {string[]} tag name / text data / comment data / doctype name */
|
|
3856
|
+
const _hStr = [];
|
|
3857
|
+
/** first attribute id of an element's contiguous run */
|
|
3858
|
+
let _hAStart = new Int32Array(0);
|
|
3859
|
+
/** attribute count of an element's run */
|
|
3860
|
+
let _hACount = new Int32Array(0);
|
|
3861
|
+
// The single doctype node's public/system ids (a document inserts at most one
|
|
3862
|
+
// doctype node — later doctype tokens are ignored — so no column is needed).
|
|
3863
|
+
/** @type {string | null} */
|
|
3864
|
+
let _hDocPub = null;
|
|
3865
|
+
/** @type {string | null} */
|
|
3866
|
+
let _hDocSys = null;
|
|
3867
|
+
|
|
3868
|
+
const NS_MASK = 3;
|
|
3869
|
+
const FLAG_SELF_CLOSING = 4;
|
|
3870
|
+
|
|
3871
|
+
/** @type {(el: HtmlNodeRef) => string} */
|
|
3872
|
+
const _tag = (el) => _hStr[el];
|
|
3873
|
+
/** @type {(el: HtmlNodeRef) => number} */
|
|
3874
|
+
const _ns = (el) => _hFl[el] & NS_MASK;
|
|
3875
|
+
|
|
3876
|
+
// === Attribute columns ===
|
|
3877
|
+
// One attribute = one integer id into these columns; an element (and a
|
|
3878
|
+
// start-tag token) holds a contiguous run. The value string is derived from
|
|
3879
|
+
// the source by offset on read — `_aVal` carries an override only for
|
|
3880
|
+
// valueless attributes (`""`) and offset-less adoption-agency clones — and the
|
|
3881
|
+
// html5lib serializer name is derived from the adjusted name plus one flag
|
|
3882
|
+
// bit, so per attribute only the interned name pointer is retained.
|
|
3883
|
+
let _aCap = 0;
|
|
3884
|
+
let _aN = 0;
|
|
3885
|
+
let _aNameStart = new Int32Array(0);
|
|
3886
|
+
let _aNameEnd = new Int32Array(0);
|
|
3887
|
+
let _aValStart = new Int32Array(0);
|
|
3888
|
+
let _aValEnd = new Int32Array(0);
|
|
3889
|
+
/** bit 0: name has a `FOREIGN_ATTR_NS` serializer name (set on foreign adjust) */
|
|
3890
|
+
let _aFl = new Uint8Array(0);
|
|
3891
|
+
/** @type {string[]} lowercased (foreign-content: adjusted) attribute name */
|
|
3892
|
+
const _aName = [];
|
|
3893
|
+
/** @type {(string | null)[]} value override (null = slice the source by offset) */
|
|
3894
|
+
const _aVal = [];
|
|
3895
|
+
/** source of the current parse, for by-offset attribute values */
|
|
3896
|
+
let _hSrc = "";
|
|
3897
|
+
|
|
3898
|
+
/** @param {number} need minimum capacity */
|
|
3899
|
+
const _aGrow = (need) => {
|
|
3900
|
+
let cap = _aCap || 4096;
|
|
3901
|
+
while (cap < need) cap *= 2;
|
|
3902
|
+
const nameStart = new Int32Array(cap);
|
|
3903
|
+
nameStart.set(_aNameStart);
|
|
3904
|
+
_aNameStart = nameStart;
|
|
3905
|
+
const nameEnd = new Int32Array(cap);
|
|
3906
|
+
nameEnd.set(_aNameEnd);
|
|
3907
|
+
_aNameEnd = nameEnd;
|
|
3908
|
+
const valStart = new Int32Array(cap);
|
|
3909
|
+
valStart.set(_aValStart);
|
|
3910
|
+
_aValStart = valStart;
|
|
3911
|
+
const valEnd = new Int32Array(cap);
|
|
3912
|
+
valEnd.set(_aValEnd);
|
|
3913
|
+
_aValEnd = valEnd;
|
|
3914
|
+
const fl = new Uint8Array(cap);
|
|
3915
|
+
fl.set(_aFl);
|
|
3916
|
+
_aFl = fl;
|
|
3917
|
+
_aCap = cap;
|
|
3918
|
+
};
|
|
3919
|
+
|
|
3920
|
+
/** @type {(name: string, value: string | null, nameStart: number, nameEnd: number, valueStart: number, valueEnd: number) => number} */
|
|
3921
|
+
const _aAlloc = (name, value, nameStart, nameEnd, valueStart, valueEnd) => {
|
|
3922
|
+
const i = ++_aN;
|
|
3923
|
+
if (i >= _aCap) _aGrow(i + 1);
|
|
3924
|
+
_aNameStart[i] = nameStart;
|
|
3925
|
+
_aNameEnd[i] = nameEnd;
|
|
3926
|
+
_aValStart[i] = valueStart;
|
|
3927
|
+
_aValEnd[i] = valueEnd;
|
|
3928
|
+
_aFl[i] = 0;
|
|
3929
|
+
// Ids are sequential, so these indexed writes append (arrays stay packed).
|
|
3930
|
+
_aName[i] = name;
|
|
3931
|
+
_aVal[i] = value;
|
|
3932
|
+
return i;
|
|
3933
|
+
};
|
|
3934
|
+
|
|
3935
|
+
/** @type {(i: number) => string} */
|
|
3936
|
+
const _aValueOf = (i) => {
|
|
3937
|
+
const v = _aVal[i];
|
|
3938
|
+
return v !== null ? v : _hSrc.slice(_aValStart[i], _aValEnd[i]);
|
|
3939
|
+
};
|
|
3940
|
+
|
|
3941
|
+
// Linear name lookup in a run — attribute lists are short, a loop beats a Map.
|
|
3942
|
+
/** @type {(start: number, count: number, name: string) => number} */
|
|
3943
|
+
const _aFind = (start, count, name) => {
|
|
3944
|
+
for (let i = start; i < start + count; i++) {
|
|
3945
|
+
if (_aName[i] === name) return i;
|
|
3946
|
+
}
|
|
3947
|
+
return 0;
|
|
3948
|
+
};
|
|
3949
|
+
|
|
3950
|
+
/** @type {(i: number) => number} exact copy of an attribute into a new id */
|
|
3951
|
+
const _aCopy = (i) => {
|
|
3952
|
+
const c = _aAlloc(
|
|
3953
|
+
_aName[i],
|
|
3954
|
+
_aVal[i],
|
|
3955
|
+
_aNameStart[i],
|
|
3956
|
+
_aNameEnd[i],
|
|
3957
|
+
_aValStart[i],
|
|
3958
|
+
_aValEnd[i]
|
|
3959
|
+
);
|
|
3960
|
+
_aFl[c] = _aFl[i];
|
|
3961
|
+
return c;
|
|
3962
|
+
};
|
|
3963
|
+
|
|
3964
|
+
// html5lib serializer name, derived: a `FOREIGN_ATTR_NS`-adjusted attribute is
|
|
3965
|
+
// flagged (its name is the table key), and a camelCase-adjusted name contains
|
|
3966
|
+
// an uppercase letter (unadjusted names are always lowercased), serializing as
|
|
3967
|
+
// itself. Everything else serializes as the plain name (undefined here).
|
|
3968
|
+
/** @type {(i: number) => string | undefined} */
|
|
3969
|
+
const _aSerializedName = (i) => {
|
|
3970
|
+
if ((_aFl[i] & 1) !== 0) return FOREIGN_ATTR_NS[_aName[i]];
|
|
3971
|
+
const name = _aName[i];
|
|
3972
|
+
return /[A-Z]/.test(name) ? name : undefined;
|
|
3973
|
+
};
|
|
3974
|
+
|
|
3975
|
+
/** @param {number} need minimum capacity */
|
|
3976
|
+
const _hGrow = (need) => {
|
|
3977
|
+
let cap = _hCap || 4096;
|
|
3978
|
+
while (cap < need) cap *= 2;
|
|
3979
|
+
const ty = new Uint8Array(cap);
|
|
3980
|
+
ty.set(_hTy);
|
|
3981
|
+
_hTy = ty;
|
|
3982
|
+
const fl = new Uint8Array(cap);
|
|
3983
|
+
fl.set(_hFl);
|
|
3984
|
+
_hFl = fl;
|
|
3985
|
+
const st = new Int32Array(cap);
|
|
3986
|
+
st.set(_hSt);
|
|
3987
|
+
_hSt = st;
|
|
3988
|
+
const en = new Int32Array(cap);
|
|
3989
|
+
en.set(_hEn);
|
|
3990
|
+
_hEn = en;
|
|
3991
|
+
const tagEnd = new Int32Array(cap);
|
|
3992
|
+
tagEnd.set(_hTagEnd);
|
|
3993
|
+
_hTagEnd = tagEnd;
|
|
3994
|
+
const nameEnd = new Int32Array(cap);
|
|
3995
|
+
nameEnd.set(_hNameEnd);
|
|
3996
|
+
_hNameEnd = nameEnd;
|
|
3997
|
+
const cEnd = new Int32Array(cap);
|
|
3998
|
+
cEnd.set(_hCEnd);
|
|
3999
|
+
_hCEnd = cEnd;
|
|
4000
|
+
const tc = new Int32Array(cap);
|
|
4001
|
+
tc.set(_hTc);
|
|
4002
|
+
_hTc = tc;
|
|
4003
|
+
const parent = new Int32Array(cap);
|
|
4004
|
+
parent.set(_hParent);
|
|
4005
|
+
_hParent = parent;
|
|
4006
|
+
const first = new Int32Array(cap);
|
|
4007
|
+
first.set(_hFirst);
|
|
4008
|
+
_hFirst = first;
|
|
4009
|
+
const last = new Int32Array(cap);
|
|
4010
|
+
last.set(_hLast);
|
|
4011
|
+
_hLast = last;
|
|
4012
|
+
const next = new Int32Array(cap);
|
|
4013
|
+
next.set(_hNext);
|
|
4014
|
+
_hNext = next;
|
|
4015
|
+
const aStart = new Int32Array(cap);
|
|
4016
|
+
aStart.set(_hAStart);
|
|
4017
|
+
_hAStart = aStart;
|
|
4018
|
+
const aCount = new Int32Array(cap);
|
|
4019
|
+
aCount.set(_hACount);
|
|
4020
|
+
_hACount = aCount;
|
|
4021
|
+
_hCap = cap;
|
|
4022
|
+
};
|
|
4023
|
+
|
|
4024
|
+
/** Start a new parse: invalidate all prior refs, release prior heap refs. */
|
|
4025
|
+
const _hReset = () => {
|
|
4026
|
+
_hN = 0;
|
|
4027
|
+
_aN = 0;
|
|
4028
|
+
_hStr.length = 0;
|
|
4029
|
+
_aName.length = 0;
|
|
4030
|
+
_aVal.length = 0;
|
|
4031
|
+
// Keep id 0 ("no node" / "no attribute") occupied so writes stay packed.
|
|
4032
|
+
_hStr.push("");
|
|
4033
|
+
_aName.push("");
|
|
4034
|
+
_aVal.push(null);
|
|
4035
|
+
_hDocPub = null;
|
|
4036
|
+
_hDocSys = null;
|
|
4037
|
+
};
|
|
4038
|
+
|
|
4039
|
+
// Release the side arrays' heap references (strings, attribute names/values)
|
|
4040
|
+
// once a walk has consumed the tree, so the retained columns don't pin the
|
|
4041
|
+
// parsed source until the next parse.
|
|
4042
|
+
const _hRelease = () => {
|
|
4043
|
+
_hN = 0;
|
|
4044
|
+
_aN = 0;
|
|
4045
|
+
_hStr.length = 0;
|
|
4046
|
+
_aName.length = 0;
|
|
4047
|
+
_aVal.length = 0;
|
|
4048
|
+
_hSrc = "";
|
|
4049
|
+
_hDocPub = null;
|
|
4050
|
+
_hDocSys = null;
|
|
4051
|
+
};
|
|
4052
|
+
|
|
4053
|
+
/** @type {(type: number, start: number, end: number) => HtmlNodeRef} */
|
|
4054
|
+
const _hAlloc = (type, start, end) => {
|
|
4055
|
+
const i = ++_hN;
|
|
4056
|
+
if (i >= _hCap) _hGrow(i + 1);
|
|
4057
|
+
_hTy[i] = type;
|
|
4058
|
+
_hFl[i] = 0;
|
|
4059
|
+
_hSt[i] = start;
|
|
4060
|
+
_hEn[i] = end;
|
|
4061
|
+
_hTagEnd[i] = 0;
|
|
4062
|
+
_hNameEnd[i] = 0;
|
|
4063
|
+
_hCEnd[i] = 0;
|
|
4064
|
+
_hTc[i] = 0;
|
|
4065
|
+
_hParent[i] = 0;
|
|
4066
|
+
_hFirst[i] = 0;
|
|
4067
|
+
_hLast[i] = 0;
|
|
4068
|
+
_hNext[i] = 0;
|
|
4069
|
+
_hAStart[i] = 0;
|
|
4070
|
+
_hACount[i] = 0;
|
|
4071
|
+
// Ids are sequential, so this indexed write appends (array stays packed).
|
|
4072
|
+
_hStr[i] = "";
|
|
4073
|
+
return i;
|
|
4074
|
+
};
|
|
4075
|
+
|
|
4076
|
+
// Raw child append — no text merging, no `<template>` content redirect (the
|
|
4077
|
+
// tree builder layers those on top). `node` must be detached (`next` = 0).
|
|
4078
|
+
/** @type {(parent: HtmlNodeRef, node: HtmlNodeRef) => void} */
|
|
4079
|
+
const _hAppend = (parent, node) => {
|
|
4080
|
+
_hParent[node] = parent;
|
|
4081
|
+
const last = _hLast[parent];
|
|
4082
|
+
if (last === 0) _hFirst[parent] = node;
|
|
4083
|
+
else _hNext[last] = node;
|
|
4084
|
+
_hLast[parent] = node;
|
|
4085
|
+
};
|
|
4086
|
+
|
|
4087
|
+
/** @type {(data: string, start: number, end: number) => HtmlNodeRef} */
|
|
4088
|
+
const _mkText = (data, start, end) => {
|
|
4089
|
+
const i = _hAlloc(NodeType.Text, start, end);
|
|
4090
|
+
_hStr[i] = data;
|
|
4091
|
+
return i;
|
|
4092
|
+
};
|
|
4093
|
+
|
|
4094
|
+
/** @type {(data: string, start: number, end: number) => HtmlNodeRef} */
|
|
4095
|
+
const _mkComment = (data, start, end) => {
|
|
4096
|
+
const i = _hAlloc(NodeType.Comment, start, end);
|
|
4097
|
+
_hStr[i] = data;
|
|
4098
|
+
return i;
|
|
4099
|
+
};
|
|
4100
|
+
|
|
4101
|
+
// Marker entry in the active-formatting-elements list (never a valid ref).
|
|
4102
|
+
const AFE_MARKER = -1;
|
|
4103
|
+
|
|
4104
|
+
// Clone of an element's attribute run: keep name/value (and the serializer
|
|
4105
|
+
// name flag) but drop source offsets so the consumer doesn't emit a duplicate
|
|
4106
|
+
// dependency for the reopened element's spans. Values are materialized since
|
|
4107
|
+
// the offsets are gone.
|
|
4108
|
+
const cloneAttrs = (/** @type {HtmlElement} */ el) => {
|
|
4109
|
+
const start = _hAStart[el];
|
|
4110
|
+
const count = _hACount[el];
|
|
4111
|
+
const newStart = _aN + 1;
|
|
4112
|
+
for (let i = start; i < start + count; i++) {
|
|
4113
|
+
const c = _aAlloc(_aName[i], _aValueOf(i), -1, -1, -1, -1);
|
|
4114
|
+
_aFl[c] = _aFl[i];
|
|
4115
|
+
}
|
|
4116
|
+
return { start: newStart, count };
|
|
4117
|
+
};
|
|
4118
|
+
|
|
4119
|
+
// Merge a repeated `<html>`/`<body>` tag's attributes into the element: only
|
|
4120
|
+
// names not already present are added (in source order after the existing
|
|
4121
|
+
// ones). Runs are contiguous, so any addition re-allocates the whole run; the
|
|
4122
|
+
// old slots are orphaned (at most once per repeated tag, rare).
|
|
4123
|
+
const mergeAttrs = (
|
|
4124
|
+
/** @type {HtmlElement} */ el,
|
|
4125
|
+
/** @type {AttributeRun} */ run
|
|
4126
|
+
) => {
|
|
4127
|
+
const start = _hAStart[el];
|
|
4128
|
+
const count = _hACount[el];
|
|
4129
|
+
let extra = 0;
|
|
4130
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
4131
|
+
if (_aFind(start, count, _aName[i]) === 0) extra++;
|
|
4132
|
+
}
|
|
4133
|
+
if (extra === 0) return;
|
|
4134
|
+
const newStart = _aN + 1;
|
|
4135
|
+
for (let i = start; i < start + count; i++) _aCopy(i);
|
|
4136
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
4137
|
+
if (_aFind(start, count, _aName[i]) === 0) _aCopy(i);
|
|
4138
|
+
}
|
|
4139
|
+
_hAStart[el] = newStart;
|
|
4140
|
+
_hACount[el] = count + extra;
|
|
4141
|
+
};
|
|
4142
|
+
|
|
4143
|
+
/**
|
|
4144
|
+
* A materialized attribute as returned by `A.attributes` (tests/tooling) —
|
|
4145
|
+
* the parser-facing representation is an id into the attribute columns, read
|
|
4146
|
+
* through the scalar `A.attr*` accessors.
|
|
3782
4147
|
* @typedef {object} HtmlAttribute
|
|
3783
4148
|
* @property {string} name lowercased (and, in foreign content, adjusted) attribute name
|
|
3784
4149
|
* @property {string} value
|
|
@@ -3790,60 +4155,27 @@ const NS_SVG = 2;
|
|
|
3790
4155
|
*/
|
|
3791
4156
|
|
|
3792
4157
|
/**
|
|
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
|
|
4158
|
+
* A node reference into the struct-of-arrays AST: an integer id indexing the
|
|
4159
|
+
* parallel `_h*` columns. Read fields through the exported accessor `A`. Refs
|
|
4160
|
+
* are only valid until the next `buildHtmlAst` call — the columns are reused
|
|
4161
|
+
* across parses — so consume a tree fully before parsing again.
|
|
4162
|
+
* @typedef {number} HtmlNodeRef
|
|
3813
4163
|
*/
|
|
3814
4164
|
|
|
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
|
-
*/
|
|
4165
|
+
/** @typedef {HtmlNodeRef} HtmlElement ref to an Element node */
|
|
4166
|
+
/** @typedef {HtmlNodeRef} HtmlText ref to a Text node */
|
|
4167
|
+
/** @typedef {HtmlNodeRef} HtmlComment ref to a Comment node */
|
|
4168
|
+
/** @typedef {HtmlNodeRef} HtmlDoctype ref to a Doctype node */
|
|
4169
|
+
/** @typedef {HtmlNodeRef} HtmlDocument ref to the Document node */
|
|
4170
|
+
/** @typedef {HtmlNodeRef} HtmlDocumentFragment ref to a DocumentFragment node */
|
|
4171
|
+
/** @typedef {HtmlNodeRef} HtmlNode */
|
|
3832
4172
|
|
|
3833
4173
|
/**
|
|
3834
|
-
*
|
|
3835
|
-
*
|
|
3836
|
-
* @
|
|
4174
|
+
* An attribute reference: an integer id into the attribute columns, read
|
|
4175
|
+
* through the `A.attr*` accessors. Same validity contract as `HtmlNodeRef`.
|
|
4176
|
+
* @typedef {number} HtmlAttributeRef
|
|
3837
4177
|
*/
|
|
3838
4178
|
|
|
3839
|
-
/**
|
|
3840
|
-
* @typedef {object} HtmlDocumentFragment
|
|
3841
|
-
* @property {typeof NodeType.DocumentFragment} type
|
|
3842
|
-
* @property {HtmlNode[]} children
|
|
3843
|
-
*/
|
|
3844
|
-
|
|
3845
|
-
/** @typedef {HtmlElement | HtmlText | HtmlComment | HtmlDoctype} HtmlNode */
|
|
3846
|
-
|
|
3847
4179
|
/** @typedef {{ start: number, end: number, tagEnd: number, nameEnd: number }} TagPos */
|
|
3848
4180
|
|
|
3849
4181
|
// Tree-construction token `type` discriminators. Numeric for the same reason
|
|
@@ -3859,7 +4191,7 @@ const TOKEN_EOF = 6;
|
|
|
3859
4191
|
/** @typedef {{ type: typeof TOKEN_CHAR, data: string, start: number, end: number }} CharToken */
|
|
3860
4192
|
/** @typedef {{ type: typeof TOKEN_COMMENT, data: string, start: number, end: number }} CommentToken */
|
|
3861
4193
|
/** @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:
|
|
4194
|
+
/** @typedef {{ type: typeof TOKEN_START_TAG, name: string, attrs: AttributeRun, selfClosing: boolean, pos: TagPos, swallowNewline?: boolean }} StartTagToken */
|
|
3863
4195
|
/** @typedef {{ type: typeof TOKEN_END_TAG, name: string, pos: TagPos }} EndTagToken */
|
|
3864
4196
|
/** @typedef {{ type: typeof TOKEN_EOF }} EofToken */
|
|
3865
4197
|
|
|
@@ -3876,10 +4208,10 @@ const TOKEN_EOF = 6;
|
|
|
3876
4208
|
* stale values that those handlers never read. Tokens that must outlive the
|
|
3877
4209
|
* current callback (buffered table characters, synthesized re-dispatches) are
|
|
3878
4210
|
* copied into fresh plain objects instead.
|
|
3879
|
-
* @typedef {{ type: number, name: string, data: string, attrs:
|
|
4211
|
+
* @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
4212
|
*/
|
|
3881
4213
|
|
|
3882
|
-
/** @typedef {{ parent:
|
|
4214
|
+
/** @typedef {{ parent: HtmlNodeRef, beforeNode: HtmlNodeRef }} InsertionPlace `beforeNode` 0 = plain append */
|
|
3883
4215
|
|
|
3884
4216
|
// Insertion modes (§13.2.4.1). Numeric for the same reason as the token and
|
|
3885
4217
|
// `NodeType` enums: `runMode` dispatches on `mode` once per token.
|
|
@@ -4043,9 +4375,11 @@ const FORMATTING = new Set([
|
|
|
4043
4375
|
const HEADING = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
4044
4376
|
|
|
4045
4377
|
const isSpecial = (/** @type {HtmlElement} */ el) => {
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
if (
|
|
4378
|
+
const ns = _ns(el);
|
|
4379
|
+
const tag = _tag(el);
|
|
4380
|
+
if (ns === NS_HTML) return SPECIAL.has(tag);
|
|
4381
|
+
if (ns === NS_MATHML) return MATHML_SPECIAL.has(tag);
|
|
4382
|
+
if (ns === NS_SVG) return SVG_SPECIAL.has(tag.toLowerCase());
|
|
4049
4383
|
return false;
|
|
4050
4384
|
};
|
|
4051
4385
|
|
|
@@ -4493,14 +4827,13 @@ const FOREIGN_BREAKOUT = new Set([
|
|
|
4493
4827
|
]);
|
|
4494
4828
|
const FONT_BREAKOUT_ATTRS = new Set(["color", "face", "size"]);
|
|
4495
4829
|
|
|
4496
|
-
//
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
);
|
|
4830
|
+
// `<font color|face|size>` breaks out of foreign content (§13.2.6.5).
|
|
4831
|
+
const hasFontBreakoutAttr = (/** @type {AttributeRun} */ run) => {
|
|
4832
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
4833
|
+
if (FONT_BREAKOUT_ATTRS.has(_aName[i])) return true;
|
|
4834
|
+
}
|
|
4835
|
+
return false;
|
|
4836
|
+
};
|
|
4504
4837
|
|
|
4505
4838
|
/**
|
|
4506
4839
|
* Hash of the ASCII-lowercased `name` for the intern tables below; must stay
|
|
@@ -4518,33 +4851,58 @@ const hashLowerName = (name) => {
|
|
|
4518
4851
|
return h;
|
|
4519
4852
|
};
|
|
4520
4853
|
|
|
4854
|
+
// Text-run scan classes for the `skip.text` fast path: 2 = stop the fast
|
|
4855
|
+
// path (& / NUL / CR), 1 = ASCII whitespace, 0 = ordinary text.
|
|
4856
|
+
const _TEXT_SCAN_CLASS = new Uint8Array(128);
|
|
4857
|
+
_TEXT_SCAN_CLASS[0x09] = 1;
|
|
4858
|
+
_TEXT_SCAN_CLASS[0x0a] = 1;
|
|
4859
|
+
_TEXT_SCAN_CLASS[0x0c] = 1;
|
|
4860
|
+
_TEXT_SCAN_CLASS[0x20] = 1;
|
|
4861
|
+
_TEXT_SCAN_CLASS[0x26] = 2;
|
|
4862
|
+
_TEXT_SCAN_CLASS[0x00] = 2;
|
|
4863
|
+
_TEXT_SCAN_CLASS[0x0d] = 2;
|
|
4864
|
+
|
|
4521
4865
|
/**
|
|
4522
4866
|
* @param {Iterable<string>} names lowercase names to intern
|
|
4523
|
-
* @returns {
|
|
4867
|
+
* @returns {{ mask: number, hashes: Int32Array, values: (string | string[] | undefined)[] }} open-addressed intern table
|
|
4524
4868
|
*/
|
|
4525
4869
|
const buildNameInternTable = (names) => {
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4870
|
+
// Open-addressed table (~25% load): the per-name probe is one or two array
|
|
4871
|
+
// reads instead of a `Map#get`, and the tables are built once at startup.
|
|
4872
|
+
const unique = [...new Set(names)];
|
|
4873
|
+
let size = 8;
|
|
4874
|
+
while (size < unique.length * 4) size <<= 1;
|
|
4875
|
+
const mask = size - 1;
|
|
4876
|
+
const hashes = new Int32Array(size);
|
|
4877
|
+
/** @type {(string | string[] | undefined)[]} */
|
|
4878
|
+
const values = Array.from({ length: size });
|
|
4879
|
+
for (const name of unique) {
|
|
4529
4880
|
const h = hashLowerName(name);
|
|
4530
|
-
|
|
4881
|
+
let slot = h & mask;
|
|
4882
|
+
while (values[slot] !== undefined && hashes[slot] !== h) {
|
|
4883
|
+
slot = (slot + 1) & mask;
|
|
4884
|
+
}
|
|
4885
|
+
const cur = values[slot];
|
|
4531
4886
|
if (cur === undefined) {
|
|
4532
|
-
|
|
4887
|
+
hashes[slot] = h;
|
|
4888
|
+
values[slot] = name;
|
|
4533
4889
|
} else if (typeof cur === "string") {
|
|
4534
|
-
|
|
4535
|
-
} else
|
|
4890
|
+
values[slot] = [cur, name];
|
|
4891
|
+
} else {
|
|
4536
4892
|
cur.push(name);
|
|
4537
4893
|
}
|
|
4538
4894
|
}
|
|
4539
|
-
return
|
|
4895
|
+
return { mask, hashes, values };
|
|
4540
4896
|
};
|
|
4541
4897
|
|
|
4898
|
+
/** @typedef {ReturnType<typeof buildNameInternTable>} NameInternTable */
|
|
4899
|
+
|
|
4542
4900
|
/**
|
|
4543
4901
|
* The lowercased name for `input[start..end)`, returning the shared interned
|
|
4544
4902
|
* string for known names — skipping the per-tag `slice().toLowerCase()`
|
|
4545
4903
|
* allocation, and making the tree builder's many Set/Map lookups and `===`
|
|
4546
4904
|
* comparisons on the name hit one string instance with a cached hash.
|
|
4547
|
-
* @param {
|
|
4905
|
+
* @param {NameInternTable} table intern table
|
|
4548
4906
|
* @param {string} input source text
|
|
4549
4907
|
* @param {number} start name start
|
|
4550
4908
|
* @param {number} end name end
|
|
@@ -4557,15 +4915,22 @@ const internLowerName = (table, input, start, end) => {
|
|
|
4557
4915
|
if (c >= 0x41 && c <= 0x5a) c += 0x20;
|
|
4558
4916
|
h = ((h << 5) - h + c) | 0;
|
|
4559
4917
|
}
|
|
4560
|
-
const
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4918
|
+
const { mask, hashes, values } = table;
|
|
4919
|
+
let slot = h & mask;
|
|
4920
|
+
for (;;) {
|
|
4921
|
+
const hit = values[slot];
|
|
4922
|
+
if (hit === undefined) break;
|
|
4923
|
+
if (hashes[slot] === h) {
|
|
4924
|
+
if (typeof hit === "string") {
|
|
4925
|
+
if (rangeEqualsLower(input, start, end, hit)) return hit;
|
|
4926
|
+
} else {
|
|
4927
|
+
for (let i = 0; i < hit.length; i++) {
|
|
4928
|
+
if (rangeEqualsLower(input, start, end, hit[i])) return hit[i];
|
|
4929
|
+
}
|
|
4567
4930
|
}
|
|
4931
|
+
break;
|
|
4568
4932
|
}
|
|
4933
|
+
slot = (slot + 1) & mask;
|
|
4569
4934
|
}
|
|
4570
4935
|
// Unknown name (custom element, data-* attribute, non-ASCII, …).
|
|
4571
4936
|
return input.slice(start, end).toLowerCase();
|
|
@@ -4604,50 +4969,71 @@ const ATTR_NAME_INTERN = buildNameInternTable(
|
|
|
4604
4969
|
// Hoisted so the many `open.some(...)` "is there an open HTML <template>?"
|
|
4605
4970
|
// checks reuse one predicate instead of allocating an arrow per call.
|
|
4606
4971
|
const isHtmlTemplateEl = (/** @type {HtmlElement} */ e) =>
|
|
4607
|
-
e
|
|
4972
|
+
_tag(e) === "template" && _ns(e) === NS_HTML;
|
|
4608
4973
|
|
|
4609
|
-
//
|
|
4610
|
-
//
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4974
|
+
// Raw text / escapable raw text elements (WHATWG §13.1.2) plus the other
|
|
4975
|
+
// RAWTEXT/RCDATA/PLAINTEXT-tokenized elements. Under `skip.text` their body's
|
|
4976
|
+
// end offset is recorded on the element (see `insertCharacters`) so a consumer
|
|
4977
|
+
// can read the raw content span without a `Text` node.
|
|
4978
|
+
const RAW_TEXT_ELEMENTS = new Set([
|
|
4979
|
+
"script",
|
|
4980
|
+
"style",
|
|
4981
|
+
"textarea",
|
|
4982
|
+
"title",
|
|
4983
|
+
"xmp",
|
|
4984
|
+
"iframe",
|
|
4985
|
+
"noembed",
|
|
4986
|
+
"noframes",
|
|
4987
|
+
"noscript",
|
|
4988
|
+
"plaintext"
|
|
4989
|
+
]);
|
|
4990
|
+
|
|
4991
|
+
/** Shared empty skip set so the common (no-skip) call allocates nothing. */
|
|
4992
|
+
const EMPTY_SKIP = Object.freeze({});
|
|
4993
|
+
|
|
4994
|
+
/**
|
|
4995
|
+
* Optional node kinds a consumer can drop from the AST for speed/memory. Each
|
|
4996
|
+
* is a pure output reduction — tree construction (and quirks detection) runs
|
|
4997
|
+
* unchanged, so element structure and offsets are identical either way.
|
|
4998
|
+
* @typedef {object} HtmlAstSkip
|
|
4999
|
+
* @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.
|
|
5000
|
+
* @property {boolean=} comments drop comment nodes entirely. Not for consumers that read comments (e.g. webpack magic comments).
|
|
5001
|
+
* @property {boolean=} doctype drop the doctype node; quirks-mode detection is unaffected.
|
|
5002
|
+
*/
|
|
4620
5003
|
|
|
4621
5004
|
/**
|
|
4622
5005
|
* @param {string} source HTML source
|
|
4623
5006
|
* @param {string=} fragmentContext context element name for fragment parsing (e.g. `td`, `svg path`); omit for a full document
|
|
4624
|
-
* @
|
|
5007
|
+
* @param {HtmlAstSkip=} skip node kinds to omit from the AST (see `HtmlAstSkip`); omit to build the full tree
|
|
5008
|
+
* @returns {HtmlDocument} ref to the document node — read through `A`; valid until the next parse
|
|
4625
5009
|
*/
|
|
4626
|
-
const buildHtmlAst = (source, fragmentContext) => {
|
|
4627
|
-
|
|
4628
|
-
const
|
|
5010
|
+
const buildHtmlAst = (source, fragmentContext, skip = EMPTY_SKIP) => {
|
|
5011
|
+
const skipText = skip.text === true;
|
|
5012
|
+
const skipComments = skip.comments === true;
|
|
5013
|
+
const skipDoctype = skip.doctype === true;
|
|
5014
|
+
_hReset();
|
|
5015
|
+
_hSrc = source;
|
|
5016
|
+
const doc = _hAlloc(NodeType.Document, 0, 0);
|
|
4629
5017
|
let mode = MODE_INITIAL;
|
|
4630
5018
|
// Mode to return to after MODE_TEXT / MODE_IN_TABLE_TEXT (0 = unset).
|
|
4631
5019
|
let originalMode = 0;
|
|
4632
5020
|
/** @type {HtmlElement[]} stack of open elements (bottom .. top) */
|
|
4633
5021
|
const open = [];
|
|
4634
|
-
/** @type {
|
|
5022
|
+
/** @type {HtmlNodeRef[]} active formatting elements (AFE_MARKER = marker) */
|
|
4635
5023
|
const afe = [];
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
/** @type {HtmlElement
|
|
4639
|
-
let
|
|
4640
|
-
/** @type {HtmlElement | null} */
|
|
4641
|
-
let form = null;
|
|
5024
|
+
/** @type {HtmlElement} 0 = none */
|
|
5025
|
+
let head = 0;
|
|
5026
|
+
/** @type {HtmlElement} 0 = none */
|
|
5027
|
+
let form = 0;
|
|
4642
5028
|
let framesetOk = true;
|
|
4643
|
-
|
|
4644
|
-
let
|
|
5029
|
+
// First attribute id of the tag currently being tokenized (0 = none).
|
|
5030
|
+
let pendAttrStart = 0;
|
|
4645
5031
|
let fosterParenting = false;
|
|
4646
5032
|
/** @type {number[]} */
|
|
4647
5033
|
const templateModes = [];
|
|
4648
5034
|
let quirks = false;
|
|
4649
|
-
/** @type {HtmlElement
|
|
4650
|
-
let fragment =
|
|
5035
|
+
/** @type {HtmlElement} fragment context element (0 = document parse) */
|
|
5036
|
+
let fragment = 0;
|
|
4651
5037
|
// End offset of the token currently being processed (for element `.end`).
|
|
4652
5038
|
let tokenEnd = 0;
|
|
4653
5039
|
|
|
@@ -4660,70 +5046,68 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4660
5046
|
const mkEl = (
|
|
4661
5047
|
/** @type {string} */ tagName,
|
|
4662
5048
|
/** @type {number} */ ns,
|
|
4663
|
-
/** @type {
|
|
5049
|
+
/** @type {AttributeRun} */ attrs,
|
|
4664
5050
|
/** @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
|
-
}));
|
|
5051
|
+
) => {
|
|
5052
|
+
const el = _hAlloc(
|
|
5053
|
+
NodeType.Element,
|
|
5054
|
+
pos ? pos.start : 0,
|
|
5055
|
+
pos ? pos.end : 0
|
|
5056
|
+
);
|
|
5057
|
+
// Void HTML elements are marked self-closing and never receive children.
|
|
5058
|
+
_hFl[el] =
|
|
5059
|
+
ns === NS_HTML && VOID.has(tagName) ? ns | FLAG_SELF_CLOSING : ns;
|
|
5060
|
+
_hStr[el] = tagName;
|
|
5061
|
+
_hAStart[el] = attrs.start;
|
|
5062
|
+
_hACount[el] = attrs.count;
|
|
5063
|
+
if (pos) {
|
|
5064
|
+
_hTagEnd[el] = pos.tagEnd;
|
|
5065
|
+
_hNameEnd[el] = pos.nameEnd;
|
|
5066
|
+
// End of a raw-text element's body under `skip.text` (defaults to the
|
|
5067
|
+
// body start, i.e. empty); lets consumers read `<script>`/`<style>`
|
|
5068
|
+
// content as [`tagEnd`, `contentEnd`] without a `Text` node.
|
|
5069
|
+
_hCEnd[el] = pos.tagEnd;
|
|
5070
|
+
}
|
|
5071
|
+
return el;
|
|
5072
|
+
};
|
|
4695
5073
|
|
|
4696
5074
|
/**
|
|
4697
|
-
*
|
|
4698
|
-
*
|
|
5075
|
+
* A `<template>`'s children live in its content fragment; inserting into a
|
|
5076
|
+
* template really inserts there (the spec's "template contents" redirect).
|
|
5077
|
+
* @param {HtmlNodeRef} parent container
|
|
5078
|
+
* @returns {HtmlNodeRef} effective container to link children into
|
|
4699
5079
|
*/
|
|
4700
|
-
const
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
5080
|
+
const effParent = (parent) => {
|
|
5081
|
+
const tc = _hTc[parent];
|
|
5082
|
+
return tc !== 0 ? tc : parent;
|
|
5083
|
+
};
|
|
4704
5084
|
|
|
4705
5085
|
const appendTo = (
|
|
4706
|
-
/** @type {
|
|
4707
|
-
/** @type {
|
|
5086
|
+
/** @type {HtmlNodeRef} */ parent,
|
|
5087
|
+
/** @type {HtmlNodeRef} */ node
|
|
4708
5088
|
) => {
|
|
4709
|
-
const
|
|
4710
|
-
const last =
|
|
4711
|
-
if (
|
|
4712
|
-
|
|
4713
|
-
|
|
5089
|
+
const p = effParent(parent);
|
|
5090
|
+
const last = _hLast[p];
|
|
5091
|
+
if (
|
|
5092
|
+
_hTy[node] === NodeType.Text &&
|
|
5093
|
+
last !== 0 &&
|
|
5094
|
+
_hTy[last] === NodeType.Text
|
|
5095
|
+
) {
|
|
5096
|
+
_hStr[last] += _hStr[node];
|
|
5097
|
+
_hEn[last] = _hEn[node];
|
|
4714
5098
|
return;
|
|
4715
5099
|
}
|
|
4716
|
-
|
|
5100
|
+
_hAppend(p, node);
|
|
4717
5101
|
};
|
|
4718
5102
|
|
|
4719
5103
|
// Reused result of `appropriatePlace` — consumed synchronously by
|
|
4720
5104
|
// `insertAtPlace` and never retained, so one shared object avoids an
|
|
4721
5105
|
// allocation per inserted node.
|
|
4722
5106
|
/** @type {InsertionPlace} */
|
|
4723
|
-
const sharedPlace = { parent: doc, beforeNode:
|
|
5107
|
+
const sharedPlace = { parent: doc, beforeNode: 0 };
|
|
4724
5108
|
const placeAt = (
|
|
4725
|
-
/** @type {
|
|
4726
|
-
/** @type {
|
|
5109
|
+
/** @type {HtmlNodeRef} */ parent,
|
|
5110
|
+
/** @type {HtmlNodeRef} */ beforeNode
|
|
4727
5111
|
) => {
|
|
4728
5112
|
sharedPlace.parent = parent;
|
|
4729
5113
|
sharedPlace.beforeNode = beforeNode;
|
|
@@ -4735,23 +5119,23 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4735
5119
|
const target = cur();
|
|
4736
5120
|
if (
|
|
4737
5121
|
fosterParenting &&
|
|
4738
|
-
TABLE_CONTEXT.has(target
|
|
4739
|
-
target
|
|
5122
|
+
TABLE_CONTEXT.has(_tag(target)) &&
|
|
5123
|
+
_ns(target) === NS_HTML
|
|
4740
5124
|
) {
|
|
4741
5125
|
// find last template / last table
|
|
4742
5126
|
let lastTemplate = -1;
|
|
4743
5127
|
let lastTable = -1;
|
|
4744
5128
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
4745
5129
|
if (
|
|
4746
|
-
open[i]
|
|
4747
|
-
open[i]
|
|
5130
|
+
_tag(open[i]) === "template" &&
|
|
5131
|
+
_ns(open[i]) === NS_HTML &&
|
|
4748
5132
|
lastTemplate === -1
|
|
4749
5133
|
) {
|
|
4750
5134
|
lastTemplate = i;
|
|
4751
5135
|
}
|
|
4752
5136
|
if (
|
|
4753
|
-
open[i]
|
|
4754
|
-
open[i]
|
|
5137
|
+
_tag(open[i]) === "table" &&
|
|
5138
|
+
_ns(open[i]) === NS_HTML &&
|
|
4755
5139
|
lastTable === -1
|
|
4756
5140
|
) {
|
|
4757
5141
|
lastTable = i;
|
|
@@ -4761,53 +5145,52 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4761
5145
|
lastTemplate !== -1 &&
|
|
4762
5146
|
(lastTable === -1 || lastTemplate > lastTable)
|
|
4763
5147
|
) {
|
|
4764
|
-
return placeAt(open[lastTemplate],
|
|
5148
|
+
return placeAt(open[lastTemplate], 0);
|
|
4765
5149
|
}
|
|
4766
5150
|
if (lastTable === -1) {
|
|
4767
|
-
return placeAt(open[0],
|
|
5151
|
+
return placeAt(open[0], 0);
|
|
4768
5152
|
}
|
|
4769
5153
|
const table = open[lastTable];
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
return placeAt(open[lastTable - 1], null);
|
|
4774
|
-
}
|
|
4775
|
-
return placeAt(target, null);
|
|
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
|
-
}
|
|
5154
|
+
const tp = _hParent[table];
|
|
5155
|
+
if (tp !== 0) return placeAt(tp, table);
|
|
5156
|
+
return placeAt(open[lastTable - 1], 0);
|
|
4791
5157
|
}
|
|
4792
|
-
return
|
|
5158
|
+
return placeAt(target, 0);
|
|
4793
5159
|
};
|
|
4794
5160
|
|
|
4795
5161
|
const insertAtPlace = (
|
|
4796
5162
|
/** @type {InsertionPlace} */ place,
|
|
4797
|
-
/** @type {
|
|
5163
|
+
/** @type {HtmlNodeRef} */ node
|
|
4798
5164
|
) => {
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
const
|
|
5165
|
+
const before = place.beforeNode;
|
|
5166
|
+
if (before !== 0) {
|
|
5167
|
+
const p = effParent(place.parent);
|
|
5168
|
+
// Find `before`'s previous sibling (insert-before is a rare foster/
|
|
5169
|
+
// adoption path, so the sibling scan stays off the hot path).
|
|
5170
|
+
let prev = 0;
|
|
5171
|
+
let c = _hFirst[p];
|
|
5172
|
+
while (c !== 0 && c !== before) {
|
|
5173
|
+
prev = c;
|
|
5174
|
+
c = _hNext[c];
|
|
5175
|
+
}
|
|
5176
|
+
if (c === 0) {
|
|
5177
|
+
// `before` not under `parent` (not reachable from the spec paths).
|
|
5178
|
+
appendTo(place.parent, node);
|
|
5179
|
+
return;
|
|
5180
|
+
}
|
|
4802
5181
|
if (
|
|
4803
|
-
node
|
|
4804
|
-
|
|
4805
|
-
|
|
5182
|
+
_hTy[node] === NodeType.Text &&
|
|
5183
|
+
prev !== 0 &&
|
|
5184
|
+
_hTy[prev] === NodeType.Text
|
|
4806
5185
|
) {
|
|
4807
|
-
|
|
5186
|
+
// Before-node merge deliberately does not bump the sibling's `end`.
|
|
5187
|
+
_hStr[prev] += _hStr[node];
|
|
4808
5188
|
return;
|
|
4809
5189
|
}
|
|
4810
|
-
|
|
5190
|
+
_hParent[node] = p;
|
|
5191
|
+
_hNext[node] = before;
|
|
5192
|
+
if (prev === 0) _hFirst[p] = node;
|
|
5193
|
+
else _hNext[prev] = node;
|
|
4811
5194
|
} else {
|
|
4812
5195
|
appendTo(place.parent, node);
|
|
4813
5196
|
}
|
|
@@ -4819,28 +5202,55 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4819
5202
|
/** @type {number} */ end
|
|
4820
5203
|
) => {
|
|
4821
5204
|
const place = appropriatePlace();
|
|
4822
|
-
if (place.parent
|
|
5205
|
+
if (_hTy[place.parent] === NodeType.Document) return; // never insert text into document
|
|
5206
|
+
// `skip.text`: drop every `Text` node — construction already used the
|
|
5207
|
+
// decoded token, so removing the node never affects element structure.
|
|
5208
|
+
// For a raw-text element record the body end so a consumer reads the span
|
|
5209
|
+
// [`tagEnd`, `contentEnd`] without a `Text` node (see `HtmlParser`).
|
|
5210
|
+
// Namespace-agnostic: `HtmlParser` extracts `<script>`/`<style>` bodies in
|
|
5211
|
+
// foreign content (e.g. SVG `<style>`) too.
|
|
5212
|
+
if (skipText) {
|
|
5213
|
+
const p = place.parent;
|
|
5214
|
+
if (_hTy[p] === NodeType.Element && RAW_TEXT_ELEMENTS.has(_tag(p))) {
|
|
5215
|
+
_hCEnd[p] = end;
|
|
5216
|
+
}
|
|
5217
|
+
return;
|
|
5218
|
+
}
|
|
4823
5219
|
// Inlined text insert: when the run merges into the adjacent text sibling
|
|
4824
5220
|
// (common with inline formatting) only the string is appended — no
|
|
4825
5221
|
// throwaway text node is allocated. Mirrors `insertAtPlace`/`appendTo`,
|
|
4826
5222
|
// including that the before-node merge does not bump `end`.
|
|
4827
|
-
const
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
5223
|
+
const p = effParent(place.parent);
|
|
5224
|
+
const before = place.beforeNode;
|
|
5225
|
+
if (before !== 0) {
|
|
5226
|
+
let prev = 0;
|
|
5227
|
+
let c = _hFirst[p];
|
|
5228
|
+
while (c !== 0 && c !== before) {
|
|
5229
|
+
prev = c;
|
|
5230
|
+
c = _hNext[c];
|
|
5231
|
+
}
|
|
5232
|
+
if (c === 0) {
|
|
5233
|
+
// `before` not under `parent` (not reachable from the spec paths).
|
|
5234
|
+
_hAppend(p, _mkText(data, start, end));
|
|
5235
|
+
return;
|
|
5236
|
+
}
|
|
5237
|
+
if (prev !== 0 && _hTy[prev] === NodeType.Text) {
|
|
5238
|
+
_hStr[prev] += data;
|
|
4833
5239
|
return;
|
|
4834
5240
|
}
|
|
4835
|
-
|
|
5241
|
+
const node = _mkText(data, start, end);
|
|
5242
|
+
_hParent[node] = p;
|
|
5243
|
+
_hNext[node] = before;
|
|
5244
|
+
if (prev === 0) _hFirst[p] = node;
|
|
5245
|
+
else _hNext[prev] = node;
|
|
4836
5246
|
} else {
|
|
4837
|
-
const last =
|
|
4838
|
-
if (last && last
|
|
4839
|
-
last
|
|
4840
|
-
last
|
|
5247
|
+
const last = _hLast[p];
|
|
5248
|
+
if (last !== 0 && _hTy[last] === NodeType.Text) {
|
|
5249
|
+
_hStr[last] += data;
|
|
5250
|
+
_hEn[last] = end;
|
|
4841
5251
|
return;
|
|
4842
5252
|
}
|
|
4843
|
-
|
|
5253
|
+
_hAppend(p, _mkText(data, start, end));
|
|
4844
5254
|
}
|
|
4845
5255
|
};
|
|
4846
5256
|
|
|
@@ -4851,16 +5261,17 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4851
5261
|
* @param {InsertionPlace=} place explicit insertion place
|
|
4852
5262
|
*/
|
|
4853
5263
|
const insertComment = (data, start, end, place) => {
|
|
5264
|
+
if (skipComments) return;
|
|
4854
5265
|
const p = place || appropriatePlace();
|
|
4855
|
-
insertAtPlace(p,
|
|
5266
|
+
insertAtPlace(p, _mkComment(data, start, end));
|
|
4856
5267
|
};
|
|
4857
5268
|
|
|
4858
5269
|
const insertHtmlElement = (
|
|
4859
5270
|
/** @type {string} */ tagName,
|
|
4860
|
-
/** @type {
|
|
5271
|
+
/** @type {AttributeRun} */ attrs,
|
|
4861
5272
|
/** @type {TagPos | null} */ pos
|
|
4862
5273
|
) => {
|
|
4863
|
-
const el = mkEl(tagName, NS_HTML,
|
|
5274
|
+
const el = mkEl(tagName, NS_HTML, attrs, pos);
|
|
4864
5275
|
const place = appropriatePlace();
|
|
4865
5276
|
insertAtPlace(place, el);
|
|
4866
5277
|
open.push(el);
|
|
@@ -4870,10 +5281,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4870
5281
|
const insertForeignElement = (
|
|
4871
5282
|
/** @type {string} */ tagName,
|
|
4872
5283
|
/** @type {number} */ ns,
|
|
4873
|
-
/** @type {
|
|
5284
|
+
/** @type {AttributeRun} */ attrs,
|
|
4874
5285
|
/** @type {TagPos | null} */ pos
|
|
4875
5286
|
) => {
|
|
4876
|
-
const el = mkEl(tagName, ns,
|
|
5287
|
+
const el = mkEl(tagName, ns, attrs, pos);
|
|
4877
5288
|
const place = appropriatePlace();
|
|
4878
5289
|
insertAtPlace(place, el);
|
|
4879
5290
|
open.push(el);
|
|
@@ -4882,10 +5293,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4882
5293
|
|
|
4883
5294
|
// ---- scopes ----
|
|
4884
5295
|
const isScopeBoundary = (/** @type {HtmlElement} */ el) => {
|
|
4885
|
-
if (el
|
|
4886
|
-
if (el
|
|
4887
|
-
if (el
|
|
4888
|
-
return SVG_SPECIAL.has(el.
|
|
5296
|
+
if (_ns(el) === NS_HTML) return HTML_SCOPE.has(_tag(el));
|
|
5297
|
+
if (_ns(el) === NS_MATHML) return MATHML_SPECIAL.has(_tag(el));
|
|
5298
|
+
if (_ns(el) === NS_SVG) {
|
|
5299
|
+
return SVG_SPECIAL.has(_tag(el).toLowerCase());
|
|
4889
5300
|
}
|
|
4890
5301
|
return false;
|
|
4891
5302
|
};
|
|
@@ -4900,10 +5311,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4900
5311
|
/** @type {number} */ kind
|
|
4901
5312
|
) => {
|
|
4902
5313
|
if (isScopeBoundary(el)) return true;
|
|
4903
|
-
if (el
|
|
4904
|
-
if (kind === SCOPE_BUTTON) return el
|
|
5314
|
+
if (_ns(el) !== NS_HTML) return false;
|
|
5315
|
+
if (kind === SCOPE_BUTTON) return _tag(el) === "button";
|
|
4905
5316
|
if (kind === SCOPE_LIST_ITEM) {
|
|
4906
|
-
return el
|
|
5317
|
+
return _tag(el) === "ol" || _tag(el) === "ul";
|
|
4907
5318
|
}
|
|
4908
5319
|
return false;
|
|
4909
5320
|
};
|
|
@@ -4915,7 +5326,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4915
5326
|
) => {
|
|
4916
5327
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
4917
5328
|
const el = open[i];
|
|
4918
|
-
if (el
|
|
5329
|
+
if (_ns(el) === NS_HTML && _tag(el) === tagName) return true;
|
|
4919
5330
|
if (isBoundaryForKind(el, kind)) return false;
|
|
4920
5331
|
}
|
|
4921
5332
|
return false;
|
|
@@ -4939,9 +5350,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4939
5350
|
const set = typeof target === "string" ? null : target;
|
|
4940
5351
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
4941
5352
|
const el = open[i];
|
|
4942
|
-
if (el
|
|
4943
|
-
if (set ? set.has(el
|
|
4944
|
-
if (TABLE_SCOPE_STOP.has(el
|
|
5353
|
+
if (_ns(el) === NS_HTML) {
|
|
5354
|
+
if (set ? set.has(_tag(el)) : _tag(el) === target) return true;
|
|
5355
|
+
if (TABLE_SCOPE_STOP.has(_tag(el))) return false;
|
|
4945
5356
|
}
|
|
4946
5357
|
}
|
|
4947
5358
|
return false;
|
|
@@ -4949,11 +5360,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4949
5360
|
const generateImpliedEndTags = (except = "") => {
|
|
4950
5361
|
while (open.length) {
|
|
4951
5362
|
const el = cur();
|
|
4952
|
-
if (
|
|
4953
|
-
el.namespace === NS_HTML &&
|
|
4954
|
-
IMPLIED.has(el.tagName) &&
|
|
4955
|
-
el.tagName !== except
|
|
4956
|
-
) {
|
|
5363
|
+
if (_ns(el) === NS_HTML && IMPLIED.has(_tag(el)) && _tag(el) !== except) {
|
|
4957
5364
|
open.pop();
|
|
4958
5365
|
} else {
|
|
4959
5366
|
break;
|
|
@@ -4963,7 +5370,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4963
5370
|
const generateImpliedEndTagsThorough = () => {
|
|
4964
5371
|
while (open.length) {
|
|
4965
5372
|
const el = cur();
|
|
4966
|
-
if (el
|
|
5373
|
+
if (_ns(el) === NS_HTML && IMPLIED_THOROUGH.has(_tag(el))) {
|
|
4967
5374
|
open.pop();
|
|
4968
5375
|
} else {
|
|
4969
5376
|
break;
|
|
@@ -4976,12 +5383,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4976
5383
|
let count = 0;
|
|
4977
5384
|
for (let i = afe.length - 1; i >= 0; i--) {
|
|
4978
5385
|
const e = afe[i];
|
|
4979
|
-
if (e
|
|
4980
|
-
if (
|
|
4981
|
-
afeEl(e).tagName === el.tagName &&
|
|
4982
|
-
afeEl(e).namespace === el.namespace &&
|
|
4983
|
-
sameAttrs(afeEl(e), el)
|
|
4984
|
-
) {
|
|
5386
|
+
if (e === AFE_MARKER) break;
|
|
5387
|
+
if (_tag(e) === _tag(el) && _ns(e) === _ns(el) && sameAttrs(e, el)) {
|
|
4985
5388
|
count++;
|
|
4986
5389
|
if (count === 3) {
|
|
4987
5390
|
afe.splice(i, 1);
|
|
@@ -4989,60 +5392,49 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
4989
5392
|
}
|
|
4990
5393
|
}
|
|
4991
5394
|
}
|
|
4992
|
-
afe.push(
|
|
5395
|
+
afe.push(el);
|
|
4993
5396
|
};
|
|
4994
5397
|
const sameAttrs = (
|
|
4995
5398
|
/** @type {HtmlElement} */ a,
|
|
4996
5399
|
/** @type {HtmlElement} */ b
|
|
4997
5400
|
) => {
|
|
4998
|
-
const
|
|
4999
|
-
const
|
|
5000
|
-
|
|
5401
|
+
const aStart = _hAStart[a];
|
|
5402
|
+
const aCount = _hACount[a];
|
|
5403
|
+
const bStart = _hAStart[b];
|
|
5404
|
+
const bCount = _hACount[b];
|
|
5405
|
+
if (aCount !== bCount) return false;
|
|
5001
5406
|
// 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;
|
|
5407
|
+
// here on this formatting-element hot path.
|
|
5408
|
+
for (let i = bStart; i < bStart + bCount; i++) {
|
|
5409
|
+
const j = _aFind(aStart, aCount, _aName[i]);
|
|
5410
|
+
if (j === 0 || _aValueOf(j) !== _aValueOf(i)) return false;
|
|
5013
5411
|
}
|
|
5014
5412
|
return true;
|
|
5015
5413
|
};
|
|
5016
|
-
const insertMarker = () => afe.push(
|
|
5414
|
+
const insertMarker = () => afe.push(AFE_MARKER);
|
|
5017
5415
|
const clearAfeToMarker = () => {
|
|
5018
5416
|
while (afe.length) {
|
|
5019
|
-
|
|
5020
|
-
if (e.marker) break;
|
|
5417
|
+
if (afe.pop() === AFE_MARKER) break;
|
|
5021
5418
|
}
|
|
5022
5419
|
};
|
|
5023
5420
|
const reconstructAfe = () => {
|
|
5024
5421
|
if (afe.length === 0) return;
|
|
5025
5422
|
let i = afe.length - 1;
|
|
5026
|
-
if (afe[i]
|
|
5423
|
+
if (afe[i] === AFE_MARKER || open.includes(afe[i])) return;
|
|
5027
5424
|
while (i > 0) {
|
|
5028
5425
|
i--;
|
|
5029
|
-
if (afe[i]
|
|
5426
|
+
if (afe[i] === AFE_MARKER || open.includes(afe[i])) {
|
|
5030
5427
|
i++;
|
|
5031
5428
|
break;
|
|
5032
5429
|
}
|
|
5033
5430
|
}
|
|
5034
5431
|
for (; i < afe.length; i++) {
|
|
5035
5432
|
const e = afe[i];
|
|
5036
|
-
const el = mkEl(
|
|
5037
|
-
afeEl(e).tagName,
|
|
5038
|
-
afeEl(e).namespace,
|
|
5039
|
-
cloneAttrs(afeEl(e).attributes),
|
|
5040
|
-
null
|
|
5041
|
-
);
|
|
5433
|
+
const el = mkEl(_tag(e), _ns(e), cloneAttrs(e), null);
|
|
5042
5434
|
const place = appropriatePlace();
|
|
5043
5435
|
insertAtPlace(place, el);
|
|
5044
5436
|
open.push(el);
|
|
5045
|
-
afe[i] =
|
|
5437
|
+
afe[i] = el;
|
|
5046
5438
|
}
|
|
5047
5439
|
};
|
|
5048
5440
|
|
|
@@ -5052,23 +5444,23 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5052
5444
|
// pop until a p has been popped
|
|
5053
5445
|
while (open.length) {
|
|
5054
5446
|
const el = /** @type {HtmlElement} */ (open.pop());
|
|
5055
|
-
el
|
|
5056
|
-
if (el
|
|
5447
|
+
_hEn[el] = tokenEnd;
|
|
5448
|
+
if (_ns(el) === NS_HTML && _tag(el) === "p") break;
|
|
5057
5449
|
}
|
|
5058
5450
|
};
|
|
5059
5451
|
|
|
5060
5452
|
const popUntil = (/** @type {string} */ tagName) => {
|
|
5061
5453
|
while (open.length) {
|
|
5062
5454
|
const el = /** @type {HtmlElement} */ (open.pop());
|
|
5063
|
-
el
|
|
5064
|
-
if (el
|
|
5455
|
+
_hEn[el] = tokenEnd;
|
|
5456
|
+
if (_ns(el) === NS_HTML && _tag(el) === tagName) break;
|
|
5065
5457
|
}
|
|
5066
5458
|
};
|
|
5067
5459
|
const popUntilOneOf = (/** @type {Set<string>} */ set) => {
|
|
5068
5460
|
while (open.length) {
|
|
5069
5461
|
const el = /** @type {HtmlElement} */ (open.pop());
|
|
5070
|
-
el
|
|
5071
|
-
if (el
|
|
5462
|
+
_hEn[el] = tokenEnd;
|
|
5463
|
+
if (_ns(el) === NS_HTML && set.has(_tag(el))) break;
|
|
5072
5464
|
}
|
|
5073
5465
|
};
|
|
5074
5466
|
|
|
@@ -5081,8 +5473,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5081
5473
|
last = true;
|
|
5082
5474
|
if (fragment) node = fragment;
|
|
5083
5475
|
}
|
|
5084
|
-
const tn = node
|
|
5085
|
-
if (node
|
|
5476
|
+
const tn = _tag(node);
|
|
5477
|
+
if (_ns(node) === NS_HTML) {
|
|
5086
5478
|
if ((tn === "td" || tn === "th") && !last) {
|
|
5087
5479
|
mode = MODE_IN_CELL;
|
|
5088
5480
|
return;
|
|
@@ -5175,7 +5567,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5175
5567
|
const useForeign =
|
|
5176
5568
|
open.length > 0 &&
|
|
5177
5569
|
ac &&
|
|
5178
|
-
ac
|
|
5570
|
+
_ns(ac) !== NS_HTML &&
|
|
5179
5571
|
ty !== TOKEN_EOF &&
|
|
5180
5572
|
shouldUseForeignRules(ac, t);
|
|
5181
5573
|
if (useForeign) {
|
|
@@ -5189,7 +5581,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5189
5581
|
/** @type {HtmlElement} */ ac,
|
|
5190
5582
|
/** @type {Token} */ t
|
|
5191
5583
|
) => {
|
|
5192
|
-
if (ac
|
|
5584
|
+
if (_ns(ac) === NS_HTML) return false;
|
|
5193
5585
|
if (t.type === TOKEN_START_TAG) {
|
|
5194
5586
|
if (
|
|
5195
5587
|
mathmlTextIntegrationPoint(ac) &&
|
|
@@ -5199,8 +5591,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5199
5591
|
return false;
|
|
5200
5592
|
}
|
|
5201
5593
|
if (
|
|
5202
|
-
ac
|
|
5203
|
-
ac
|
|
5594
|
+
_ns(ac) === NS_MATHML &&
|
|
5595
|
+
_tag(ac) === "annotation-xml" &&
|
|
5204
5596
|
t.name === "svg"
|
|
5205
5597
|
) {
|
|
5206
5598
|
return false;
|
|
@@ -5219,20 +5611,19 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5219
5611
|
};
|
|
5220
5612
|
|
|
5221
5613
|
const mathmlTextIntegrationPoint = (/** @type {HtmlElement} */ el) =>
|
|
5222
|
-
el
|
|
5614
|
+
_ns(el) === NS_MATHML && MATHML_TEXT_INTEGRATION.has(_tag(el));
|
|
5223
5615
|
const htmlIntegrationPoint = (/** @type {HtmlElement} */ el) => {
|
|
5224
|
-
if (el
|
|
5225
|
-
const enc =
|
|
5226
|
-
if (
|
|
5227
|
-
enc
|
|
5228
|
-
(
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
return true;
|
|
5616
|
+
if (_ns(el) === NS_MATHML && _tag(el) === "annotation-xml") {
|
|
5617
|
+
const enc = _aFind(_hAStart[el], _hACount[el], "encoding");
|
|
5618
|
+
if (enc !== 0) {
|
|
5619
|
+
const value = _aValueOf(enc).toLowerCase();
|
|
5620
|
+
if (value === "text/html" || value === "application/xhtml+xml") {
|
|
5621
|
+
return true;
|
|
5622
|
+
}
|
|
5232
5623
|
}
|
|
5233
5624
|
return false;
|
|
5234
5625
|
}
|
|
5235
|
-
if (el
|
|
5626
|
+
if (_ns(el) === NS_SVG && SVG_SPECIAL.has(_tag(el).toLowerCase())) {
|
|
5236
5627
|
return true;
|
|
5237
5628
|
}
|
|
5238
5629
|
return false;
|
|
@@ -5240,37 +5631,28 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5240
5631
|
|
|
5241
5632
|
const adjustSvgTag = (/** @type {string} */ name) =>
|
|
5242
5633
|
/** @type {Record<string, string>} */ (SVG_TAG_ADJUST)[name] || name;
|
|
5634
|
+
// Adjust a foreign start tag's attribute run in place (the run is consumed
|
|
5635
|
+
// only by this tag's element): SVG camelCase names are rewritten, and
|
|
5636
|
+
// namespaced names get the serializer-name flag (see `_aSerializedName`).
|
|
5243
5637
|
const adjustForeignAttrs = (
|
|
5244
|
-
/** @type {
|
|
5638
|
+
/** @type {AttributeRun} */ run,
|
|
5245
5639
|
/** @type {number} */ ns
|
|
5246
5640
|
) => {
|
|
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;
|
|
5641
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
5642
|
+
const name = _aName[i];
|
|
5257
5643
|
if (
|
|
5258
5644
|
ns === NS_SVG &&
|
|
5259
5645
|
/** @type {Record<string, string>} */ (SVG_ATTR_ADJUST)[name]
|
|
5260
5646
|
) {
|
|
5261
|
-
|
|
5262
|
-
|
|
5647
|
+
_aName[i] = /** @type {Record<string, string>} */ (SVG_ATTR_ADJUST)[
|
|
5648
|
+
name
|
|
5649
|
+
];
|
|
5263
5650
|
}
|
|
5264
|
-
if (/** @type {Record<string, string>} */ (FOREIGN_ATTR_NS)[
|
|
5265
|
-
|
|
5266
|
-
FOREIGN_ATTR_NS
|
|
5267
|
-
)[a.name];
|
|
5651
|
+
if (/** @type {Record<string, string>} */ (FOREIGN_ATTR_NS)[name]) {
|
|
5652
|
+
_aFl[i] |= 1;
|
|
5268
5653
|
}
|
|
5269
|
-
if (name === a.name && serializedName === undefined) continue;
|
|
5270
|
-
if (out === attrs) out = [...attrs];
|
|
5271
|
-
out[i] = { ...a, name, serializedName };
|
|
5272
5654
|
}
|
|
5273
|
-
return
|
|
5655
|
+
return run;
|
|
5274
5656
|
};
|
|
5275
5657
|
|
|
5276
5658
|
const foreignContent = (/** @type {Token} */ t) => {
|
|
@@ -5287,17 +5669,16 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5287
5669
|
}
|
|
5288
5670
|
if (t.type === TOKEN_DOCTYPE) return;
|
|
5289
5671
|
if (t.type === TOKEN_START_TAG) {
|
|
5290
|
-
const acn = adjustedCurrent()
|
|
5672
|
+
const acn = _ns(adjustedCurrent());
|
|
5291
5673
|
if (
|
|
5292
5674
|
FOREIGN_BREAKOUT.has(t.name) ||
|
|
5293
|
-
(t.name === "font" &&
|
|
5294
|
-
t.attrs.some((a) => FONT_BREAKOUT_ATTRS.has(a.name)))
|
|
5675
|
+
(t.name === "font" && hasFontBreakoutAttr(t.attrs))
|
|
5295
5676
|
) {
|
|
5296
5677
|
// parse error; pop until integration point / html / mathml-text-integration
|
|
5297
5678
|
while (open.length > 1) {
|
|
5298
5679
|
const c = cur();
|
|
5299
5680
|
if (
|
|
5300
|
-
c
|
|
5681
|
+
_ns(c) === NS_HTML ||
|
|
5301
5682
|
mathmlTextIntegrationPoint(c) ||
|
|
5302
5683
|
htmlIntegrationPoint(c)
|
|
5303
5684
|
) {
|
|
@@ -5327,8 +5708,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5327
5708
|
if (t.type === TOKEN_END_TAG) {
|
|
5328
5709
|
if (
|
|
5329
5710
|
t.name === "script" &&
|
|
5330
|
-
cur()
|
|
5331
|
-
cur()
|
|
5711
|
+
_tag(cur()) === "script" &&
|
|
5712
|
+
_ns(cur()) === NS_SVG
|
|
5332
5713
|
) {
|
|
5333
5714
|
open.pop();
|
|
5334
5715
|
return;
|
|
@@ -5338,7 +5719,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5338
5719
|
if (t.name === "p" || t.name === "br") {
|
|
5339
5720
|
while (
|
|
5340
5721
|
open.length > 1 &&
|
|
5341
|
-
cur()
|
|
5722
|
+
_ns(cur()) !== NS_HTML &&
|
|
5342
5723
|
!mathmlTextIntegrationPoint(cur()) &&
|
|
5343
5724
|
!htmlIntegrationPoint(cur())
|
|
5344
5725
|
) {
|
|
@@ -5350,21 +5731,18 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5350
5731
|
// any other end tag
|
|
5351
5732
|
let i = open.length - 1;
|
|
5352
5733
|
let node = open[i];
|
|
5353
|
-
if (node.
|
|
5734
|
+
if (_tag(node).toLowerCase() !== t.name) {
|
|
5354
5735
|
/* parse error */
|
|
5355
5736
|
}
|
|
5356
5737
|
while (i >= 0) {
|
|
5357
5738
|
node = open[i];
|
|
5358
5739
|
if (i === 0) return;
|
|
5359
|
-
if (
|
|
5360
|
-
node.namespace !== NS_HTML &&
|
|
5361
|
-
node.tagName.toLowerCase() === t.name
|
|
5362
|
-
) {
|
|
5740
|
+
if (_ns(node) !== NS_HTML && _tag(node).toLowerCase() === t.name) {
|
|
5363
5741
|
while (open.length > i) open.pop();
|
|
5364
5742
|
return;
|
|
5365
5743
|
}
|
|
5366
5744
|
i--;
|
|
5367
|
-
if (open[i] && open[i]
|
|
5745
|
+
if (open[i] && _ns(open[i]) === NS_HTML) {
|
|
5368
5746
|
runMode(t);
|
|
5369
5747
|
return;
|
|
5370
5748
|
}
|
|
@@ -5379,11 +5757,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5379
5757
|
) => {
|
|
5380
5758
|
// step 1
|
|
5381
5759
|
const c = cur();
|
|
5382
|
-
if (
|
|
5383
|
-
c.namespace === NS_HTML &&
|
|
5384
|
-
c.tagName === subject &&
|
|
5385
|
-
!afe.some((e) => !e.marker && e.element === c)
|
|
5386
|
-
) {
|
|
5760
|
+
if (_ns(c) === NS_HTML && _tag(c) === subject && !afe.includes(c)) {
|
|
5387
5761
|
open.pop();
|
|
5388
5762
|
return true;
|
|
5389
5763
|
}
|
|
@@ -5393,17 +5767,14 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5393
5767
|
// find formatting element
|
|
5394
5768
|
let fmtIdx = -1;
|
|
5395
5769
|
for (let i = afe.length - 1; i >= 0; i--) {
|
|
5396
|
-
if (afe[i]
|
|
5397
|
-
if (
|
|
5398
|
-
afeEl(afe[i]).tagName === subject &&
|
|
5399
|
-
afeEl(afe[i]).namespace === NS_HTML
|
|
5400
|
-
) {
|
|
5770
|
+
if (afe[i] === AFE_MARKER) break;
|
|
5771
|
+
if (_tag(afe[i]) === subject && _ns(afe[i]) === NS_HTML) {
|
|
5401
5772
|
fmtIdx = i;
|
|
5402
5773
|
break;
|
|
5403
5774
|
}
|
|
5404
5775
|
}
|
|
5405
5776
|
if (fmtIdx === -1) return false; // act as any other end tag
|
|
5406
|
-
const fmt =
|
|
5777
|
+
const fmt = afe[fmtIdx];
|
|
5407
5778
|
const openIdx = open.indexOf(fmt);
|
|
5408
5779
|
if (openIdx === -1) {
|
|
5409
5780
|
afe.splice(fmtIdx, 1);
|
|
@@ -5435,10 +5806,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5435
5806
|
nodeIdx--;
|
|
5436
5807
|
node = open[nodeIdx];
|
|
5437
5808
|
if (node === fmt) break;
|
|
5438
|
-
|
|
5439
|
-
let nodeAfeIdx = afe.findIndex(
|
|
5440
|
-
(e) => !e.marker && e.element === nodeRef
|
|
5441
|
-
);
|
|
5809
|
+
let nodeAfeIdx = afe.indexOf(node);
|
|
5442
5810
|
if (inner > 3 && nodeAfeIdx !== -1) {
|
|
5443
5811
|
afe.splice(nodeAfeIdx, 1);
|
|
5444
5812
|
if (nodeAfeIdx < bookmark) bookmark--;
|
|
@@ -5449,13 +5817,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5449
5817
|
continue;
|
|
5450
5818
|
}
|
|
5451
5819
|
// create clone
|
|
5452
|
-
const clone = mkEl(
|
|
5453
|
-
|
|
5454
|
-
node.namespace,
|
|
5455
|
-
cloneAttrs(node.attributes),
|
|
5456
|
-
null
|
|
5457
|
-
);
|
|
5458
|
-
afe[nodeAfeIdx] = { element: clone };
|
|
5820
|
+
const clone = mkEl(_tag(node), _ns(node), cloneAttrs(node), null);
|
|
5821
|
+
afe[nodeAfeIdx] = clone;
|
|
5459
5822
|
open[nodeIdx] = clone;
|
|
5460
5823
|
node = clone;
|
|
5461
5824
|
if (lastNode === furthest) bookmark = nodeAfeIdx + 1;
|
|
@@ -5469,23 +5832,27 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5469
5832
|
const place = placeForCommonAncestor(commonAncestor);
|
|
5470
5833
|
insertAtPlace(place, lastNode);
|
|
5471
5834
|
// create element for fmt token, take children of furthest
|
|
5472
|
-
const cloneFmt = mkEl(
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5835
|
+
const cloneFmt = mkEl(_tag(fmt), _ns(fmt), cloneAttrs(fmt), null);
|
|
5836
|
+
// Take all direct children of `furthest` (a template's content fragment
|
|
5837
|
+
// deliberately stays put — mirrors childrenOf-less spec behavior here).
|
|
5838
|
+
let k = _hFirst[furthest];
|
|
5839
|
+
_hFirst[furthest] = 0;
|
|
5840
|
+
_hLast[furthest] = 0;
|
|
5841
|
+
while (k !== 0) {
|
|
5842
|
+
const next = _hNext[k];
|
|
5843
|
+
_hNext[k] = 0;
|
|
5844
|
+
_hParent[k] = 0;
|
|
5845
|
+
appendTo(cloneFmt, k);
|
|
5846
|
+
k = next;
|
|
5847
|
+
}
|
|
5481
5848
|
appendTo(furthest, cloneFmt);
|
|
5482
5849
|
// remove fmt from afe, insert clone at bookmark
|
|
5483
|
-
const curFmtIdx = afe.
|
|
5850
|
+
const curFmtIdx = afe.indexOf(fmt);
|
|
5484
5851
|
if (curFmtIdx !== -1) {
|
|
5485
5852
|
afe.splice(curFmtIdx, 1);
|
|
5486
5853
|
if (curFmtIdx < bookmark) bookmark--;
|
|
5487
5854
|
}
|
|
5488
|
-
afe.splice(bookmark, 0,
|
|
5855
|
+
afe.splice(bookmark, 0, cloneFmt);
|
|
5489
5856
|
// remove fmt from open, insert clone below furthest
|
|
5490
5857
|
const ofi = open.indexOf(fmt);
|
|
5491
5858
|
if (ofi !== -1) open.splice(ofi, 1);
|
|
@@ -5499,8 +5866,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5499
5866
|
/** @type {HtmlElement} */ commonAncestor
|
|
5500
5867
|
) => {
|
|
5501
5868
|
if (
|
|
5502
|
-
TABLE_CONTEXT.has(commonAncestor
|
|
5503
|
-
commonAncestor
|
|
5869
|
+
TABLE_CONTEXT.has(_tag(commonAncestor)) &&
|
|
5870
|
+
_ns(commonAncestor) === NS_HTML
|
|
5504
5871
|
) {
|
|
5505
5872
|
// foster
|
|
5506
5873
|
// reuse appropriatePlace logic but rooted differently: emulate
|
|
@@ -5508,15 +5875,15 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5508
5875
|
let lastTable = -1;
|
|
5509
5876
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
5510
5877
|
if (
|
|
5511
|
-
open[i]
|
|
5512
|
-
open[i]
|
|
5878
|
+
_tag(open[i]) === "template" &&
|
|
5879
|
+
_ns(open[i]) === NS_HTML &&
|
|
5513
5880
|
lastTemplate === -1
|
|
5514
5881
|
) {
|
|
5515
5882
|
lastTemplate = i;
|
|
5516
5883
|
}
|
|
5517
5884
|
if (
|
|
5518
|
-
open[i]
|
|
5519
|
-
open[i]
|
|
5885
|
+
_tag(open[i]) === "table" &&
|
|
5886
|
+
_ns(open[i]) === NS_HTML &&
|
|
5520
5887
|
lastTable === -1
|
|
5521
5888
|
) {
|
|
5522
5889
|
lastTable = i;
|
|
@@ -5526,23 +5893,32 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5526
5893
|
lastTemplate !== -1 &&
|
|
5527
5894
|
(lastTable === -1 || lastTemplate > lastTable)
|
|
5528
5895
|
) {
|
|
5529
|
-
return { parent: open[lastTemplate], beforeNode:
|
|
5896
|
+
return { parent: open[lastTemplate], beforeNode: 0 };
|
|
5530
5897
|
}
|
|
5531
|
-
if (lastTable === -1) return { parent: open[0], beforeNode:
|
|
5898
|
+
if (lastTable === -1) return { parent: open[0], beforeNode: 0 };
|
|
5532
5899
|
const table = open[lastTable];
|
|
5533
|
-
const tp =
|
|
5534
|
-
if (tp) return { parent: tp, beforeNode: table };
|
|
5535
|
-
return { parent: open[lastTable - 1], beforeNode:
|
|
5900
|
+
const tp = _hParent[table];
|
|
5901
|
+
if (tp !== 0) return { parent: tp, beforeNode: table };
|
|
5902
|
+
return { parent: open[lastTable - 1], beforeNode: 0 };
|
|
5536
5903
|
}
|
|
5537
|
-
return { parent: commonAncestor, beforeNode:
|
|
5904
|
+
return { parent: commonAncestor, beforeNode: 0 };
|
|
5538
5905
|
};
|
|
5539
5906
|
|
|
5540
|
-
const detach = (/** @type {
|
|
5541
|
-
const p =
|
|
5542
|
-
if (
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5907
|
+
const detach = (/** @type {HtmlNodeRef} */ node) => {
|
|
5908
|
+
const p = _hParent[node];
|
|
5909
|
+
if (p === 0) return;
|
|
5910
|
+
let prev = 0;
|
|
5911
|
+
let c = _hFirst[p];
|
|
5912
|
+
while (c !== 0 && c !== node) {
|
|
5913
|
+
prev = c;
|
|
5914
|
+
c = _hNext[c];
|
|
5915
|
+
}
|
|
5916
|
+
if (c === 0) return;
|
|
5917
|
+
if (prev === 0) _hFirst[p] = _hNext[node];
|
|
5918
|
+
else _hNext[prev] = _hNext[node];
|
|
5919
|
+
if (_hLast[p] === node) _hLast[p] = prev;
|
|
5920
|
+
_hNext[node] = 0;
|
|
5921
|
+
_hParent[node] = 0;
|
|
5546
5922
|
};
|
|
5547
5923
|
|
|
5548
5924
|
// ---------- insertion modes ----------
|
|
@@ -5700,18 +6076,20 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5700
6076
|
return;
|
|
5701
6077
|
}
|
|
5702
6078
|
if (t.type === TOKEN_COMMENT) {
|
|
5703
|
-
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode:
|
|
6079
|
+
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode: 0 });
|
|
5704
6080
|
return;
|
|
5705
6081
|
}
|
|
5706
6082
|
if (t.type === TOKEN_DOCTYPE) {
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
6083
|
+
// `skip.doctype` drops the node only; quirks detection below is unaffected.
|
|
6084
|
+
if (!skipDoctype) {
|
|
6085
|
+
const dt = _hAlloc(NodeType.Doctype, t.start, t.end);
|
|
6086
|
+
_hStr[dt] = t.name;
|
|
6087
|
+
// At most one doctype node is ever inserted (later doctype tokens
|
|
6088
|
+
// are ignored), so its ids live in two per-parse scalars.
|
|
6089
|
+
_hDocPub = t.publicId;
|
|
6090
|
+
_hDocSys = t.systemId;
|
|
6091
|
+
_hAppend(doc, dt);
|
|
6092
|
+
}
|
|
5715
6093
|
quirks = isQuirky(t.name, t.publicId, t.systemId);
|
|
5716
6094
|
mode = MODE_BEFORE_HTML;
|
|
5717
6095
|
return;
|
|
@@ -5724,7 +6102,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5724
6102
|
modes.beforeHtml = (t) => {
|
|
5725
6103
|
if (t.type === TOKEN_DOCTYPE) return;
|
|
5726
6104
|
if (t.type === TOKEN_COMMENT) {
|
|
5727
|
-
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode:
|
|
6105
|
+
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode: 0 });
|
|
5728
6106
|
return;
|
|
5729
6107
|
}
|
|
5730
6108
|
if (t.type === TOKEN_CHAR) {
|
|
@@ -5734,7 +6112,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5734
6112
|
}
|
|
5735
6113
|
if (t.type === TOKEN_START_TAG && t.name === "html") {
|
|
5736
6114
|
const el = mkEl("html", NS_HTML, t.attrs, t.pos);
|
|
5737
|
-
doc
|
|
6115
|
+
_hAppend(doc, el);
|
|
5738
6116
|
open.push(el);
|
|
5739
6117
|
mode = MODE_BEFORE_HEAD;
|
|
5740
6118
|
return;
|
|
@@ -5742,8 +6120,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5742
6120
|
if (t.type === TOKEN_END_TAG && !HEAD_BODY_HTML_BR.has(t.name)) {
|
|
5743
6121
|
return;
|
|
5744
6122
|
}
|
|
5745
|
-
const el = mkEl("html", NS_HTML,
|
|
5746
|
-
doc
|
|
6123
|
+
const el = mkEl("html", NS_HTML, EMPTY_ATTRS, null);
|
|
6124
|
+
_hAppend(doc, el);
|
|
5747
6125
|
open.push(el);
|
|
5748
6126
|
mode = MODE_BEFORE_HEAD;
|
|
5749
6127
|
process(t);
|
|
@@ -5819,7 +6197,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5819
6197
|
mode = MODE_IN_TEMPLATE;
|
|
5820
6198
|
templateModes.push(MODE_IN_TEMPLATE);
|
|
5821
6199
|
const el = cur();
|
|
5822
|
-
|
|
6200
|
+
const fragment = _hAlloc(NodeType.DocumentFragment, 0, 0);
|
|
6201
|
+
// Parent link so the iterative walk can ascend out of the content.
|
|
6202
|
+
_hParent[fragment] = el;
|
|
6203
|
+
_hTc[el] = fragment;
|
|
5823
6204
|
return;
|
|
5824
6205
|
}
|
|
5825
6206
|
if (t.name === "head") return;
|
|
@@ -5887,7 +6268,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5887
6268
|
if (t.type === TOKEN_CHAR) {
|
|
5888
6269
|
const r = leadingWs(t, true);
|
|
5889
6270
|
if (!r) return;
|
|
5890
|
-
insertHtmlElement("body",
|
|
6271
|
+
insertHtmlElement("body", EMPTY_ATTRS, null);
|
|
5891
6272
|
mode = MODE_IN_BODY;
|
|
5892
6273
|
process(r);
|
|
5893
6274
|
return;
|
|
@@ -5924,7 +6305,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5924
6305
|
if (t.name === "template") return modes.inHead(t);
|
|
5925
6306
|
if (!BODY_HTML_BR.has(t.name)) return;
|
|
5926
6307
|
}
|
|
5927
|
-
insertHtmlElement("body",
|
|
6308
|
+
insertHtmlElement("body", EMPTY_ATTRS, null);
|
|
5928
6309
|
mode = MODE_IN_BODY;
|
|
5929
6310
|
process(t);
|
|
5930
6311
|
};
|
|
@@ -5962,10 +6343,10 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5962
6343
|
const anyOtherEndTag = (/** @type {string} */ name) => {
|
|
5963
6344
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
5964
6345
|
const node = open[i];
|
|
5965
|
-
if (node
|
|
6346
|
+
if (_ns(node) === NS_HTML && _tag(node) === name) {
|
|
5966
6347
|
generateImpliedEndTags(name);
|
|
5967
6348
|
while (open.length > i) {
|
|
5968
|
-
open[open.length - 1]
|
|
6349
|
+
_hEn[open[open.length - 1]] = tokenEnd;
|
|
5969
6350
|
open.pop();
|
|
5970
6351
|
}
|
|
5971
6352
|
return;
|
|
@@ -5980,12 +6361,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5980
6361
|
if (open.some(isHtmlTemplateEl)) {
|
|
5981
6362
|
return;
|
|
5982
6363
|
}
|
|
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
|
-
}
|
|
6364
|
+
mergeAttrs(open[0], t.attrs);
|
|
5989
6365
|
return;
|
|
5990
6366
|
}
|
|
5991
6367
|
if (HEAD_ELEMENTS.has(name)) {
|
|
@@ -5993,20 +6369,16 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
5993
6369
|
}
|
|
5994
6370
|
if (name === "body") {
|
|
5995
6371
|
const second = open[1];
|
|
5996
|
-
if (!second || second
|
|
6372
|
+
if (!second || _tag(second) !== "body" || open.some(isHtmlTemplateEl)) {
|
|
5997
6373
|
return;
|
|
5998
6374
|
}
|
|
5999
6375
|
framesetOk = false;
|
|
6000
|
-
|
|
6001
|
-
if (!second.attributes.some((x) => x.name === a.name)) {
|
|
6002
|
-
second.attributes.push(a);
|
|
6003
|
-
}
|
|
6004
|
-
}
|
|
6376
|
+
mergeAttrs(second, t.attrs);
|
|
6005
6377
|
return;
|
|
6006
6378
|
}
|
|
6007
6379
|
if (name === "frameset") {
|
|
6008
6380
|
const second = open[1];
|
|
6009
|
-
if (!second || second
|
|
6381
|
+
if (!second || _tag(second) !== "body") return;
|
|
6010
6382
|
if (!framesetOk) return;
|
|
6011
6383
|
detach(second);
|
|
6012
6384
|
while (open.length > 1) open.pop();
|
|
@@ -6021,7 +6393,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6021
6393
|
}
|
|
6022
6394
|
if (HEADING.has(name)) {
|
|
6023
6395
|
closeIfPInButtonScope();
|
|
6024
|
-
if (cur()
|
|
6396
|
+
if (_ns(cur()) === NS_HTML && HEADING.has(_tag(cur()))) open.pop();
|
|
6025
6397
|
insertHtmlElement(name, t.attrs, t.pos);
|
|
6026
6398
|
return;
|
|
6027
6399
|
}
|
|
@@ -6047,14 +6419,14 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6047
6419
|
framesetOk = false;
|
|
6048
6420
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
6049
6421
|
const node = open[i];
|
|
6050
|
-
if (node
|
|
6422
|
+
if (_ns(node) === NS_HTML && _tag(node) === "li") {
|
|
6051
6423
|
generateImpliedEndTags("li");
|
|
6052
6424
|
popUntil("li");
|
|
6053
6425
|
break;
|
|
6054
6426
|
}
|
|
6055
6427
|
if (
|
|
6056
6428
|
isSpecial(node) &&
|
|
6057
|
-
!(node
|
|
6429
|
+
!(_ns(node) === NS_HTML && ADDRESS_DIV_P.has(_tag(node)))
|
|
6058
6430
|
) {
|
|
6059
6431
|
break;
|
|
6060
6432
|
}
|
|
@@ -6068,16 +6440,16 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6068
6440
|
for (let i = open.length - 1; i >= 0; i--) {
|
|
6069
6441
|
const node = open[i];
|
|
6070
6442
|
if (
|
|
6071
|
-
node
|
|
6072
|
-
(node
|
|
6443
|
+
_ns(node) === NS_HTML &&
|
|
6444
|
+
(_tag(node) === "dd" || _tag(node) === "dt")
|
|
6073
6445
|
) {
|
|
6074
|
-
generateImpliedEndTags(node
|
|
6075
|
-
popUntil(node
|
|
6446
|
+
generateImpliedEndTags(_tag(node));
|
|
6447
|
+
popUntil(_tag(node));
|
|
6076
6448
|
break;
|
|
6077
6449
|
}
|
|
6078
6450
|
if (
|
|
6079
6451
|
isSpecial(node) &&
|
|
6080
|
-
!(node
|
|
6452
|
+
!(_ns(node) === NS_HTML && ADDRESS_DIV_P.has(_tag(node)))
|
|
6081
6453
|
) {
|
|
6082
6454
|
break;
|
|
6083
6455
|
}
|
|
@@ -6104,14 +6476,12 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6104
6476
|
if (name === "a") {
|
|
6105
6477
|
// if there's an <a> in afe after last marker
|
|
6106
6478
|
for (let i = afe.length - 1; i >= 0; i--) {
|
|
6107
|
-
if (afe[i]
|
|
6108
|
-
if (
|
|
6479
|
+
if (afe[i] === AFE_MARKER) break;
|
|
6480
|
+
if (_tag(afe[i]) === "a") {
|
|
6109
6481
|
adoptionAgency("a", t.pos);
|
|
6110
|
-
const idx = afe.findIndex(
|
|
6111
|
-
(e) => !e.marker && afeEl(e).tagName === "a"
|
|
6112
|
-
);
|
|
6482
|
+
const idx = afe.findIndex((e) => e !== AFE_MARKER && _tag(e) === "a");
|
|
6113
6483
|
if (idx !== -1) {
|
|
6114
|
-
const el =
|
|
6484
|
+
const el = afe[idx];
|
|
6115
6485
|
afe.splice(idx, 1);
|
|
6116
6486
|
const oi = open.indexOf(el);
|
|
6117
6487
|
if (oi !== -1) open.splice(oi, 1);
|
|
@@ -6171,16 +6541,18 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6171
6541
|
resetInsertionMode();
|
|
6172
6542
|
} else if (
|
|
6173
6543
|
fragment &&
|
|
6174
|
-
fragment
|
|
6175
|
-
fragment
|
|
6544
|
+
_ns(fragment) === NS_HTML &&
|
|
6545
|
+
_tag(fragment) === "select"
|
|
6176
6546
|
) {
|
|
6177
6547
|
return;
|
|
6178
6548
|
}
|
|
6179
6549
|
reconstructAfe();
|
|
6180
6550
|
insertHtmlElement("input", t.attrs, t.pos);
|
|
6181
6551
|
open.pop();
|
|
6182
|
-
const ty =
|
|
6183
|
-
if (
|
|
6552
|
+
const ty = _aFind(t.attrs.start, t.attrs.count, "type");
|
|
6553
|
+
if (ty === 0 || _aValueOf(ty).toLowerCase() !== "hidden") {
|
|
6554
|
+
framesetOk = false;
|
|
6555
|
+
}
|
|
6184
6556
|
return;
|
|
6185
6557
|
}
|
|
6186
6558
|
if (PARAM_SOURCE_TRACK.has(name)) {
|
|
@@ -6189,8 +6561,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6189
6561
|
return;
|
|
6190
6562
|
}
|
|
6191
6563
|
if (name === "hr") {
|
|
6192
|
-
if (cur()
|
|
6193
|
-
if (cur()
|
|
6564
|
+
if (_ns(cur()) === NS_HTML && _tag(cur()) === "option") open.pop();
|
|
6565
|
+
if (_ns(cur()) === NS_HTML && _tag(cur()) === "optgroup") {
|
|
6194
6566
|
open.pop();
|
|
6195
6567
|
}
|
|
6196
6568
|
closeIfPInButtonScope();
|
|
@@ -6239,11 +6611,11 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6239
6611
|
return;
|
|
6240
6612
|
}
|
|
6241
6613
|
if (name === "optgroup" || name === "option") {
|
|
6242
|
-
if (cur()
|
|
6614
|
+
if (_ns(cur()) === NS_HTML && _tag(cur()) === "option") open.pop();
|
|
6243
6615
|
if (
|
|
6244
6616
|
name === "optgroup" &&
|
|
6245
|
-
cur()
|
|
6246
|
-
cur()
|
|
6617
|
+
_ns(cur()) === NS_HTML &&
|
|
6618
|
+
_tag(cur()) === "optgroup"
|
|
6247
6619
|
) {
|
|
6248
6620
|
open.pop();
|
|
6249
6621
|
}
|
|
@@ -6283,20 +6655,13 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6283
6655
|
insertHtmlElement(name, t.attrs, t.pos);
|
|
6284
6656
|
};
|
|
6285
6657
|
|
|
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
|
-
}
|
|
6658
|
+
// Only `definitionurl` is rewritten (in place — the run is consumed only by
|
|
6659
|
+
// this tag's element); the camelCase name serializes as itself.
|
|
6660
|
+
const adjustMathmlAttrs = (/** @type {AttributeRun} */ run) => {
|
|
6661
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
6662
|
+
if (_aName[i] === "definitionurl") _aName[i] = "definitionURL";
|
|
6298
6663
|
}
|
|
6299
|
-
return
|
|
6664
|
+
return run;
|
|
6300
6665
|
};
|
|
6301
6666
|
|
|
6302
6667
|
const endTagInBody = (/** @type {EndTagToken} */ t) => {
|
|
@@ -6323,7 +6688,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6323
6688
|
if (name === "form") {
|
|
6324
6689
|
if (!open.some(isHtmlTemplateEl)) {
|
|
6325
6690
|
const node = form;
|
|
6326
|
-
form =
|
|
6691
|
+
form = 0;
|
|
6327
6692
|
if (!node || !inScopeEl(node)) return;
|
|
6328
6693
|
generateImpliedEndTags();
|
|
6329
6694
|
const idx = open.indexOf(node);
|
|
@@ -6420,7 +6785,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6420
6785
|
// current element: a non-matching name (e.g. a fragment context), or
|
|
6421
6786
|
// a name that ran straight to EOF with no delimiter (`</script` at
|
|
6422
6787
|
// EOF — the tokenizer still emits a partial tag there).
|
|
6423
|
-
if (cur() && (t.name !== cur()
|
|
6788
|
+
if (cur() && (t.name !== _tag(cur()) || t.pos.end === t.pos.nameEnd)) {
|
|
6424
6789
|
insertCharacters(
|
|
6425
6790
|
source.slice(t.pos.start, t.pos.end),
|
|
6426
6791
|
t.pos.start,
|
|
@@ -6440,7 +6805,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6440
6805
|
modes.inTable = (t) => {
|
|
6441
6806
|
if (t.type === TOKEN_CHAR) {
|
|
6442
6807
|
const c = cur();
|
|
6443
|
-
if (TABLE_CONTEXT.has(c
|
|
6808
|
+
if (TABLE_CONTEXT.has(_tag(c)) && _ns(c) === NS_HTML) {
|
|
6444
6809
|
pendingTableChars = { list: [], hasNonWs: false };
|
|
6445
6810
|
originalMode = mode;
|
|
6446
6811
|
mode = MODE_IN_TABLE_TEXT;
|
|
@@ -6495,8 +6860,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6495
6860
|
return modes.inHead(t);
|
|
6496
6861
|
}
|
|
6497
6862
|
if (name === "input") {
|
|
6498
|
-
const ty =
|
|
6499
|
-
if (ty && ty.
|
|
6863
|
+
const ty = _aFind(t.attrs.start, t.attrs.count, "type");
|
|
6864
|
+
if (ty !== 0 && _aValueOf(ty).toLowerCase() === "hidden") {
|
|
6500
6865
|
insertHtmlElement("input", t.attrs, t.pos);
|
|
6501
6866
|
open.pop();
|
|
6502
6867
|
return;
|
|
@@ -6566,7 +6931,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6566
6931
|
const clearStackToTableContext = () => {
|
|
6567
6932
|
while (open.length) {
|
|
6568
6933
|
const c = cur();
|
|
6569
|
-
if (c
|
|
6934
|
+
if (_ns(c) === NS_HTML && CLEAR_TABLE.has(_tag(c))) {
|
|
6570
6935
|
break;
|
|
6571
6936
|
}
|
|
6572
6937
|
open.pop();
|
|
@@ -6575,7 +6940,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6575
6940
|
const clearStackToTableBodyContext = () => {
|
|
6576
6941
|
while (open.length) {
|
|
6577
6942
|
const c = cur();
|
|
6578
|
-
if (c
|
|
6943
|
+
if (_ns(c) === NS_HTML && CLEAR_TABLE_BODY.has(_tag(c))) {
|
|
6579
6944
|
break;
|
|
6580
6945
|
}
|
|
6581
6946
|
open.pop();
|
|
@@ -6584,7 +6949,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6584
6949
|
const clearStackToTableRowContext = () => {
|
|
6585
6950
|
while (open.length) {
|
|
6586
6951
|
const c = cur();
|
|
6587
|
-
if (c
|
|
6952
|
+
if (_ns(c) === NS_HTML && CLEAR_TABLE_ROW.has(_tag(c))) {
|
|
6588
6953
|
break;
|
|
6589
6954
|
}
|
|
6590
6955
|
open.pop();
|
|
@@ -6617,7 +6982,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6617
6982
|
if (t.type === TOKEN_CHAR) {
|
|
6618
6983
|
const r = leadingWs(t, true);
|
|
6619
6984
|
if (!r) return;
|
|
6620
|
-
if (cur()
|
|
6985
|
+
if (_tag(cur()) !== "colgroup") return;
|
|
6621
6986
|
open.pop();
|
|
6622
6987
|
mode = MODE_IN_TABLE;
|
|
6623
6988
|
process(r);
|
|
@@ -6635,7 +7000,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6635
7000
|
return;
|
|
6636
7001
|
}
|
|
6637
7002
|
if (t.type === TOKEN_END_TAG && t.name === "colgroup") {
|
|
6638
|
-
if (cur()
|
|
7003
|
+
if (_tag(cur()) !== "colgroup") return;
|
|
6639
7004
|
open.pop();
|
|
6640
7005
|
mode = MODE_IN_TABLE;
|
|
6641
7006
|
return;
|
|
@@ -6648,7 +7013,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6648
7013
|
return modes.inHead(t);
|
|
6649
7014
|
}
|
|
6650
7015
|
if (t.type === TOKEN_EOF) return modes.inBody(t);
|
|
6651
|
-
if (cur()
|
|
7016
|
+
if (_tag(cur()) !== "colgroup") return;
|
|
6652
7017
|
open.pop();
|
|
6653
7018
|
mode = MODE_IN_TABLE;
|
|
6654
7019
|
process(t);
|
|
@@ -6798,7 +7163,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6798
7163
|
if (t.type === TOKEN_COMMENT) {
|
|
6799
7164
|
insertComment(t.data, t.start, t.end, {
|
|
6800
7165
|
parent: open[0],
|
|
6801
|
-
beforeNode:
|
|
7166
|
+
beforeNode: 0
|
|
6802
7167
|
});
|
|
6803
7168
|
return;
|
|
6804
7169
|
}
|
|
@@ -6831,9 +7196,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6831
7196
|
return;
|
|
6832
7197
|
}
|
|
6833
7198
|
if (t.type === TOKEN_END_TAG && t.name === "frameset") {
|
|
6834
|
-
if (cur()
|
|
7199
|
+
if (_tag(cur()) === "html") return;
|
|
6835
7200
|
open.pop();
|
|
6836
|
-
if (!fragment && cur()
|
|
7201
|
+
if (!fragment && _tag(cur()) !== "frameset") mode = MODE_AFTER_FRAMESET;
|
|
6837
7202
|
return;
|
|
6838
7203
|
}
|
|
6839
7204
|
if (t.type === TOKEN_START_TAG && t.name === "frame") {
|
|
@@ -6869,7 +7234,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6869
7234
|
|
|
6870
7235
|
modes.afterAfterBody = (t) => {
|
|
6871
7236
|
if (t.type === TOKEN_COMMENT) {
|
|
6872
|
-
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode:
|
|
7237
|
+
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode: 0 });
|
|
6873
7238
|
return;
|
|
6874
7239
|
}
|
|
6875
7240
|
if (t.type === TOKEN_DOCTYPE) return modes.inBody(t);
|
|
@@ -6882,7 +7247,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6882
7247
|
|
|
6883
7248
|
modes.afterAfterFrameset = (t) => {
|
|
6884
7249
|
if (t.type === TOKEN_COMMENT) {
|
|
6885
|
-
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode:
|
|
7250
|
+
insertComment(t.data, t.start, t.end, { parent: doc, beforeNode: 0 });
|
|
6886
7251
|
return;
|
|
6887
7252
|
}
|
|
6888
7253
|
if (t.type === TOKEN_DOCTYPE) return modes.inBody(t);
|
|
@@ -6904,9 +7269,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6904
7269
|
ctxNs = NS_MATHML;
|
|
6905
7270
|
ctxName = ctxName.slice(5);
|
|
6906
7271
|
}
|
|
6907
|
-
fragment = mkEl(ctxName, ctxNs,
|
|
6908
|
-
const htmlEl = mkEl("html", NS_HTML,
|
|
6909
|
-
doc
|
|
7272
|
+
fragment = mkEl(ctxName, ctxNs, EMPTY_ATTRS, null);
|
|
7273
|
+
const htmlEl = mkEl("html", NS_HTML, EMPTY_ATTRS, null);
|
|
7274
|
+
_hAppend(doc, htmlEl);
|
|
6910
7275
|
open.push(htmlEl);
|
|
6911
7276
|
if (ctxNs !== NS_HTML) {
|
|
6912
7277
|
mode = MODE_IN_BODY;
|
|
@@ -6949,7 +7314,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6949
7314
|
type: TOKEN_EOF,
|
|
6950
7315
|
name: "",
|
|
6951
7316
|
data: "",
|
|
6952
|
-
attrs:
|
|
7317
|
+
attrs: { start: 0, count: 0 },
|
|
6953
7318
|
selfClosing: false,
|
|
6954
7319
|
start: 0,
|
|
6955
7320
|
end: 0,
|
|
@@ -6964,9 +7329,9 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6964
7329
|
walkHtmlTokens(source, 0, {
|
|
6965
7330
|
isForeign: () => {
|
|
6966
7331
|
const ac = adjustedCurrent();
|
|
6967
|
-
return open.length > 0 && ac && ac
|
|
7332
|
+
return open.length > 0 && ac !== 0 && _ns(ac) !== NS_HTML;
|
|
6968
7333
|
},
|
|
6969
|
-
fragmentContext: fragment ? fragment
|
|
7334
|
+
fragmentContext: fragment ? _tag(fragment) : undefined,
|
|
6970
7335
|
parseError: (input, code) => {
|
|
6971
7336
|
if (code === "eof-in-tag") eofInTag = true;
|
|
6972
7337
|
},
|
|
@@ -6988,7 +7353,7 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
6988
7353
|
// The tokenizer emits CDATA sections through this callback too.
|
|
6989
7354
|
if (input.startsWith("<![CDATA[", start)) {
|
|
6990
7355
|
const ac = adjustedCurrent();
|
|
6991
|
-
if (open.length > 0 && ac && ac
|
|
7356
|
+
if (open.length > 0 && ac && _ns(ac) !== NS_HTML) {
|
|
6992
7357
|
const innerEnd = input.endsWith("]]>", end) ? end - 3 : end;
|
|
6993
7358
|
const data = input.slice(start + 9, innerEnd).replace(/\r\n?/g, "\n");
|
|
6994
7359
|
if (data !== "") {
|
|
@@ -7030,17 +7395,40 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7030
7395
|
return end;
|
|
7031
7396
|
},
|
|
7032
7397
|
text: (input, start, end) => {
|
|
7398
|
+
// `skip.text` fast path: node dropped, so only whitespace-ness matters.
|
|
7399
|
+
// With no `&`/`\0`/`\r` (and no pending newline-swallow) the decoded value
|
|
7400
|
+
// equals the raw range — dispatch a canonical marker (`" "`/`"x"`) without
|
|
7401
|
+
// the slice + entity decode; anything trickier falls through.
|
|
7402
|
+
if (skipText && !swallowNextNewline) {
|
|
7403
|
+
let hasNonWs = false;
|
|
7404
|
+
let i = start;
|
|
7405
|
+
for (; i < end; i++) {
|
|
7406
|
+
const c = input.charCodeAt(i);
|
|
7407
|
+
// 2 = break (& / NUL / CR), 1 = whitespace, 0 = other.
|
|
7408
|
+
const cls = c < 128 ? _TEXT_SCAN_CLASS[c] : 0;
|
|
7409
|
+
if (cls === 2) break;
|
|
7410
|
+
if (cls === 0) hasNonWs = true;
|
|
7411
|
+
}
|
|
7412
|
+
if (i === end) {
|
|
7413
|
+
tok.type = TOKEN_CHAR;
|
|
7414
|
+
tok.data = hasNonWs ? "x" : " ";
|
|
7415
|
+
tok.start = start;
|
|
7416
|
+
tok.end = end;
|
|
7417
|
+
dispatch();
|
|
7418
|
+
return end;
|
|
7419
|
+
}
|
|
7420
|
+
}
|
|
7033
7421
|
const raw = input.slice(start, end);
|
|
7034
7422
|
// CR normalization only when a CR is actually present (common case has none).
|
|
7035
7423
|
const s = !raw.includes("\r") ? raw : raw.replace(/\r\n?/g, "\n");
|
|
7036
7424
|
const top = adjustedCurrent();
|
|
7037
7425
|
const rawMode =
|
|
7038
7426
|
mode === MODE_TEXT ||
|
|
7039
|
-
(top && top
|
|
7427
|
+
(top && _ns(top) === NS_HTML && _tag(top) === "plaintext");
|
|
7040
7428
|
const noDecode =
|
|
7041
7429
|
top &&
|
|
7042
|
-
top
|
|
7043
|
-
NO_DECODE_TEXT.has(top
|
|
7430
|
+
_ns(top) === NS_HTML &&
|
|
7431
|
+
NO_DECODE_TEXT.has(_tag(top)) &&
|
|
7044
7432
|
(mode === MODE_TEXT || mode === MODE_IN_BODY);
|
|
7045
7433
|
let data = noDecode ? s : decode(s, false);
|
|
7046
7434
|
// In RAWTEXT/RCDATA/script/PLAINTEXT, NULL becomes U+FFFD (tokenizer
|
|
@@ -7061,32 +7449,29 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7061
7449
|
},
|
|
7062
7450
|
attribute: (input, nameStart, nameEnd, valueStart, valueEnd, quoteType) => {
|
|
7063
7451
|
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) : "";
|
|
7452
|
+
if (pendAttrStart === 0) pendAttrStart = _aN + 1;
|
|
7068
7453
|
// Drop duplicate attribute names (per spec). Plain loop avoids a
|
|
7069
7454
|
// per-attribute closure allocation on this hot path.
|
|
7070
7455
|
let dup = false;
|
|
7071
|
-
for (let i =
|
|
7072
|
-
if (
|
|
7456
|
+
for (let i = pendAttrStart; i <= _aN; i++) {
|
|
7457
|
+
if (_aName[i] === name) {
|
|
7073
7458
|
dup = true;
|
|
7074
7459
|
break;
|
|
7075
7460
|
}
|
|
7076
7461
|
}
|
|
7077
7462
|
if (!dup) {
|
|
7078
|
-
//
|
|
7079
|
-
//
|
|
7080
|
-
//
|
|
7081
|
-
|
|
7463
|
+
// The raw (undecoded) value is read from the source by offset on
|
|
7464
|
+
// demand: consumers re-resolve requests from it and the offsets must
|
|
7465
|
+
// stay aligned with the source. Only a valueless attribute stores an
|
|
7466
|
+
// override ("").
|
|
7467
|
+
_aAlloc(
|
|
7082
7468
|
name,
|
|
7083
|
-
|
|
7084
|
-
serializedName: undefined,
|
|
7469
|
+
valueStart !== -1 ? null : "",
|
|
7085
7470
|
nameStart,
|
|
7086
7471
|
nameEnd,
|
|
7087
7472
|
valueStart,
|
|
7088
7473
|
valueEnd
|
|
7089
|
-
|
|
7474
|
+
);
|
|
7090
7475
|
}
|
|
7091
7476
|
if (valueStart === -1) return nameEnd;
|
|
7092
7477
|
return quoteType !== QUOTE_NONE ? valueEnd + 1 : valueEnd;
|
|
@@ -7095,25 +7480,19 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7095
7480
|
// A start tag the tokenizer only emitted because it hit EOF mid-tag
|
|
7096
7481
|
// is dropped, matching the spec's eof-in-tag handling.
|
|
7097
7482
|
if (eofInTag) {
|
|
7098
|
-
|
|
7483
|
+
// Any attribute slots already allocated for it are orphaned.
|
|
7484
|
+
pendAttrStart = 0;
|
|
7099
7485
|
return end;
|
|
7100
7486
|
}
|
|
7101
7487
|
const name = internLowerName(TAG_NAME_INTERN, input, nameStart, nameEnd);
|
|
7102
7488
|
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
7489
|
tok.type = TOKEN_START_TAG;
|
|
7115
7490
|
tok.name = name;
|
|
7116
|
-
|
|
7491
|
+
// The reused token carries the tag's attribute run; every consumer of
|
|
7492
|
+
// `t.attrs` runs synchronously within this dispatch.
|
|
7493
|
+
tok.attrs.start = pendAttrStart;
|
|
7494
|
+
tok.attrs.count = pendAttrStart === 0 ? 0 : _aN + 1 - pendAttrStart;
|
|
7495
|
+
pendAttrStart = 0;
|
|
7117
7496
|
tok.selfClosing = selfClosing;
|
|
7118
7497
|
tok.swallowNewline = false;
|
|
7119
7498
|
tok.pos.start = start;
|
|
@@ -7126,8 +7505,8 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7126
7505
|
},
|
|
7127
7506
|
closeTag: (input, start, end, nameStart, nameEnd) => {
|
|
7128
7507
|
const name = internLowerName(TAG_NAME_INTERN, input, nameStart, nameEnd);
|
|
7129
|
-
// End tags drop any parsed attributes
|
|
7130
|
-
|
|
7508
|
+
// End tags drop any parsed attributes (the slots are orphaned).
|
|
7509
|
+
pendAttrStart = 0;
|
|
7131
7510
|
tok.type = TOKEN_END_TAG;
|
|
7132
7511
|
tok.name = name;
|
|
7133
7512
|
tok.pos.start = start;
|
|
@@ -7141,63 +7520,70 @@ const buildHtmlAst = (source, fragmentContext) => {
|
|
|
7141
7520
|
tok.type = TOKEN_EOF;
|
|
7142
7521
|
dispatch();
|
|
7143
7522
|
|
|
7144
|
-
if (sawSelectedContent) mirrorSelectedContent(doc,
|
|
7523
|
+
if (sawSelectedContent) mirrorSelectedContent(doc, 0);
|
|
7145
7524
|
|
|
7146
7525
|
return doc;
|
|
7147
7526
|
};
|
|
7148
7527
|
|
|
7149
7528
|
/**
|
|
7150
|
-
* Deep-clone a node, dropping source offsets
|
|
7151
|
-
* re-emit dependencies.
|
|
7152
|
-
* @param {
|
|
7153
|
-
* @returns {
|
|
7529
|
+
* Deep-clone a node into fresh ids, dropping attribute source offsets and the
|
|
7530
|
+
* raw-text body span so cloned content does not re-emit dependencies.
|
|
7531
|
+
* @param {HtmlNodeRef} node node
|
|
7532
|
+
* @returns {HtmlNodeRef} clone
|
|
7154
7533
|
*/
|
|
7155
7534
|
const cloneSubtree = (node) => {
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7535
|
+
const ty = _hTy[node];
|
|
7536
|
+
const clone = _hAlloc(ty, _hSt[node], _hEn[node]);
|
|
7537
|
+
_hStr[clone] = _hStr[node];
|
|
7538
|
+
if (ty !== NodeType.Element) return clone;
|
|
7539
|
+
_hFl[clone] = _hFl[node];
|
|
7540
|
+
const attrs = cloneAttrs(node);
|
|
7541
|
+
_hAStart[clone] = attrs.start;
|
|
7542
|
+
_hACount[clone] = attrs.count;
|
|
7543
|
+
_hTagEnd[clone] = _hTagEnd[node];
|
|
7544
|
+
_hNameEnd[clone] = _hNameEnd[node];
|
|
7545
|
+
// Empty body span: the clone re-emits no raw-text dependency.
|
|
7546
|
+
_hCEnd[clone] = _hTagEnd[node];
|
|
7547
|
+
for (let k = _hFirst[node]; k !== 0; k = _hNext[k]) {
|
|
7548
|
+
_hAppend(clone, cloneSubtree(k));
|
|
7549
|
+
}
|
|
7550
|
+
// Clone `<template>` content into the clone's own fragment.
|
|
7551
|
+
const tc = _hTc[node];
|
|
7552
|
+
if (tc !== 0) {
|
|
7553
|
+
const fragment = _hAlloc(NodeType.DocumentFragment, 0, 0);
|
|
7554
|
+
// Parent link so the iterative walk can ascend out of the content.
|
|
7555
|
+
_hParent[fragment] = clone;
|
|
7556
|
+
_hTc[clone] = fragment;
|
|
7557
|
+
for (let k = _hFirst[tc]; k !== 0; k = _hNext[k]) {
|
|
7558
|
+
_hAppend(fragment, cloneSubtree(k));
|
|
7559
|
+
}
|
|
7560
|
+
}
|
|
7561
|
+
return clone;
|
|
7177
7562
|
};
|
|
7178
7563
|
|
|
7179
7564
|
/**
|
|
7180
7565
|
* The selected option of a select: the last `<option selected>`, else the
|
|
7181
7566
|
* first option (scanning direct children and `<optgroup>` children).
|
|
7182
7567
|
* @param {HtmlElement} select select element
|
|
7183
|
-
* @returns {HtmlElement
|
|
7568
|
+
* @returns {HtmlElement} selected option (0 = none)
|
|
7184
7569
|
*/
|
|
7185
7570
|
const selectedOption = (select) => {
|
|
7186
7571
|
/** @type {HtmlElement[]} */
|
|
7187
7572
|
const options = [];
|
|
7188
7573
|
/** @param {HtmlElement} el element */
|
|
7189
7574
|
const collect = (el) => {
|
|
7190
|
-
for (
|
|
7191
|
-
if (c
|
|
7192
|
-
if (c
|
|
7193
|
-
else if (c
|
|
7575
|
+
for (let c = _hFirst[el]; c !== 0; c = _hNext[c]) {
|
|
7576
|
+
if (_hTy[c] !== NodeType.Element || _ns(c) !== NS_HTML) continue;
|
|
7577
|
+
if (_hStr[c] === "option") options.push(c);
|
|
7578
|
+
else if (_hStr[c] === "optgroup") collect(c);
|
|
7194
7579
|
}
|
|
7195
7580
|
};
|
|
7196
7581
|
collect(select);
|
|
7197
|
-
if (options.length === 0) return
|
|
7582
|
+
if (options.length === 0) return 0;
|
|
7198
7583
|
for (let i = options.length - 1; i >= 0; i--) {
|
|
7199
|
-
|
|
7200
|
-
|
|
7584
|
+
const el = options[i];
|
|
7585
|
+
if (_aFind(_hAStart[el], _hACount[el], "selected") !== 0) {
|
|
7586
|
+
return el;
|
|
7201
7587
|
}
|
|
7202
7588
|
}
|
|
7203
7589
|
return options[0];
|
|
@@ -7206,25 +7592,31 @@ const selectedOption = (select) => {
|
|
|
7206
7592
|
/**
|
|
7207
7593
|
* Fill each `<selectedcontent>` with a clone of its `<select>`'s selected
|
|
7208
7594
|
* option subtree (the customizable-select mirroring behavior).
|
|
7209
|
-
* @param {
|
|
7210
|
-
* @param {HtmlElement
|
|
7595
|
+
* @param {HtmlNodeRef} node node
|
|
7596
|
+
* @param {HtmlElement} select nearest ancestor select (0 = none)
|
|
7211
7597
|
*/
|
|
7212
7598
|
const mirrorSelectedContent = (node, select) => {
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
if (child
|
|
7219
|
-
if (child.namespace === NS_HTML && child.tagName === "select") {
|
|
7599
|
+
// A `<template>`'s children live in its content fragment.
|
|
7600
|
+
const tc = _hTc[node];
|
|
7601
|
+
const container = tc !== 0 ? tc : node;
|
|
7602
|
+
for (let child = _hFirst[container]; child !== 0; child = _hNext[child]) {
|
|
7603
|
+
if (_hTy[child] !== NodeType.Element) continue;
|
|
7604
|
+
if (_ns(child) === NS_HTML && _hStr[child] === "select") {
|
|
7220
7605
|
mirrorSelectedContent(child, child);
|
|
7221
7606
|
} else if (
|
|
7222
|
-
select &&
|
|
7223
|
-
child
|
|
7224
|
-
child
|
|
7607
|
+
select !== 0 &&
|
|
7608
|
+
_ns(child) === NS_HTML &&
|
|
7609
|
+
_hStr[child] === "selectedcontent"
|
|
7225
7610
|
) {
|
|
7226
7611
|
const option = selectedOption(select);
|
|
7227
|
-
if (option
|
|
7612
|
+
if (option !== 0) {
|
|
7613
|
+
// Replace the children with clones of the option's subtree.
|
|
7614
|
+
_hFirst[child] = 0;
|
|
7615
|
+
_hLast[child] = 0;
|
|
7616
|
+
for (let k = _hFirst[option]; k !== 0; k = _hNext[k]) {
|
|
7617
|
+
_hAppend(child, cloneSubtree(k));
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7228
7620
|
} else {
|
|
7229
7621
|
mirrorSelectedContent(child, select);
|
|
7230
7622
|
}
|
|
@@ -7257,37 +7649,20 @@ const parseDoctype = (/** @type {string} */ raw) => {
|
|
|
7257
7649
|
return { name, publicId, systemId };
|
|
7258
7650
|
};
|
|
7259
7651
|
|
|
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
7652
|
/** @typedef {HtmlNode | HtmlDocument | HtmlDocumentFragment} HtmlVisitableNode */
|
|
7277
7653
|
|
|
7278
7654
|
// HTML-typed views over the generic visitor machinery (`util/SourceProcessor`).
|
|
7279
7655
|
/**
|
|
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
|
|
7656
|
+
* @typedef {import("../util/SourceProcessor").VisitorFn<HtmlPath>} VisitorFn
|
|
7657
|
+
* @typedef {import("../util/SourceProcessor").VisitorBucket<HtmlPath>} VisitorBucket
|
|
7658
|
+
* @typedef {import("../util/SourceProcessor").VisitorMap<HtmlPath>} VisitorMap
|
|
7659
|
+
* @typedef {import("../util/SourceProcessor").CompiledVisitorMap<HtmlPath>} CompiledVisitorMap
|
|
7285
7660
|
*/
|
|
7286
7661
|
|
|
7287
7662
|
/**
|
|
7288
7663
|
* @typedef {object} HtmlProcessOptions
|
|
7289
|
-
* @property {string=} fragmentContext context element tag name for fragment parsing (see `buildHtmlAst`)
|
|
7290
|
-
* @property {
|
|
7664
|
+
* @property {string=} fragmentContext context element tag name for fragment parsing (see `buildHtmlAst`); the HTML analog of the CSS parser's `as` parse-mode option
|
|
7665
|
+
* @property {HtmlAstSkip=} skip node kinds to omit from the AST for speed/memory (see `HtmlAstSkip`)
|
|
7291
7666
|
*/
|
|
7292
7667
|
|
|
7293
7668
|
/**
|
|
@@ -7299,54 +7674,97 @@ const NodeType = {
|
|
|
7299
7674
|
* @param {HtmlProcessOptions} options process options
|
|
7300
7675
|
*/
|
|
7301
7676
|
const grammar = (input, visitors, options) => {
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7677
|
+
const root = buildHtmlAst(input, options.fragmentContext, options.skip);
|
|
7678
|
+
|
|
7679
|
+
// Iterative depth-first walk over the link columns: `firstChild`
|
|
7680
|
+
// / `nextSibling` descend, the `parent` column ascends, so arbitrarily deep
|
|
7681
|
+
// markup can't overflow the call stack (the old recursive walk died at
|
|
7682
|
+
// ~10⁵ nesting). A `<template>`'s content fragment is visited before the
|
|
7683
|
+
// element's children; ascending out of it continues with those children
|
|
7684
|
+
// (the fragment is never in a sibling chain, `_hNext` = 0).
|
|
7685
|
+
/**
|
|
7686
|
+
* Fire a node's `enter` visitors; true when the walk may descend.
|
|
7687
|
+
* @param {HtmlNodeRef} node node
|
|
7688
|
+
* @returns {boolean} false when a visitor called `skipChildren()`
|
|
7689
|
+
*/
|
|
7690
|
+
const enter = (node) => {
|
|
7691
|
+
const b = visitors[_hTy[node]];
|
|
7692
|
+
if (b === undefined || b.enter.length === 0) return true;
|
|
7693
|
+
_walkSkip = false;
|
|
7694
|
+
_curNode = node;
|
|
7695
|
+
const p = _hParent[node];
|
|
7696
|
+
_curParent = p === 0 ? null : p;
|
|
7697
|
+
const e = b.enter;
|
|
7698
|
+
for (let i = 0; i < e.length; i++) e[i](A);
|
|
7699
|
+
const skip = _walkSkip;
|
|
7700
|
+
_walkSkip = false;
|
|
7701
|
+
return !skip;
|
|
7308
7702
|
};
|
|
7309
|
-
|
|
7310
7703
|
/**
|
|
7311
|
-
*
|
|
7312
|
-
*
|
|
7313
|
-
* @param {HtmlVisitableNode} node subtree root
|
|
7314
|
-
* @param {HtmlVisitableNode | null} parent enclosing node
|
|
7704
|
+
* Fire a node's `exit` visitors.
|
|
7705
|
+
* @param {HtmlNodeRef} node node
|
|
7315
7706
|
*/
|
|
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.
|
|
7707
|
+
const exit = (node) => {
|
|
7708
|
+
const b = visitors[_hTy[node]];
|
|
7709
|
+
if (b === undefined) return;
|
|
7710
|
+
_curNode = node;
|
|
7711
|
+
const p = _hParent[node];
|
|
7712
|
+
_curParent = p === 0 ? null : p;
|
|
7713
|
+
const x = b.exit;
|
|
7714
|
+
for (let i = 0; i < x.length; i++) x[i](A);
|
|
7715
|
+
};
|
|
7716
|
+
/**
|
|
7717
|
+
* First node to visit inside `node` (template content before children).
|
|
7718
|
+
* @param {HtmlNodeRef} node node
|
|
7719
|
+
* @returns {HtmlNodeRef} first inner node (0 = leaf)
|
|
7720
|
+
*/
|
|
7721
|
+
const firstInner = (node) => {
|
|
7722
|
+
const ty = _hTy[node];
|
|
7723
|
+
if (ty === NodeType.Element) {
|
|
7724
|
+
const tc = _hTc[node];
|
|
7725
|
+
return tc !== 0 ? tc : _hFirst[node];
|
|
7342
7726
|
}
|
|
7343
|
-
if (
|
|
7344
|
-
|
|
7345
|
-
for (let i = 0; i < x.length; i++) x[i](node, parent, visitorCtx);
|
|
7727
|
+
if (ty === NodeType.Document || ty === NodeType.DocumentFragment) {
|
|
7728
|
+
return _hFirst[node];
|
|
7346
7729
|
}
|
|
7730
|
+
return 0;
|
|
7347
7731
|
};
|
|
7348
7732
|
|
|
7349
|
-
|
|
7733
|
+
let node = root;
|
|
7734
|
+
descend: for (;;) {
|
|
7735
|
+
if (enter(node)) {
|
|
7736
|
+
const inner = firstInner(node);
|
|
7737
|
+
if (inner !== 0) {
|
|
7738
|
+
node = inner;
|
|
7739
|
+
continue;
|
|
7740
|
+
}
|
|
7741
|
+
}
|
|
7742
|
+
// Leaf (or skipped): exit and move sideways / upwards.
|
|
7743
|
+
for (;;) {
|
|
7744
|
+
exit(node);
|
|
7745
|
+
if (node === root) break descend;
|
|
7746
|
+
const parent = _hParent[node];
|
|
7747
|
+
// Out of a template's content fragment: the element's children follow.
|
|
7748
|
+
if (_hTc[parent] === node) {
|
|
7749
|
+
const first = _hFirst[parent];
|
|
7750
|
+
if (first !== 0) {
|
|
7751
|
+
node = first;
|
|
7752
|
+
continue descend;
|
|
7753
|
+
}
|
|
7754
|
+
} else {
|
|
7755
|
+
const sibling = _hNext[node];
|
|
7756
|
+
if (sibling !== 0) {
|
|
7757
|
+
node = sibling;
|
|
7758
|
+
continue descend;
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
7761
|
+
node = parent;
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
|
|
7765
|
+
// The walk consumed the tree: release the side arrays' heap references so
|
|
7766
|
+
// the reused columns don't pin this parse's strings until the next parse.
|
|
7767
|
+
_hRelease();
|
|
7350
7768
|
};
|
|
7351
7769
|
|
|
7352
7770
|
/**
|
|
@@ -7354,10 +7772,10 @@ const grammar = (input, visitors, options) => {
|
|
|
7354
7772
|
* `grammar`. Babel-style usage:
|
|
7355
7773
|
*
|
|
7356
7774
|
* ```
|
|
7357
|
-
* processor.use({ [NodeType.Element]: (
|
|
7775
|
+
* processor.use({ [NodeType.Element]: (path) => {}, [NodeType.Comment]: { enter, exit } });
|
|
7358
7776
|
* processor.process(source);
|
|
7359
7777
|
* ```
|
|
7360
|
-
* @extends {GenericSourceProcessor<
|
|
7778
|
+
* @extends {GenericSourceProcessor<HtmlPath, HtmlProcessOptions>}
|
|
7361
7779
|
*/
|
|
7362
7780
|
class SourceProcessor extends GenericSourceProcessor {
|
|
7363
7781
|
constructor() {
|
|
@@ -7377,12 +7795,14 @@ const SMALL_LETTER_W = "w".charCodeAt(0);
|
|
|
7377
7795
|
const SMALL_LETTER_X = "x".charCodeAt(0);
|
|
7378
7796
|
const SMALL_LETTER_H = "h".charCodeAt(0);
|
|
7379
7797
|
// (Don't use \s, to avoid matching non-breaking space)
|
|
7798
|
+
// Sticky so `collectCharacters` matches at an offset without slicing the
|
|
7799
|
+
// whole remaining input per call (which made srcset parsing quadratic).
|
|
7380
7800
|
// eslint-disable-next-line no-control-regex
|
|
7381
|
-
const LEADING_SPACES_REGEXP =
|
|
7801
|
+
const LEADING_SPACES_REGEXP = /[ \t\n\r\u000C]+/y;
|
|
7382
7802
|
// eslint-disable-next-line no-control-regex
|
|
7383
|
-
const LEADING_COMMAS_OR_SPACES_REGEXP =
|
|
7803
|
+
const LEADING_COMMAS_OR_SPACES_REGEXP = /[, \t\n\r\u000C]+/y;
|
|
7384
7804
|
// eslint-disable-next-line no-control-regex
|
|
7385
|
-
const LEADING_NOT_SPACES =
|
|
7805
|
+
const LEADING_NOT_SPACES = /[^ \t\n\r\u000C]+/y;
|
|
7386
7806
|
const TRAILING_COMMAS_REGEXP = /[,]+$/;
|
|
7387
7807
|
const NON_NEGATIVE_INTEGER_REGEXP = /^\d+$/;
|
|
7388
7808
|
// ( Positive or negative or unsigned integers or decimals, without or without exponents.
|
|
@@ -7405,8 +7825,8 @@ const parseSrcset = (input) => {
|
|
|
7405
7825
|
let url;
|
|
7406
7826
|
/** @type {string[]} */
|
|
7407
7827
|
let descriptors;
|
|
7408
|
-
/** @type {
|
|
7409
|
-
let
|
|
7828
|
+
/** @type {number} */
|
|
7829
|
+
let descriptorStart;
|
|
7410
7830
|
/** @type {string} */
|
|
7411
7831
|
let state;
|
|
7412
7832
|
/** @type {number} */
|
|
@@ -7420,16 +7840,15 @@ const parseSrcset = (input) => {
|
|
|
7420
7840
|
const candidates = [];
|
|
7421
7841
|
|
|
7422
7842
|
/**
|
|
7423
|
-
* @param {RegExp} regExp reg exp to collect characters
|
|
7843
|
+
* @param {RegExp} regExp sticky reg exp to collect characters
|
|
7424
7844
|
* @returns {string | undefined} characters
|
|
7425
7845
|
*/
|
|
7426
7846
|
function collectCharacters(regExp) {
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
const match = regExp.exec(input.slice(Math.max(0, position)));
|
|
7847
|
+
regExp.lastIndex = Math.max(0, position);
|
|
7848
|
+
const match = regExp.exec(input);
|
|
7430
7849
|
|
|
7431
7850
|
if (match) {
|
|
7432
|
-
[chars] = match;
|
|
7851
|
+
const [chars] = match;
|
|
7433
7852
|
position += chars.length;
|
|
7434
7853
|
|
|
7435
7854
|
return chars;
|
|
@@ -7560,7 +7979,8 @@ const parseSrcset = (input) => {
|
|
|
7560
7979
|
collectCharacters(LEADING_SPACES_REGEXP);
|
|
7561
7980
|
|
|
7562
7981
|
// 8.2. Let current descriptor be the empty string.
|
|
7563
|
-
|
|
7982
|
+
// (Tracked as a start offset, `-1` = empty; sliced once per descriptor.)
|
|
7983
|
+
descriptorStart = -1;
|
|
7564
7984
|
|
|
7565
7985
|
// 8.3. Let state be in descriptor.
|
|
7566
7986
|
state = "in descriptor";
|
|
@@ -7582,9 +8002,9 @@ const parseSrcset = (input) => {
|
|
|
7582
8002
|
// descriptors and let current descriptor be the empty string.
|
|
7583
8003
|
// Set state to after descriptor.
|
|
7584
8004
|
if (isSpace(charCode)) {
|
|
7585
|
-
if (
|
|
7586
|
-
descriptors.push(
|
|
7587
|
-
|
|
8005
|
+
if (descriptorStart !== -1) {
|
|
8006
|
+
descriptors.push(input.slice(descriptorStart, position));
|
|
8007
|
+
descriptorStart = -1;
|
|
7588
8008
|
state = "after descriptor";
|
|
7589
8009
|
}
|
|
7590
8010
|
}
|
|
@@ -7595,8 +8015,8 @@ const parseSrcset = (input) => {
|
|
|
7595
8015
|
else if (charCode === COMMA) {
|
|
7596
8016
|
position += 1;
|
|
7597
8017
|
|
|
7598
|
-
if (
|
|
7599
|
-
descriptors.push(
|
|
8018
|
+
if (descriptorStart !== -1) {
|
|
8019
|
+
descriptors.push(input.slice(descriptorStart, position - 1));
|
|
7600
8020
|
}
|
|
7601
8021
|
|
|
7602
8022
|
parseDescriptors();
|
|
@@ -7606,15 +8026,15 @@ const parseSrcset = (input) => {
|
|
|
7606
8026
|
// U+0028 LEFT PARENTHESIS (()
|
|
7607
8027
|
// Append charCode to current descriptor. Set state to in parens.
|
|
7608
8028
|
else if (charCode === LEFT_PARENTHESIS) {
|
|
7609
|
-
|
|
8029
|
+
if (descriptorStart === -1) descriptorStart = position;
|
|
7610
8030
|
state = "in parens";
|
|
7611
8031
|
}
|
|
7612
8032
|
// EOF
|
|
7613
8033
|
// If current descriptor is not empty, append current descriptor to
|
|
7614
8034
|
// descriptors. Jump to the step labeled descriptor parser.
|
|
7615
8035
|
else if (Number.isNaN(charCode)) {
|
|
7616
|
-
if (
|
|
7617
|
-
descriptors.push(
|
|
8036
|
+
if (descriptorStart !== -1) {
|
|
8037
|
+
descriptors.push(input.slice(descriptorStart, position));
|
|
7618
8038
|
}
|
|
7619
8039
|
|
|
7620
8040
|
parseDescriptors();
|
|
@@ -7623,8 +8043,8 @@ const parseSrcset = (input) => {
|
|
|
7623
8043
|
|
|
7624
8044
|
// Anything else
|
|
7625
8045
|
// Append charCode to current descriptor.
|
|
7626
|
-
} else {
|
|
7627
|
-
|
|
8046
|
+
} else if (descriptorStart === -1) {
|
|
8047
|
+
descriptorStart = position;
|
|
7628
8048
|
}
|
|
7629
8049
|
}
|
|
7630
8050
|
// In parens
|
|
@@ -7632,22 +8052,18 @@ const parseSrcset = (input) => {
|
|
|
7632
8052
|
// U+0029 RIGHT PARENTHESIS ())
|
|
7633
8053
|
// Append charCode to current descriptor. Set state to in descriptor.
|
|
7634
8054
|
if (charCode === RIGHT_PARENTHESIS) {
|
|
7635
|
-
currentDescriptor += input.charAt(position);
|
|
7636
8055
|
state = "in descriptor";
|
|
7637
8056
|
}
|
|
7638
8057
|
// EOF
|
|
7639
8058
|
// Append current descriptor to descriptors. Jump to the step labeled
|
|
7640
8059
|
// descriptor parser.
|
|
7641
8060
|
else if (Number.isNaN(charCode)) {
|
|
7642
|
-
descriptors.push(
|
|
8061
|
+
descriptors.push(input.slice(descriptorStart, position));
|
|
7643
8062
|
parseDescriptors();
|
|
7644
8063
|
return;
|
|
7645
8064
|
}
|
|
7646
8065
|
// Anything else
|
|
7647
|
-
// Append charCode to current descriptor.
|
|
7648
|
-
else {
|
|
7649
|
-
currentDescriptor += input.charAt(position);
|
|
7650
|
-
}
|
|
8066
|
+
// Append charCode to current descriptor. (Covered by the tracked range.)
|
|
7651
8067
|
}
|
|
7652
8068
|
// After descriptor
|
|
7653
8069
|
else if (state === "after descriptor") {
|
|
@@ -7719,6 +8135,270 @@ const parseSrcset = (input) => {
|
|
|
7719
8135
|
}
|
|
7720
8136
|
};
|
|
7721
8137
|
|
|
8138
|
+
// Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
|
|
8139
|
+
// `enter` dispatch, consumed by the walk.
|
|
8140
|
+
let _walkSkip = false;
|
|
8141
|
+
// The walk's current position (`A.node` / `A.parent` read these; module-level
|
|
8142
|
+
// so the accessor methods' defaults avoid self-referential `this` typing).
|
|
8143
|
+
/** @type {HtmlNodeRef} */
|
|
8144
|
+
let _curNode = 0;
|
|
8145
|
+
/** @type {HtmlNodeRef | null} */
|
|
8146
|
+
let _curParent = null;
|
|
8147
|
+
|
|
8148
|
+
/* eslint-disable jsdoc/require-template -- `A` below is the accessor const, not a type parameter */
|
|
8149
|
+
/**
|
|
8150
|
+
* The HTML path (Babel's `path` shape): the AST accessor with the walk's
|
|
8151
|
+
* current position on it — the single argument every visitor receives.
|
|
8152
|
+
* @typedef {typeof A} HtmlPath
|
|
8153
|
+
*/
|
|
8154
|
+
/* eslint-enable jsdoc/require-template */
|
|
8155
|
+
|
|
8156
|
+
// AST field-access seam (mirrors the CSS parser's `A`): every AST field a
|
|
8157
|
+
// consumer reads goes through one of these accessors, so the node
|
|
8158
|
+
// representation can change underneath without touching consumers. `n` is an
|
|
8159
|
+
// `HtmlNodeRef`; results are valid until the next `buildHtmlAst` call.
|
|
8160
|
+
const A = {
|
|
8161
|
+
// === path position (rebound by the walk before every visitor call) ===
|
|
8162
|
+
/**
|
|
8163
|
+
* @returns {HtmlNodeRef} current node — only valid during a visitor callback
|
|
8164
|
+
*/
|
|
8165
|
+
get node() {
|
|
8166
|
+
return _curNode;
|
|
8167
|
+
},
|
|
8168
|
+
/**
|
|
8169
|
+
* @returns {HtmlNodeRef | null} enclosing node (null = the document root)
|
|
8170
|
+
*/
|
|
8171
|
+
get parent() {
|
|
8172
|
+
return _curParent;
|
|
8173
|
+
},
|
|
8174
|
+
/** Stop the walk descending into the current node (enter only). */
|
|
8175
|
+
skipChildren() {
|
|
8176
|
+
_walkSkip = true;
|
|
8177
|
+
},
|
|
8178
|
+
// === field reads — `n` defaults to the current node ===
|
|
8179
|
+
/**
|
|
8180
|
+
* @param {HtmlNodeRef=} n node
|
|
8181
|
+
* @returns {number} `NodeType`
|
|
8182
|
+
*/
|
|
8183
|
+
type(n = _curNode) {
|
|
8184
|
+
return _hTy[n];
|
|
8185
|
+
},
|
|
8186
|
+
/**
|
|
8187
|
+
* @param {HtmlNodeRef=} n node
|
|
8188
|
+
* @returns {number} start offset
|
|
8189
|
+
*/
|
|
8190
|
+
start(n = _curNode) {
|
|
8191
|
+
return _hSt[n];
|
|
8192
|
+
},
|
|
8193
|
+
/**
|
|
8194
|
+
* @param {HtmlNodeRef=} n node
|
|
8195
|
+
* @returns {number} end offset
|
|
8196
|
+
*/
|
|
8197
|
+
end(n = _curNode) {
|
|
8198
|
+
return _hEn[n];
|
|
8199
|
+
},
|
|
8200
|
+
/**
|
|
8201
|
+
* @param {HtmlElement=} n element
|
|
8202
|
+
* @returns {string} lowercased (foreign-content: adjusted) tag name
|
|
8203
|
+
*/
|
|
8204
|
+
tagName(n = _curNode) {
|
|
8205
|
+
return _hStr[n];
|
|
8206
|
+
},
|
|
8207
|
+
/**
|
|
8208
|
+
* @param {HtmlElement=} n element
|
|
8209
|
+
* @returns {number} `NS_*` namespace
|
|
8210
|
+
*/
|
|
8211
|
+
namespace(n = _curNode) {
|
|
8212
|
+
return _hFl[n] & NS_MASK;
|
|
8213
|
+
},
|
|
8214
|
+
/**
|
|
8215
|
+
* @param {HtmlElement=} n element
|
|
8216
|
+
* @returns {boolean} true for void elements
|
|
8217
|
+
*/
|
|
8218
|
+
selfClosing(n = _curNode) {
|
|
8219
|
+
return (_hFl[n] & FLAG_SELF_CLOSING) !== 0;
|
|
8220
|
+
},
|
|
8221
|
+
// materialized attribute list — test/tooling convenience, allocates; the
|
|
8222
|
+
// parser reads attributes through the scalar accessors below
|
|
8223
|
+
/**
|
|
8224
|
+
* @param {HtmlElement=} n element
|
|
8225
|
+
* @returns {HtmlAttribute[]} materialized attributes
|
|
8226
|
+
*/
|
|
8227
|
+
attributes(n = _curNode) {
|
|
8228
|
+
const out = [];
|
|
8229
|
+
const start = _hAStart[n];
|
|
8230
|
+
for (let i = start; i < start + _hACount[n]; i++) {
|
|
8231
|
+
out.push({
|
|
8232
|
+
name: _aName[i],
|
|
8233
|
+
value: _aValueOf(i),
|
|
8234
|
+
serializedName: _aSerializedName(i),
|
|
8235
|
+
nameStart: _aNameStart[i],
|
|
8236
|
+
nameEnd: _aNameEnd[i],
|
|
8237
|
+
valueStart: _aValStart[i],
|
|
8238
|
+
valueEnd: _aValEnd[i]
|
|
8239
|
+
});
|
|
8240
|
+
}
|
|
8241
|
+
return out;
|
|
8242
|
+
},
|
|
8243
|
+
/**
|
|
8244
|
+
* @param {HtmlElement=} n element
|
|
8245
|
+
* @returns {number} attribute count
|
|
8246
|
+
*/
|
|
8247
|
+
attributeCount(n = _curNode) {
|
|
8248
|
+
return _hACount[n];
|
|
8249
|
+
},
|
|
8250
|
+
/**
|
|
8251
|
+
* The i-th attribute of an element, as an id for the `attribute*` reads.
|
|
8252
|
+
* @param {number} i attribute index
|
|
8253
|
+
* @param {HtmlElement=} n element
|
|
8254
|
+
* @returns {HtmlAttributeRef} attribute ref
|
|
8255
|
+
*/
|
|
8256
|
+
attributeAt(i, n = _curNode) {
|
|
8257
|
+
return _hAStart[n] + i;
|
|
8258
|
+
},
|
|
8259
|
+
/**
|
|
8260
|
+
* Linear lookup by (lowercased) name.
|
|
8261
|
+
* @param {string} name attribute name
|
|
8262
|
+
* @param {HtmlElement=} n element
|
|
8263
|
+
* @returns {HtmlAttributeRef} attribute ref (0 = not present)
|
|
8264
|
+
*/
|
|
8265
|
+
findAttribute(name, n = _curNode) {
|
|
8266
|
+
return _aFind(_hAStart[n], _hACount[n], name);
|
|
8267
|
+
},
|
|
8268
|
+
/**
|
|
8269
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8270
|
+
* @returns {string} lowercased (foreign-content: adjusted) attribute name
|
|
8271
|
+
*/
|
|
8272
|
+
attributeName(a) {
|
|
8273
|
+
return _aName[a];
|
|
8274
|
+
},
|
|
8275
|
+
/**
|
|
8276
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8277
|
+
* @returns {string} raw (undecoded) attribute value ("" when valueless)
|
|
8278
|
+
*/
|
|
8279
|
+
attributeValue(a) {
|
|
8280
|
+
return _aValueOf(a);
|
|
8281
|
+
},
|
|
8282
|
+
/**
|
|
8283
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8284
|
+
* @returns {number} name start offset
|
|
8285
|
+
*/
|
|
8286
|
+
attributeNameStart(a) {
|
|
8287
|
+
return _aNameStart[a];
|
|
8288
|
+
},
|
|
8289
|
+
/**
|
|
8290
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8291
|
+
* @returns {number} name end offset
|
|
8292
|
+
*/
|
|
8293
|
+
attributeNameEnd(a) {
|
|
8294
|
+
return _aNameEnd[a];
|
|
8295
|
+
},
|
|
8296
|
+
/**
|
|
8297
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8298
|
+
* @returns {number} value start offset (-1 when valueless or on adoption-agency clones)
|
|
8299
|
+
*/
|
|
8300
|
+
attributeValueStart(a) {
|
|
8301
|
+
return _aValStart[a];
|
|
8302
|
+
},
|
|
8303
|
+
/**
|
|
8304
|
+
* @param {HtmlAttributeRef} a attribute ref
|
|
8305
|
+
* @returns {number} value end offset
|
|
8306
|
+
*/
|
|
8307
|
+
attributeValueEnd(a) {
|
|
8308
|
+
return _aValEnd[a];
|
|
8309
|
+
},
|
|
8310
|
+
/**
|
|
8311
|
+
* @param {HtmlElement=} n element
|
|
8312
|
+
* @returns {number} end offset of the opening tag (after `>`)
|
|
8313
|
+
*/
|
|
8314
|
+
tagEnd(n = _curNode) {
|
|
8315
|
+
return _hTagEnd[n];
|
|
8316
|
+
},
|
|
8317
|
+
/**
|
|
8318
|
+
* @param {HtmlElement=} n element
|
|
8319
|
+
* @returns {number} end offset of the tag name
|
|
8320
|
+
*/
|
|
8321
|
+
nameEnd(n = _curNode) {
|
|
8322
|
+
return _hNameEnd[n];
|
|
8323
|
+
},
|
|
8324
|
+
/**
|
|
8325
|
+
* @param {HtmlElement=} n element
|
|
8326
|
+
* @returns {number} under `skip.text`, end offset of a raw-text element's body (`tagEnd` when empty)
|
|
8327
|
+
*/
|
|
8328
|
+
contentEnd(n = _curNode) {
|
|
8329
|
+
return _hCEnd[n];
|
|
8330
|
+
},
|
|
8331
|
+
/**
|
|
8332
|
+
* @param {HtmlElement=} n element
|
|
8333
|
+
* @returns {HtmlDocumentFragment} `<template>` content fragment (0 = none)
|
|
8334
|
+
*/
|
|
8335
|
+
templateContent(n = _curNode) {
|
|
8336
|
+
return _hTc[n];
|
|
8337
|
+
},
|
|
8338
|
+
/**
|
|
8339
|
+
* @param {HtmlText | HtmlComment=} n text / comment node
|
|
8340
|
+
* @returns {string} decoded text / comment data
|
|
8341
|
+
*/
|
|
8342
|
+
data(n = _curNode) {
|
|
8343
|
+
return _hStr[n];
|
|
8344
|
+
},
|
|
8345
|
+
/**
|
|
8346
|
+
* @param {HtmlDoctype=} n doctype node
|
|
8347
|
+
* @returns {string} doctype name
|
|
8348
|
+
*/
|
|
8349
|
+
doctypeName(n = _curNode) {
|
|
8350
|
+
return _hStr[n];
|
|
8351
|
+
},
|
|
8352
|
+
// The doctype ids are per-parse scalars (a document has at most one
|
|
8353
|
+
// doctype node); the node parameter is accepted for call-shape uniformity.
|
|
8354
|
+
/**
|
|
8355
|
+
* @param {HtmlDoctype=} _n doctype node
|
|
8356
|
+
* @returns {string | null} doctype public id
|
|
8357
|
+
*/
|
|
8358
|
+
doctypePublicId(_n) {
|
|
8359
|
+
return _hDocPub;
|
|
8360
|
+
},
|
|
8361
|
+
/**
|
|
8362
|
+
* @param {HtmlDoctype=} _n doctype node
|
|
8363
|
+
* @returns {string | null} doctype system id
|
|
8364
|
+
*/
|
|
8365
|
+
doctypeSystemId(_n) {
|
|
8366
|
+
return _hDocSys;
|
|
8367
|
+
},
|
|
8368
|
+
// === tree links (0 = none) ===
|
|
8369
|
+
/**
|
|
8370
|
+
* @param {HtmlNodeRef=} n node
|
|
8371
|
+
* @returns {HtmlNodeRef} first child
|
|
8372
|
+
*/
|
|
8373
|
+
firstChild(n = _curNode) {
|
|
8374
|
+
return _hFirst[n];
|
|
8375
|
+
},
|
|
8376
|
+
/**
|
|
8377
|
+
* @param {HtmlNodeRef=} n node
|
|
8378
|
+
* @returns {HtmlNodeRef} next sibling
|
|
8379
|
+
*/
|
|
8380
|
+
nextSibling(n = _curNode) {
|
|
8381
|
+
return _hNext[n];
|
|
8382
|
+
},
|
|
8383
|
+
/**
|
|
8384
|
+
* @param {HtmlNodeRef=} n node
|
|
8385
|
+
* @returns {HtmlNodeRef} parent node (a `<template>`'s content links to its fragment)
|
|
8386
|
+
*/
|
|
8387
|
+
parentOf(n = _curNode) {
|
|
8388
|
+
return _hParent[n];
|
|
8389
|
+
},
|
|
8390
|
+
/**
|
|
8391
|
+
* @param {HtmlNodeRef=} n node
|
|
8392
|
+
* @returns {HtmlNodeRef[]} materialized child list — test/tooling convenience, allocates
|
|
8393
|
+
*/
|
|
8394
|
+
children(n = _curNode) {
|
|
8395
|
+
const out = [];
|
|
8396
|
+
for (let c = _hFirst[n]; c !== 0; c = _hNext[c]) out.push(c);
|
|
8397
|
+
return out;
|
|
8398
|
+
}
|
|
8399
|
+
};
|
|
8400
|
+
|
|
8401
|
+
module.exports.A = A;
|
|
7722
8402
|
module.exports.NS_HTML = NS_HTML;
|
|
7723
8403
|
module.exports.NS_MATHML = NS_MATHML;
|
|
7724
8404
|
module.exports.NS_SVG = NS_SVG;
|
|
@@ -7733,6 +8413,5 @@ module.exports.SourceProcessor = SourceProcessor;
|
|
|
7733
8413
|
module.exports.buildHtmlAst = buildHtmlAst;
|
|
7734
8414
|
module.exports.decodeHtmlEntities = decodeHtmlEntities;
|
|
7735
8415
|
module.exports.decodeHtmlEntitiesWithMap = decodeHtmlEntitiesWithMap;
|
|
7736
|
-
module.exports.findAttr = findAttr;
|
|
7737
8416
|
module.exports.parseSrcset = parseSrcset;
|
|
7738
8417
|
module.exports.walkHtmlTokens = walkHtmlTokens;
|