webpack 5.111.0 → 5.111.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/APIPlugin.js +3 -4
- package/lib/CompatibilityPlugin.js +3 -4
- package/lib/Compilation.js +16 -35
- package/lib/Compiler.js +4 -5
- package/lib/ConstPlugin.js +16 -132
- package/lib/DependencyTemplate.js +2 -1
- package/lib/DotenvPlugin.js +24 -28
- package/lib/ErrorHelpers.js +113 -0
- package/lib/ExportsInfo.js +20 -16
- package/lib/ExternalModule.js +6 -8
- package/lib/FlagDependencyExportsPlugin.js +8 -13
- package/lib/FlagDependencyUsagePlugin.js +9 -13
- package/lib/Generator.js +24 -2
- package/lib/HotModuleReplacementPlugin.js +2 -0
- package/lib/IgnorePlugin.js +1 -1
- package/lib/LoaderOptionsPlugin.js +1 -1
- package/lib/Module.js +3 -3
- package/lib/ModuleGraph.js +6 -9
- package/lib/MultiCompiler.js +6 -16
- package/lib/NormalModule.js +96 -44
- package/lib/RuntimeTemplate.js +33 -43
- package/lib/SourceMapDevToolPlugin.js +53 -103
- package/lib/TemplatedPathPlugin.js +16 -59
- package/lib/asset/AssetBytesGenerator.js +7 -1
- package/lib/asset/AssetGenerator.js +25 -16
- package/lib/asset/AssetSourceGenerator.js +7 -1
- package/lib/buildChunkGraph.js +11 -15
- package/lib/cache/MemoryCachePlugin.js +4 -4
- package/lib/cache/MemoryWithGcCachePlugin.js +7 -8
- package/lib/cache/PackFileCacheStrategy.js +3 -18
- package/lib/cache/getLazyHashedEtag.js +3 -5
- package/lib/config/browserslistTargetHandler.js +2 -8
- package/lib/config/defaults.js +52 -78
- package/lib/config/getClaimedAssetTypes.js +10 -11
- package/lib/config/target.js +3 -4
- package/lib/{ContextExclusionPlugin.js → context/ContextExclusionPlugin.js} +1 -1
- package/lib/{ContextModule.js → context/ContextModule.js} +37 -34
- package/lib/{ContextModuleFactory.js → context/ContextModuleFactory.js} +19 -21
- package/lib/{ContextReplacementPlugin.js → context/ContextReplacementPlugin.js} +4 -4
- package/lib/css/CssGenerator.js +19 -11
- package/lib/css/CssParser.js +18 -20
- package/lib/css/cssMinify.js +32 -29
- package/lib/css/data.js +797 -288
- package/lib/css/syntax.js +2250 -934
- package/lib/debug/ProfilingPlugin.js +3 -4
- package/lib/dependencies/CommonJsExportRequireDependency.js +10 -11
- package/lib/dependencies/CommonJsImportsParserPlugin.js +4 -5
- package/lib/dependencies/CommonJsSelfReferenceDependency.js +2 -4
- package/lib/dependencies/ContextDependency.js +1 -1
- package/lib/dependencies/ContextDependencyHelpers.js +3 -9
- package/lib/dependencies/ContextElementDependency.js +1 -1
- package/lib/dependencies/CssIcssExportDependency.js +6 -8
- package/lib/dependencies/CssIcssImportDependency.js +3 -6
- package/lib/dependencies/CssUrlDependency.js +3 -5
- package/lib/dependencies/HarmonyCompatibilityDependency.js +11 -20
- package/lib/dependencies/HarmonyDetectionParserPlugin.js +3 -4
- package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +2 -4
- package/lib/dependencies/HarmonyExportExpressionDependency.js +3 -4
- package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +3 -4
- package/lib/dependencies/HarmonyExportInitFragment.js +26 -19
- package/lib/dependencies/HarmonyExportSpecifierDependency.js +7 -28
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +103 -16
- package/lib/dependencies/HarmonyImportSideEffectDependency.js +3 -4
- package/lib/dependencies/HarmonyImportSpecifierDependency.js +16 -2
- package/lib/dependencies/HtmlEntryDependency.js +74 -102
- package/lib/dependencies/HtmlInlineScriptDependency.js +3 -4
- package/lib/dependencies/HtmlInlineStyleDependency.js +3 -4
- package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +1 -1
- package/lib/dependencies/ImportMetaGlobHelpers.js +1 -1
- package/lib/dependencies/ImportMetaPlugin.js +8 -10
- package/lib/dependencies/ImportParserPlugin.js +1 -1
- package/lib/dependencies/LoaderPlugin.js +26 -1
- package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
- package/lib/dependencies/URLContextDependency.js +1 -1
- package/lib/dependencies/URLDependency.js +6 -5
- package/lib/dependencies/WorkerAndWorkletPlugin.js +15 -21
- package/lib/dependencies/WorkerDependency.js +6 -5
- package/lib/dependencies/WorkletDependency.js +6 -9
- package/lib/errors/HookWebpackError.js +3 -2
- package/lib/hmr/LazyCompilationPlugin.js +3 -5
- package/lib/html/HtmlGenerator.js +61 -82
- package/lib/html/HtmlModulesPlugin.js +88 -139
- package/lib/html/HtmlParser.js +97 -141
- package/lib/html/data.js +21 -0
- package/lib/html/htmlMinify.js +10 -11
- package/lib/html/syntax.js +883 -993
- package/lib/index.js +46 -3
- package/lib/javascript/ChunkFormatHelpers.js +4 -3
- package/lib/javascript/JavascriptGenerator.js +8 -1
- package/lib/javascript/JavascriptModulesPlugin.js +21 -48
- package/lib/javascript/JavascriptParser.js +721 -178
- package/lib/javascript/parser.js +6 -0
- package/lib/javascript/syntax.js +420 -92
- package/lib/json/JsonGenerator.js +7 -1
- package/lib/library/ModuleLibraryPlugin.js +64 -136
- package/lib/loaders/LoaderRunner.js +137 -9
- package/lib/node/NodeWatchFileSystem.js +3 -9
- package/lib/optimize/AggressiveMergingPlugin.js +3 -5
- package/lib/optimize/ConcatenatedModule.js +81 -100
- package/lib/optimize/FlagIncludedChunksPlugin.js +3 -11
- package/lib/optimize/InnerGraphPlugin.js +7 -13
- package/lib/optimize/LimitChunkCountPlugin.js +5 -8
- package/lib/optimize/MangleExportsPlugin.js +3 -4
- package/lib/optimize/ModuleConcatenationPlugin.js +3 -4
- package/lib/optimize/SideEffectsFlagPlugin.js +7 -19
- package/lib/optimize/SplitChunksPlugin.js +2 -4
- package/lib/performance/BroadContextsPlugin.js +1 -1
- package/lib/performance/CacheEffectivenessPlugin.js +3 -4
- package/lib/performance/SizeLimitsPlugin.js +2 -4
- package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +6 -8
- package/lib/prefetch/ResourceHintPlugin.js +17 -21
- package/lib/prefetch/StartupAssetHintRuntimeModule.js +7 -10
- package/lib/runtime/AsyncModuleRuntimeModule.js +3 -5
- package/lib/runtime/GetChunkFilenameRuntimeModule.js +3 -4
- package/lib/runtime/GlobalRuntimeModule.js +2 -4
- package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +55 -85
- package/lib/schemes/DataUriPlugin.js +3 -6
- package/lib/schemes/VirtualUrlPlugin.js +1 -1
- package/lib/serialization/BinaryMiddleware.js +9 -9
- package/lib/serialization/FileMiddleware.js +16 -16
- package/lib/serialization/ObjectMiddleware.js +30 -33
- package/lib/stats/DefaultStatsFactoryPlugin.js +3 -4
- package/lib/typescript/TypeScriptPlugin.js +2 -3
- package/lib/url/URLParserPlugin.js +6 -8
- package/lib/util/LocConverter.js +4 -7
- package/lib/util/SourceProcessor.js +15 -22
- package/lib/util/chainedImports.js +3 -4
- package/lib/util/concatenate.js +4 -4
- package/lib/util/createHooksRegistry.js +1 -2
- package/lib/util/dataURL.js +2 -3
- package/lib/util/deterministicGrouping.js +8 -13
- package/lib/util/findGraphRoots.js +6 -13
- package/lib/util/globUtils.js +1 -1
- package/lib/util/hash/wasm-hash.js +3 -4
- package/lib/util/internalSerializables.js +1 -1
- package/lib/util/semver.js +5 -3
- package/lib/wasm-async/AsyncWebAssemblyGenerator.js +6 -1
- package/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js +5 -1
- package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +3 -4
- package/lib/wasm-sync/WebAssemblyGenerator.js +6 -1
- package/lib/wasm-sync/WebAssemblyJavascriptGenerator.js +5 -1
- package/lib/wasm-sync/WebAssemblyUtils.js +2 -4
- package/package.json +30 -23
- package/types.d.ts +299 -280
package/lib/html/syntax.js
CHANGED
|
@@ -114,6 +114,7 @@ const {
|
|
|
114
114
|
IMPLIED,
|
|
115
115
|
IMPLIED_THOROUGH,
|
|
116
116
|
INTEGER_ATTRIBUTES,
|
|
117
|
+
INVALID_VALUE_DEFAULT_DIFFERS,
|
|
117
118
|
IN_HEAD_NOSCRIPT_PASSTHROUGH,
|
|
118
119
|
IN_TABLE_IGNORED_ENDS,
|
|
119
120
|
JAVASCRIPT_SCRIPT_TYPES,
|
|
@@ -314,18 +315,14 @@ const QUOTE_DOUBLE = 1;
|
|
|
314
315
|
const QUOTE_SINGLE = 2;
|
|
315
316
|
const QUOTE_NONE = 0;
|
|
316
317
|
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
// the decoder's longest-prefix backtrack so pathological inputs (e.g. `&`
|
|
321
|
-
// followed by thousands of alphanumerics) stay linear-time.
|
|
318
|
+
// The longest WHATWG named entity is 32 chars with its `;`, 31 without. Capping
|
|
319
|
+
// the tokenizer's reference run and the decoder's longest-prefix backtrack there
|
|
320
|
+
// keeps `&` followed by thousands of alphanumerics linear-time.
|
|
322
321
|
const MAX_ENTITY_NAME_LEN = 32;
|
|
323
322
|
|
|
324
|
-
// ASCII character-class bit flags
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
// per-call comparison chains. Code points >= 0x80 are never in any of these
|
|
328
|
-
// classes, so callers short-circuit on `cc < 0x80` before indexing.
|
|
323
|
+
// ASCII character-class bit flags in one table: a load and a mask in place of
|
|
324
|
+
// the comparison chain these per-code-point predicates would otherwise run. No
|
|
325
|
+
// code point >= 0x80 is in any class, so callers short-circuit on `cc < 0x80`.
|
|
329
326
|
const CCLS_SPACE = 1;
|
|
330
327
|
const CCLS_DIGIT = 2;
|
|
331
328
|
const CCLS_UPPER = 4;
|
|
@@ -581,10 +578,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
581
578
|
let attributeValueStart = -1;
|
|
582
579
|
let attrQuoteType = QUOTE_NONE;
|
|
583
580
|
let commentStart = pos;
|
|
584
|
-
// Data range of the comment being scanned
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
// tracked here rather than re-derived by trimming delimiters off the token.
|
|
581
|
+
// Data range of the comment being scanned, tracked rather than re-derived by
|
|
582
|
+
// trimming delimiters: the comment states append characters that were never
|
|
583
|
+
// consumed, such as the `--` an inner `-->` turns into.
|
|
588
584
|
let commentDataStart = pos;
|
|
589
585
|
let commentDataEnd = pos;
|
|
590
586
|
// Sub-ranges of the DOCTYPE token being scanned (-1 = the spec's
|
|
@@ -601,10 +597,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
601
597
|
// derived from these lazily (only for special-content tags).
|
|
602
598
|
let lastOpenTagStart = -1;
|
|
603
599
|
let lastOpenTagEnd = -1;
|
|
604
|
-
//
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
// Avoids growing a buffer for pathological inputs with long alpha runs.
|
|
600
|
+
// How much of `"script"` the ASCII-alpha run after `<` / `</` has matched,
|
|
601
|
+
// for SCRIPT_DATA_DOUBLE_ESCAPE_{START,END}: 0..6 chars, or 7 for no longer
|
|
602
|
+
// matching. A counter rather than a buffer, which a long run would grow.
|
|
608
603
|
let scriptMatch = 0;
|
|
609
604
|
let namedEntityConsumed = 0;
|
|
610
605
|
// Offset of the opening `&` and the running numeric value (clamped past the
|
|
@@ -614,10 +609,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
614
609
|
// Tracks whether the current tag has parsed any attributes — used to
|
|
615
610
|
// fire the `end-tag-with-attributes` parse error when an end tag emits.
|
|
616
611
|
let tagHasAttributes = false;
|
|
617
|
-
// Memoized next
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
// (`len` = no further occurrence).
|
|
612
|
+
// Memoized next `<` / `&` / NUL for the text-run fast-forwards, where native
|
|
613
|
+
// `indexOf` beats a per-char loop. `pos` only moves forward, so each memo is
|
|
614
|
+
// refreshed at most once per occurrence; `len` means no further one.
|
|
621
615
|
let nextLt = -1;
|
|
622
616
|
let nextAmp = -1;
|
|
623
617
|
let nextNul = -1;
|
|
@@ -688,12 +682,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
688
682
|
}
|
|
689
683
|
};
|
|
690
684
|
|
|
691
|
-
// Content mode for the tag just opened
|
|
692
|
-
// foreign content
|
|
693
|
-
//
|
|
694
|
-
// normal markup. `lastOpenTagName` (the lowercased name compared by the
|
|
695
|
-
// special end-tag states) is materialized only when a special mode is
|
|
696
|
-
// actually entered — ordinary tags never allocate it.
|
|
685
|
+
// Content mode for the tag just opened, named at `lastOpenTagStart..End`. In
|
|
686
|
+
// foreign content `isForeign` vetoes the RAWTEXT/RCDATA/script switch, so an
|
|
687
|
+
// SVG `<title>` is markup; `lastOpenTagName` is materialized only there.
|
|
697
688
|
const contentModeAfterOpenTag = () => {
|
|
698
689
|
const m = getContentModeForRange(input, lastOpenTagStart, lastOpenTagEnd);
|
|
699
690
|
// Ordinary tags stay in the data state whatever the tree builder did, so
|
|
@@ -731,11 +722,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
731
722
|
if (callbacks.text !== undefined) {
|
|
732
723
|
callbacks.text(input, textStart, endPos);
|
|
733
724
|
}
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
// emit. emitOpenTag / emitCloseTag overwrite `textStart` with
|
|
738
|
-
// their own `nextPos` anyway, so this doesn't shift their start.
|
|
725
|
+
// Advancing `textStart` makes a second `flushText` over the same span a
|
|
726
|
+
// no-op instead of a duplicate emit. `emitOpenTag` / `emitCloseTag`
|
|
727
|
+
// overwrite it with their own `nextPos`, so their start is unaffected.
|
|
739
728
|
textStart = endPos;
|
|
740
729
|
}
|
|
741
730
|
};
|
|
@@ -745,11 +734,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
745
734
|
* @returns {number} next position
|
|
746
735
|
*/
|
|
747
736
|
const emitAttribute = (endPos) => {
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
// the callback is expected to do the same advance based on the
|
|
752
|
-
// reported `quoteType`.
|
|
737
|
+
// The default `nextPos` advances past the closing quote, if any, so the
|
|
738
|
+
// machine continues without an `attribute` callback. A callback's return
|
|
739
|
+
// value overrides it, and owes the same advance from its `quoteType`.
|
|
753
740
|
let nextPos = attrQuoteType === QUOTE_NONE ? endPos : endPos + 1;
|
|
754
741
|
if (callbacks.attribute !== undefined && attributeNameStart !== -1) {
|
|
755
742
|
nextPos = callbacks.attribute(
|
|
@@ -822,10 +809,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
822
809
|
while (pos < len) {
|
|
823
810
|
const cc = input.charCodeAt(pos);
|
|
824
811
|
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
// cdata-in-html-content, `*-in-input-stream
|
|
828
|
-
// left to `decodeEntities`.
|
|
812
|
+
// Every WHATWG tokenizer state is handled. Omitted: the parse errors
|
|
813
|
+
// needing state an offset scanner lacks (duplicate-attribute,
|
|
814
|
+
// cdata-in-html-content, `*-in-input-stream`) and reference substitution.
|
|
829
815
|
switch (state) {
|
|
830
816
|
// https://html.spec.whatwg.org/multipage/parsing.html#data-state
|
|
831
817
|
case STATE_DATA:
|
|
@@ -849,10 +835,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
849
835
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
850
836
|
pos++;
|
|
851
837
|
} else {
|
|
852
|
-
// Fast-forward over
|
|
853
|
-
// the
|
|
854
|
-
//
|
|
855
|
-
// exactly the code points with their own arcs (`<`, `&`, NUL).
|
|
838
|
+
// Fast-forward over ordinary text without re-entering the switch.
|
|
839
|
+
// Only the data state's "anything else" arc is batched: the stops
|
|
840
|
+
// are exactly the code points with arcs of their own.
|
|
856
841
|
pos++;
|
|
857
842
|
if (nextLt < pos) {
|
|
858
843
|
nextLt = input.indexOf("<", pos);
|
|
@@ -925,10 +910,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
925
910
|
pos++;
|
|
926
911
|
}
|
|
927
912
|
} else if (cc === CC_QUESTION_MARK) {
|
|
928
|
-
//
|
|
929
|
-
//
|
|
930
|
-
// Create a comment token whose data is the empty string. Reconsume in the
|
|
931
|
-
// bogus comment state.
|
|
913
|
+
// `?`: unexpected-question-mark-instead-of-tag-name parse error. Create a
|
|
914
|
+
// comment token with empty data, reconsume in bogus comment.
|
|
932
915
|
reportError(
|
|
933
916
|
"unexpected-question-mark-instead-of-tag-name",
|
|
934
917
|
pos,
|
|
@@ -959,26 +942,20 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
959
942
|
|
|
960
943
|
// https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state
|
|
961
944
|
case STATE_END_TAG_OPEN:
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
// the `<` `/` arc above, the only way into this state, so `cc` is never
|
|
965
|
-
// ASCII alpha here.
|
|
945
|
+
// The spec's ASCII-alpha arc is taken inline by the `<` `/` arc above,
|
|
946
|
+
// the only way into this state, so `cc` is never ASCII alpha here.
|
|
966
947
|
if (cc === CC_GREATER_THAN) {
|
|
967
|
-
//
|
|
968
|
-
//
|
|
969
|
-
//
|
|
970
|
-
// text run: a browser renders nothing for it, and keeping it would
|
|
971
|
-
// resurface as visible text once the text node is re-escaped.
|
|
948
|
+
// `>`: missing-end-tag-name parse error, switch to data. Emitting no
|
|
949
|
+
// token drops `</>` rather than keeping it in the text run, where
|
|
950
|
+
// re-escaping the text node would resurface it as visible text.
|
|
972
951
|
reportError("missing-end-tag-name", pos, pos + 1, "warning");
|
|
973
952
|
flushText(tagStart);
|
|
974
953
|
state = STATE_DATA;
|
|
975
954
|
pos++;
|
|
976
955
|
textStart = pos;
|
|
977
956
|
} else {
|
|
978
|
-
// Anything else
|
|
979
|
-
//
|
|
980
|
-
// comment token whose data is the empty string. Reconsume in the bogus
|
|
981
|
-
// comment state.
|
|
957
|
+
// Anything else: invalid-first-character-of-tag-name parse error. Create a
|
|
958
|
+
// comment token with empty data, reconsume in bogus comment.
|
|
982
959
|
reportError(
|
|
983
960
|
"invalid-first-character-of-tag-name",
|
|
984
961
|
pos,
|
|
@@ -996,11 +973,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
996
973
|
|
|
997
974
|
// https://html.spec.whatwg.org/multipage/parsing.html#tag-name-state
|
|
998
975
|
case STATE_TAG_NAME:
|
|
999
|
-
//
|
|
1000
|
-
// U+0009 CHARACTER TABULATION (tab)
|
|
1001
|
-
// U+000A LINE FEED (LF)
|
|
1002
|
-
// U+000C FORM FEED (FF)
|
|
1003
|
-
// U+0020 SPACE
|
|
976
|
+
// Whitespace (tab, LF, FF, space)
|
|
1004
977
|
// Switch to the before attribute name state.
|
|
1005
978
|
if (isSpace(cc)) {
|
|
1006
979
|
tagNameEnd = pos;
|
|
@@ -1043,20 +1016,13 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1043
1016
|
|
|
1044
1017
|
// https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state
|
|
1045
1018
|
case STATE_BEFORE_ATTRIBUTE_NAME:
|
|
1046
|
-
//
|
|
1047
|
-
// U+0009 CHARACTER TABULATION (tab)
|
|
1048
|
-
// U+000A LINE FEED (LF)
|
|
1049
|
-
// U+000C FORM FEED (FF)
|
|
1050
|
-
// U+0020 SPACE
|
|
1019
|
+
// Whitespace (tab, LF, FF, space)
|
|
1051
1020
|
// Ignore the character.
|
|
1052
1021
|
// Reconsume so space is handled in BEFORE_ATTRIBUTE_NAME
|
|
1053
1022
|
if (isSpace(cc)) {
|
|
1054
1023
|
pos++;
|
|
1055
1024
|
} else if (cc === CC_SOLIDUS || cc === CC_GREATER_THAN) {
|
|
1056
|
-
//
|
|
1057
|
-
// U+003E GREATER-THAN SIGN (>)
|
|
1058
|
-
// EOF
|
|
1059
|
-
// Reconsume in the after attribute name state.
|
|
1025
|
+
// `/` / `>` / EOF: reconsume in after attribute name.
|
|
1060
1026
|
state = STATE_AFTER_ATTRIBUTE_NAME;
|
|
1061
1027
|
// Reconsume
|
|
1062
1028
|
} else if (cc === CC_EQUALS) {
|
|
@@ -1108,15 +1074,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1108
1074
|
|
|
1109
1075
|
// https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state
|
|
1110
1076
|
case STATE_ATTRIBUTE_NAME:
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
1113
|
-
// U+000A LINE FEED (LF)
|
|
1114
|
-
// U+000C FORM FEED (FF)
|
|
1115
|
-
// U+0020 SPACE
|
|
1116
|
-
// U+002F SOLIDUS (/)
|
|
1117
|
-
// U+003E GREATER-THAN SIGN (>)
|
|
1118
|
-
// EOF
|
|
1119
|
-
// Reconsume in the after attribute name state.
|
|
1077
|
+
// Whitespace (tab, LF, FF, space) / `/` / `>` / EOF: reconsume in after
|
|
1078
|
+
// attribute name.
|
|
1120
1079
|
if (isSpace(cc) || cc === CC_SOLIDUS || cc === CC_GREATER_THAN) {
|
|
1121
1080
|
attributeNameEnd = pos;
|
|
1122
1081
|
state = STATE_AFTER_ATTRIBUTE_NAME;
|
|
@@ -1141,10 +1100,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1141
1100
|
"warning"
|
|
1142
1101
|
);
|
|
1143
1102
|
}
|
|
1144
|
-
// Fast-forward over the
|
|
1145
|
-
//
|
|
1146
|
-
//
|
|
1147
|
-
// outer switch then re-handles.
|
|
1103
|
+
// Fast-forward over the attribute name, stopping on a terminator
|
|
1104
|
+
// (space, `/`, `>`, `=`) or on a char needing a per-occurrence parse
|
|
1105
|
+
// error (NUL, `"`, `'`, `<`), which the outer switch re-handles.
|
|
1148
1106
|
pos++;
|
|
1149
1107
|
while (pos < len) {
|
|
1150
1108
|
const c2 = input.charCodeAt(pos);
|
|
@@ -1158,11 +1116,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1158
1116
|
|
|
1159
1117
|
// https://html.spec.whatwg.org/multipage/parsing.html#after-attribute-name-state
|
|
1160
1118
|
case STATE_AFTER_ATTRIBUTE_NAME:
|
|
1161
|
-
//
|
|
1162
|
-
// U+0009 CHARACTER TABULATION (tab)
|
|
1163
|
-
// U+000A LINE FEED (LF)
|
|
1164
|
-
// U+000C FORM FEED (FF)
|
|
1165
|
-
// U+0020 SPACE
|
|
1119
|
+
// Whitespace (tab, LF, FF, space)
|
|
1166
1120
|
// Ignore the character.
|
|
1167
1121
|
if (isSpace(cc)) {
|
|
1168
1122
|
pos++;
|
|
@@ -1201,11 +1155,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1201
1155
|
|
|
1202
1156
|
// https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-value-state
|
|
1203
1157
|
case STATE_BEFORE_ATTRIBUTE_VALUE:
|
|
1204
|
-
//
|
|
1205
|
-
// U+0009 CHARACTER TABULATION (tab)
|
|
1206
|
-
// U+000A LINE FEED (LF)
|
|
1207
|
-
// U+000C FORM FEED (FF)
|
|
1208
|
-
// U+0020 SPACE
|
|
1158
|
+
// Whitespace (tab, LF, FF, space)
|
|
1209
1159
|
// Ignore the character.
|
|
1210
1160
|
if (isSpace(cc)) {
|
|
1211
1161
|
pos++;
|
|
@@ -1254,11 +1204,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1254
1204
|
}
|
|
1255
1205
|
pos = Math.min(nextSQuote, nextAmp, nextNul);
|
|
1256
1206
|
} else if (cc === CC_GREATER_THAN) {
|
|
1257
|
-
//
|
|
1258
|
-
//
|
|
1259
|
-
//
|
|
1260
|
-
// an empty value range pointing at the `>` so the open-tag offset range
|
|
1261
|
-
// still includes the `>`.
|
|
1207
|
+
// `>`: missing-attribute-value parse error, switch to data and emit
|
|
1208
|
+
// the tag token. The attribute's empty value range points at the `>`,
|
|
1209
|
+
// so the open tag's own range still includes it.
|
|
1262
1210
|
reportError("missing-attribute-value", pos, pos + 1, "warning");
|
|
1263
1211
|
attributeValueStart = pos;
|
|
1264
1212
|
attrQuoteType = QUOTE_NONE;
|
|
@@ -1409,11 +1357,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1409
1357
|
|
|
1410
1358
|
// https://html.spec.whatwg.org/multipage/parsing.html#after-attribute-value-(quoted)-state
|
|
1411
1359
|
case STATE_AFTER_ATTRIBUTE_VALUE_QUOTED:
|
|
1412
|
-
//
|
|
1413
|
-
// U+0009 CHARACTER TABULATION (tab)
|
|
1414
|
-
// U+000A LINE FEED (LF)
|
|
1415
|
-
// U+000C FORM FEED (FF)
|
|
1416
|
-
// U+0020 SPACE
|
|
1360
|
+
// Whitespace (tab, LF, FF, space)
|
|
1417
1361
|
// Switch to the before attribute name state.
|
|
1418
1362
|
if (isSpace(cc)) {
|
|
1419
1363
|
state = STATE_BEFORE_ATTRIBUTE_NAME;
|
|
@@ -1449,10 +1393,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1449
1393
|
|
|
1450
1394
|
// https://html.spec.whatwg.org/multipage/parsing.html#self-closing-start-tag-state
|
|
1451
1395
|
case STATE_SELF_CLOSING_START_TAG:
|
|
1452
|
-
//
|
|
1453
|
-
//
|
|
1454
|
-
// Set the self-closing flag of the current tag token. Switch to the data
|
|
1455
|
-
// state. Emit the current tag token.
|
|
1396
|
+
// `>`: Set the self-closing flag of the tag token, switch to data. Emit the
|
|
1397
|
+
// tag token.
|
|
1456
1398
|
if (cc === CC_GREATER_THAN) {
|
|
1457
1399
|
if (input.charCodeAt(tagStart + 1) === CC_SOLIDUS) {
|
|
1458
1400
|
// An end tag emitted with the self-closing flag set is an
|
|
@@ -1481,10 +1423,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1481
1423
|
|
|
1482
1424
|
// https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
|
|
1483
1425
|
case STATE_MARKUP_DECLARATION_OPEN:
|
|
1484
|
-
//
|
|
1485
|
-
//
|
|
1486
|
-
// Consume those two characters, create a comment token whose data
|
|
1487
|
-
// is the empty string, and switch to the comment start state.
|
|
1426
|
+
// `--`: Consume those two characters, create a comment token with empty
|
|
1427
|
+
// data, and switch to the comment start state.
|
|
1488
1428
|
if (
|
|
1489
1429
|
cc === CC_HYPHEN_MINUS &&
|
|
1490
1430
|
input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS
|
|
@@ -1516,13 +1456,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1516
1456
|
doctypeForceQuirks = false;
|
|
1517
1457
|
state = STATE_DOCTYPE;
|
|
1518
1458
|
} else if (
|
|
1519
|
-
//
|
|
1520
|
-
//
|
|
1521
|
-
//
|
|
1522
|
-
// Only when there is an adjusted current node and it is not an
|
|
1523
|
-
// element in the HTML namespace: everywhere else this is the
|
|
1524
|
-
// "anything else" branch below, a bogus comment ending at the first
|
|
1525
|
-
// `>`, which leaves the rest (typically `]]>`) as text.
|
|
1459
|
+
// `[CDATA[`: consume it and switch to CDATA section, but only with an
|
|
1460
|
+
// adjusted current node outside the HTML namespace. Elsewhere it is a
|
|
1461
|
+
// bogus comment, leaving the rest as text.
|
|
1526
1462
|
cc === CC_LEFT_SQUARE_BRACKET &&
|
|
1527
1463
|
input.charCodeAt(pos + 1) === 0x43 /* C */ &&
|
|
1528
1464
|
input.charCodeAt(pos + 2) === 0x44 /* D */ &&
|
|
@@ -1539,10 +1475,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1539
1475
|
commentDataEnd = pos;
|
|
1540
1476
|
state = STATE_CDATA_SECTION;
|
|
1541
1477
|
} else {
|
|
1542
|
-
// Anything else
|
|
1543
|
-
//
|
|
1544
|
-
//
|
|
1545
|
-
// consume anything in the current state).
|
|
1478
|
+
// Anything else: incorrectly-opened-comment parse error. Create a comment
|
|
1479
|
+
// token with empty data, switch to bogus comment (don't consume anything
|
|
1480
|
+
// in the current state).
|
|
1546
1481
|
reportError("incorrectly-opened-comment", tagStart, pos, "warning");
|
|
1547
1482
|
commentStart = tagStart;
|
|
1548
1483
|
commentDataStart = pos;
|
|
@@ -1681,10 +1616,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1681
1616
|
state = STATE_COMMENT_END;
|
|
1682
1617
|
pos++;
|
|
1683
1618
|
} else {
|
|
1684
|
-
// Anything else
|
|
1685
|
-
//
|
|
1686
|
-
// Reconsume in the comment state (so e.g. NULL and `<` are
|
|
1687
|
-
// handled there).
|
|
1619
|
+
// Anything else: append a `-` to the comment data and reconsume in
|
|
1620
|
+
// comment, where NUL and `<` are handled.
|
|
1688
1621
|
commentDataEnd = pos;
|
|
1689
1622
|
state = STATE_COMMENT;
|
|
1690
1623
|
// Reconsume
|
|
@@ -1721,10 +1654,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1721
1654
|
commentDataEnd++;
|
|
1722
1655
|
pos++;
|
|
1723
1656
|
} else {
|
|
1724
|
-
// Anything else
|
|
1725
|
-
//
|
|
1726
|
-
// data. Reconsume in the comment state (so NULL and `<` are
|
|
1727
|
-
// handled there).
|
|
1657
|
+
// Anything else: append two `-` to the comment data and reconsume in
|
|
1658
|
+
// comment, where NUL and `<` are handled.
|
|
1728
1659
|
commentDataEnd = pos;
|
|
1729
1660
|
state = STATE_COMMENT;
|
|
1730
1661
|
// Reconsume
|
|
@@ -1733,11 +1664,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1733
1664
|
|
|
1734
1665
|
// https://html.spec.whatwg.org/multipage/parsing.html#comment-end-bang-state
|
|
1735
1666
|
case STATE_COMMENT_END_BANG:
|
|
1736
|
-
//
|
|
1737
|
-
//
|
|
1738
|
-
// Append two U+002D HYPHEN-MINUS characters (-) and a U+0021 EXCLAMATION
|
|
1739
|
-
// MARK character (!) to the comment token's data. Switch to the comment end
|
|
1740
|
-
// dash state.
|
|
1667
|
+
// `-`: Append two `-` and a `!` to the comment data, switch to comment end
|
|
1668
|
+
// dash.
|
|
1741
1669
|
if (cc === CC_HYPHEN_MINUS) {
|
|
1742
1670
|
commentDataEnd = pos;
|
|
1743
1671
|
state = STATE_COMMENT_END_DASH;
|
|
@@ -1761,10 +1689,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1761
1689
|
textStart = nextPos;
|
|
1762
1690
|
pos = nextPos;
|
|
1763
1691
|
} else {
|
|
1764
|
-
// Anything else
|
|
1765
|
-
//
|
|
1766
|
-
// MARK character (!) to the comment token's data. Reconsume in the comment
|
|
1767
|
-
// state (so NULL and `<` are handled there).
|
|
1692
|
+
// Anything else: append two `-` and a `!` to the comment data and
|
|
1693
|
+
// reconsume in comment, where NUL and `<` are handled.
|
|
1768
1694
|
commentDataEnd = pos;
|
|
1769
1695
|
state = STATE_COMMENT;
|
|
1770
1696
|
// Reconsume
|
|
@@ -1813,10 +1739,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1813
1739
|
|
|
1814
1740
|
// https://html.spec.whatwg.org/multipage/parsing.html#comment-less-than-sign-state
|
|
1815
1741
|
case STATE_COMMENT_LESS_THAN_SIGN:
|
|
1816
|
-
//
|
|
1817
|
-
//
|
|
1818
|
-
// Append the current input character to the comment token's data. Switch to
|
|
1819
|
-
// the comment less-than sign bang state.
|
|
1742
|
+
// `!`: Append the input character to the comment data, switch to comment
|
|
1743
|
+
// less-than sign bang.
|
|
1820
1744
|
if (cc === CC_EXCLAMATION_MARK) {
|
|
1821
1745
|
state = STATE_COMMENT_LESS_THAN_SIGN_BANG;
|
|
1822
1746
|
pos++;
|
|
@@ -1868,12 +1792,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1868
1792
|
|
|
1869
1793
|
// https://html.spec.whatwg.org/multipage/parsing.html#comment-less-than-sign-bang-dash-dash-state
|
|
1870
1794
|
case STATE_COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH:
|
|
1871
|
-
//
|
|
1872
|
-
//
|
|
1873
|
-
// EOF
|
|
1874
|
-
// Reconsume in the comment end state.
|
|
1875
|
-
// Anything else
|
|
1876
|
-
// This is a nested-comment parse error. Reconsume in the comment end state.
|
|
1795
|
+
// `>` / EOF: reconsume in comment end. Anything else: nested-comment parse
|
|
1796
|
+
// error, reconsume in comment end.
|
|
1877
1797
|
if (cc !== CC_GREATER_THAN) {
|
|
1878
1798
|
reportError("nested-comment", pos, pos + 1, "warning");
|
|
1879
1799
|
}
|
|
@@ -1883,11 +1803,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1883
1803
|
|
|
1884
1804
|
// https://html.spec.whatwg.org/multipage/parsing.html#doctype-state
|
|
1885
1805
|
case STATE_DOCTYPE:
|
|
1886
|
-
//
|
|
1887
|
-
// U+0009 CHARACTER TABULATION (tab)
|
|
1888
|
-
// U+000A LINE FEED (LF)
|
|
1889
|
-
// U+000C FORM FEED (FF)
|
|
1890
|
-
// U+0020 SPACE
|
|
1806
|
+
// Whitespace (tab, LF, FF, space)
|
|
1891
1807
|
// Switch to the before DOCTYPE name state.
|
|
1892
1808
|
if (isSpace(cc)) {
|
|
1893
1809
|
state = STATE_BEFORE_DOCTYPE_NAME;
|
|
@@ -1912,29 +1828,21 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1912
1828
|
|
|
1913
1829
|
// https://html.spec.whatwg.org/multipage/parsing.html#before-doctype-name-state
|
|
1914
1830
|
case STATE_BEFORE_DOCTYPE_NAME:
|
|
1915
|
-
//
|
|
1916
|
-
// U+0009 CHARACTER TABULATION (tab)
|
|
1917
|
-
// U+000A LINE FEED (LF)
|
|
1918
|
-
// U+000C FORM FEED (FF)
|
|
1919
|
-
// U+0020 SPACE
|
|
1831
|
+
// Whitespace (tab, LF, FF, space)
|
|
1920
1832
|
// Ignore the character.
|
|
1921
1833
|
if (isSpace(cc)) {
|
|
1922
1834
|
pos++;
|
|
1923
1835
|
} else if (cc === CC_NULL) {
|
|
1924
|
-
//
|
|
1925
|
-
//
|
|
1926
|
-
// token. Set the token's name to a U+FFFD REPLACEMENT CHARACTER character.
|
|
1927
|
-
// Switch to the DOCTYPE name state.
|
|
1836
|
+
// NUL: unexpected-null-character parse error. Create a new DOCTYPE token.
|
|
1837
|
+
// Set the token's name to a U+FFFD, switch to DOCTYPE name.
|
|
1928
1838
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
1929
1839
|
state = STATE_DOCTYPE_NAME;
|
|
1930
1840
|
doctypeNameStart = pos;
|
|
1931
1841
|
pos++;
|
|
1932
1842
|
doctypeNameEnd = pos;
|
|
1933
1843
|
} else if (cc === CC_GREATER_THAN) {
|
|
1934
|
-
//
|
|
1935
|
-
//
|
|
1936
|
-
// Set its force-quirks flag to on. Switch to the data state. Emit the
|
|
1937
|
-
// current token.
|
|
1844
|
+
// `>`: missing-doctype-name parse error. Create a new DOCTYPE token, force
|
|
1845
|
+
// quirks, switch to data. Emit the token.
|
|
1938
1846
|
reportError("missing-doctype-name", pos, pos + 1, "warning");
|
|
1939
1847
|
doctypeForceQuirks = true;
|
|
1940
1848
|
let nextPos = pos + 1;
|
|
@@ -1956,13 +1864,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1956
1864
|
textStart = nextPos;
|
|
1957
1865
|
pos = nextPos;
|
|
1958
1866
|
} else {
|
|
1959
|
-
// ASCII upper alpha
|
|
1960
|
-
//
|
|
1961
|
-
//
|
|
1962
|
-
// point). Switch to the DOCTYPE name state.
|
|
1963
|
-
// Anything else
|
|
1964
|
-
// Create a new DOCTYPE token. Set the token's name to the current input
|
|
1965
|
-
// character. Switch to the DOCTYPE name state.
|
|
1867
|
+
// ASCII upper alpha, and anything else: create a DOCTYPE token, set
|
|
1868
|
+
// its name to the input character — lowercased in the first case —
|
|
1869
|
+
// and switch to DOCTYPE name.
|
|
1966
1870
|
state = STATE_DOCTYPE_NAME;
|
|
1967
1871
|
doctypeNameStart = pos;
|
|
1968
1872
|
pos++;
|
|
@@ -1972,11 +1876,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
1972
1876
|
|
|
1973
1877
|
// https://html.spec.whatwg.org/multipage/parsing.html#doctype-name-state
|
|
1974
1878
|
case STATE_DOCTYPE_NAME:
|
|
1975
|
-
//
|
|
1976
|
-
// U+0009 CHARACTER TABULATION (tab)
|
|
1977
|
-
// U+000A LINE FEED (LF)
|
|
1978
|
-
// U+000C FORM FEED (FF)
|
|
1979
|
-
// U+0020 SPACE
|
|
1879
|
+
// Whitespace (tab, LF, FF, space)
|
|
1980
1880
|
// Switch to the after DOCTYPE name state.
|
|
1981
1881
|
if (isSpace(cc)) {
|
|
1982
1882
|
state = STATE_AFTER_DOCTYPE_NAME;
|
|
@@ -2010,11 +1910,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2010
1910
|
pos++;
|
|
2011
1911
|
doctypeNameEnd = pos;
|
|
2012
1912
|
} else {
|
|
2013
|
-
// ASCII upper alpha
|
|
2014
|
-
//
|
|
2015
|
-
//
|
|
2016
|
-
// Anything else
|
|
2017
|
-
// Append the current input character to the current DOCTYPE token's name.
|
|
1913
|
+
// ASCII upper alpha: Append the lowercased input character to the DOCTYPE
|
|
1914
|
+
// token's name. Anything else: Append the input character to the DOCTYPE
|
|
1915
|
+
// token's name.
|
|
2018
1916
|
pos++;
|
|
2019
1917
|
doctypeNameEnd = pos;
|
|
2020
1918
|
}
|
|
@@ -2024,10 +1922,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2024
1922
|
case STATE_AFTER_DOCTYPE_NAME:
|
|
2025
1923
|
// Consume the next input character:
|
|
2026
1924
|
if (isSpace(cc)) {
|
|
2027
|
-
//
|
|
2028
|
-
// U+000A LINE FEED (LF)
|
|
2029
|
-
// U+000C FORM FEED (FF)
|
|
2030
|
-
// U+0020 SPACE
|
|
1925
|
+
// Whitespace (tab, LF, FF, space)
|
|
2031
1926
|
// Ignore the character.
|
|
2032
1927
|
pos++;
|
|
2033
1928
|
} else if (cc === CC_GREATER_THAN) {
|
|
@@ -2076,10 +1971,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2076
1971
|
pos += 6;
|
|
2077
1972
|
state = STATE_AFTER_DOCTYPE_SYSTEM_KEYWORD;
|
|
2078
1973
|
} else {
|
|
2079
|
-
// Anything else
|
|
2080
|
-
//
|
|
2081
|
-
// the current DOCTYPE token's force-quirks flag to on. Reconsume in the
|
|
2082
|
-
// bogus DOCTYPE state.
|
|
1974
|
+
// Anything else: invalid-character-sequence-after-doctype-name parse
|
|
1975
|
+
// error, force quirks, reconsume in bogus DOCTYPE.
|
|
2083
1976
|
reportError(
|
|
2084
1977
|
"invalid-character-sequence-after-doctype-name",
|
|
2085
1978
|
pos,
|
|
@@ -2095,19 +1988,14 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2095
1988
|
case STATE_AFTER_DOCTYPE_PUBLIC_KEYWORD:
|
|
2096
1989
|
// Consume the next input character:
|
|
2097
1990
|
if (isSpace(cc)) {
|
|
2098
|
-
//
|
|
2099
|
-
// U+000A LINE FEED (LF)
|
|
2100
|
-
// U+000C FORM FEED (FF)
|
|
2101
|
-
// U+0020 SPACE
|
|
1991
|
+
// Whitespace (tab, LF, FF, space)
|
|
2102
1992
|
// Switch to the before DOCTYPE public identifier state.
|
|
2103
1993
|
state = STATE_BEFORE_DOCTYPE_PUBLIC_IDENTIFIER;
|
|
2104
1994
|
pos++;
|
|
2105
1995
|
} else if (cc === CC_QUOTATION_MARK) {
|
|
2106
|
-
//
|
|
2107
|
-
//
|
|
2108
|
-
//
|
|
2109
|
-
// (not missing), then switch to the DOCTYPE public identifier
|
|
2110
|
-
// (double-quoted) state.
|
|
1996
|
+
// `"`: missing-whitespace-after-doctype-public-keyword parse error. Set
|
|
1997
|
+
// the DOCTYPE token's public identifier to the empty string (not missing),
|
|
1998
|
+
// then switch to the DOCTYPE public identifier (double-quoted) state.
|
|
2111
1999
|
reportError(
|
|
2112
2000
|
"missing-whitespace-after-doctype-public-keyword",
|
|
2113
2001
|
pos,
|
|
@@ -2119,11 +2007,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2119
2007
|
doctypePublicStart = pos;
|
|
2120
2008
|
doctypePublicEnd = pos;
|
|
2121
2009
|
} else if (cc === CC_APOSTROPHE) {
|
|
2122
|
-
//
|
|
2123
|
-
//
|
|
2124
|
-
//
|
|
2125
|
-
// (not missing), then switch to the DOCTYPE public identifier
|
|
2126
|
-
// (single-quoted) state.
|
|
2010
|
+
// `'`: missing-whitespace-after-doctype-public-keyword parse error. Set
|
|
2011
|
+
// the DOCTYPE token's public identifier to the empty string (not missing),
|
|
2012
|
+
// then switch to the DOCTYPE public identifier (single-quoted) state.
|
|
2127
2013
|
reportError(
|
|
2128
2014
|
"missing-whitespace-after-doctype-public-keyword",
|
|
2129
2015
|
pos,
|
|
@@ -2135,10 +2021,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2135
2021
|
doctypePublicStart = pos;
|
|
2136
2022
|
doctypePublicEnd = pos;
|
|
2137
2023
|
} else if (cc === CC_GREATER_THAN) {
|
|
2138
|
-
//
|
|
2139
|
-
//
|
|
2140
|
-
// DOCTYPE token's force-quirks flag to on. Switch to the data state. Emit
|
|
2141
|
-
// the current DOCTYPE token.
|
|
2024
|
+
// `>`: missing-doctype-public-identifier parse error, force quirks, switch
|
|
2025
|
+
// to data. Emit the DOCTYPE token.
|
|
2142
2026
|
reportError(
|
|
2143
2027
|
"missing-doctype-public-identifier",
|
|
2144
2028
|
pos,
|
|
@@ -2165,10 +2049,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2165
2049
|
textStart = nextPos;
|
|
2166
2050
|
pos = nextPos;
|
|
2167
2051
|
} else {
|
|
2168
|
-
// Anything else
|
|
2169
|
-
//
|
|
2170
|
-
// the current DOCTYPE token's force-quirks flag to on. Reconsume in the
|
|
2171
|
-
// bogus DOCTYPE state.
|
|
2052
|
+
// Anything else: missing-quote-before-doctype-public-identifier parse
|
|
2053
|
+
// error, force quirks, reconsume in bogus DOCTYPE.
|
|
2172
2054
|
reportError(
|
|
2173
2055
|
"missing-quote-before-doctype-public-identifier",
|
|
2174
2056
|
pos,
|
|
@@ -2184,35 +2066,28 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2184
2066
|
case STATE_BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:
|
|
2185
2067
|
// Consume the next input character:
|
|
2186
2068
|
if (isSpace(cc)) {
|
|
2187
|
-
//
|
|
2188
|
-
// U+000A LINE FEED (LF)
|
|
2189
|
-
// U+000C FORM FEED (FF)
|
|
2190
|
-
// U+0020 SPACE
|
|
2069
|
+
// Whitespace (tab, LF, FF, space)
|
|
2191
2070
|
// Ignore the character.
|
|
2192
2071
|
pos++;
|
|
2193
2072
|
} else if (cc === CC_QUOTATION_MARK) {
|
|
2194
|
-
//
|
|
2195
|
-
//
|
|
2196
|
-
//
|
|
2197
|
-
// (double-quoted) state.
|
|
2073
|
+
// `"`: Set the DOCTYPE token's public identifier to the empty string (not
|
|
2074
|
+
// missing), then switch to the DOCTYPE public identifier (double-quoted)
|
|
2075
|
+
// state.
|
|
2198
2076
|
state = STATE_DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED;
|
|
2199
2077
|
pos++;
|
|
2200
2078
|
doctypePublicStart = pos;
|
|
2201
2079
|
doctypePublicEnd = pos;
|
|
2202
2080
|
} else if (cc === CC_APOSTROPHE) {
|
|
2203
|
-
//
|
|
2204
|
-
//
|
|
2205
|
-
//
|
|
2206
|
-
// (single-quoted) state.
|
|
2081
|
+
// `'`: Set the DOCTYPE token's public identifier to the empty string (not
|
|
2082
|
+
// missing), then switch to the DOCTYPE public identifier (single-quoted)
|
|
2083
|
+
// state.
|
|
2207
2084
|
state = STATE_DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED;
|
|
2208
2085
|
pos++;
|
|
2209
2086
|
doctypePublicStart = pos;
|
|
2210
2087
|
doctypePublicEnd = pos;
|
|
2211
2088
|
} else if (cc === CC_GREATER_THAN) {
|
|
2212
|
-
//
|
|
2213
|
-
//
|
|
2214
|
-
// DOCTYPE token's force-quirks flag to on. Switch to the data state. Emit
|
|
2215
|
-
// the current DOCTYPE token.
|
|
2089
|
+
// `>`: missing-doctype-public-identifier parse error, force quirks, switch
|
|
2090
|
+
// to data. Emit the DOCTYPE token.
|
|
2216
2091
|
reportError(
|
|
2217
2092
|
"missing-doctype-public-identifier",
|
|
2218
2093
|
pos,
|
|
@@ -2239,10 +2114,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2239
2114
|
textStart = nextPos;
|
|
2240
2115
|
pos = nextPos;
|
|
2241
2116
|
} else {
|
|
2242
|
-
// Anything else
|
|
2243
|
-
//
|
|
2244
|
-
// the current DOCTYPE token's force-quirks flag to on. Reconsume in the
|
|
2245
|
-
// bogus DOCTYPE state.
|
|
2117
|
+
// Anything else: missing-quote-before-doctype-public-identifier parse
|
|
2118
|
+
// error, force quirks, reconsume in bogus DOCTYPE.
|
|
2246
2119
|
reportError(
|
|
2247
2120
|
"missing-quote-before-doctype-public-identifier",
|
|
2248
2121
|
pos,
|
|
@@ -2263,18 +2136,14 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2263
2136
|
state = STATE_AFTER_DOCTYPE_PUBLIC_IDENTIFIER;
|
|
2264
2137
|
pos++;
|
|
2265
2138
|
} else if (cc === CC_NULL) {
|
|
2266
|
-
// U+
|
|
2267
|
-
//
|
|
2268
|
-
// REPLACEMENT CHARACTER character to the current DOCTYPE token's public
|
|
2269
|
-
// identifier.
|
|
2139
|
+
// NUL: unexpected-null-character parse error. Append a U+FFFD to the
|
|
2140
|
+
// DOCTYPE token's public identifier.
|
|
2270
2141
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2271
2142
|
pos++;
|
|
2272
2143
|
doctypePublicEnd = pos;
|
|
2273
2144
|
} else if (cc === CC_GREATER_THAN) {
|
|
2274
|
-
//
|
|
2275
|
-
//
|
|
2276
|
-
// DOCTYPE token's force-quirks flag to on. Switch to the data state. Emit
|
|
2277
|
-
// the current DOCTYPE token.
|
|
2145
|
+
// `>`: abrupt-doctype-public-identifier parse error, force quirks, switch
|
|
2146
|
+
// to data. Emit the DOCTYPE token.
|
|
2278
2147
|
reportError(
|
|
2279
2148
|
"abrupt-doctype-public-identifier",
|
|
2280
2149
|
pos,
|
|
@@ -2318,18 +2187,14 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2318
2187
|
state = STATE_AFTER_DOCTYPE_PUBLIC_IDENTIFIER;
|
|
2319
2188
|
pos++;
|
|
2320
2189
|
} else if (cc === CC_NULL) {
|
|
2321
|
-
// U+
|
|
2322
|
-
//
|
|
2323
|
-
// REPLACEMENT CHARACTER character to the current DOCTYPE token's public
|
|
2324
|
-
// identifier.
|
|
2190
|
+
// NUL: unexpected-null-character parse error. Append a U+FFFD to the
|
|
2191
|
+
// DOCTYPE token's public identifier.
|
|
2325
2192
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2326
2193
|
pos++;
|
|
2327
2194
|
doctypePublicEnd = pos;
|
|
2328
2195
|
} else if (cc === CC_GREATER_THAN) {
|
|
2329
|
-
//
|
|
2330
|
-
//
|
|
2331
|
-
// DOCTYPE token's force-quirks flag to on. Switch to the data state. Emit
|
|
2332
|
-
// the current DOCTYPE token.
|
|
2196
|
+
// `>`: abrupt-doctype-public-identifier parse error, force quirks, switch
|
|
2197
|
+
// to data. Emit the DOCTYPE token.
|
|
2333
2198
|
reportError(
|
|
2334
2199
|
"abrupt-doctype-public-identifier",
|
|
2335
2200
|
pos,
|
|
@@ -2368,10 +2233,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2368
2233
|
case STATE_AFTER_DOCTYPE_PUBLIC_IDENTIFIER:
|
|
2369
2234
|
// Consume the next input character:
|
|
2370
2235
|
if (isSpace(cc)) {
|
|
2371
|
-
//
|
|
2372
|
-
// U+000A LINE FEED (LF)
|
|
2373
|
-
// U+000C FORM FEED (FF)
|
|
2374
|
-
// U+0020 SPACE
|
|
2236
|
+
// Whitespace (tab, LF, FF, space)
|
|
2375
2237
|
// Switch to the between DOCTYPE public and system identifiers state.
|
|
2376
2238
|
state = STATE_BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS;
|
|
2377
2239
|
pos++;
|
|
@@ -2397,11 +2259,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2397
2259
|
textStart = nextPos;
|
|
2398
2260
|
pos = nextPos;
|
|
2399
2261
|
} else if (cc === CC_QUOTATION_MARK) {
|
|
2400
|
-
//
|
|
2401
|
-
//
|
|
2402
|
-
//
|
|
2403
|
-
// identifier to the empty string (not missing), then switch
|
|
2404
|
-
// to the DOCTYPE system identifier (double-quoted) state.
|
|
2262
|
+
// `"`: missing-whitespace-between-doctype-public-and-system-identifiers
|
|
2263
|
+
// parse error. Set the system identifier to empty (not missing), then
|
|
2264
|
+
// switch to DOCTYPE system identifier (double-quoted).
|
|
2405
2265
|
reportError(
|
|
2406
2266
|
"missing-whitespace-between-doctype-public-and-system-identifiers",
|
|
2407
2267
|
pos,
|
|
@@ -2413,11 +2273,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2413
2273
|
doctypeSystemStart = pos;
|
|
2414
2274
|
doctypeSystemEnd = pos;
|
|
2415
2275
|
} else if (cc === CC_APOSTROPHE) {
|
|
2416
|
-
//
|
|
2417
|
-
//
|
|
2418
|
-
//
|
|
2419
|
-
// identifier to the empty string (not missing), then switch
|
|
2420
|
-
// to the DOCTYPE system identifier (single-quoted) state.
|
|
2276
|
+
// `'`: missing-whitespace-between-doctype-public-and-system-identifiers
|
|
2277
|
+
// parse error. Set the system identifier to empty (not missing), then
|
|
2278
|
+
// switch to DOCTYPE system identifier (single-quoted).
|
|
2421
2279
|
reportError(
|
|
2422
2280
|
"missing-whitespace-between-doctype-public-and-system-identifiers",
|
|
2423
2281
|
pos,
|
|
@@ -2429,10 +2287,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2429
2287
|
doctypeSystemStart = pos;
|
|
2430
2288
|
doctypeSystemEnd = pos;
|
|
2431
2289
|
} else {
|
|
2432
|
-
// Anything else
|
|
2433
|
-
//
|
|
2434
|
-
// the current DOCTYPE token's force-quirks flag to on. Reconsume in the
|
|
2435
|
-
// bogus DOCTYPE state.
|
|
2290
|
+
// Anything else: missing-quote-before-doctype-system-identifier parse
|
|
2291
|
+
// error, force quirks, reconsume in bogus DOCTYPE.
|
|
2436
2292
|
reportError(
|
|
2437
2293
|
"missing-quote-before-doctype-system-identifier",
|
|
2438
2294
|
pos,
|
|
@@ -2448,10 +2304,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2448
2304
|
case STATE_BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:
|
|
2449
2305
|
// Consume the next input character:
|
|
2450
2306
|
if (isSpace(cc)) {
|
|
2451
|
-
//
|
|
2452
|
-
// U+000A LINE FEED (LF)
|
|
2453
|
-
// U+000C FORM FEED (FF)
|
|
2454
|
-
// U+0020 SPACE
|
|
2307
|
+
// Whitespace (tab, LF, FF, space)
|
|
2455
2308
|
// Ignore the character.
|
|
2456
2309
|
pos++;
|
|
2457
2310
|
} else if (cc === CC_GREATER_THAN) {
|
|
@@ -2476,28 +2329,24 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2476
2329
|
textStart = nextPos;
|
|
2477
2330
|
pos = nextPos;
|
|
2478
2331
|
} else if (cc === CC_QUOTATION_MARK) {
|
|
2479
|
-
//
|
|
2480
|
-
//
|
|
2481
|
-
//
|
|
2482
|
-
// (double-quoted) state.
|
|
2332
|
+
// `"`: Set the DOCTYPE token's system identifier to the empty string (not
|
|
2333
|
+
// missing), then switch to the DOCTYPE system identifier (double-quoted)
|
|
2334
|
+
// state.
|
|
2483
2335
|
state = STATE_DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED;
|
|
2484
2336
|
pos++;
|
|
2485
2337
|
doctypeSystemStart = pos;
|
|
2486
2338
|
doctypeSystemEnd = pos;
|
|
2487
2339
|
} else if (cc === CC_APOSTROPHE) {
|
|
2488
|
-
//
|
|
2489
|
-
//
|
|
2490
|
-
//
|
|
2491
|
-
// (single-quoted) state.
|
|
2340
|
+
// `'`: Set the DOCTYPE token's system identifier to the empty string (not
|
|
2341
|
+
// missing), then switch to the DOCTYPE system identifier (single-quoted)
|
|
2342
|
+
// state.
|
|
2492
2343
|
state = STATE_DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED;
|
|
2493
2344
|
pos++;
|
|
2494
2345
|
doctypeSystemStart = pos;
|
|
2495
2346
|
doctypeSystemEnd = pos;
|
|
2496
2347
|
} else {
|
|
2497
|
-
// Anything else
|
|
2498
|
-
//
|
|
2499
|
-
// the current DOCTYPE token's force-quirks flag to on. Reconsume in the
|
|
2500
|
-
// bogus DOCTYPE state.
|
|
2348
|
+
// Anything else: missing-quote-before-doctype-system-identifier parse
|
|
2349
|
+
// error, force quirks, reconsume in bogus DOCTYPE.
|
|
2501
2350
|
reportError(
|
|
2502
2351
|
"missing-quote-before-doctype-system-identifier",
|
|
2503
2352
|
pos,
|
|
@@ -2513,19 +2362,14 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2513
2362
|
case STATE_AFTER_DOCTYPE_SYSTEM_KEYWORD:
|
|
2514
2363
|
// Consume the next input character:
|
|
2515
2364
|
if (isSpace(cc)) {
|
|
2516
|
-
//
|
|
2517
|
-
// U+000A LINE FEED (LF)
|
|
2518
|
-
// U+000C FORM FEED (FF)
|
|
2519
|
-
// U+0020 SPACE
|
|
2365
|
+
// Whitespace (tab, LF, FF, space)
|
|
2520
2366
|
// Switch to the before DOCTYPE system identifier state.
|
|
2521
2367
|
state = STATE_BEFORE_DOCTYPE_SYSTEM_IDENTIFIER;
|
|
2522
2368
|
pos++;
|
|
2523
2369
|
} else if (cc === CC_QUOTATION_MARK) {
|
|
2524
|
-
//
|
|
2525
|
-
//
|
|
2526
|
-
//
|
|
2527
|
-
// (not missing), then switch to the DOCTYPE system identifier
|
|
2528
|
-
// (double-quoted) state.
|
|
2370
|
+
// `"`: missing-whitespace-after-doctype-system-keyword parse error. Set
|
|
2371
|
+
// the DOCTYPE token's system identifier to the empty string (not missing),
|
|
2372
|
+
// then switch to the DOCTYPE system identifier (double-quoted) state.
|
|
2529
2373
|
reportError(
|
|
2530
2374
|
"missing-whitespace-after-doctype-system-keyword",
|
|
2531
2375
|
pos,
|
|
@@ -2537,11 +2381,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2537
2381
|
doctypeSystemStart = pos;
|
|
2538
2382
|
doctypeSystemEnd = pos;
|
|
2539
2383
|
} else if (cc === CC_APOSTROPHE) {
|
|
2540
|
-
//
|
|
2541
|
-
//
|
|
2542
|
-
//
|
|
2543
|
-
// (not missing), then switch to the DOCTYPE system identifier
|
|
2544
|
-
// (single-quoted) state.
|
|
2384
|
+
// `'`: missing-whitespace-after-doctype-system-keyword parse error. Set
|
|
2385
|
+
// the DOCTYPE token's system identifier to the empty string (not missing),
|
|
2386
|
+
// then switch to the DOCTYPE system identifier (single-quoted) state.
|
|
2545
2387
|
reportError(
|
|
2546
2388
|
"missing-whitespace-after-doctype-system-keyword",
|
|
2547
2389
|
pos,
|
|
@@ -2553,10 +2395,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2553
2395
|
doctypeSystemStart = pos;
|
|
2554
2396
|
doctypeSystemEnd = pos;
|
|
2555
2397
|
} else if (cc === CC_GREATER_THAN) {
|
|
2556
|
-
//
|
|
2557
|
-
//
|
|
2558
|
-
// DOCTYPE token's force-quirks flag to on. Switch to the data state. Emit
|
|
2559
|
-
// the current DOCTYPE token.
|
|
2398
|
+
// `>`: missing-doctype-system-identifier parse error, force quirks, switch
|
|
2399
|
+
// to data. Emit the DOCTYPE token.
|
|
2560
2400
|
reportError(
|
|
2561
2401
|
"missing-doctype-system-identifier",
|
|
2562
2402
|
pos,
|
|
@@ -2583,10 +2423,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2583
2423
|
textStart = nextPos;
|
|
2584
2424
|
pos = nextPos;
|
|
2585
2425
|
} else {
|
|
2586
|
-
// Anything else
|
|
2587
|
-
//
|
|
2588
|
-
// the current DOCTYPE token's force-quirks flag to on. Reconsume in the
|
|
2589
|
-
// bogus DOCTYPE state.
|
|
2426
|
+
// Anything else: missing-quote-before-doctype-system-identifier parse
|
|
2427
|
+
// error, force quirks, reconsume in bogus DOCTYPE.
|
|
2590
2428
|
reportError(
|
|
2591
2429
|
"missing-quote-before-doctype-system-identifier",
|
|
2592
2430
|
pos,
|
|
@@ -2602,35 +2440,28 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2602
2440
|
case STATE_BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:
|
|
2603
2441
|
// Consume the next input character:
|
|
2604
2442
|
if (isSpace(cc)) {
|
|
2605
|
-
//
|
|
2606
|
-
// U+000A LINE FEED (LF)
|
|
2607
|
-
// U+000C FORM FEED (FF)
|
|
2608
|
-
// U+0020 SPACE
|
|
2443
|
+
// Whitespace (tab, LF, FF, space)
|
|
2609
2444
|
// Ignore the character.
|
|
2610
2445
|
pos++;
|
|
2611
2446
|
} else if (cc === CC_QUOTATION_MARK) {
|
|
2612
|
-
//
|
|
2613
|
-
//
|
|
2614
|
-
//
|
|
2615
|
-
// (double-quoted) state.
|
|
2447
|
+
// `"`: Set the DOCTYPE token's system identifier to the empty string (not
|
|
2448
|
+
// missing), then switch to the DOCTYPE system identifier (double-quoted)
|
|
2449
|
+
// state.
|
|
2616
2450
|
state = STATE_DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED;
|
|
2617
2451
|
pos++;
|
|
2618
2452
|
doctypeSystemStart = pos;
|
|
2619
2453
|
doctypeSystemEnd = pos;
|
|
2620
2454
|
} else if (cc === CC_APOSTROPHE) {
|
|
2621
|
-
//
|
|
2622
|
-
//
|
|
2623
|
-
//
|
|
2624
|
-
// (single-quoted) state.
|
|
2455
|
+
// `'`: Set the DOCTYPE token's system identifier to the empty string (not
|
|
2456
|
+
// missing), then switch to the DOCTYPE system identifier (single-quoted)
|
|
2457
|
+
// state.
|
|
2625
2458
|
state = STATE_DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED;
|
|
2626
2459
|
pos++;
|
|
2627
2460
|
doctypeSystemStart = pos;
|
|
2628
2461
|
doctypeSystemEnd = pos;
|
|
2629
2462
|
} else if (cc === CC_GREATER_THAN) {
|
|
2630
|
-
//
|
|
2631
|
-
//
|
|
2632
|
-
// DOCTYPE token's force-quirks flag to on. Switch to the data state. Emit
|
|
2633
|
-
// the current DOCTYPE token.
|
|
2463
|
+
// `>`: missing-doctype-system-identifier parse error, force quirks, switch
|
|
2464
|
+
// to data. Emit the DOCTYPE token.
|
|
2634
2465
|
reportError(
|
|
2635
2466
|
"missing-doctype-system-identifier",
|
|
2636
2467
|
pos,
|
|
@@ -2657,10 +2488,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2657
2488
|
textStart = nextPos;
|
|
2658
2489
|
pos = nextPos;
|
|
2659
2490
|
} else {
|
|
2660
|
-
// Anything else
|
|
2661
|
-
//
|
|
2662
|
-
// the current DOCTYPE token's force-quirks flag to on. Reconsume in the
|
|
2663
|
-
// bogus DOCTYPE state.
|
|
2491
|
+
// Anything else: missing-quote-before-doctype-system-identifier parse
|
|
2492
|
+
// error, force quirks, reconsume in bogus DOCTYPE.
|
|
2664
2493
|
reportError(
|
|
2665
2494
|
"missing-quote-before-doctype-system-identifier",
|
|
2666
2495
|
pos,
|
|
@@ -2681,18 +2510,14 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2681
2510
|
state = STATE_AFTER_DOCTYPE_SYSTEM_IDENTIFIER;
|
|
2682
2511
|
pos++;
|
|
2683
2512
|
} else if (cc === CC_NULL) {
|
|
2684
|
-
// U+
|
|
2685
|
-
//
|
|
2686
|
-
// REPLACEMENT CHARACTER character to the current DOCTYPE token's system
|
|
2687
|
-
// identifier.
|
|
2513
|
+
// NUL: unexpected-null-character parse error. Append a U+FFFD to the
|
|
2514
|
+
// DOCTYPE token's system identifier.
|
|
2688
2515
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2689
2516
|
pos++;
|
|
2690
2517
|
doctypeSystemEnd = pos;
|
|
2691
2518
|
} else if (cc === CC_GREATER_THAN) {
|
|
2692
|
-
//
|
|
2693
|
-
//
|
|
2694
|
-
// DOCTYPE token's force-quirks flag to on. Switch to the data state. Emit
|
|
2695
|
-
// the current DOCTYPE token.
|
|
2519
|
+
// `>`: abrupt-doctype-system-identifier parse error, force quirks, switch
|
|
2520
|
+
// to data. Emit the DOCTYPE token.
|
|
2696
2521
|
reportError(
|
|
2697
2522
|
"abrupt-doctype-system-identifier",
|
|
2698
2523
|
pos,
|
|
@@ -2736,18 +2561,14 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2736
2561
|
state = STATE_AFTER_DOCTYPE_SYSTEM_IDENTIFIER;
|
|
2737
2562
|
pos++;
|
|
2738
2563
|
} else if (cc === CC_NULL) {
|
|
2739
|
-
// U+
|
|
2740
|
-
//
|
|
2741
|
-
// REPLACEMENT CHARACTER character to the current DOCTYPE token's system
|
|
2742
|
-
// identifier.
|
|
2564
|
+
// NUL: unexpected-null-character parse error. Append a U+FFFD to the
|
|
2565
|
+
// DOCTYPE token's system identifier.
|
|
2743
2566
|
reportError("unexpected-null-character", pos, pos + 1, "warning");
|
|
2744
2567
|
pos++;
|
|
2745
2568
|
doctypeSystemEnd = pos;
|
|
2746
2569
|
} else if (cc === CC_GREATER_THAN) {
|
|
2747
|
-
//
|
|
2748
|
-
//
|
|
2749
|
-
// DOCTYPE token's force-quirks flag to on. Switch to the data state. Emit
|
|
2750
|
-
// the current DOCTYPE token.
|
|
2570
|
+
// `>`: abrupt-doctype-system-identifier parse error, force quirks, switch
|
|
2571
|
+
// to data. Emit the DOCTYPE token.
|
|
2751
2572
|
reportError(
|
|
2752
2573
|
"abrupt-doctype-system-identifier",
|
|
2753
2574
|
pos,
|
|
@@ -2786,10 +2607,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2786
2607
|
case STATE_AFTER_DOCTYPE_SYSTEM_IDENTIFIER:
|
|
2787
2608
|
// Consume the next input character:
|
|
2788
2609
|
if (isSpace(cc)) {
|
|
2789
|
-
//
|
|
2790
|
-
// U+000A LINE FEED (LF)
|
|
2791
|
-
// U+000C FORM FEED (FF)
|
|
2792
|
-
// U+0020 SPACE
|
|
2610
|
+
// Whitespace (tab, LF, FF, space)
|
|
2793
2611
|
// Ignore the character.
|
|
2794
2612
|
pos++;
|
|
2795
2613
|
} else if (cc === CC_GREATER_THAN) {
|
|
@@ -2814,10 +2632,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2814
2632
|
textStart = nextPos;
|
|
2815
2633
|
pos = nextPos;
|
|
2816
2634
|
} else {
|
|
2817
|
-
// Anything else
|
|
2818
|
-
// This
|
|
2819
|
-
//
|
|
2820
|
-
// current DOCTYPE token's force-quirks flag to on.)
|
|
2635
|
+
// Anything else: unexpected-character-after-doctype-system-identifier
|
|
2636
|
+
// parse error, reconsume in bogus DOCTYPE. (This does not set the DOCTYPE
|
|
2637
|
+
// token's force-quirks flag to on.)
|
|
2821
2638
|
reportError(
|
|
2822
2639
|
"unexpected-character-after-doctype-system-identifier",
|
|
2823
2640
|
pos,
|
|
@@ -2873,10 +2690,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2873
2690
|
state = STATE_CDATA_SECTION_BRACKET;
|
|
2874
2691
|
pos++;
|
|
2875
2692
|
} else {
|
|
2876
|
-
// Anything else
|
|
2877
|
-
//
|
|
2878
|
-
// Fast-forward to the next `]` (the only code point with its own
|
|
2879
|
-
// arc) in one native scan.
|
|
2693
|
+
// Anything else: emit the input character. Fast-forwards to the next
|
|
2694
|
+
// `]`, the only code point with an arc of its own, in one scan.
|
|
2880
2695
|
pos = input.indexOf("]", pos + 1);
|
|
2881
2696
|
if (pos === -1) pos = len;
|
|
2882
2697
|
}
|
|
@@ -2937,10 +2752,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2937
2752
|
case STATE_RCDATA:
|
|
2938
2753
|
// Consume the next input character:
|
|
2939
2754
|
if (cc === CC_AMPERSAND) {
|
|
2940
|
-
//
|
|
2941
|
-
//
|
|
2942
|
-
//
|
|
2943
|
-
// RAWTEXT/script/PLAINTEXT do not.)
|
|
2755
|
+
// `&`: Set the return state to the RCDATA state, switch to character
|
|
2756
|
+
// reference. (RCDATA processes references; RAWTEXT/script/PLAINTEXT do
|
|
2757
|
+
// not.)
|
|
2944
2758
|
returnState = STATE_RCDATA;
|
|
2945
2759
|
state = STATE_CHARACTER_REFERENCE;
|
|
2946
2760
|
pos++;
|
|
@@ -2983,11 +2797,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
2983
2797
|
|
|
2984
2798
|
// https://html.spec.whatwg.org/multipage/parsing.html#rcdata-less-than-sign-state
|
|
2985
2799
|
case STATE_RCDATA_LESS_THAN_SIGN:
|
|
2986
|
-
//
|
|
2987
|
-
//
|
|
2988
|
-
// Switch to the RCDATA end tag open state. (Spec sets a
|
|
2989
|
-
// temporary buffer here; we track the would-be content via
|
|
2990
|
-
// offset ranges instead.)
|
|
2800
|
+
// `/`: switch to RCDATA end tag open. (Spec sets a temporary buffer here;
|
|
2801
|
+
// we track the would-be content via offset ranges instead.)
|
|
2991
2802
|
if (cc === CC_SOLIDUS) {
|
|
2992
2803
|
state = STATE_RCDATA_END_TAG_OPEN;
|
|
2993
2804
|
pos++;
|
|
@@ -3002,10 +2813,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3002
2813
|
|
|
3003
2814
|
// https://html.spec.whatwg.org/multipage/parsing.html#rcdata-end-tag-open-state
|
|
3004
2815
|
case STATE_RCDATA_END_TAG_OPEN:
|
|
3005
|
-
//
|
|
3006
|
-
//
|
|
3007
|
-
// Create a new end tag token, set its tag name to the empty string.
|
|
3008
|
-
// Reconsume in the RCDATA end tag name state.
|
|
2816
|
+
// ASCII alpha: Create a new end tag token, set its tag name to the empty
|
|
2817
|
+
// string, reconsume in RCDATA end tag name.
|
|
3009
2818
|
if (isAsciiAlpha(cc)) {
|
|
3010
2819
|
tagNameStart = pos;
|
|
3011
2820
|
state = STATE_RCDATA_END_TAG_NAME;
|
|
@@ -3021,14 +2830,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3021
2830
|
|
|
3022
2831
|
// https://html.spec.whatwg.org/multipage/parsing.html#rcdata-end-tag-name-state
|
|
3023
2832
|
case STATE_RCDATA_END_TAG_NAME:
|
|
3024
|
-
//
|
|
3025
|
-
//
|
|
3026
|
-
//
|
|
3027
|
-
// U+000C FORM FEED (FF)
|
|
3028
|
-
// U+0020 SPACE
|
|
3029
|
-
// If the current end tag token is an appropriate end tag token, then switch
|
|
3030
|
-
// to the before attribute name state. Otherwise, treat it as per the
|
|
3031
|
-
// "anything else" entry below.
|
|
2833
|
+
// Whitespace (tab, LF, FF, space): If the current end tag token is an
|
|
2834
|
+
// appropriate end tag token, then switch to the before attribute name
|
|
2835
|
+
// state. Otherwise, treat it as per the "anything else" entry below.
|
|
3032
2836
|
if (isSpace(cc)) {
|
|
3033
2837
|
tagNameEnd = pos;
|
|
3034
2838
|
if (
|
|
@@ -3047,10 +2851,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3047
2851
|
// Reconsume
|
|
3048
2852
|
}
|
|
3049
2853
|
} else if (cc === CC_SOLIDUS) {
|
|
3050
|
-
//
|
|
3051
|
-
//
|
|
3052
|
-
//
|
|
3053
|
-
// "anything else" entry below.
|
|
2854
|
+
// `/`: If the current end tag token is an appropriate end tag token, then
|
|
2855
|
+
// switch to the self-closing start tag state. Otherwise, treat it as per
|
|
2856
|
+
// the "anything else" entry below.
|
|
3054
2857
|
tagNameEnd = pos;
|
|
3055
2858
|
if (
|
|
3056
2859
|
rangeEqualsLowerCase(
|
|
@@ -3068,9 +2871,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3068
2871
|
// Reconsume
|
|
3069
2872
|
}
|
|
3070
2873
|
} else if (cc === CC_GREATER_THAN) {
|
|
3071
|
-
//
|
|
3072
|
-
//
|
|
3073
|
-
// to the data state and emit the current tag token. Otherwise, treat it as
|
|
2874
|
+
// `>`: If the current end tag token is an appropriate end tag token, then
|
|
2875
|
+
// switch to the data state and emit the tag token. Otherwise, treat it as
|
|
3074
2876
|
// per the "anything else" entry below.
|
|
3075
2877
|
tagNameEnd = pos;
|
|
3076
2878
|
if (
|
|
@@ -3089,19 +2891,13 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3089
2891
|
// Reconsume
|
|
3090
2892
|
}
|
|
3091
2893
|
} else if (isAsciiAlpha(cc)) {
|
|
3092
|
-
// ASCII
|
|
3093
|
-
//
|
|
3094
|
-
// current tag token's tag name. Append the current input character to
|
|
3095
|
-
// the temporary buffer.
|
|
3096
|
-
// Fused: consume the whole alpha run in one dispatch.
|
|
2894
|
+
// ASCII alpha: append it lowercased to the tag name and as written to
|
|
2895
|
+
// the temporary buffer. Fused, so the whole run is one dispatch.
|
|
3097
2896
|
pos++;
|
|
3098
2897
|
while (pos < len && isAsciiAlpha(input.charCodeAt(pos))) pos++;
|
|
3099
2898
|
} else {
|
|
3100
|
-
// Anything else
|
|
3101
|
-
//
|
|
3102
|
-
// token, and a character token for each of the characters in the temporary
|
|
3103
|
-
// buffer (in the order they were added to the buffer). Reconsume in the
|
|
3104
|
-
// RCDATA state.
|
|
2899
|
+
// Anything else: emit `<`, `/` and the temporary buffer in order,
|
|
2900
|
+
// then reconsume in RCDATA.
|
|
3105
2901
|
state = STATE_RCDATA;
|
|
3106
2902
|
// Reconsume
|
|
3107
2903
|
}
|
|
@@ -3145,10 +2941,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3145
2941
|
|
|
3146
2942
|
// https://html.spec.whatwg.org/multipage/parsing.html#rawtext-less-than-sign-state
|
|
3147
2943
|
case STATE_RAWTEXT_LESS_THAN_SIGN:
|
|
3148
|
-
//
|
|
3149
|
-
//
|
|
3150
|
-
// Switch to the RAWTEXT end tag open state. (Spec sets a
|
|
3151
|
-
// temporary buffer here; we track via offset ranges instead.)
|
|
2944
|
+
// `/`: switch to RAWTEXT end tag open. (Spec sets a temporary buffer here;
|
|
2945
|
+
// we track via offset ranges instead.)
|
|
3152
2946
|
if (cc === CC_SOLIDUS) {
|
|
3153
2947
|
state = STATE_RAWTEXT_END_TAG_OPEN;
|
|
3154
2948
|
pos++;
|
|
@@ -3163,10 +2957,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3163
2957
|
|
|
3164
2958
|
// https://html.spec.whatwg.org/multipage/parsing.html#rawtext-end-tag-open-state
|
|
3165
2959
|
case STATE_RAWTEXT_END_TAG_OPEN:
|
|
3166
|
-
//
|
|
3167
|
-
//
|
|
3168
|
-
// Create a new end tag token, set its tag name to the empty string.
|
|
3169
|
-
// Reconsume in the RAWTEXT end tag name state.
|
|
2960
|
+
// ASCII alpha: Create a new end tag token, set its tag name to the empty
|
|
2961
|
+
// string, reconsume in RAWTEXT end tag name.
|
|
3170
2962
|
if (isAsciiAlpha(cc)) {
|
|
3171
2963
|
tagNameStart = pos;
|
|
3172
2964
|
state = STATE_RAWTEXT_END_TAG_NAME;
|
|
@@ -3182,14 +2974,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3182
2974
|
|
|
3183
2975
|
// https://html.spec.whatwg.org/multipage/parsing.html#rawtext-end-tag-name-state
|
|
3184
2976
|
case STATE_RAWTEXT_END_TAG_NAME:
|
|
3185
|
-
//
|
|
3186
|
-
//
|
|
3187
|
-
//
|
|
3188
|
-
// U+000C FORM FEED (FF)
|
|
3189
|
-
// U+0020 SPACE
|
|
3190
|
-
// If the current end tag token is an appropriate end tag token, then switch
|
|
3191
|
-
// to the before attribute name state. Otherwise, treat it as per the
|
|
3192
|
-
// "anything else" entry below.
|
|
2977
|
+
// Whitespace (tab, LF, FF, space): If the current end tag token is an
|
|
2978
|
+
// appropriate end tag token, then switch to the before attribute name
|
|
2979
|
+
// state. Otherwise, treat it as per the "anything else" entry below.
|
|
3193
2980
|
if (isSpace(cc)) {
|
|
3194
2981
|
tagNameEnd = pos;
|
|
3195
2982
|
if (
|
|
@@ -3207,10 +2994,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3207
2994
|
state = STATE_RAWTEXT;
|
|
3208
2995
|
}
|
|
3209
2996
|
} else if (cc === CC_SOLIDUS) {
|
|
3210
|
-
//
|
|
3211
|
-
//
|
|
3212
|
-
//
|
|
3213
|
-
// "anything else" entry below.
|
|
2997
|
+
// `/`: If the current end tag token is an appropriate end tag token, then
|
|
2998
|
+
// switch to the self-closing start tag state. Otherwise, treat it as per
|
|
2999
|
+
// the "anything else" entry below.
|
|
3214
3000
|
tagNameEnd = pos;
|
|
3215
3001
|
if (
|
|
3216
3002
|
rangeEqualsLowerCase(
|
|
@@ -3227,9 +3013,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3227
3013
|
state = STATE_RAWTEXT;
|
|
3228
3014
|
}
|
|
3229
3015
|
} else if (cc === CC_GREATER_THAN) {
|
|
3230
|
-
//
|
|
3231
|
-
//
|
|
3232
|
-
// to the data state and emit the current tag token. Otherwise, treat it as
|
|
3016
|
+
// `>`: If the current end tag token is an appropriate end tag token, then
|
|
3017
|
+
// switch to the data state and emit the tag token. Otherwise, treat it as
|
|
3233
3018
|
// per the "anything else" entry below.
|
|
3234
3019
|
tagNameEnd = pos;
|
|
3235
3020
|
if (
|
|
@@ -3247,19 +3032,13 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3247
3032
|
state = STATE_RAWTEXT;
|
|
3248
3033
|
}
|
|
3249
3034
|
} else if (isAsciiAlpha(cc)) {
|
|
3250
|
-
// ASCII
|
|
3251
|
-
//
|
|
3252
|
-
// current tag token's tag name. Append the current input character to
|
|
3253
|
-
// the temporary buffer.
|
|
3254
|
-
// Fused: consume the whole alpha run in one dispatch.
|
|
3035
|
+
// ASCII alpha: append it lowercased to the tag name and as written to
|
|
3036
|
+
// the temporary buffer. Fused, so the whole run is one dispatch.
|
|
3255
3037
|
pos++;
|
|
3256
3038
|
while (pos < len && isAsciiAlpha(input.charCodeAt(pos))) pos++;
|
|
3257
3039
|
} else {
|
|
3258
|
-
// Anything else
|
|
3259
|
-
//
|
|
3260
|
-
// token, and a character token for each of the characters in the temporary
|
|
3261
|
-
// buffer (in the order they were added to the buffer). Reconsume in the
|
|
3262
|
-
// RAWTEXT state.
|
|
3040
|
+
// Anything else: emit `<`, `/` and the temporary buffer in order,
|
|
3041
|
+
// then reconsume in RAWTEXT.
|
|
3263
3042
|
state = STATE_RAWTEXT;
|
|
3264
3043
|
// Reconsume
|
|
3265
3044
|
}
|
|
@@ -3303,10 +3082,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3303
3082
|
|
|
3304
3083
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-less-than-sign-state
|
|
3305
3084
|
case STATE_SCRIPT_DATA_LESS_THAN_SIGN:
|
|
3306
|
-
//
|
|
3307
|
-
//
|
|
3308
|
-
// Switch to the script data end tag open state. (Spec sets a
|
|
3309
|
-
// temporary buffer here; we track via offset ranges instead.)
|
|
3085
|
+
// `/`: switch to script data end tag open. (Spec sets a temporary buffer
|
|
3086
|
+
// here; we track via offset ranges instead.)
|
|
3310
3087
|
if (cc === CC_SOLIDUS) {
|
|
3311
3088
|
state = STATE_SCRIPT_DATA_END_TAG_OPEN;
|
|
3312
3089
|
pos++;
|
|
@@ -3327,10 +3104,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3327
3104
|
|
|
3328
3105
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-end-tag-open-state
|
|
3329
3106
|
case STATE_SCRIPT_DATA_END_TAG_OPEN:
|
|
3330
|
-
//
|
|
3331
|
-
//
|
|
3332
|
-
// Create a new end tag token, set its tag name to the empty string.
|
|
3333
|
-
// Reconsume in the script data end tag name state.
|
|
3107
|
+
// ASCII alpha: Create a new end tag token, set its tag name to the empty
|
|
3108
|
+
// string, reconsume in script data end tag name.
|
|
3334
3109
|
if (isAsciiAlpha(cc)) {
|
|
3335
3110
|
tagNameStart = pos;
|
|
3336
3111
|
state = STATE_SCRIPT_DATA_END_TAG_NAME;
|
|
@@ -3346,14 +3121,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3346
3121
|
|
|
3347
3122
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-end-tag-name-state
|
|
3348
3123
|
case STATE_SCRIPT_DATA_END_TAG_NAME:
|
|
3349
|
-
//
|
|
3350
|
-
//
|
|
3351
|
-
//
|
|
3352
|
-
// U+000C FORM FEED (FF)
|
|
3353
|
-
// U+0020 SPACE
|
|
3354
|
-
// If the current end tag token is an appropriate end tag token, then switch
|
|
3355
|
-
// to the before attribute name state. Otherwise, treat it as per the
|
|
3356
|
-
// "anything else" entry below.
|
|
3124
|
+
// Whitespace (tab, LF, FF, space): If the current end tag token is an
|
|
3125
|
+
// appropriate end tag token, then switch to the before attribute name
|
|
3126
|
+
// state. Otherwise, treat it as per the "anything else" entry below.
|
|
3357
3127
|
if (isSpace(cc)) {
|
|
3358
3128
|
tagNameEnd = pos;
|
|
3359
3129
|
if (
|
|
@@ -3371,10 +3141,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3371
3141
|
state = STATE_SCRIPT_DATA;
|
|
3372
3142
|
}
|
|
3373
3143
|
} else if (cc === CC_SOLIDUS) {
|
|
3374
|
-
//
|
|
3375
|
-
//
|
|
3376
|
-
//
|
|
3377
|
-
// "anything else" entry below.
|
|
3144
|
+
// `/`: If the current end tag token is an appropriate end tag token, then
|
|
3145
|
+
// switch to the self-closing start tag state. Otherwise, treat it as per
|
|
3146
|
+
// the "anything else" entry below.
|
|
3378
3147
|
tagNameEnd = pos;
|
|
3379
3148
|
if (
|
|
3380
3149
|
rangeEqualsLowerCase(
|
|
@@ -3391,9 +3160,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3391
3160
|
state = STATE_SCRIPT_DATA;
|
|
3392
3161
|
}
|
|
3393
3162
|
} else if (cc === CC_GREATER_THAN) {
|
|
3394
|
-
//
|
|
3395
|
-
//
|
|
3396
|
-
// to the data state and emit the current tag token. Otherwise, treat it as
|
|
3163
|
+
// `>`: If the current end tag token is an appropriate end tag token, then
|
|
3164
|
+
// switch to the data state and emit the tag token. Otherwise, treat it as
|
|
3397
3165
|
// per the "anything else" entry below.
|
|
3398
3166
|
tagNameEnd = pos;
|
|
3399
3167
|
if (
|
|
@@ -3416,11 +3184,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3416
3184
|
pos++;
|
|
3417
3185
|
while (pos < len && isAsciiAlpha(input.charCodeAt(pos))) pos++;
|
|
3418
3186
|
} else {
|
|
3419
|
-
// Anything else
|
|
3420
|
-
//
|
|
3421
|
-
// token, and a character token for each of the characters in the temporary
|
|
3422
|
-
// buffer (in the order they were added to the buffer). Reconsume in the
|
|
3423
|
-
// script data state.
|
|
3187
|
+
// Anything else: emit `<`, `/` and the temporary buffer in order,
|
|
3188
|
+
// then reconsume in script data.
|
|
3424
3189
|
state = STATE_SCRIPT_DATA;
|
|
3425
3190
|
// Reconsume
|
|
3426
3191
|
}
|
|
@@ -3428,10 +3193,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3428
3193
|
|
|
3429
3194
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-escape-start-state
|
|
3430
3195
|
case STATE_SCRIPT_DATA_ESCAPE_START:
|
|
3431
|
-
//
|
|
3432
|
-
//
|
|
3433
|
-
// Switch to the script data escape start dash state. Emit a U+002D
|
|
3434
|
-
// HYPHEN-MINUS character token.
|
|
3196
|
+
// `-`: switch to script data escape start dash. Emit a U+002D HYPHEN-MINUS
|
|
3197
|
+
// character token.
|
|
3435
3198
|
if (cc === CC_HYPHEN_MINUS) {
|
|
3436
3199
|
state = STATE_SCRIPT_DATA_ESCAPE_START_DASH;
|
|
3437
3200
|
pos++;
|
|
@@ -3445,10 +3208,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3445
3208
|
|
|
3446
3209
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-escape-start-dash-state
|
|
3447
3210
|
case STATE_SCRIPT_DATA_ESCAPE_START_DASH:
|
|
3448
|
-
//
|
|
3449
|
-
//
|
|
3450
|
-
// Switch to the script data escaped dash dash state. Emit a U+002D
|
|
3451
|
-
// HYPHEN-MINUS character token.
|
|
3211
|
+
// `-`: switch to script data escaped dash dash. Emit a U+002D HYPHEN-MINUS
|
|
3212
|
+
// character token.
|
|
3452
3213
|
if (cc === CC_HYPHEN_MINUS) {
|
|
3453
3214
|
state = STATE_SCRIPT_DATA_ESCAPED_DASH_DASH;
|
|
3454
3215
|
pos++;
|
|
@@ -3462,9 +3223,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3462
3223
|
|
|
3463
3224
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-escaped-state
|
|
3464
3225
|
case STATE_SCRIPT_DATA_ESCAPED:
|
|
3465
|
-
//
|
|
3466
|
-
// U+002D HYPHEN-MINUS (-)
|
|
3467
|
-
// Switch to the script data escaped dash state. Emit a U+002D HYPHEN-MINUS
|
|
3226
|
+
// `-`: switch to script data escaped dash. Emit a U+002D HYPHEN-MINUS
|
|
3468
3227
|
// character token.
|
|
3469
3228
|
if (cc === CC_HYPHEN_MINUS) {
|
|
3470
3229
|
state = STATE_SCRIPT_DATA_ESCAPED_DASH;
|
|
@@ -3501,10 +3260,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3501
3260
|
|
|
3502
3261
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-escaped-dash-state
|
|
3503
3262
|
case STATE_SCRIPT_DATA_ESCAPED_DASH:
|
|
3504
|
-
//
|
|
3505
|
-
//
|
|
3506
|
-
// Switch to the script data escaped dash dash state. Emit a U+002D
|
|
3507
|
-
// HYPHEN-MINUS character token.
|
|
3263
|
+
// `-`: switch to script data escaped dash dash. Emit a U+002D HYPHEN-MINUS
|
|
3264
|
+
// character token.
|
|
3508
3265
|
if (cc === CC_HYPHEN_MINUS) {
|
|
3509
3266
|
state = STATE_SCRIPT_DATA_ESCAPED_DASH_DASH;
|
|
3510
3267
|
pos++;
|
|
@@ -3559,18 +3316,15 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3559
3316
|
|
|
3560
3317
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-escaped-less-than-sign-state
|
|
3561
3318
|
case STATE_SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:
|
|
3562
|
-
//
|
|
3563
|
-
//
|
|
3564
|
-
// Switch to the script data escaped end tag open state.
|
|
3565
|
-
// (Spec sets a temporary buffer; we track via offset ranges.)
|
|
3319
|
+
// `/`: switch to script data escaped end tag open. (Spec sets a temporary
|
|
3320
|
+
// buffer; we track via offset ranges.)
|
|
3566
3321
|
if (cc === CC_SOLIDUS) {
|
|
3567
3322
|
state = STATE_SCRIPT_DATA_ESCAPED_END_TAG_OPEN;
|
|
3568
3323
|
pos++;
|
|
3569
3324
|
} else if (isAsciiAlpha(cc)) {
|
|
3570
|
-
// ASCII alpha
|
|
3571
|
-
//
|
|
3572
|
-
//
|
|
3573
|
-
// state.
|
|
3325
|
+
// ASCII alpha: Set the temporary buffer to the empty string. Emit a U+003C
|
|
3326
|
+
// LESS-THAN SIGN character token, reconsume in script data double escape
|
|
3327
|
+
// start.
|
|
3574
3328
|
scriptMatch = 0;
|
|
3575
3329
|
state = STATE_SCRIPT_DATA_DOUBLE_ESCAPE_START;
|
|
3576
3330
|
// Reconsume
|
|
@@ -3585,10 +3339,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3585
3339
|
|
|
3586
3340
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-escaped-end-tag-open-state
|
|
3587
3341
|
case STATE_SCRIPT_DATA_ESCAPED_END_TAG_OPEN:
|
|
3588
|
-
//
|
|
3589
|
-
//
|
|
3590
|
-
// Create a new end tag token, set its tag name to the empty string.
|
|
3591
|
-
// Reconsume in the script data escaped end tag name state.
|
|
3342
|
+
// ASCII alpha: Create a new end tag token, set its tag name to the empty
|
|
3343
|
+
// string, reconsume in script data escaped end tag name.
|
|
3592
3344
|
if (isAsciiAlpha(cc)) {
|
|
3593
3345
|
tagNameStart = pos;
|
|
3594
3346
|
state = STATE_SCRIPT_DATA_ESCAPED_END_TAG_NAME;
|
|
@@ -3604,14 +3356,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3604
3356
|
|
|
3605
3357
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-escaped-end-tag-name-state
|
|
3606
3358
|
case STATE_SCRIPT_DATA_ESCAPED_END_TAG_NAME:
|
|
3607
|
-
//
|
|
3608
|
-
//
|
|
3609
|
-
//
|
|
3610
|
-
// U+000C FORM FEED (FF)
|
|
3611
|
-
// U+0020 SPACE
|
|
3612
|
-
// If the current end tag token is an appropriate end tag token, then switch
|
|
3613
|
-
// to the before attribute name state. Otherwise, treat it as per the
|
|
3614
|
-
// "anything else" entry below.
|
|
3359
|
+
// Whitespace (tab, LF, FF, space): If the current end tag token is an
|
|
3360
|
+
// appropriate end tag token, then switch to the before attribute name
|
|
3361
|
+
// state. Otherwise, treat it as per the "anything else" entry below.
|
|
3615
3362
|
if (isSpace(cc)) {
|
|
3616
3363
|
tagNameEnd = pos;
|
|
3617
3364
|
if (
|
|
@@ -3629,10 +3376,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3629
3376
|
state = STATE_SCRIPT_DATA_ESCAPED;
|
|
3630
3377
|
}
|
|
3631
3378
|
} else if (cc === CC_SOLIDUS) {
|
|
3632
|
-
//
|
|
3633
|
-
//
|
|
3634
|
-
//
|
|
3635
|
-
// "anything else" entry below.
|
|
3379
|
+
// `/`: If the current end tag token is an appropriate end tag token, then
|
|
3380
|
+
// switch to the self-closing start tag state. Otherwise, treat it as per
|
|
3381
|
+
// the "anything else" entry below.
|
|
3636
3382
|
tagNameEnd = pos;
|
|
3637
3383
|
if (
|
|
3638
3384
|
rangeEqualsLowerCase(
|
|
@@ -3649,9 +3395,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3649
3395
|
state = STATE_SCRIPT_DATA_ESCAPED;
|
|
3650
3396
|
}
|
|
3651
3397
|
} else if (cc === CC_GREATER_THAN) {
|
|
3652
|
-
//
|
|
3653
|
-
//
|
|
3654
|
-
// to the data state and emit the current tag token. Otherwise, treat it as
|
|
3398
|
+
// `>`: If the current end tag token is an appropriate end tag token, then
|
|
3399
|
+
// switch to the data state and emit the tag token. Otherwise, treat it as
|
|
3655
3400
|
// per the "anything else" entry below.
|
|
3656
3401
|
tagNameEnd = pos;
|
|
3657
3402
|
if (
|
|
@@ -3674,11 +3419,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3674
3419
|
pos++;
|
|
3675
3420
|
while (pos < len && isAsciiAlpha(input.charCodeAt(pos))) pos++;
|
|
3676
3421
|
} else {
|
|
3677
|
-
// Anything else
|
|
3678
|
-
//
|
|
3679
|
-
// token, and a character token for each of the characters in the temporary
|
|
3680
|
-
// buffer (in the order they were added to the buffer). Reconsume in the
|
|
3681
|
-
// script data escaped state.
|
|
3422
|
+
// Anything else: emit `<`, `/` and the temporary buffer in order,
|
|
3423
|
+
// then reconsume in script data escaped.
|
|
3682
3424
|
state = STATE_SCRIPT_DATA_ESCAPED;
|
|
3683
3425
|
// Reconsume
|
|
3684
3426
|
}
|
|
@@ -3686,16 +3428,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3686
3428
|
|
|
3687
3429
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escape-start-state
|
|
3688
3430
|
case STATE_SCRIPT_DATA_DOUBLE_ESCAPE_START:
|
|
3689
|
-
//
|
|
3690
|
-
//
|
|
3691
|
-
//
|
|
3692
|
-
// U+000C FORM FEED (FF)
|
|
3693
|
-
// U+0020 SPACE
|
|
3694
|
-
// U+002F SOLIDUS (/)
|
|
3695
|
-
// U+003E GREATER-THAN SIGN (>)
|
|
3696
|
-
// If the temporary buffer is the string "script", then switch to the script
|
|
3697
|
-
// data double escaped state. Otherwise, switch to the script data escaped
|
|
3698
|
-
// state. Emit the current input character as a character token.
|
|
3431
|
+
// Whitespace, `/` or `>`: switch to script data double escaped where the
|
|
3432
|
+
// temporary buffer is "script", otherwise to script data escaped, and
|
|
3433
|
+
// emit the input character.
|
|
3699
3434
|
if (isSpace(cc) || cc === CC_SOLIDUS || cc === CC_GREATER_THAN) {
|
|
3700
3435
|
state =
|
|
3701
3436
|
scriptMatch === 6
|
|
@@ -3703,10 +3438,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3703
3438
|
: STATE_SCRIPT_DATA_ESCAPED;
|
|
3704
3439
|
pos++;
|
|
3705
3440
|
} else if (isAsciiUpperAlpha(cc) || isAsciiLowerAlpha(cc)) {
|
|
3706
|
-
// ASCII alpha
|
|
3707
|
-
//
|
|
3708
|
-
//
|
|
3709
|
-
// match. No buffer allocation.
|
|
3441
|
+
// ASCII alpha: advance the `"script"` match counter while the
|
|
3442
|
+
// lowercased char is the expected one, otherwise snap to the
|
|
3443
|
+
// sentinel so a later char cannot revive the match.
|
|
3710
3444
|
const lower = isAsciiUpperAlpha(cc) ? cc + 0x20 : cc;
|
|
3711
3445
|
if (scriptMatch < 6 && lower === "script".charCodeAt(scriptMatch)) {
|
|
3712
3446
|
scriptMatch++;
|
|
@@ -3724,9 +3458,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3724
3458
|
|
|
3725
3459
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
|
|
3726
3460
|
case STATE_SCRIPT_DATA_DOUBLE_ESCAPED:
|
|
3727
|
-
//
|
|
3728
|
-
// U+002D HYPHEN-MINUS (-)
|
|
3729
|
-
// Switch to the script data double escaped dash state. Emit a U+002D
|
|
3461
|
+
// `-`: switch to script data double escaped dash. Emit a U+002D
|
|
3730
3462
|
// HYPHEN-MINUS character token.
|
|
3731
3463
|
if (cc === CC_HYPHEN_MINUS) {
|
|
3732
3464
|
state = STATE_SCRIPT_DATA_DOUBLE_ESCAPED_DASH;
|
|
@@ -3763,9 +3495,7 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3763
3495
|
|
|
3764
3496
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-dash-state
|
|
3765
3497
|
case STATE_SCRIPT_DATA_DOUBLE_ESCAPED_DASH:
|
|
3766
|
-
//
|
|
3767
|
-
// U+002D HYPHEN-MINUS (-)
|
|
3768
|
-
// Switch to the script data double escaped dash dash state. Emit a U+002D
|
|
3498
|
+
// `-`: switch to script data double escaped dash dash. Emit a U+002D
|
|
3769
3499
|
// HYPHEN-MINUS character token.
|
|
3770
3500
|
if (cc === CC_HYPHEN_MINUS) {
|
|
3771
3501
|
state = STATE_SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH;
|
|
@@ -3821,10 +3551,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3821
3551
|
|
|
3822
3552
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-less-than-sign-state
|
|
3823
3553
|
case STATE_SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN:
|
|
3824
|
-
//
|
|
3825
|
-
// U+002F SOLIDUS
|
|
3826
|
-
// Set the temporary buffer to the empty string. Switch to the script data
|
|
3827
|
-
// double escape end state. Emit a U+002F SOLIDUS character token.
|
|
3554
|
+
// `/`: Set the temporary buffer to the empty string, switch to script data
|
|
3555
|
+
// double escape end. Emit a U+002F SOLIDUS character token.
|
|
3828
3556
|
if (cc === CC_SOLIDUS) {
|
|
3829
3557
|
scriptMatch = 0;
|
|
3830
3558
|
state = STATE_SCRIPT_DATA_DOUBLE_ESCAPE_END;
|
|
@@ -3839,16 +3567,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3839
3567
|
|
|
3840
3568
|
// https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escape-end-state
|
|
3841
3569
|
case STATE_SCRIPT_DATA_DOUBLE_ESCAPE_END:
|
|
3842
|
-
//
|
|
3843
|
-
//
|
|
3844
|
-
//
|
|
3845
|
-
// U+000C FORM FEED (FF)
|
|
3846
|
-
// U+0020 SPACE
|
|
3847
|
-
// U+002F SOLIDUS (/)
|
|
3848
|
-
// U+003E GREATER-THAN SIGN (>)
|
|
3849
|
-
// If the temporary buffer is the string "script", then switch to the script
|
|
3850
|
-
// data escaped state. Otherwise, switch to the script data double escaped
|
|
3851
|
-
// state. Emit the current input character as a character token.
|
|
3570
|
+
// Whitespace, `/` or `>`: switch to script data escaped where the
|
|
3571
|
+
// temporary buffer is "script", otherwise to script data double escaped,
|
|
3572
|
+
// and emit the input character.
|
|
3852
3573
|
if (isSpace(cc) || cc === CC_SOLIDUS || cc === CC_GREATER_THAN) {
|
|
3853
3574
|
state =
|
|
3854
3575
|
scriptMatch === 6
|
|
@@ -3856,10 +3577,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3856
3577
|
: STATE_SCRIPT_DATA_DOUBLE_ESCAPED;
|
|
3857
3578
|
pos++;
|
|
3858
3579
|
} else if (isAsciiUpperAlpha(cc) || isAsciiLowerAlpha(cc)) {
|
|
3859
|
-
// ASCII alpha
|
|
3860
|
-
//
|
|
3861
|
-
//
|
|
3862
|
-
// match. No buffer allocation.
|
|
3580
|
+
// ASCII alpha: advance the `"script"` match counter while the
|
|
3581
|
+
// lowercased char is the expected one, otherwise snap to the
|
|
3582
|
+
// sentinel so a later char cannot revive the match.
|
|
3863
3583
|
const lower = isAsciiUpperAlpha(cc) ? cc + 0x20 : cc;
|
|
3864
3584
|
if (scriptMatch < 6 && lower === "script".charCodeAt(scriptMatch)) {
|
|
3865
3585
|
scriptMatch++;
|
|
@@ -3904,10 +3624,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3904
3624
|
state = STATE_NAMED_CHARACTER_REFERENCE;
|
|
3905
3625
|
// Reconsume
|
|
3906
3626
|
} else if (cc === CC_NUMBER_SIGN) {
|
|
3907
|
-
//
|
|
3908
|
-
//
|
|
3909
|
-
// Set the character reference code to zero. Switch to the
|
|
3910
|
-
// numeric character reference state.
|
|
3627
|
+
// `#`: Append the input character to the temporary buffer. Set the
|
|
3628
|
+
// character reference code to zero, switch to numeric character reference.
|
|
3911
3629
|
charRefCode = 0;
|
|
3912
3630
|
state = STATE_NUMERIC_CHARACTER_REFERENCE;
|
|
3913
3631
|
pos++;
|
|
@@ -3922,16 +3640,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3922
3640
|
|
|
3923
3641
|
// https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
|
|
3924
3642
|
case STATE_NAMED_CHARACTER_REFERENCE: {
|
|
3925
|
-
// Consume the
|
|
3926
|
-
//
|
|
3927
|
-
//
|
|
3928
|
-
//
|
|
3929
|
-
// We measure the longest run of ASCII alphanumeric characters
|
|
3930
|
-
// (capped at MAX_ENTITY_NAME_LEN - 1 since the optional `;` is
|
|
3931
|
-
// handled separately), then walk that run from longest to
|
|
3932
|
-
// shortest looking for the first prefix that exists in the
|
|
3933
|
-
// entity table (with a trailing `;` if present, otherwise the
|
|
3934
|
-
// legacy bare form).
|
|
3643
|
+
// Consume the longest run naming a character reference: measure the
|
|
3644
|
+
// alphanumeric run, capped at MAX_ENTITY_NAME_LEN - 1 as the `;` is
|
|
3645
|
+
// separate, then walk it longest-first for the first prefix in the table.
|
|
3935
3646
|
let runLen = 0;
|
|
3936
3647
|
while (
|
|
3937
3648
|
pos + runLen < len &&
|
|
@@ -3966,11 +3677,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
3966
3677
|
}
|
|
3967
3678
|
}
|
|
3968
3679
|
if (namedEntityConsumed > 0) {
|
|
3969
|
-
// A legacy match without
|
|
3970
|
-
// missing-semicolon-after-character-reference parse error,
|
|
3971
|
-
//
|
|
3972
|
-
// consumed in an attribute value and the next char is `=` or
|
|
3973
|
-
// ASCII alphanumeric, the reference is left undecoded silently.
|
|
3680
|
+
// A legacy match without its `;` is a
|
|
3681
|
+
// missing-semicolon-after-character-reference parse error, except
|
|
3682
|
+
// where the spec's attribute rule silences it.
|
|
3974
3683
|
if (!matchedWithSemicolon) {
|
|
3975
3684
|
const next = input.charCodeAt(pos + namedEntityConsumed);
|
|
3976
3685
|
const inAttribute =
|
|
@@ -4003,11 +3712,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4003
3712
|
case STATE_AMBIGUOUS_AMPERSAND:
|
|
4004
3713
|
// Consume the next input character:
|
|
4005
3714
|
if (isAsciiAlphanumeric(cc)) {
|
|
4006
|
-
// ASCII alphanumeric
|
|
4007
|
-
//
|
|
4008
|
-
// attribute, then append the current input character to the
|
|
4009
|
-
// current attribute's value. Otherwise, emit the current
|
|
4010
|
-
// input character as a character token.
|
|
3715
|
+
// ASCII alphanumeric: append the input character to the attribute's
|
|
3716
|
+
// value where the reference was consumed in one, otherwise emit it.
|
|
4011
3717
|
pos++;
|
|
4012
3718
|
} else if (cc === CC_SEMICOLON) {
|
|
4013
3719
|
// U+003B SEMICOLON (;)
|
|
@@ -4034,11 +3740,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4034
3740
|
// Set the character reference code to zero (0).
|
|
4035
3741
|
// Consume the next input character:
|
|
4036
3742
|
if (cc === 0x78 || cc === 0x58) {
|
|
4037
|
-
// U+0078
|
|
4038
|
-
//
|
|
4039
|
-
// Append the current input character to the temporary
|
|
4040
|
-
// buffer. Switch to the hexadecimal character reference
|
|
4041
|
-
// start state.
|
|
3743
|
+
// U+0078 / U+0058: Append the input character to the temporary buffer,
|
|
3744
|
+
// switch to hexadecimal character reference start.
|
|
4042
3745
|
state = STATE_HEXADECIMAL_CHARACTER_REFERENCE_START;
|
|
4043
3746
|
pos++;
|
|
4044
3747
|
} else {
|
|
@@ -4051,11 +3754,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4051
3754
|
|
|
4052
3755
|
// https://html.spec.whatwg.org/multipage/parsing.html#hexadecimal-character-reference-start-state
|
|
4053
3756
|
case STATE_HEXADECIMAL_CHARACTER_REFERENCE_START:
|
|
4054
|
-
//
|
|
4055
|
-
// ASCII hex digit: reconsume in the hexadecimal character reference state.
|
|
3757
|
+
// ASCII hex digit: reconsume in hexadecimal character reference.
|
|
4056
3758
|
// Anything else: absence-of-digits-in-numeric-character-reference parse
|
|
4057
|
-
// error
|
|
4058
|
-
// in the return state.
|
|
3759
|
+
// error, flush the consumed code points and reconsume in the return.
|
|
4059
3760
|
if (isAsciiHexDigit(cc)) {
|
|
4060
3761
|
state = STATE_HEXADECIMAL_CHARACTER_REFERENCE;
|
|
4061
3762
|
} else {
|
|
@@ -4072,11 +3773,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4072
3773
|
|
|
4073
3774
|
// https://html.spec.whatwg.org/multipage/parsing.html#decimal-character-reference-start-state
|
|
4074
3775
|
case STATE_DECIMAL_CHARACTER_REFERENCE_START:
|
|
4075
|
-
//
|
|
4076
|
-
//
|
|
4077
|
-
//
|
|
4078
|
-
// error. Flush code points consumed as a character reference. Reconsume
|
|
4079
|
-
// in the return state.
|
|
3776
|
+
// ASCII digit: reconsume in the decimal character reference state. Anything
|
|
3777
|
+
// else: absence-of-digits-in-numeric-character-reference parse error. Flush
|
|
3778
|
+
// code points consumed as a character reference, reconsume in return.
|
|
4080
3779
|
if (isAsciiDigit(cc)) {
|
|
4081
3780
|
state = STATE_DECIMAL_CHARACTER_REFERENCE;
|
|
4082
3781
|
} else {
|
|
@@ -4095,12 +3794,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4095
3794
|
case STATE_HEXADECIMAL_CHARACTER_REFERENCE:
|
|
4096
3795
|
// Consume the next input character:
|
|
4097
3796
|
if (isAsciiHexDigit(cc)) {
|
|
4098
|
-
// ASCII digit
|
|
4099
|
-
//
|
|
4100
|
-
//
|
|
4101
|
-
// reference code. Stop accumulating once past the Unicode
|
|
4102
|
-
// range so the value can't overflow (still flags as
|
|
4103
|
-
// outside-range at the end).
|
|
3797
|
+
// ASCII hex digit: multiply the reference code by 16 and add the
|
|
3798
|
+
// input character's value. Accumulation stops past the Unicode range
|
|
3799
|
+
// so it cannot overflow, and still flags as outside-range.
|
|
4104
3800
|
if (charRefCode < 0x110000) {
|
|
4105
3801
|
const v = cc <= 0x39 ? cc - 0x30 : (cc | 0x20) - 0x61 + 10;
|
|
4106
3802
|
charRefCode = charRefCode * 16 + v;
|
|
@@ -4112,10 +3808,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4112
3808
|
state = STATE_NUMERIC_CHARACTER_REFERENCE_END;
|
|
4113
3809
|
pos++;
|
|
4114
3810
|
} else {
|
|
4115
|
-
// Anything else
|
|
4116
|
-
//
|
|
4117
|
-
// parse error. Reconsume in the numeric character reference
|
|
4118
|
-
// end state.
|
|
3811
|
+
// Anything else: missing-semicolon-after-character-reference parse error,
|
|
3812
|
+
// reconsume in numeric character reference end.
|
|
4119
3813
|
reportError(
|
|
4120
3814
|
"missing-semicolon-after-character-reference",
|
|
4121
3815
|
pos,
|
|
@@ -4131,12 +3825,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4131
3825
|
case STATE_DECIMAL_CHARACTER_REFERENCE:
|
|
4132
3826
|
// Consume the next input character:
|
|
4133
3827
|
if (isAsciiDigit(cc)) {
|
|
4134
|
-
// ASCII digit
|
|
4135
|
-
//
|
|
4136
|
-
//
|
|
4137
|
-
// from the character's code point) to the character reference
|
|
4138
|
-
// code. Stop accumulating once past the Unicode range so the
|
|
4139
|
-
// value can't overflow (still flags as outside-range).
|
|
3828
|
+
// ASCII digit: multiply the reference code by 10 and add the input
|
|
3829
|
+
// character's value. Accumulation stops past the Unicode range so it
|
|
3830
|
+
// cannot overflow, and still flags as outside-range.
|
|
4140
3831
|
if (charRefCode < 0x110000) {
|
|
4141
3832
|
charRefCode = charRefCode * 10 + (cc - 0x30);
|
|
4142
3833
|
}
|
|
@@ -4147,10 +3838,8 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4147
3838
|
state = STATE_NUMERIC_CHARACTER_REFERENCE_END;
|
|
4148
3839
|
pos++;
|
|
4149
3840
|
} else {
|
|
4150
|
-
// Anything else
|
|
4151
|
-
//
|
|
4152
|
-
// parse error. Reconsume in the numeric character reference
|
|
4153
|
-
// end state.
|
|
3841
|
+
// Anything else: missing-semicolon-after-character-reference parse error,
|
|
3842
|
+
// reconsume in numeric character reference end.
|
|
4154
3843
|
reportError(
|
|
4155
3844
|
"missing-semicolon-after-character-reference",
|
|
4156
3845
|
pos,
|
|
@@ -4179,17 +3868,13 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4179
3868
|
}
|
|
4180
3869
|
}
|
|
4181
3870
|
|
|
4182
|
-
//
|
|
4183
|
-
//
|
|
4184
|
-
//
|
|
4185
|
-
//
|
|
4186
|
-
//
|
|
4187
|
-
//
|
|
4188
|
-
//
|
|
4189
|
-
// pending text span (which still contains the lone `<`).
|
|
4190
|
-
// EOF inside a character-reference state: run the end-of-reference
|
|
4191
|
-
// processing (numeric end states never run when the reference ends at
|
|
4192
|
-
// EOF), then resume in the return state for the branches below.
|
|
3871
|
+
// WHY: EOF in a non-data state, per the WHATWG spec. An in-progress comment,
|
|
3872
|
+
// doctype, cdata or tag emits its partial range and an `eof-in-X` parse
|
|
3873
|
+
// error, at severity `"error"` because that range is missing its terminator.
|
|
3874
|
+
// A data / `<` / `</` / `<!` input emits `eof-before-tag-name` and falls
|
|
3875
|
+
// through to flush the pending text, which still holds the lone `<`. Inside a
|
|
3876
|
+
// character reference, the end-of-reference processing runs first — the
|
|
3877
|
+
// numeric end states never do at EOF — then the return state's branch below.
|
|
4193
3878
|
if (
|
|
4194
3879
|
state >= STATE_CHARACTER_REFERENCE &&
|
|
4195
3880
|
state <= STATE_NUMERIC_CHARACTER_REFERENCE_END
|
|
@@ -4231,10 +3916,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4231
3916
|
state === STATE_SCRIPT_DATA_END_TAG_NAME ||
|
|
4232
3917
|
state === STATE_SCRIPT_DATA_ESCAPED_END_TAG_NAME
|
|
4233
3918
|
) {
|
|
4234
|
-
//
|
|
4235
|
-
//
|
|
4236
|
-
//
|
|
4237
|
-
// dropping the in-progress tag, we emit its offset range up to EOF.
|
|
3919
|
+
// A deliberate deviation from the spec's per-character emission
|
|
3920
|
+
// model — the in-progress tag is emitted as its offset range up to EOF
|
|
3921
|
+
// rather than dropped, so the consumer still sees it.
|
|
4238
3922
|
reportError("eof-in-tag", len, len, "error");
|
|
4239
3923
|
// If we hit EOF mid-attribute-name, the name runs to EOF. Set
|
|
4240
3924
|
// attributeNameEnd here so the emitted attribute range is valid.
|
|
@@ -4242,11 +3926,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4242
3926
|
attributeNameEnd = len;
|
|
4243
3927
|
}
|
|
4244
3928
|
if (attributeNameStart !== -1) emitAttribute(len);
|
|
4245
|
-
//
|
|
4246
|
-
//
|
|
4247
|
-
//
|
|
4248
|
-
// — covers `<div` open-tag EOFs as well as `<title>x</tit` and other
|
|
4249
|
-
// content-mode end-tag-name EOFs.
|
|
3929
|
+
// At EOF before the tag-name end was recorded the name runs to EOF, and
|
|
3930
|
+
// `tagNameEnd` may carry over from a previous tag — so it is reset
|
|
3931
|
+
// whenever missing or stale, below `tagNameStart`.
|
|
4250
3932
|
if (tagNameStart !== -1 && tagNameEnd < tagNameStart) {
|
|
4251
3933
|
tagNameEnd = len;
|
|
4252
3934
|
}
|
|
@@ -4319,11 +4001,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4319
4001
|
state === STATE_SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN ||
|
|
4320
4002
|
state === STATE_SCRIPT_DATA_DOUBLE_ESCAPE_END
|
|
4321
4003
|
) {
|
|
4322
|
-
// Inside `<script><!-- … ` at EOF
|
|
4323
|
-
// eof-in-script-html-comment-like-text parse error. The
|
|
4324
|
-
//
|
|
4325
|
-
// into the (double-)escaped state on EOF per spec, which then
|
|
4326
|
-
// hits this same error.
|
|
4004
|
+
// Inside `<script><!-- … ` at EOF: an
|
|
4005
|
+
// eof-in-script-html-comment-like-text parse error. The less-than-sign
|
|
4006
|
+
// and double-escape-end states reconsume into it at EOF, per spec.
|
|
4327
4007
|
reportError("eof-in-script-html-comment-like-text", len, len, "error");
|
|
4328
4008
|
} else if (state === STATE_TAG_OPEN || state === STATE_END_TAG_OPEN) {
|
|
4329
4009
|
// `<` or `</` with nothing after; spec calls this
|
|
@@ -4339,10 +4019,9 @@ const tokenize = (input, pos = 0, callbacks = {}) => {
|
|
|
4339
4019
|
return pos;
|
|
4340
4020
|
};
|
|
4341
4021
|
|
|
4342
|
-
// WHATWG numeric-character-reference-end
|
|
4343
|
-
//
|
|
4344
|
-
//
|
|
4345
|
-
// raw C1 control character.
|
|
4022
|
+
// WHATWG numeric-character-reference-end remap for 0x80-0x9F: these C1 control
|
|
4023
|
+
// code points decode to the matching Windows-1252 glyph, with a parse error,
|
|
4024
|
+
// rather than to the control character itself.
|
|
4346
4025
|
const NUMERIC_C1_REMAP = {
|
|
4347
4026
|
0x80: "€",
|
|
4348
4027
|
0x82: "‚",
|
|
@@ -4378,12 +4057,10 @@ const NUMERIC_C1_REMAP = {
|
|
|
4378
4057
|
* @returns {string} decoded character per WHATWG remap rules
|
|
4379
4058
|
*/
|
|
4380
4059
|
const decodeNumericReference = (code) => {
|
|
4381
|
-
//
|
|
4382
|
-
//
|
|
4383
|
-
//
|
|
4384
|
-
//
|
|
4385
|
-
// code point itself; we don't surface the spec's parse-error
|
|
4386
|
-
// classes here since decoding is happening after the scanner ran.
|
|
4060
|
+
// WHY: numeric-character-reference-end-state maps 0x00, anything above
|
|
4061
|
+
// 0x10FFFF and the surrogates to U+FFFD, 0x80-0x9F through the table above,
|
|
4062
|
+
// and everything else — noncharacters and C0 controls included — to itself.
|
|
4063
|
+
// Its parse-error classes are not raised here: the scanner has already run.
|
|
4387
4064
|
if (code === 0 || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) {
|
|
4388
4065
|
return "�";
|
|
4389
4066
|
}
|
|
@@ -4419,12 +4096,9 @@ const decodeNamedReference = (str, start, end, nextCharCode, isAttribute) => {
|
|
|
4419
4096
|
const name = str.slice(start + 1, end);
|
|
4420
4097
|
const matchEndsWithSemi = name.charCodeAt(name.length - 1) === 0x3b;
|
|
4421
4098
|
|
|
4422
|
-
// Attribute-context guard:
|
|
4423
|
-
//
|
|
4424
|
-
//
|
|
4425
|
-
// rather than decode. The greedy scan already absorbed any
|
|
4426
|
-
// trailing alphanumerics, so the only candidate "next char" here
|
|
4427
|
-
// is `=` (or any non-alphanumeric).
|
|
4099
|
+
// Attribute-context guard: a match not ending in `;` whose next source
|
|
4100
|
+
// character is `=` or alphanumeric is flushed literally rather than decoded.
|
|
4101
|
+
// The greedy scan absorbed the alphanumerics, so only `=` can reach here.
|
|
4428
4102
|
if (isAttribute && !matchEndsWithSemi && nextCharCode === 0x3d /* = */) {
|
|
4429
4103
|
return undefined;
|
|
4430
4104
|
}
|
|
@@ -4436,11 +4110,9 @@ const decodeNamedReference = (str, start, end, nextCharCode, isAttribute) => {
|
|
|
4436
4110
|
if (full !== undefined) return full;
|
|
4437
4111
|
}
|
|
4438
4112
|
|
|
4439
|
-
//
|
|
4440
|
-
//
|
|
4441
|
-
//
|
|
4442
|
-
// verbatim as part of `name.slice(i)`. The full-length case was just
|
|
4443
|
-
// handled above, so start one shorter when it's the cap.
|
|
4113
|
+
// Capping the longest-prefix search at MAX_ENTITY_NAME_LEN keeps `&` plus
|
|
4114
|
+
// thousands of alphanumerics linear-time; nothing past it can match, so it
|
|
4115
|
+
// rides along in `name.slice(i)`. The full length was handled just above.
|
|
4444
4116
|
const searchLen =
|
|
4445
4117
|
name.length > MAX_ENTITY_NAME_LEN ? MAX_ENTITY_NAME_LEN : name.length - 1;
|
|
4446
4118
|
for (let i = searchLen; i > 0; i--) {
|
|
@@ -4501,13 +4173,12 @@ const decodeNamedReference = (str, start, end, nextCharCode, isAttribute) => {
|
|
|
4501
4173
|
* @returns {string | DecodedEntitiesWithMap} decoded string (with map when `withMap`)
|
|
4502
4174
|
*/
|
|
4503
4175
|
const decodeEntities = (str, isAttribute, withMap) => {
|
|
4504
|
-
//
|
|
4505
|
-
//
|
|
4506
|
-
//
|
|
4507
|
-
//
|
|
4508
|
-
//
|
|
4509
|
-
//
|
|
4510
|
-
// dominant no-map mode pays nothing for the unified implementation.
|
|
4176
|
+
// WHY: a hand-rolled scan of the three reference forms — `&#x<hex>+`,
|
|
4177
|
+
// `&#<dec>+`, `&<alpha><alnum>*`, kept apart so `Ab` does not eat the `b`
|
|
4178
|
+
// as hex. Entity-dense text then pays no regex machinery and no per-match
|
|
4179
|
+
// object, and a string whose references all stay literal is returned as it
|
|
4180
|
+
// came. Map bookkeeping is gated on `withMap` at the rare per-reference
|
|
4181
|
+
// points, so the dominant no-map mode pays nothing for sharing this code.
|
|
4511
4182
|
let amp = str.indexOf("&");
|
|
4512
4183
|
if (amp === -1) return withMap ? { text: str, map: undefined } : str;
|
|
4513
4184
|
let out = "";
|
|
@@ -4515,10 +4186,9 @@ const decodeEntities = (str, isAttribute, withMap) => {
|
|
|
4515
4186
|
/** @type {number[] | undefined} */
|
|
4516
4187
|
let map;
|
|
4517
4188
|
do {
|
|
4518
|
-
// `end` stays 0
|
|
4519
|
-
//
|
|
4520
|
-
//
|
|
4521
|
-
// same replacement) so it needs no slice at all.
|
|
4189
|
+
// `end` stays 0 where the `&` starts no well-formed reference. A numeric
|
|
4190
|
+
// reference accumulates during the digit scan, saturating above the
|
|
4191
|
+
// code-point range since every overflow decodes alike, so it needs no slice.
|
|
4522
4192
|
let end = 0;
|
|
4523
4193
|
let code = -1;
|
|
4524
4194
|
let cc = str.charCodeAt(amp + 1);
|
|
@@ -4642,17 +4312,9 @@ const escapeAttribute = (s, delimiter, minimal) => {
|
|
|
4642
4312
|
switch (s.charCodeAt(i)) {
|
|
4643
4313
|
case CC_AMPERSAND: {
|
|
4644
4314
|
// §13.2.5.41: a `&` the parser cannot continue into a reference is
|
|
4645
|
-
// data
|
|
4646
|
-
|
|
4647
|
-
if (
|
|
4648
|
-
minimal === true &&
|
|
4649
|
-
next !== CC_NUMBER_SIGN &&
|
|
4650
|
-
!(
|
|
4651
|
-
(next >= 0x30 && next <= 0x39) ||
|
|
4652
|
-
(next >= 0x41 && next <= 0x5a) ||
|
|
4653
|
-
(next >= 0x61 && next <= 0x7a)
|
|
4654
|
-
)
|
|
4655
|
-
) {
|
|
4315
|
+
// data — asked of the table, since `&c` names none. The minifier
|
|
4316
|
+
// writes it bare; generated markup escapes it.
|
|
4317
|
+
if (minimal === true && !_startsCharacterReference(s, i, true)) {
|
|
4656
4318
|
continue;
|
|
4657
4319
|
}
|
|
4658
4320
|
rep = "&";
|
|
@@ -4827,23 +4489,20 @@ const EMPTY_ATTRS = /** @type {AttributeRun} */ (
|
|
|
4827
4489
|
Object.freeze({ start: 0, count: 0 })
|
|
4828
4490
|
);
|
|
4829
4491
|
|
|
4830
|
-
//
|
|
4831
|
-
//
|
|
4832
|
-
//
|
|
4833
|
-
//
|
|
4834
|
-
//
|
|
4835
|
-
//
|
|
4836
|
-
//
|
|
4837
|
-
//
|
|
4838
|
-
//
|
|
4839
|
-
// the next `parseHtml` call. Id 0 is reserved as "no node" so the link columns
|
|
4840
|
-
// can use 0 as null.
|
|
4492
|
+
// WHY: struct-of-arrays AST backend. One node is one integer id
|
|
4493
|
+
// (`HtmlNodeRef`) into the parallel columns below, so there is no per-node
|
|
4494
|
+
// object and no per-parent children array: shape lives in the four link
|
|
4495
|
+
// columns, and the only heap references are the payload and attribute side
|
|
4496
|
+
// arrays. The columns are module-level and reused across parses — grown, and
|
|
4497
|
+
// shrunk on release only past `_COLUMN_SHRINK_CAPACITY` — so a steady-state
|
|
4498
|
+
// parse allocates almost nothing per node, at the price of a tree being valid
|
|
4499
|
+
// only until the next `parseHtml`. Id 0 is reserved so the links can use it as
|
|
4500
|
+
// null.
|
|
4841
4501
|
let _nodeCapacity = 0;
|
|
4842
4502
|
let _nodeCount = 0;
|
|
4843
|
-
// Highest id `_nodeCount`
|
|
4844
|
-
//
|
|
4845
|
-
//
|
|
4846
|
-
// strings alive until a later parse happens to overwrite them.
|
|
4503
|
+
// Highest id `_nodeCount` reached this parse. Streaming rewinds the allocator,
|
|
4504
|
+
// so `_nodeCount` no longer bounds the ids written, and the release pass has to
|
|
4505
|
+
// clear up to here or a recycled slot keeps its strings alive.
|
|
4847
4506
|
let _nodeHighWater = 0;
|
|
4848
4507
|
/** `NodeType` per node */
|
|
4849
4508
|
let _nodeTypes = new Uint8Array(0);
|
|
@@ -4917,12 +4576,10 @@ const _templateContentOf = (el) =>
|
|
|
4917
4576
|
const _namespaceOf = (el) => _nodeFlags[el] & NS_MASK;
|
|
4918
4577
|
|
|
4919
4578
|
// === Attribute columns ===
|
|
4920
|
-
|
|
4921
|
-
//
|
|
4922
|
-
// the source by offset
|
|
4923
|
-
//
|
|
4924
|
-
// html5lib serializer name is derived from the adjusted name plus one flag
|
|
4925
|
-
// bit, so per attribute only the interned name pointer is retained.
|
|
4579
|
+
|
|
4580
|
+
// One attribute is one integer id here, and an element holds a contiguous run.
|
|
4581
|
+
// Values are read from the source by offset and the serializer name from the
|
|
4582
|
+
// adjusted name, so only the name pointer is retained per attribute.
|
|
4926
4583
|
let _attrCapacity = 0;
|
|
4927
4584
|
let _attrCount = 0;
|
|
4928
4585
|
// Elements a repeated `<html>` / `<body>` start tag merged attributes onto
|
|
@@ -5038,6 +4695,15 @@ const _attrValueOf = (i) => {
|
|
|
5038
4695
|
: _htmlSource.slice(_attrValueStarts[i], _attrValueEnds[i]);
|
|
5039
4696
|
};
|
|
5040
4697
|
|
|
4698
|
+
/**
|
|
4699
|
+
* An attribute's value as the element is handed it. The tree construction
|
|
4700
|
+
* matches values the tokenizer has already resolved references in, so how one
|
|
4701
|
+
* is spelled must not decide which branch a token takes.
|
|
4702
|
+
* @param {number} i the attribute
|
|
4703
|
+
* @returns {string} the value with its character references resolved
|
|
4704
|
+
*/
|
|
4705
|
+
const _attrValueDecoded = (i) => decodeEntities(_attrValueOf(i), true);
|
|
4706
|
+
|
|
5041
4707
|
// Linear name lookup in a run — attribute lists are short, a loop beats a Map.
|
|
5042
4708
|
/** @type {(start: number, count: number, name: string) => number} */
|
|
5043
4709
|
const _findAttr = (start, count, name) => {
|
|
@@ -5061,10 +4727,9 @@ const _copyAttr = (i) => {
|
|
|
5061
4727
|
return c;
|
|
5062
4728
|
};
|
|
5063
4729
|
|
|
5064
|
-
// html5lib serializer name
|
|
5065
|
-
// flagged
|
|
5066
|
-
//
|
|
5067
|
-
// itself. Everything else serializes as the plain name (undefined here).
|
|
4730
|
+
// Derived html5lib serializer name: a `FOREIGN_ATTR_NS`-adjusted attribute is
|
|
4731
|
+
// flagged and keyed by its name, a camelCase-adjusted one carries an uppercase
|
|
4732
|
+
// letter and serializes as itself, and everything else is the plain name.
|
|
5068
4733
|
/** @type {(i: number) => string | undefined} */
|
|
5069
4734
|
const _attrSerializedName = (i) => {
|
|
5070
4735
|
if ((_attrFlags[i] & 1) !== 0) return FOREIGN_ATTR_NS[_attrNames[i]];
|
|
@@ -5159,10 +4824,9 @@ const _resetAstColumns = () => {
|
|
|
5159
4824
|
_doctypeSystemId = null;
|
|
5160
4825
|
};
|
|
5161
4826
|
|
|
5162
|
-
// The typed-array columns grow to the largest document
|
|
5163
|
-
//
|
|
5164
|
-
//
|
|
5165
|
-
// ~17 bytes/attribute of capacity across the columns).
|
|
4827
|
+
// The typed-array columns grow to the largest document parsed, and are shrunk
|
|
4828
|
+
// on release above this capacity so one pathological file cannot pin tens of MB
|
|
4829
|
+
// at module level for the process lifetime.
|
|
5166
4830
|
const _COLUMN_SHRINK_CAPACITY = 65536;
|
|
5167
4831
|
|
|
5168
4832
|
// Release the side arrays' heap references (strings, attribute names/values)
|
|
@@ -5531,10 +5195,9 @@ const buildNameInternTable = (names) => {
|
|
|
5531
5195
|
hashes[slot] = h;
|
|
5532
5196
|
values[slot] = name;
|
|
5533
5197
|
}
|
|
5534
|
-
// `memo` short-circuits the hash walk for a name seen
|
|
5535
|
-
//
|
|
5536
|
-
//
|
|
5537
|
-
// only ones the next parse drops — and `noted` keeps each in it once.
|
|
5198
|
+
// `memo` short-circuits the hash walk for a name just seen, with `""` as the
|
|
5199
|
+
// empty slot so the load stays monomorphic. `sourceBacked` lists the slots
|
|
5200
|
+
// holding a slice of the input — the only ones the next parse drops.
|
|
5538
5201
|
return {
|
|
5539
5202
|
mask,
|
|
5540
5203
|
hashes,
|
|
@@ -5583,10 +5246,9 @@ const _dropSourceBackedNames = (table) => {
|
|
|
5583
5246
|
* @returns {string} lowercased name
|
|
5584
5247
|
*/
|
|
5585
5248
|
const internLowerName = (table, input, start, end) => {
|
|
5586
|
-
// A document spells the same few names over and over, so a direct-mapped
|
|
5587
|
-
//
|
|
5588
|
-
//
|
|
5589
|
-
// against the current range, so a collision only ever misses, never lies.
|
|
5249
|
+
// A document spells the same few names over and over, so a direct-mapped memo
|
|
5250
|
+
// keyed on folded first char and length answers most calls in one compare.
|
|
5251
|
+
// Every hit re-verifies, so a collision misses rather than lies.
|
|
5590
5252
|
let c0 = input.charCodeAt(start);
|
|
5591
5253
|
if (c0 >= 0x41 && c0 <= 0x5a) c0 += 0x20;
|
|
5592
5254
|
const memo = table.memo;
|
|
@@ -5912,7 +5574,7 @@ const htmlIntegrationPoint = (el) => {
|
|
|
5912
5574
|
if (_namespaceOf(el) === NS_MATHML && _tagNameOf(el) === "annotation-xml") {
|
|
5913
5575
|
const enc = _findAttr(_nodeAttrStarts[el], _nodeAttrCounts[el], "encoding");
|
|
5914
5576
|
if (enc !== 0) {
|
|
5915
|
-
const value =
|
|
5577
|
+
const value = _attrValueDecoded(enc).toLowerCase();
|
|
5916
5578
|
if (value === "text/html" || value === "application/xhtml+xml") {
|
|
5917
5579
|
return true;
|
|
5918
5580
|
}
|
|
@@ -5967,14 +5629,12 @@ const adjustMathmlAttrs = (run) => {
|
|
|
5967
5629
|
};
|
|
5968
5630
|
|
|
5969
5631
|
// ---- HTML tree-construction parser state ----
|
|
5970
|
-
// One active parse at a time
|
|
5971
|
-
//
|
|
5972
|
-
// instead of re-created as closures on every parse.
|
|
5632
|
+
// One active parse at a time, its result valid only until the next `parseHtml`:
|
|
5633
|
+
// module scope is what defines the helpers below once rather than per parse.
|
|
5973
5634
|
let source = "";
|
|
5974
|
-
// Whole-document properties, so a substring cannot
|
|
5975
|
-
//
|
|
5976
|
-
//
|
|
5977
|
-
// so an unset flag only ever costs the old scan.
|
|
5635
|
+
// Whole-document properties, so a substring cannot hold what the input does not,
|
|
5636
|
+
// and decoding introduces neither: a numeric reference to 0 yields U+FFFD, and
|
|
5637
|
+
// CR normalization runs on the raw slice first. An unset flag costs the old scan.
|
|
5978
5638
|
let inputHasCr = true;
|
|
5979
5639
|
let inputHasNul = true;
|
|
5980
5640
|
let skipText = false;
|
|
@@ -6003,8 +5663,21 @@ const templateModes = [];
|
|
|
6003
5663
|
let quirks = false;
|
|
6004
5664
|
/** @type {HtmlElement} fragment context element (0 = document parse) */
|
|
6005
5665
|
let fragment = 0;
|
|
5666
|
+
|
|
5667
|
+
/**
|
|
5668
|
+
* @returns {boolean} whether the parser is parsing template contents
|
|
5669
|
+
*/
|
|
5670
|
+
const parsingTemplateContents = () =>
|
|
5671
|
+
open.some(isHtmlTemplateEl) || (fragment !== 0 && isHtmlTemplateEl(fragment));
|
|
5672
|
+
|
|
6006
5673
|
let tokenEnd = 0;
|
|
6007
5674
|
let swallowNextNewline = false;
|
|
5675
|
+
// Which of `</html>` / `</head>` / `</body>` the source spelled, per parse: the
|
|
5676
|
+
// tree records no end tag, and `removeImpliedTags` keeps the ones that were written.
|
|
5677
|
+
let sourceShellEnds = 0;
|
|
5678
|
+
const SHELL_END_HTML = 1;
|
|
5679
|
+
const SHELL_END_HEAD = 2;
|
|
5680
|
+
const SHELL_END_BODY = 4;
|
|
6008
5681
|
let eofInTag = false;
|
|
6009
5682
|
let sawSelectedContent = false;
|
|
6010
5683
|
const decode = decodeEntities;
|
|
@@ -6208,12 +5881,9 @@ const _recordFostered = (table, node) => {
|
|
|
6208
5881
|
const insertCharacters = (data, start, end) => {
|
|
6209
5882
|
const place = appropriatePlace();
|
|
6210
5883
|
if (_nodeTypes[place.parent] === NodeType.Document) return; // never insert text into document
|
|
6211
|
-
// `skip.text
|
|
6212
|
-
//
|
|
6213
|
-
//
|
|
6214
|
-
// [`tagEnd`, `contentEnd`] without a `Text` node (see `HtmlParser`).
|
|
6215
|
-
// Namespace-agnostic: `HtmlParser` extracts `<script>`/`<style>` bodies in
|
|
6216
|
-
// foreign content (e.g. SVG `<style>`) too.
|
|
5884
|
+
// `skip.text` drops every `Text` node, which construction has already used,
|
|
5885
|
+
// so element structure is unaffected. A raw-text element instead records its
|
|
5886
|
+
// body end, giving a consumer the span [`tagEnd`, `contentEnd`].
|
|
6217
5887
|
if (skipText) {
|
|
6218
5888
|
const p = place.parent;
|
|
6219
5889
|
if (
|
|
@@ -6224,10 +5894,9 @@ const insertCharacters = (data, start, end) => {
|
|
|
6224
5894
|
}
|
|
6225
5895
|
return;
|
|
6226
5896
|
}
|
|
6227
|
-
// Inlined text insert:
|
|
6228
|
-
//
|
|
6229
|
-
// throwaway
|
|
6230
|
-
// including that the before-node merge does not bump `end`.
|
|
5897
|
+
// Inlined text insert: a run merging into the adjacent text sibling, common
|
|
5898
|
+
// with inline formatting, appends the string alone rather than allocating a
|
|
5899
|
+
// throwaway node. Mirrors `insertAtPlace` / `appendTo`, `end` included.
|
|
6231
5900
|
const p = effParent(place.parent);
|
|
6232
5901
|
const before = place.beforeNode;
|
|
6233
5902
|
if (before !== 0) {
|
|
@@ -6316,9 +5985,8 @@ const insertForeignElement = (tagName, ns, attrs, pos) => {
|
|
|
6316
5985
|
};
|
|
6317
5986
|
|
|
6318
5987
|
// ---- scopes ----
|
|
6319
|
-
// Scope "kind" selects which extra elements act as boundaries
|
|
6320
|
-
//
|
|
6321
|
-
// (these run several times per body tag).
|
|
5988
|
+
// Scope "kind" selects which extra elements act as boundaries, as a small int so
|
|
5989
|
+
// the checks below — several per body tag — allocate no predicate closure.
|
|
6322
5990
|
const SCOPE_DEFAULT = 0;
|
|
6323
5991
|
const SCOPE_BUTTON = 1;
|
|
6324
5992
|
const SCOPE_LIST_ITEM = 2;
|
|
@@ -6606,10 +6274,9 @@ const resetInsertionMode = () => {
|
|
|
6606
6274
|
const leadingWs = (t, insert) => {
|
|
6607
6275
|
const m = /^[\t\n\f\r ]+/.exec(t.data);
|
|
6608
6276
|
const ws = m ? m[0] : "";
|
|
6609
|
-
// `t.data` is decoded
|
|
6610
|
-
//
|
|
6611
|
-
//
|
|
6612
|
-
// source that is not this text (see the `Text` printer).
|
|
6277
|
+
// `t.data` is decoded and the offsets are raw, so counting one in the other
|
|
6278
|
+
// lines up only where decoding changed no length. Otherwise the whole span is
|
|
6279
|
+
// kept: too wide is harmless, too narrow names source that is not this text.
|
|
6613
6280
|
const aligned = t.end - t.start === t.data.length;
|
|
6614
6281
|
if (ws && insert) {
|
|
6615
6282
|
insertCharacters(ws, t.start, aligned ? t.start + ws.length : t.end);
|
|
@@ -6895,10 +6562,9 @@ const adoptionAgency = (subject, pos) => {
|
|
|
6895
6562
|
cloneAttrs(fmt),
|
|
6896
6563
|
null
|
|
6897
6564
|
);
|
|
6898
|
-
// Take
|
|
6899
|
-
//
|
|
6900
|
-
//
|
|
6901
|
-
// the tree when it attached, so it stays with the host it hangs off.
|
|
6565
|
+
// Take the direct children of `furthest`; a template's content fragment
|
|
6566
|
+
// stays put. A declarative shadow root is not one of them — the engine took
|
|
6567
|
+
// it out of the tree on attaching, so it stays with its host.
|
|
6902
6568
|
let k = _nodeFirstChildren[furthest];
|
|
6903
6569
|
_nodeFirstChildren[furthest] = 0;
|
|
6904
6570
|
_nodeLastChildren[furthest] = 0;
|
|
@@ -7185,7 +6851,7 @@ modes.inHead = (t) => {
|
|
|
7185
6851
|
_nodeTypes[host] === NodeType.Element &&
|
|
7186
6852
|
_isShadowHost(host)
|
|
7187
6853
|
) {
|
|
7188
|
-
const mode =
|
|
6854
|
+
const mode = _attrValueDecoded(shadowRootMode).toLowerCase();
|
|
7189
6855
|
if (mode === "open" || mode === "closed") {
|
|
7190
6856
|
_nodeFlags[el] |= FLAG_SHADOW_ROOT;
|
|
7191
6857
|
_nodeFlags[host] |= FLAG_SHADOW_HOST;
|
|
@@ -7197,6 +6863,7 @@ modes.inHead = (t) => {
|
|
|
7197
6863
|
}
|
|
7198
6864
|
if (t.type === TOKEN_END_TAG) {
|
|
7199
6865
|
if (t.name === "head") {
|
|
6866
|
+
sourceShellEnds |= SHELL_END_HEAD;
|
|
7200
6867
|
popOpen();
|
|
7201
6868
|
mode = MODE_AFTER_HEAD;
|
|
7202
6869
|
return;
|
|
@@ -7256,6 +6923,7 @@ modes.afterHead = (t) => {
|
|
|
7256
6923
|
const r = leadingWs(t, true);
|
|
7257
6924
|
if (!r) return;
|
|
7258
6925
|
insertHtmlElement("body", EMPTY_ATTRS, null);
|
|
6926
|
+
framesetOk = true;
|
|
7259
6927
|
mode = MODE_IN_BODY;
|
|
7260
6928
|
process(r);
|
|
7261
6929
|
return;
|
|
@@ -7293,6 +6961,7 @@ modes.afterHead = (t) => {
|
|
|
7293
6961
|
if (!BODY_HTML_BR.has(t.name)) return;
|
|
7294
6962
|
}
|
|
7295
6963
|
insertHtmlElement("body", EMPTY_ATTRS, null);
|
|
6964
|
+
framesetOk = true;
|
|
7296
6965
|
mode = MODE_IN_BODY;
|
|
7297
6966
|
process(t);
|
|
7298
6967
|
};
|
|
@@ -7305,9 +6974,8 @@ modes.inBody = (t) => {
|
|
|
7305
6974
|
if (t.data === "") return;
|
|
7306
6975
|
reconstructAfe();
|
|
7307
6976
|
insertCharacters(t.data, t.start, t.end);
|
|
7308
|
-
//
|
|
7309
|
-
// per-text-token whitespace scan
|
|
7310
|
-
// real documents).
|
|
6977
|
+
// After the implicit-body reset in "after head", `framesetOk` only goes
|
|
6978
|
+
// true→false, so once false skip this per-text-token whitespace scan.
|
|
7311
6979
|
if (framesetOk && !isAllWs(t.data)) framesetOk = false;
|
|
7312
6980
|
return;
|
|
7313
6981
|
}
|
|
@@ -7409,7 +7077,7 @@ const startTagInBody = (t) => {
|
|
|
7409
7077
|
return;
|
|
7410
7078
|
}
|
|
7411
7079
|
if (name === "form") {
|
|
7412
|
-
if (form && !
|
|
7080
|
+
if (form && !parsingTemplateContents()) {
|
|
7413
7081
|
return;
|
|
7414
7082
|
}
|
|
7415
7083
|
closeIfPInButtonScope();
|
|
@@ -7417,7 +7085,7 @@ const startTagInBody = (t) => {
|
|
|
7417
7085
|
// not, so anything open between the two forms moves.
|
|
7418
7086
|
if (_formResetWouldPop()) _printFromSource = true;
|
|
7419
7087
|
const el = insertHtmlElement("form", t.attrs, t.pos);
|
|
7420
|
-
if (!
|
|
7088
|
+
if (!parsingTemplateContents()) {
|
|
7421
7089
|
form = el;
|
|
7422
7090
|
}
|
|
7423
7091
|
return;
|
|
@@ -7578,7 +7246,7 @@ const startTagInBody = (t) => {
|
|
|
7578
7246
|
insertHtmlElement("input", t.attrs, t.pos);
|
|
7579
7247
|
popOpen();
|
|
7580
7248
|
const ty = _findAttr(t.attrs.start, t.attrs.count, "type");
|
|
7581
|
-
if (ty === 0 ||
|
|
7249
|
+
if (ty === 0 || _attrValueDecoded(ty).toLowerCase() !== "hidden") {
|
|
7582
7250
|
framesetOk = false;
|
|
7583
7251
|
}
|
|
7584
7252
|
return;
|
|
@@ -7697,6 +7365,7 @@ const endTagInBody = (t) => {
|
|
|
7697
7365
|
}
|
|
7698
7366
|
if (name === "body" || name === "html") {
|
|
7699
7367
|
if (!inScope("body")) return;
|
|
7368
|
+
sourceShellEnds |= name === "html" ? SHELL_END_HTML : SHELL_END_BODY;
|
|
7700
7369
|
mode = MODE_AFTER_BODY;
|
|
7701
7370
|
if (name === "html") process(t);
|
|
7702
7371
|
return;
|
|
@@ -7708,7 +7377,7 @@ const endTagInBody = (t) => {
|
|
|
7708
7377
|
return;
|
|
7709
7378
|
}
|
|
7710
7379
|
if (name === "form") {
|
|
7711
|
-
if (!
|
|
7380
|
+
if (!parsingTemplateContents()) {
|
|
7712
7381
|
const node = form;
|
|
7713
7382
|
form = 0;
|
|
7714
7383
|
if (!node || !inScopeEl(node)) return;
|
|
@@ -7756,10 +7425,9 @@ const endTagInBody = (t) => {
|
|
|
7756
7425
|
/* take a deep breath */
|
|
7757
7426
|
}
|
|
7758
7427
|
if (FORMATTING.has(name)) {
|
|
7759
|
-
// With no such element in the list,
|
|
7760
|
-
//
|
|
7761
|
-
//
|
|
7762
|
-
// longer names.
|
|
7428
|
+
// With no such element in the list, §13.2.6.4.7 hands the token back rather
|
|
7429
|
+
// than swallowing it — which is what Noah's Ark leaves behind: an element
|
|
7430
|
+
// still open that the list no longer names.
|
|
7763
7431
|
if (!adoptionAgency(name, t.pos)) anyOtherEndTag(name);
|
|
7764
7432
|
return;
|
|
7765
7433
|
}
|
|
@@ -7816,10 +7484,9 @@ modes.text = (t) => {
|
|
|
7816
7484
|
return;
|
|
7817
7485
|
}
|
|
7818
7486
|
if (t.type === TOKEN_END_TAG) {
|
|
7819
|
-
//
|
|
7820
|
-
//
|
|
7821
|
-
//
|
|
7822
|
-
// EOF — the tokenizer still emits a partial tag there).
|
|
7487
|
+
// Rawtext rather than an end tag where it cannot close the current
|
|
7488
|
+
// element: a name that does not match, or one running to EOF with no
|
|
7489
|
+
// delimiter, which the tokenizer still emits as a partial tag.
|
|
7823
7490
|
if (
|
|
7824
7491
|
cur() &&
|
|
7825
7492
|
(t.name !== _tagNameOf(cur()) || t.pos.end === t.pos.nameEnd)
|
|
@@ -7900,17 +7567,19 @@ modes.inTable = (t) => {
|
|
|
7900
7567
|
}
|
|
7901
7568
|
if (name === "input") {
|
|
7902
7569
|
const ty = _findAttr(t.attrs.start, t.attrs.count, "type");
|
|
7903
|
-
if (ty !== 0 &&
|
|
7570
|
+
if (ty !== 0 && _attrValueDecoded(ty).toLowerCase() === "hidden") {
|
|
7904
7571
|
insertHtmlElement("input", t.attrs, t.pos);
|
|
7905
7572
|
popOpen();
|
|
7906
7573
|
return;
|
|
7907
7574
|
}
|
|
7908
7575
|
}
|
|
7909
7576
|
if (name === "form") {
|
|
7910
|
-
|
|
7577
|
+
const inTemplate = parsingTemplateContents();
|
|
7578
|
+
if (form && !inTemplate) {
|
|
7911
7579
|
return;
|
|
7912
7580
|
}
|
|
7913
|
-
|
|
7581
|
+
const el = insertHtmlElement("form", t.attrs, t.pos);
|
|
7582
|
+
if (!inTemplate) form = el;
|
|
7914
7583
|
popOpen();
|
|
7915
7584
|
return;
|
|
7916
7585
|
}
|
|
@@ -8211,6 +7880,7 @@ modes.afterBody = (t) => {
|
|
|
8211
7880
|
if (t.type === TOKEN_START_TAG && t.name === "html") return modes.inBody(t);
|
|
8212
7881
|
if (t.type === TOKEN_END_TAG && t.name === "html") {
|
|
8213
7882
|
if (fragment) return;
|
|
7883
|
+
sourceShellEnds |= SHELL_END_HTML;
|
|
8214
7884
|
mode = MODE_AFTER_AFTER_BODY;
|
|
8215
7885
|
return;
|
|
8216
7886
|
}
|
|
@@ -8317,11 +7987,11 @@ const dispatch = () => {
|
|
|
8317
7987
|
}
|
|
8318
7988
|
};
|
|
8319
7989
|
|
|
8320
|
-
//
|
|
8321
|
-
//
|
|
8322
|
-
//
|
|
8323
|
-
//
|
|
8324
|
-
//
|
|
7990
|
+
// WHY: tokenizer callbacks hoisted to module scope, over the module-scope
|
|
7991
|
+
// parser state, so `tokenize`'s indirect `callbacks.text(...)` / `openTag` /
|
|
7992
|
+
// `attribute(...)` sites see one stable function identity across every parse.
|
|
7993
|
+
// They stay monomorphic and V8 inlines them; a fresh per-parse closure made
|
|
7994
|
+
// them megamorphic and blocked that. `parseHtml` refreshes only
|
|
8325
7995
|
// `fragmentContext` on the shared object before each tokenize.
|
|
8326
7996
|
|
|
8327
7997
|
/** @type {() => boolean} */
|
|
@@ -8380,10 +8050,9 @@ const handleDoctypeToken = (
|
|
|
8380
8050
|
// newline; anything else clears the flag (§13.2.6.4.7 "the next token").
|
|
8381
8051
|
swallowNextNewline = false;
|
|
8382
8052
|
tok.type = TOKEN_DOCTYPE;
|
|
8383
|
-
// A missing name and an empty one are
|
|
8384
|
-
// spec's name states always append
|
|
8385
|
-
//
|
|
8386
|
-
// every other character unchanged, so a name of `Ð` stays `Ð`.
|
|
8053
|
+
// A missing name and an empty one are alike to every consumer here, since the
|
|
8054
|
+
// spec's name states always append a character. Folding is ASCII-only, as
|
|
8055
|
+
// those states lowercase upper alpha and append the rest as written.
|
|
8387
8056
|
tok.name =
|
|
8388
8057
|
nameStart === -1
|
|
8389
8058
|
? ""
|
|
@@ -8506,10 +8175,9 @@ const handleCommentToken = (input, start, end, dataStart, dataEnd) => {
|
|
|
8506
8175
|
};
|
|
8507
8176
|
/** @type {(input: string, start: number, end: number) => number} */
|
|
8508
8177
|
const handleTextToken = (input, start, end) => {
|
|
8509
|
-
// `skip.text`
|
|
8510
|
-
//
|
|
8511
|
-
//
|
|
8512
|
-
// the slice + entity decode; anything trickier falls through.
|
|
8178
|
+
// `skip.text` drops the node, so only whitespace-ness matters. Absent `&`,
|
|
8179
|
+
// `\0`, `\r` and a pending newline-swallow the decoded value is the raw range,
|
|
8180
|
+
// so a canonical marker is dispatched without slicing or decoding.
|
|
8513
8181
|
if (skipText && !swallowNextNewline) {
|
|
8514
8182
|
let hasNonWhitespace = false;
|
|
8515
8183
|
let i = start;
|
|
@@ -8586,10 +8254,9 @@ const handleAttributeToken = (
|
|
|
8586
8254
|
}
|
|
8587
8255
|
if (dup) pendAttrDropped = true;
|
|
8588
8256
|
if (!dup) {
|
|
8589
|
-
// The raw
|
|
8590
|
-
//
|
|
8591
|
-
//
|
|
8592
|
-
// override ("").
|
|
8257
|
+
// The raw value is read from the source by offset on demand, since
|
|
8258
|
+
// consumers re-resolve requests from it and the offsets have to stay
|
|
8259
|
+
// aligned. Only a valueless attribute stores an override.
|
|
8593
8260
|
_allocAttr(
|
|
8594
8261
|
name,
|
|
8595
8262
|
valueStart !== -1 ? null : "",
|
|
@@ -8710,6 +8377,7 @@ const parseHtml = (input, pos = 0, options = {}) => {
|
|
|
8710
8377
|
_dropSourceBackedNames(TAG_NAME_INTERN);
|
|
8711
8378
|
_dropSourceBackedNames(ATTR_NAME_INTERN);
|
|
8712
8379
|
const { fragmentContext, skip = EMPTY_SKIP } = options;
|
|
8380
|
+
sourceShellEnds = 0;
|
|
8713
8381
|
skipText = skip.text === true;
|
|
8714
8382
|
skipComments = skip.comments === true;
|
|
8715
8383
|
skipDoctype = skip.doctype === true;
|
|
@@ -8721,26 +8389,21 @@ const parseHtml = (input, pos = 0, options = {}) => {
|
|
|
8721
8389
|
_openStackChanged = false;
|
|
8722
8390
|
_openSpliced = false;
|
|
8723
8391
|
_streamHalted = false;
|
|
8724
|
-
//
|
|
8725
|
-
//
|
|
8726
|
-
// and
|
|
8727
|
-
//
|
|
8728
|
-
//
|
|
8729
|
-
//
|
|
8730
|
-
//
|
|
8731
|
-
//
|
|
8732
|
-
//
|
|
8733
|
-
// its node estimate uses `len/24`; attributes are unaffected by the skip and
|
|
8734
|
-
// keep `len/48`.
|
|
8392
|
+
// WHY: pre-size the columns from the input length. A document past
|
|
8393
|
+
// `_COLUMN_SHRINK_CAPACITY` re-enters with released columns, and the doubling
|
|
8394
|
+
// cascade would re-allocate and copy every one of them several times per
|
|
8395
|
+
// parse. The densest realistic HTML runs about 12 bytes per node and 50 per
|
|
8396
|
+
// attribute, so `len/12` and `len/48` reach the final capacity in one
|
|
8397
|
+
// allocation, a sparser document over-allocates scratch the release frees,
|
|
8398
|
+
// and doubling stays as the net under a short estimate. `skip.text`, which
|
|
8399
|
+
// `HtmlParser` uses, drops roughly half the nodes and so estimates `len/24`;
|
|
8400
|
+
// attributes are untouched by it.
|
|
8735
8401
|
let nodeEstimate = (input.length / (skipText ? 24 : 12)) | 0;
|
|
8736
8402
|
const attrEstimate = (input.length / 48) | 0;
|
|
8737
8403
|
if (_streamEligible) {
|
|
8738
|
-
//
|
|
8739
|
-
//
|
|
8740
|
-
//
|
|
8741
|
-
// document below it is unaffected and still pre-sizes exactly. Attributes
|
|
8742
|
-
// are not recycled (`_attrCount` only resets between parses), so their
|
|
8743
|
-
// estimate stands either way.
|
|
8404
|
+
// Streaming recycles node ids, so a document past the threshold peaks at
|
|
8405
|
+
// the activation point plus a batch of headroom rather than at the whole
|
|
8406
|
+
// estimate. Attributes are not recycled, so their estimate stands.
|
|
8744
8407
|
const cap = _STREAM_MIN_NODES + _FLUSH_BATCH * 2;
|
|
8745
8408
|
if (nodeEstimate > cap) nodeEstimate = cap;
|
|
8746
8409
|
}
|
|
@@ -8835,10 +8498,10 @@ const parseHtml = (input, pos = 0, options = {}) => {
|
|
|
8835
8498
|
tok.type = TOKEN_EOF;
|
|
8836
8499
|
dispatch();
|
|
8837
8500
|
|
|
8838
|
-
//
|
|
8839
|
-
// text behind it, and the printer emits what it walks — so serializing
|
|
8840
|
-
// would write the selected option's subtree into the document,
|
|
8841
|
-
// engine that
|
|
8501
|
+
// WHY: not while printing. The mirror is content the engine makes, with no
|
|
8502
|
+
// source text behind it, and the printer emits what it walks — so serializing
|
|
8503
|
+
// it would write the selected option's subtree into the document, which the
|
|
8504
|
+
// engine reading that output would then mirror a second time.
|
|
8842
8505
|
if (sawSelectedContent && _writer === undefined) {
|
|
8843
8506
|
mirrorSelectedContent(doc, 0);
|
|
8844
8507
|
}
|
|
@@ -9009,7 +8672,7 @@ const mirrorSelectedContent = (node, select) => {
|
|
|
9009
8672
|
* @property {boolean=} mergeScripts print a run of adjacent bare `<script>` elements whose bodies this print writes itself as one, which removes elements, hoists a later body's declarations into the ones before it, and lets a failing body end the whole run (default false)
|
|
9010
8673
|
* @property {boolean=} sortAttributes print an element's attributes commonest name first, ties by name, which nothing in HTML reads (default false)
|
|
9011
8674
|
* @property {boolean=} sortTokenLists print every token list the DOM reads as a set (`class`, `rel`, `part`, …) in token order (default false)
|
|
9012
|
-
* @property {(boolean | "smart" | "all")=} removeRedundantAttributes drop an attribute whose value is the element's own default; `true` is `"smart"`, and `"all"` also drops the spec defaults a selector can match (default false)
|
|
8675
|
+
* @property {(boolean | "smart" | "all")=} removeRedundantAttributes drop an attribute whose value is the element's own default; `true` is `"smart"`, and `"all"` also drops the spec defaults a selector can match, and with them a value naming no keyword where that says the same (default false)
|
|
9013
8676
|
* @property {(boolean | "smart" | "all")=} removeImpliedTags how much of the `<html>` / `<head>` / `<body>` shell §13.1.2.4 lets the parser imply may be left out: `"smart"` leaves out only the `<html>` start tag, `true` (or `"all"`) all six, `false` none (default `"smart"`)
|
|
9014
8677
|
* @property {boolean=} deferSrcdoc whether an `<iframe srcdoc>` is among what `deferEmbeddedSource` collects (default true); false for a caller that minifies them itself, which keeps the attribute on the normal path and its shorter delimiter
|
|
9015
8678
|
* @property {DeferredEmbeddedSource[]=} deferEmbeddedSource collects what `renderEmbeddedSource` would be offered instead of offering it, for a caller whose renderer is asynchronous: the print leaves a marker for each and `finish` puts the answers in their place, so one parse serves both. Takes precedence over `renderEmbeddedSource`
|
|
@@ -9063,19 +8726,20 @@ const _tailFusesWithNextTag = (s) => {
|
|
|
9063
8726
|
* escape — and neither does the `&` in a query string.
|
|
9064
8727
|
* @param {string} s decoded text
|
|
9065
8728
|
* @param {number} i the `&`'s index
|
|
8729
|
+
* @param {boolean=} terminated whether nothing can follow `s` in the same run
|
|
9066
8730
|
* @returns {boolean} true when it has to be escaped
|
|
9067
8731
|
*/
|
|
9068
|
-
const _startsCharacterReference = (s, i) => {
|
|
9069
|
-
//
|
|
9070
|
-
//
|
|
9071
|
-
//
|
|
8732
|
+
const _startsCharacterReference = (s, i, terminated) => {
|
|
8733
|
+
// Unterminated, the next sibling's first characters could complete the run
|
|
8734
|
+
// once a comment between them is dropped (`a &am` + `p;`) — the same reason
|
|
8735
|
+
// the source fast path keeps out. An attribute value ends at its delimiter.
|
|
9072
8736
|
let end = i + 1;
|
|
9073
8737
|
while (end < s.length) {
|
|
9074
8738
|
const c = s.charCodeAt(end);
|
|
9075
8739
|
if (!isAsciiAlphanumeric(c) && c !== 0x23) break;
|
|
9076
8740
|
end++;
|
|
9077
8741
|
}
|
|
9078
|
-
if (end === s.length) return true;
|
|
8742
|
+
if (end === s.length && terminated !== true) return true;
|
|
9079
8743
|
const next = s.charCodeAt(i + 1);
|
|
9080
8744
|
// `#` opens a numeric reference.
|
|
9081
8745
|
if (next === 0x23) return true;
|
|
@@ -9089,6 +8753,26 @@ const _startsCharacterReference = (s, i) => {
|
|
|
9089
8753
|
return false;
|
|
9090
8754
|
};
|
|
9091
8755
|
|
|
8756
|
+
/**
|
|
8757
|
+
* Whether the `<` at `at` would leave §13.2.5.6's data state rather than stand
|
|
8758
|
+
* as a character. A trailing one counts: what follows it in the output is the
|
|
8759
|
+
* next sibling's first character, which this string does not hold.
|
|
8760
|
+
* @param {string} s decoded text
|
|
8761
|
+
* @param {number} at index of the `<`
|
|
8762
|
+
* @returns {boolean} true when the `<` has to be written as a reference
|
|
8763
|
+
*/
|
|
8764
|
+
const _opensMarkup = (s, at) => {
|
|
8765
|
+
if (at + 1 === s.length) return true;
|
|
8766
|
+
const next = s.charCodeAt(at + 1);
|
|
8767
|
+
return (
|
|
8768
|
+
next === 0x21 ||
|
|
8769
|
+
next === 0x2f ||
|
|
8770
|
+
next === 0x3f ||
|
|
8771
|
+
(next >= 0x41 && next <= 0x5a) ||
|
|
8772
|
+
(next >= 0x61 && next <= 0x7a)
|
|
8773
|
+
);
|
|
8774
|
+
};
|
|
8775
|
+
|
|
9092
8776
|
/**
|
|
9093
8777
|
* Escape a text node's decoded data for re-serialization: `&` and `<`, which
|
|
9094
8778
|
* start a character reference and a tag, plus `>` outside minification, where
|
|
@@ -9103,15 +8787,15 @@ const _startsCharacterReference = (s, i) => {
|
|
|
9103
8787
|
*/
|
|
9104
8788
|
const _escapeTextContent = (s, minify) => {
|
|
9105
8789
|
// A bare `>` is only ever a character in text, so §13.3's escape of it buys
|
|
9106
|
-
// nothing but bytes. `<`
|
|
9107
|
-
//
|
|
8790
|
+
// nothing but bytes. A `<` only opens markup before what §13.2.5.6 leaves the
|
|
8791
|
+
// data state for, and an `&` only sometimes starts a character reference.
|
|
9108
8792
|
if (minify) {
|
|
9109
8793
|
if (!s.includes("&") && !s.includes("<") && !s.includes("\r")) return s;
|
|
9110
8794
|
let out = "";
|
|
9111
8795
|
let last = 0;
|
|
9112
8796
|
for (let i = 0; i < s.length; i++) {
|
|
9113
8797
|
const c = s.charCodeAt(i);
|
|
9114
|
-
if (c === 0x3c) {
|
|
8798
|
+
if (c === 0x3c && _opensMarkup(s, i)) {
|
|
9115
8799
|
out += `${s.slice(last, i)}<`;
|
|
9116
8800
|
last = i + 1;
|
|
9117
8801
|
} else if (c === 0x26 && _startsCharacterReference(s, i)) {
|
|
@@ -9430,7 +9114,12 @@ const _impliedStartTagShape = (path, node, child) => {
|
|
|
9430
9114
|
if (path.attributeCount(node) !== 0 || path.namespace(node) !== NS_HTML) {
|
|
9431
9115
|
return false;
|
|
9432
9116
|
}
|
|
9433
|
-
|
|
9117
|
+
// What will print, not what the tree holds: a comment minifying drops is not
|
|
9118
|
+
// content the tag has to stay for, the same way the end tags read it.
|
|
9119
|
+
let first = path.firstChild(node);
|
|
9120
|
+
while (first !== 0 && _printsNothing(path, first)) {
|
|
9121
|
+
first = path.nextSibling(first);
|
|
9122
|
+
}
|
|
9434
9123
|
return (
|
|
9435
9124
|
first !== 0 &&
|
|
9436
9125
|
path.type(first) === NodeType.Element &&
|
|
@@ -9599,24 +9288,34 @@ let _deferEmbeddedSource;
|
|
|
9599
9288
|
// them itself keeps the attribute on the normal path, which re-quotes it to
|
|
9600
9289
|
// whichever delimiter its content makes shorter.
|
|
9601
9290
|
/**
|
|
9602
|
-
* One
|
|
9603
|
-
*
|
|
9604
|
-
*
|
|
9605
|
-
* @param {string} name the attribute name
|
|
9291
|
+
* One value, written with whichever delimiter it needs fewer escapes inside — a
|
|
9292
|
+
* minified document is mostly `"`, so a value holding a `"` is shorter quoted
|
|
9293
|
+
* with `'`. Where neither costs anything the source's own delimiter is kept.
|
|
9606
9294
|
* @param {string} value the value to write
|
|
9607
9295
|
* @param {string} source the delimiter the source spelled it with, if any
|
|
9608
|
-
* @returns {string} the
|
|
9296
|
+
* @returns {string} the value, delimiters and all
|
|
9609
9297
|
*/
|
|
9610
|
-
const
|
|
9298
|
+
const _quoteDecodedValue = (value, source) => {
|
|
9611
9299
|
const double = value.includes('"');
|
|
9612
9300
|
const single = value.includes("'");
|
|
9613
9301
|
const quote =
|
|
9614
9302
|
double !== single ? (double ? 39 : 34) : source === "'" ? 39 : 34;
|
|
9615
9303
|
const delimiter = String.fromCharCode(quote);
|
|
9616
9304
|
|
|
9617
|
-
return
|
|
9305
|
+
return `${delimiter}${escapeAttribute(value, quote, true)}${delimiter}`;
|
|
9618
9306
|
};
|
|
9619
9307
|
|
|
9308
|
+
/**
|
|
9309
|
+
* One attribute, written with the delimiter `_quoteDecodedValue` picks for its
|
|
9310
|
+
* value.
|
|
9311
|
+
* @param {string} name the attribute name
|
|
9312
|
+
* @param {string} value the value to write
|
|
9313
|
+
* @param {string} source the delimiter the source spelled it with, if any
|
|
9314
|
+
* @returns {string} the attribute, delimiter and all
|
|
9315
|
+
*/
|
|
9316
|
+
const _writeQuotedAttribute = (name, value, source) =>
|
|
9317
|
+
` ${name}=${_quoteDecodedValue(value, source)}`;
|
|
9318
|
+
|
|
9620
9319
|
let _deferSrcdoc = true;
|
|
9621
9320
|
|
|
9622
9321
|
/**
|
|
@@ -9748,9 +9447,8 @@ let _mergeScripts = false;
|
|
|
9748
9447
|
let _absorbed = null;
|
|
9749
9448
|
|
|
9750
9449
|
// Foster parenting puts a node before the table it was written inside, so tree
|
|
9751
|
-
//
|
|
9752
|
-
//
|
|
9753
|
-
// tag wrote, or a list item a special element stopped the loop from closing.
|
|
9450
|
+
// and source order disagree there; it is recorded where it happens. The tree
|
|
9451
|
+
// then holds what no arrangement of tags reaches.
|
|
9754
9452
|
let _printFromSource = false;
|
|
9755
9453
|
/** @type {HtmlNodeRef[] | null} this parse's fosters, flat: table, then node */
|
|
9756
9454
|
let _fosteredLog = null;
|
|
@@ -9773,14 +9471,14 @@ let _fosterOpenTail = null;
|
|
|
9773
9471
|
/** @type {number} how far the furthest verbatim span reached */
|
|
9774
9472
|
let _fosterSpanEnd = 0;
|
|
9775
9473
|
|
|
9776
|
-
//
|
|
9777
|
-
// parser imply may be left out
|
|
9778
|
-
//
|
|
9779
|
-
//
|
|
9780
|
-
//
|
|
9781
|
-
//
|
|
9782
|
-
//
|
|
9783
|
-
//
|
|
9474
|
+
// WHY: how much of the `<html>` / `<head>` / `<body>` shell §13.1.2.4 lets the
|
|
9475
|
+
// parser imply may be left out. Every other optional tag goes unconditionally,
|
|
9476
|
+
// since nothing can observe that, but these six are what a consumer reading the
|
|
9477
|
+
// page with a regexp rather than a parser looks for. `"smart"` drops the one
|
|
9478
|
+
// such a reader never matches: the `<html>` start tag, omittable only with no
|
|
9479
|
+
// attribute at all, so the `<html lang=en>` anyone greps for keeps its tag.
|
|
9480
|
+
// `</html>` stays with it, since a truncation check reads a page as complete by
|
|
9481
|
+
// finding one.
|
|
9784
9482
|
/** @type {"none" | "smart" | "all"} */
|
|
9785
9483
|
let _removeImpliedTags = "smart";
|
|
9786
9484
|
|
|
@@ -9807,7 +9505,11 @@ const _canOmitShellStart = (path, name, node) => {
|
|
|
9807
9505
|
if (name === "html") return true;
|
|
9808
9506
|
if (_removeImpliedTags !== "all") return false;
|
|
9809
9507
|
if (name === "head") return type === NodeType.Element;
|
|
9810
|
-
|
|
9508
|
+
// Whitespace `"all"` drops is not whitespace the tag has to stay for; any
|
|
9509
|
+
// lesser tier keeps it, and without the tag the parser drops it on the way in.
|
|
9510
|
+
if (type === NodeType.Text) {
|
|
9511
|
+
return !startsWithWs(path.data(first)) || _collapseWhitespace === "all";
|
|
9512
|
+
}
|
|
9811
9513
|
return !BODY_START_KEPT_BEFORE.has(path.tagName(first));
|
|
9812
9514
|
};
|
|
9813
9515
|
|
|
@@ -9847,6 +9549,32 @@ const _shellEndTagOmittable = (path, name, node) => {
|
|
|
9847
9549
|
const _canOmitShellEnd = (path, name, node) =>
|
|
9848
9550
|
_removeImpliedTags === "all" && _shellEndTagOmittable(path, name, node);
|
|
9849
9551
|
|
|
9552
|
+
/**
|
|
9553
|
+
* The end tag a transparent implied element still prints: the parser re-implies
|
|
9554
|
+
* the start tag behind it, never the end tag, and outside `"all"` a shell tag
|
|
9555
|
+
* the source spelled stays — `</html>` is what a truncation check looks for.
|
|
9556
|
+
* @param {HtmlPath} path the accessor
|
|
9557
|
+
* @param {string} name the element's lowercased tag name
|
|
9558
|
+
* @param {HtmlNodeRef} node the element
|
|
9559
|
+
* @returns {string} the end tag, or `""` when nothing needs it
|
|
9560
|
+
*/
|
|
9561
|
+
const _impliedCloseTag = (path, name, node) => {
|
|
9562
|
+
if (!SHELL_ELEMENTS.has(name)) {
|
|
9563
|
+
return _canOmitEndTag(path, name, node) ? "" : `</${name}>`;
|
|
9564
|
+
}
|
|
9565
|
+
const spelled =
|
|
9566
|
+
name === "html"
|
|
9567
|
+
? SHELL_END_HTML
|
|
9568
|
+
: name === "head"
|
|
9569
|
+
? SHELL_END_HEAD
|
|
9570
|
+
: SHELL_END_BODY;
|
|
9571
|
+
return (sourceShellEnds & spelled) === 0 || _removeImpliedTags === "all"
|
|
9572
|
+
? _shellEndTagOmittable(path, name, node)
|
|
9573
|
+
? ""
|
|
9574
|
+
: `</${name}>`
|
|
9575
|
+
: `</${name}>`;
|
|
9576
|
+
};
|
|
9577
|
+
|
|
9850
9578
|
/**
|
|
9851
9579
|
* Whether a parser-inserted table group has to print a start tag the source
|
|
9852
9580
|
* never spelled. With `removeOptionalTags` off its end tag prints, and a
|
|
@@ -9901,9 +9629,8 @@ const _minifyStyleBody = (css) =>
|
|
|
9901
9629
|
css.trim() === "" ? css : _renderEmbedded(css, CSS_TYPE);
|
|
9902
9630
|
|
|
9903
9631
|
// The throwaway rule a `style` attribute's declaration list is minified inside.
|
|
9904
|
-
//
|
|
9905
|
-
//
|
|
9906
|
-
// out cannot drift from what was written in.
|
|
9632
|
+
// Its selector is only written and read back here, so it is the shortest one
|
|
9633
|
+
// the printer cannot rewrite, and both halves are named from one place.
|
|
9907
9634
|
|
|
9908
9635
|
/**
|
|
9909
9636
|
* How one attribute is written out, and whether it leaves the tag ending in an
|
|
@@ -9914,15 +9641,27 @@ const _minifyStyleBody = (css) =>
|
|
|
9914
9641
|
* @param {string} rawValue its value as the source spelled it
|
|
9915
9642
|
* @param {string | null} decoded the value decoded, for one carrying character references, or null
|
|
9916
9643
|
* @param {boolean} quoted whether the source spelled the value with a delimiter, which is two characters this has to beat
|
|
9644
|
+
* @param {string} delimiter the delimiter the source spelled it with, if any
|
|
9917
9645
|
* @returns {{ text: string, unquotedTail: boolean } | null} what to emit, or null to leave the source alone
|
|
9918
9646
|
*/
|
|
9919
|
-
const _writeAttribute = (
|
|
9647
|
+
const _writeAttribute = (
|
|
9648
|
+
attributeName,
|
|
9649
|
+
value,
|
|
9650
|
+
rawValue,
|
|
9651
|
+
decoded,
|
|
9652
|
+
quoted,
|
|
9653
|
+
delimiter
|
|
9654
|
+
) => {
|
|
9655
|
+
// `normalizeAttributeQuotes` holds back the delimiters, not the spelling
|
|
9656
|
+
// inside them, so a frozen value keeps the delimiter the source chose.
|
|
9657
|
+
const frozen = !_transforms.normalizeAttributeQuotes;
|
|
9920
9658
|
const minimal =
|
|
9921
|
-
|
|
9922
|
-
?
|
|
9923
|
-
:
|
|
9924
|
-
|
|
9925
|
-
|
|
9659
|
+
decoded === null
|
|
9660
|
+
? null
|
|
9661
|
+
: frozen
|
|
9662
|
+
? _quoteDecodedValue(decoded, delimiter)
|
|
9663
|
+
: _minimalAttributeValue(decoded);
|
|
9664
|
+
const unquotable = !frozen && _canUnquoteAttributeValue(value);
|
|
9926
9665
|
|
|
9927
9666
|
if (
|
|
9928
9667
|
minimal !== null &&
|
|
@@ -9942,7 +9681,9 @@ const _writeAttribute = (attributeName, value, rawValue, decoded, quoted) => {
|
|
|
9942
9681
|
}
|
|
9943
9682
|
|
|
9944
9683
|
if (value !== rawValue) {
|
|
9945
|
-
const quotedValue =
|
|
9684
|
+
const quotedValue = frozen
|
|
9685
|
+
? _quoteDecodedValue(value, delimiter)
|
|
9686
|
+
: _quoteAttributeValue(value);
|
|
9946
9687
|
|
|
9947
9688
|
if (quotedValue !== null) {
|
|
9948
9689
|
return { text: ` ${attributeName}=${quotedValue}`, unquotedTail: false };
|
|
@@ -10029,11 +9770,12 @@ const _minimalAttributeValue = (value) => {
|
|
|
10029
9770
|
};
|
|
10030
9771
|
|
|
10031
9772
|
/**
|
|
10032
|
-
* Whether an attribute only restates the element's own default
|
|
10033
|
-
*
|
|
10034
|
-
*
|
|
10035
|
-
*
|
|
10036
|
-
*
|
|
9773
|
+
* Whether an attribute only restates the element's own default — its value
|
|
9774
|
+
* naming that default, or naming no keyword at all where the invalid value
|
|
9775
|
+
* default is that same state — so dropping it leaves the DOM reading the same
|
|
9776
|
+
* value, but leaves an attribute selector, which matches the content attribute,
|
|
9777
|
+
* with nothing to match. Both tables are looked up per element by the caller,
|
|
9778
|
+
* not per attribute here: the answer is the same for every attribute of one tag.
|
|
10037
9779
|
* @param {Record<string, string> | undefined} markers the element's marker table
|
|
10038
9780
|
* @param {Record<string, string> | undefined} defaults the element's spec-default table
|
|
10039
9781
|
* @param {string} tagName lowercased element name
|
|
@@ -10042,19 +9784,30 @@ const _minimalAttributeValue = (value) => {
|
|
|
10042
9784
|
* @returns {boolean} true when the attribute may be dropped
|
|
10043
9785
|
*/
|
|
10044
9786
|
const _isRedundantAttribute = (markers, defaults, tagName, name, value) => {
|
|
10045
|
-
|
|
9787
|
+
// Looked up by what the value says, not how it was spelled — guarded on the
|
|
9788
|
+
// `&` so a value carrying no reference pays nothing.
|
|
9789
|
+
const lowered = _asciiLowerCase(
|
|
9790
|
+
_asciiTrim(value.includes("&") ? decodeEntities(value, true) : value)
|
|
9791
|
+
);
|
|
10046
9792
|
// `<script type>`: only a spelling that names JavaScript, which is what the
|
|
10047
9793
|
// element already runs. `module` and the empty spelling say something.
|
|
10048
9794
|
if (tagName === "script" && name === "type") {
|
|
10049
|
-
// Read the way `_scriptType` reads it, or the two disagree about which
|
|
10050
|
-
// `<script>` this is and the body's handler and this one part ways.
|
|
10051
|
-
const type = _asciiLowerCase(_asciiTrim(decodeEntities(value, true)));
|
|
10052
9795
|
return (
|
|
10053
|
-
|
|
9796
|
+
lowered !== "" &&
|
|
9797
|
+
lowered !== "module" &&
|
|
9798
|
+
JAVASCRIPT_SCRIPT_TYPES.has(lowered)
|
|
10054
9799
|
);
|
|
10055
9800
|
}
|
|
10056
9801
|
if (markers !== undefined && markers[name] === lowered) return true;
|
|
10057
|
-
|
|
9802
|
+
if (defaults === undefined || defaults[name] === undefined) return false;
|
|
9803
|
+
if (defaults[name] === lowered) return true;
|
|
9804
|
+
// A value naming no keyword takes the attribute's invalid value default,
|
|
9805
|
+
// which is the state a missing attribute names except where
|
|
9806
|
+
// `INVALID_VALUE_DEFAULT_DIFFERS` says the two part.
|
|
9807
|
+
const enumerated = ENUMERATED_KEYWORDS[tagName];
|
|
9808
|
+
const keywords = enumerated === undefined ? undefined : enumerated[name];
|
|
9809
|
+
if (keywords === undefined || keywords.has(lowered)) return false;
|
|
9810
|
+
return !INVALID_VALUE_DEFAULT_DIFFERS.has(`${tagName} ${name}`);
|
|
10058
9811
|
};
|
|
10059
9812
|
|
|
10060
9813
|
/**
|
|
@@ -10069,9 +9822,12 @@ const _isRemovableEmptyAttribute = (elementName, name, value) => {
|
|
|
10069
9822
|
if (!_removeEmptyAttributes) return false;
|
|
10070
9823
|
const on = EMPTY_REMOVABLE_ATTRIBUTES.get(name);
|
|
10071
9824
|
if (on === undefined || (on !== null && !on.has(elementName))) return false;
|
|
10072
|
-
// Read
|
|
10073
|
-
//
|
|
10074
|
-
|
|
9825
|
+
// Read decoded: a reference is how a value is spelled, not what it says, and
|
|
9826
|
+
// the tokenizer hands the element the same empty either way. Guarded on the
|
|
9827
|
+
// `&`, and `_asciiTrim` returns the input when there is nothing to trim.
|
|
9828
|
+
return (
|
|
9829
|
+
_asciiTrim(value.includes("&") ? decodeEntities(value, true) : value) === ""
|
|
9830
|
+
);
|
|
10075
9831
|
};
|
|
10076
9832
|
|
|
10077
9833
|
/**
|
|
@@ -10483,7 +10239,12 @@ const _scriptType = (path, element) => {
|
|
|
10483
10239
|
const _isViewportMeta = (path) => {
|
|
10484
10240
|
for (const attribute of path.attributes()) {
|
|
10485
10241
|
if (attribute.name === "name") {
|
|
10486
|
-
|
|
10242
|
+
const value = attribute.value;
|
|
10243
|
+
return (
|
|
10244
|
+
_asciiLowerCase(
|
|
10245
|
+
_asciiTrim(value.includes("&") ? decodeEntities(value, true) : value)
|
|
10246
|
+
) === "viewport"
|
|
10247
|
+
);
|
|
10487
10248
|
}
|
|
10488
10249
|
}
|
|
10489
10250
|
return false;
|
|
@@ -10511,7 +10272,12 @@ const _minifyInlineJson = (json) =>
|
|
|
10511
10272
|
const _isCssStyleElement = (path, element) => {
|
|
10512
10273
|
for (const attribute of path.attributes(element)) {
|
|
10513
10274
|
if (attribute.name !== "type") continue;
|
|
10514
|
-
|
|
10275
|
+
// Read the way `_scriptType` reads its own: the value the spec matches is
|
|
10276
|
+
// the one the parser read, so a reference is decoded first.
|
|
10277
|
+
const raw = attribute.value;
|
|
10278
|
+
const value = _asciiLowerCase(
|
|
10279
|
+
_asciiTrim(raw.includes("&") ? decodeEntities(raw, true) : raw)
|
|
10280
|
+
);
|
|
10515
10281
|
return value === "" || value === "text/css";
|
|
10516
10282
|
}
|
|
10517
10283
|
return true;
|
|
@@ -10536,6 +10302,47 @@ const _onlyTextChild = (element) => {
|
|
|
10536
10302
|
: 0;
|
|
10537
10303
|
};
|
|
10538
10304
|
|
|
10305
|
+
/**
|
|
10306
|
+
* Whether an attribute reaches the output: one this print drops is not a
|
|
10307
|
+
* difference between two elements, nor an attribute that keeps a run apart.
|
|
10308
|
+
* @param {string} elementName lowercased element name
|
|
10309
|
+
* @param {string} name lowercased attribute name
|
|
10310
|
+
* @param {string} value its raw value
|
|
10311
|
+
* @returns {boolean} true when it prints
|
|
10312
|
+
*/
|
|
10313
|
+
const _attributePrints = (elementName, name, value) => {
|
|
10314
|
+
const markers =
|
|
10315
|
+
_removeRedundantAttributes !== "none"
|
|
10316
|
+
? REDUNDANT_TYPE_ATTRIBUTES[elementName]
|
|
10317
|
+
: undefined;
|
|
10318
|
+
const defaults =
|
|
10319
|
+
_removeRedundantAttributes === "all"
|
|
10320
|
+
? REDUNDANT_DEFAULT_ATTRIBUTES[elementName]
|
|
10321
|
+
: undefined;
|
|
10322
|
+
if (
|
|
10323
|
+
(markers !== undefined || defaults !== undefined) &&
|
|
10324
|
+
_isRedundantAttribute(markers, defaults, elementName, name, value)
|
|
10325
|
+
) {
|
|
10326
|
+
return false;
|
|
10327
|
+
}
|
|
10328
|
+
return !_isRemovableEmptyAttribute(elementName, name, value);
|
|
10329
|
+
};
|
|
10330
|
+
|
|
10331
|
+
/**
|
|
10332
|
+
* How many of an element's attributes this print keeps.
|
|
10333
|
+
* @param {HtmlPath} path the accessor
|
|
10334
|
+
* @param {HtmlNodeRef} element the element
|
|
10335
|
+
* @returns {number} the count
|
|
10336
|
+
*/
|
|
10337
|
+
const _printedAttributeCount = (path, element) => {
|
|
10338
|
+
const name = path.tagName(element);
|
|
10339
|
+
let kept = 0;
|
|
10340
|
+
for (const attribute of path.attributes(element)) {
|
|
10341
|
+
if (_attributePrints(name, attribute.name, attribute.value)) kept++;
|
|
10342
|
+
}
|
|
10343
|
+
return kept;
|
|
10344
|
+
};
|
|
10345
|
+
|
|
10539
10346
|
/**
|
|
10540
10347
|
* Whether two elements carry exactly the same attributes. Values compare raw:
|
|
10541
10348
|
* two spellings of one value are rare enough not to be worth decoding for, and
|
|
@@ -10546,19 +10353,21 @@ const _onlyTextChild = (element) => {
|
|
|
10546
10353
|
* @returns {boolean} true when the two agree on every attribute
|
|
10547
10354
|
*/
|
|
10548
10355
|
const _sameAttributes = (path, one, other) => {
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
if (
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10356
|
+
// What each will spell, not what the tree holds: a `type` this print drops
|
|
10357
|
+
// as redundant is not a difference between them.
|
|
10358
|
+
const elementName = path.tagName(one);
|
|
10359
|
+
let count = 0;
|
|
10360
|
+
for (const attribute of path.attributes(one)) {
|
|
10361
|
+
if (!_attributePrints(elementName, attribute.name, attribute.value)) {
|
|
10362
|
+
continue;
|
|
10363
|
+
}
|
|
10364
|
+
count++;
|
|
10365
|
+
const match = path.findAttribute(attribute.name, other);
|
|
10366
|
+
if (match === 0 || path.attributeValue(match) !== attribute.value) {
|
|
10558
10367
|
return false;
|
|
10559
10368
|
}
|
|
10560
10369
|
}
|
|
10561
|
-
return
|
|
10370
|
+
return count === _printedAttributeCount(path, other);
|
|
10562
10371
|
};
|
|
10563
10372
|
|
|
10564
10373
|
/**
|
|
@@ -10582,6 +10391,12 @@ const _mergeableStyleAfter = (path, element) => {
|
|
|
10582
10391
|
if (!isAllWs(_nodeStrings[next])) return 0;
|
|
10583
10392
|
continue;
|
|
10584
10393
|
}
|
|
10394
|
+
// A comment this print drops leaves the two adjacent, so it is not what
|
|
10395
|
+
// stands between them; one it keeps still is.
|
|
10396
|
+
if (type === NodeType.Comment) {
|
|
10397
|
+
if (_printsNothing(path, next)) continue;
|
|
10398
|
+
return 0;
|
|
10399
|
+
}
|
|
10585
10400
|
return type === NodeType.Element &&
|
|
10586
10401
|
path.namespace(next) === NS_HTML &&
|
|
10587
10402
|
path.tagName(next) === "style" &&
|
|
@@ -10797,7 +10612,7 @@ const _opensWithHtmlCloseComment = (js) => {
|
|
|
10797
10612
|
* @returns {HtmlElement} the next `<script>` of the run, or 0
|
|
10798
10613
|
*/
|
|
10799
10614
|
const _mergeableScriptAfter = (path, element) => {
|
|
10800
|
-
if (path
|
|
10615
|
+
if (_printedAttributeCount(path, element) !== 0) return 0;
|
|
10801
10616
|
for (
|
|
10802
10617
|
let next = _nodeNextSiblings[element];
|
|
10803
10618
|
next !== 0;
|
|
@@ -10808,10 +10623,16 @@ const _mergeableScriptAfter = (path, element) => {
|
|
|
10808
10623
|
if (!isAllWs(_nodeStrings[next])) return 0;
|
|
10809
10624
|
continue;
|
|
10810
10625
|
}
|
|
10626
|
+
// A comment this print drops leaves the two adjacent, so it is not what
|
|
10627
|
+
// stands between them; one it keeps still is.
|
|
10628
|
+
if (type === NodeType.Comment) {
|
|
10629
|
+
if (_printsNothing(path, next)) continue;
|
|
10630
|
+
return 0;
|
|
10631
|
+
}
|
|
10811
10632
|
return type === NodeType.Element &&
|
|
10812
10633
|
path.namespace(next) === NS_HTML &&
|
|
10813
10634
|
path.tagName(next) === "script" &&
|
|
10814
|
-
path
|
|
10635
|
+
_printedAttributeCount(path, next) === 0 &&
|
|
10815
10636
|
_onlyTextChild(next) !== 0
|
|
10816
10637
|
? next
|
|
10817
10638
|
: 0;
|
|
@@ -10933,7 +10754,7 @@ const _canUnquoteAttributeValue = (raw) => {
|
|
|
10933
10754
|
c === 0x3d ||
|
|
10934
10755
|
c === 0x3e ||
|
|
10935
10756
|
c === 0x60 ||
|
|
10936
|
-
(c >= 0x09 && c <= 0x0d)
|
|
10757
|
+
(c >= 0x09 && c <= 0x0d && c !== 0x0b)
|
|
10937
10758
|
) {
|
|
10938
10759
|
return false;
|
|
10939
10760
|
}
|
|
@@ -10942,9 +10763,8 @@ const _canUnquoteAttributeValue = (raw) => {
|
|
|
10942
10763
|
};
|
|
10943
10764
|
|
|
10944
10765
|
// How often each attribute name occurs in the document being printed, or null
|
|
10945
|
-
// until `sortAttributes` asks
|
|
10946
|
-
//
|
|
10947
|
-
// already interned there, in one flat run.
|
|
10766
|
+
// until `sortAttributes` asks. Built from the parse's attribute column rather
|
|
10767
|
+
// than by walking the tree, which already holds every one in a flat run.
|
|
10948
10768
|
/** @type {Map<string, number> | null} */
|
|
10949
10769
|
let _attributeFrequencies = null;
|
|
10950
10770
|
|
|
@@ -10982,14 +10802,53 @@ const _attributeRank = () => {
|
|
|
10982
10802
|
};
|
|
10983
10803
|
|
|
10984
10804
|
/**
|
|
10805
|
+
* How often each attribute name reaches the output. Counted over the runs the
|
|
10806
|
+
* elements own rather than the whole column, because an attribute a transform
|
|
10807
|
+
* drops is not one the print order should be ranked on.
|
|
10985
10808
|
* @returns {Map<string, number>} how often each attribute name occurs
|
|
10986
10809
|
*/
|
|
10987
10810
|
const _attributeFrequency = () => {
|
|
10988
10811
|
if (_attributeFrequencies !== null) return _attributeFrequencies;
|
|
10989
10812
|
const frequency = new Map();
|
|
10990
|
-
for (let
|
|
10991
|
-
|
|
10992
|
-
|
|
10813
|
+
for (let node = 1; node <= _nodeCount; node++) {
|
|
10814
|
+
if (_nodeTypes[node] !== NodeType.Element) continue;
|
|
10815
|
+
const count = _nodeAttrCounts[node];
|
|
10816
|
+
if (count === 0) continue;
|
|
10817
|
+
const start = _nodeAttrStarts[node];
|
|
10818
|
+
// Foreign content keeps every attribute it was written with, so only an
|
|
10819
|
+
// HTML element is asked what it drops — the same test the printer makes.
|
|
10820
|
+
const html = _namespaceOf(node) === NS_HTML;
|
|
10821
|
+
const elementName = _tagNameOf(node);
|
|
10822
|
+
const markers =
|
|
10823
|
+
html && _removeRedundantAttributes !== "none"
|
|
10824
|
+
? REDUNDANT_TYPE_ATTRIBUTES[elementName]
|
|
10825
|
+
: undefined;
|
|
10826
|
+
const defaults =
|
|
10827
|
+
html && _removeRedundantAttributes === "all"
|
|
10828
|
+
? REDUNDANT_DEFAULT_ATTRIBUTES[elementName]
|
|
10829
|
+
: undefined;
|
|
10830
|
+
const mayDrop = markers !== undefined || defaults !== undefined;
|
|
10831
|
+
for (let i = start; i < start + count; i++) {
|
|
10832
|
+
const name = _attrNames[i];
|
|
10833
|
+
if (html) {
|
|
10834
|
+
// Valueless reads as the empty value here as it does there.
|
|
10835
|
+
const value = _attrValueOf(i);
|
|
10836
|
+
if (
|
|
10837
|
+
(mayDrop &&
|
|
10838
|
+
_isRedundantAttribute(
|
|
10839
|
+
markers,
|
|
10840
|
+
defaults,
|
|
10841
|
+
elementName,
|
|
10842
|
+
name,
|
|
10843
|
+
value
|
|
10844
|
+
)) ||
|
|
10845
|
+
_isRemovableEmptyAttribute(elementName, name, value)
|
|
10846
|
+
) {
|
|
10847
|
+
continue;
|
|
10848
|
+
}
|
|
10849
|
+
}
|
|
10850
|
+
frequency.set(name, (frequency.get(name) || 0) + 1);
|
|
10851
|
+
}
|
|
10993
10852
|
}
|
|
10994
10853
|
_attributeFrequencies = frequency;
|
|
10995
10854
|
return frequency;
|
|
@@ -11092,10 +10951,9 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11092
10951
|
// deferred answer held to the shape the built-in was written in.
|
|
11093
10952
|
const tailIsRead = !html && open.charCodeAt(open.length - 2) === 47;
|
|
11094
10953
|
let consumedEnd = path.nameEnd();
|
|
11095
|
-
// HTML only, which is
|
|
11096
|
-
//
|
|
11097
|
-
//
|
|
11098
|
-
// answer out of these, and they are the hottest lookups in the printer.
|
|
10954
|
+
// HTML only, which is what keeps `consumedEnd` meaningful: it is read once
|
|
10955
|
+
// more below, for a foreign element's `/>` alone. Per element rather than per
|
|
10956
|
+
// attribute, these being the printer's hottest lookups.
|
|
11099
10957
|
const markers =
|
|
11100
10958
|
html && _removeRedundantAttributes !== "none"
|
|
11101
10959
|
? REDUNDANT_TYPE_ATTRIBUTES[elementName]
|
|
@@ -11137,6 +10995,7 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11137
10995
|
// at the closing one, so a quoted value's span is one wider on each side.
|
|
11138
10996
|
const quote = open.charCodeAt(valueStart - 1 - tagStart);
|
|
11139
10997
|
const quoted = quote === 34 || quote === 39;
|
|
10998
|
+
const sourceDelimiter = quoted ? open[valueStart - tagStart - 1] : "";
|
|
11140
10999
|
const rawEnd = path.attributeValueEnd(attribute);
|
|
11141
11000
|
const valueEnd = rawEnd + (quoted ? 1 : 0);
|
|
11142
11001
|
const rawValue = open.slice(valueStart - tagStart, rawEnd - tagStart);
|
|
@@ -11173,7 +11032,7 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11173
11032
|
if (_deferEmbeddedSource !== undefined && _deferSrcdoc) {
|
|
11174
11033
|
// Declining leaves the attribute as written, delimiter and all: a bare
|
|
11175
11034
|
// value has none, and quoting one would only lengthen it.
|
|
11176
|
-
const delimiter =
|
|
11035
|
+
const delimiter = sourceDelimiter;
|
|
11177
11036
|
const asWritten = ` ${attributeName}=${delimiter}${rawValue}${delimiter}`;
|
|
11178
11037
|
|
|
11179
11038
|
out += _deferEmbedded(document, HTML_TYPE, (rendered) =>
|
|
@@ -11204,8 +11063,8 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11204
11063
|
// `style`, and a bare value is already the shortest spelling of itself.
|
|
11205
11064
|
if (quoted || html || attributeName === "style" || rawValue.includes("&")) {
|
|
11206
11065
|
const hasReference = rawValue.includes("&");
|
|
11207
|
-
//
|
|
11208
|
-
//
|
|
11066
|
+
// Rewritten from the source text where it carries no reference; one that
|
|
11067
|
+
// does is rewritten from what it decodes to, below.
|
|
11209
11068
|
let value = rawValue;
|
|
11210
11069
|
if (!hasReference) {
|
|
11211
11070
|
if (html) {
|
|
@@ -11223,22 +11082,42 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11223
11082
|
value = _minifyStyleAttribute(rawValue);
|
|
11224
11083
|
}
|
|
11225
11084
|
}
|
|
11226
|
-
//
|
|
11227
|
-
//
|
|
11228
|
-
//
|
|
11229
|
-
//
|
|
11230
|
-
//
|
|
11231
|
-
//
|
|
11232
|
-
//
|
|
11233
|
-
//
|
|
11085
|
+
// WHY: an empty value is the bare name — the after-attribute-name state
|
|
11086
|
+
// reads `x` in `<p x>` and `<p x="">` as one attribute with one empty
|
|
11087
|
+
// value, in foreign content too — and a boolean spelled by its own name
|
|
11088
|
+
// is the same presence, which the spec does canonicalize. Writing either
|
|
11089
|
+
// as the bare name is just the `=""` going, a quoting decision, so it
|
|
11090
|
+
// answers to the switch naming the rest of them. The decode is guarded on
|
|
11091
|
+
// the reference to stay off the hot path, and tried before unquoting so
|
|
11092
|
+
// every reference it writes stays bare.
|
|
11093
|
+
const decoded = hasReference ? decodeEntities(rawValue, true) : null;
|
|
11094
|
+
const spellable = decoded;
|
|
11095
|
+
// The name the presence is spelled with is what it decodes to, so
|
|
11096
|
+
// `checked="c…"` is the same presence as `checked="checked"`.
|
|
11097
|
+
const presence = spellable !== null ? spellable : rawValue;
|
|
11098
|
+
// The grammars read what the value decodes to, so a referenced one is
|
|
11099
|
+
// rewritten from that and spelled back. `style` has its own path below.
|
|
11100
|
+
const rewritten =
|
|
11101
|
+
spellable !== null && html && attributeName !== "style"
|
|
11102
|
+
? _rewriteAttributeValue(
|
|
11103
|
+
elementName,
|
|
11104
|
+
attributeName,
|
|
11105
|
+
spellable,
|
|
11106
|
+
isViewportMeta,
|
|
11107
|
+
enumeratedOn
|
|
11108
|
+
)
|
|
11109
|
+
: spellable;
|
|
11110
|
+
// What the attribute ends up saying, whichever spelling it arrived in:
|
|
11111
|
+
// the bare name answers to that, not to the side the value was written.
|
|
11112
|
+
const settled = rewritten !== null ? rewritten : value;
|
|
11234
11113
|
if (
|
|
11235
11114
|
_transforms.normalizeAttributeQuotes &&
|
|
11236
|
-
(
|
|
11115
|
+
(settled === "" ||
|
|
11237
11116
|
(html &&
|
|
11238
11117
|
_transforms.collapseBooleanAttributes &&
|
|
11239
11118
|
(_transforms.collapseBooleanAttributes === "all" ||
|
|
11240
|
-
(
|
|
11241
|
-
_asciiLowerCase(
|
|
11119
|
+
(presence.length === attributeName.length &&
|
|
11120
|
+
_asciiLowerCase(presence) === attributeName)) &&
|
|
11242
11121
|
_isBooleanAttribute(elementName, attributeName)))
|
|
11243
11122
|
) {
|
|
11244
11123
|
out += ` ${attributeName}`;
|
|
@@ -11246,16 +11125,6 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11246
11125
|
consumedEnd = valueEnd;
|
|
11247
11126
|
continue;
|
|
11248
11127
|
}
|
|
11249
|
-
// Guarded on the reference, so the decode stays off the hot path — and
|
|
11250
|
-
// tried before unquoting, which keeps every reference it writes bare.
|
|
11251
|
-
const decoded = hasReference ? decodeEntities(rawValue, true) : null;
|
|
11252
|
-
// A reference spelling whitespace is deliberate: writing it out would
|
|
11253
|
-
// let `removeEmptyAttributes` read the attribute as empty on a re-run.
|
|
11254
|
-
const spellable =
|
|
11255
|
-
decoded !== null &&
|
|
11256
|
-
(_asciiTrim(decoded) !== "" || _asciiTrim(rawValue) === "")
|
|
11257
|
-
? decoded
|
|
11258
|
-
: null;
|
|
11259
11128
|
const isStyle = attributeName === "style";
|
|
11260
11129
|
// A `style=""` carrying a character reference is a declaration list like
|
|
11261
11130
|
// any other once decoded — `"` is a quote to CSS, not an entity — so
|
|
@@ -11268,17 +11137,32 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11268
11137
|
*/
|
|
11269
11138
|
const writeStyle = (minified) =>
|
|
11270
11139
|
declarations !== null
|
|
11271
|
-
? _writeAttribute(
|
|
11140
|
+
? _writeAttribute(
|
|
11141
|
+
attributeName,
|
|
11142
|
+
rawValue,
|
|
11143
|
+
rawValue,
|
|
11144
|
+
minified,
|
|
11145
|
+
quoted,
|
|
11146
|
+
sourceDelimiter
|
|
11147
|
+
)
|
|
11272
11148
|
: _writeAttribute(
|
|
11273
11149
|
attributeName,
|
|
11274
11150
|
minified,
|
|
11275
11151
|
rawValue,
|
|
11276
11152
|
spellable,
|
|
11277
|
-
quoted
|
|
11153
|
+
quoted,
|
|
11154
|
+
sourceDelimiter
|
|
11278
11155
|
);
|
|
11279
11156
|
const written = isStyle
|
|
11280
11157
|
? writeStyle(declarations !== null ? declarations : value)
|
|
11281
|
-
: _writeAttribute(
|
|
11158
|
+
: _writeAttribute(
|
|
11159
|
+
attributeName,
|
|
11160
|
+
value,
|
|
11161
|
+
rawValue,
|
|
11162
|
+
rewritten,
|
|
11163
|
+
quoted,
|
|
11164
|
+
sourceDelimiter
|
|
11165
|
+
);
|
|
11282
11166
|
// The JavaScript an event handler holds, offered decoded — `&&`
|
|
11283
11167
|
// is an operator to JavaScript rather than an entity. No built-in.
|
|
11284
11168
|
const handler =
|
|
@@ -11292,7 +11176,7 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11292
11176
|
: null;
|
|
11293
11177
|
|
|
11294
11178
|
if (handler !== null && _asciiTrim(handler) !== "") {
|
|
11295
|
-
const delimiter =
|
|
11179
|
+
const delimiter = sourceDelimiter;
|
|
11296
11180
|
// A foreign `/>` reads back whether this value ends unquoted, which a
|
|
11297
11181
|
// deferred answer is not there to decide: the source's quoting stands.
|
|
11298
11182
|
const holdQuoted = tailIsRead && _deferEmbeddedSource !== undefined;
|
|
@@ -11326,7 +11210,8 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11326
11210
|
escapeAttribute(rendered, CC_QUOTATION_MARK, true),
|
|
11327
11211
|
rawValue,
|
|
11328
11212
|
rendered,
|
|
11329
|
-
quoted
|
|
11213
|
+
quoted,
|
|
11214
|
+
sourceDelimiter
|
|
11330
11215
|
);
|
|
11331
11216
|
|
|
11332
11217
|
return answered !== null && answered.text.length < asWritten.length
|
|
@@ -11364,7 +11249,7 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11364
11249
|
if (isStyle && _deferEmbeddedSource !== undefined) {
|
|
11365
11250
|
// Declining leaves the attribute as written, delimiter and all — an
|
|
11366
11251
|
// answer nothing beats is the renderer keeping what it was given.
|
|
11367
|
-
const delimiter =
|
|
11252
|
+
const delimiter = sourceDelimiter;
|
|
11368
11253
|
const asWritten = ` ${attributeName}=${delimiter}${rawValue}${delimiter}`;
|
|
11369
11254
|
// What stands until an answer arrives, and the tag ending it commits
|
|
11370
11255
|
// to: the built-in's text where it beat the source, else the source.
|
|
@@ -11402,6 +11287,14 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11402
11287
|
consumedEnd = valueEnd;
|
|
11403
11288
|
continue;
|
|
11404
11289
|
}
|
|
11290
|
+
// Quoting may be frozen, but a rewrite changed what the value says, so
|
|
11291
|
+
// the source's own spelling of it cannot stand.
|
|
11292
|
+
if (rewritten !== null && rewritten !== spellable) {
|
|
11293
|
+
out += _writeQuotedAttribute(attributeName, rewritten, sourceDelimiter);
|
|
11294
|
+
unquotedTail = false;
|
|
11295
|
+
consumedEnd = valueEnd;
|
|
11296
|
+
continue;
|
|
11297
|
+
}
|
|
11405
11298
|
}
|
|
11406
11299
|
// A quoted value carries its quotes over; an unquoted one starts at the
|
|
11407
11300
|
// value itself.
|
|
@@ -11412,10 +11305,10 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11412
11305
|
unquotedTail = !quoted;
|
|
11413
11306
|
consumedEnd = valueEnd;
|
|
11414
11307
|
}
|
|
11415
|
-
//
|
|
11416
|
-
//
|
|
11417
|
-
//
|
|
11418
|
-
//
|
|
11308
|
+
// WHY: a source `/>` self-closes a foreign element and survives there, but
|
|
11309
|
+
// only where the `/` is the flag: in `<a href=x/>` it ends an unquoted value,
|
|
11310
|
+
// and after one of those it needs a space or it fuses into the value. An HTML
|
|
11311
|
+
// element's parser ignores it, so there it goes.
|
|
11419
11312
|
if (
|
|
11420
11313
|
path.namespace() !== NS_HTML &&
|
|
11421
11314
|
open.charCodeAt(open.length - 2) === 47 &&
|
|
@@ -11437,10 +11330,9 @@ const _minifyOpenTag = (path, open) => {
|
|
|
11437
11330
|
* @returns {boolean} true to keep the comment
|
|
11438
11331
|
*/
|
|
11439
11332
|
const _keepComment = (data, source) => {
|
|
11440
|
-
// `<?php … ?>`
|
|
11441
|
-
//
|
|
11442
|
-
//
|
|
11443
|
-
// no level drops it.
|
|
11333
|
+
// `<?php … ?>` and `<%… %>` reach the tree as bogus comments (§13.2.5.42),
|
|
11334
|
+
// and the forms below drive IE branching or a server-side include. Every one
|
|
11335
|
+
// is code rather than a comment, so no level drops it.
|
|
11444
11336
|
if (source.charCodeAt(1) === 63) return true;
|
|
11445
11337
|
const trimmed = data.trimStart();
|
|
11446
11338
|
// `<![` covers the downlevel-revealed forms: `<!--<![endif]-->` closers and
|
|
@@ -11558,6 +11450,13 @@ const _contentOpensOutside = (node) => {
|
|
|
11558
11450
|
// so each streamed element costs no result object.
|
|
11559
11451
|
let _streamOpen = "";
|
|
11560
11452
|
let _streamClose = "";
|
|
11453
|
+
// The close that goes with a held-back start tag once it prints: an element
|
|
11454
|
+
// whose tag the parser no longer re-implies answers to `removeImpliedTags`.
|
|
11455
|
+
let _streamCloseIfOpened = "";
|
|
11456
|
+
// Which held-back opener `_emitPiece` settled into a start tag, `-1` for none.
|
|
11457
|
+
let _materializedPendingSlot = -1;
|
|
11458
|
+
/** @type {string[]} */
|
|
11459
|
+
const _printCloseIfOpened = [];
|
|
11561
11460
|
let _streamImplied = "";
|
|
11562
11461
|
|
|
11563
11462
|
/**
|
|
@@ -11628,11 +11527,8 @@ const _streamTags = (path, minify) => {
|
|
|
11628
11527
|
if (path.attributeCount() === 0 && TRANSPARENT_IMPLIED_ELEMENTS.has(name)) {
|
|
11629
11528
|
// The parser re-implies the start tag, but not the end tag the source
|
|
11630
11529
|
// carried: without it whatever follows moves inside this element.
|
|
11631
|
-
_streamClose = (
|
|
11632
|
-
|
|
11633
|
-
? _shellEndTagOmittable(path, name, path.node)
|
|
11634
|
-
: _canOmitEndTag(path, name, path.node)
|
|
11635
|
-
)
|
|
11530
|
+
_streamClose = _impliedCloseTag(path, name, path.node);
|
|
11531
|
+
_streamCloseIfOpened = _canOmitEndTag(path, name, path.node)
|
|
11636
11532
|
? ""
|
|
11637
11533
|
: `</${name}>`;
|
|
11638
11534
|
// Leading whitespace is decided on the first piece emitted; what the modes
|
|
@@ -12091,10 +11987,9 @@ const _digest = (root) => {
|
|
|
12091
11987
|
attributes.push(`${name}=${_DIGEST_OPAQUE}`);
|
|
12092
11988
|
continue;
|
|
12093
11989
|
}
|
|
12094
|
-
// The value as the DOM holds it
|
|
12095
|
-
//
|
|
12096
|
-
//
|
|
12097
|
-
// presence the printer writes it back as.
|
|
11990
|
+
// The value as the DOM holds it, since another quote and another escape
|
|
11991
|
+
// spell the same attribute, with a boolean named by itself as the bare
|
|
11992
|
+
// presence the printer writes back.
|
|
12098
11993
|
attributes.push(
|
|
12099
11994
|
`${name}=${
|
|
12100
11995
|
html &&
|
|
@@ -12349,13 +12244,7 @@ const printer = (path, writer) => {
|
|
|
12349
12244
|
}
|
|
12350
12245
|
// The parser re-implies the start tag, but not the end tag the
|
|
12351
12246
|
// source carried: without it whatever follows moves inside.
|
|
12352
|
-
return (
|
|
12353
|
-
SHELL_ELEMENTS.has(name)
|
|
12354
|
-
? _shellEndTagOmittable(path, name, path.node)
|
|
12355
|
-
: _canOmitEndTag(path, name, path.node)
|
|
12356
|
-
)
|
|
12357
|
-
? inner
|
|
12358
|
-
: `${inner}</${name}>`;
|
|
12247
|
+
return inner + _impliedCloseTag(path, name, path.node);
|
|
12359
12248
|
}
|
|
12360
12249
|
// Anything else tag-less (adoption-agency clone, reconstructed
|
|
12361
12250
|
// formatting element) must materialize or its formatting is lost.
|
|
@@ -12401,7 +12290,7 @@ const printer = (path, writer) => {
|
|
|
12401
12290
|
// A bare `<script>` is the classic one a run may fold into, and
|
|
12402
12291
|
// the run's first prints all of it — the rest printed into it here.
|
|
12403
12292
|
let body = data;
|
|
12404
|
-
if (_mergeScripts && path
|
|
12293
|
+
if (_mergeScripts && _printedAttributeCount(path, parent) === 0) {
|
|
12405
12294
|
if (_absorbed !== null && _absorbed.has(parent)) return "";
|
|
12406
12295
|
body = _mergedScriptRun(path, parent, data);
|
|
12407
12296
|
}
|
|
@@ -12466,18 +12355,17 @@ const printer = (path, writer) => {
|
|
|
12466
12355
|
}
|
|
12467
12356
|
}
|
|
12468
12357
|
const raw = path.source();
|
|
12469
|
-
//
|
|
12470
|
-
// `<%= x %>` to `<%= x %>` in HTML webpack only passes
|
|
12471
|
-
// that decoded to itself
|
|
12472
|
-
// The guards
|
|
12473
|
-
//
|
|
12474
|
-
//
|
|
12475
|
-
//
|
|
12476
|
-
//
|
|
12477
|
-
//
|
|
12478
|
-
//
|
|
12479
|
-
//
|
|
12480
|
-
// without changing length — so it is excluded by name.
|
|
12358
|
+
// WHY: off-spec workaround. §13.3 escapes `<` and `>` in text, which
|
|
12359
|
+
// would rewrite `<%= x %>` to `<%= x %>` in HTML webpack only passes
|
|
12360
|
+
// through, so text that decoded to itself is emitted as its source bytes.
|
|
12361
|
+
// The guards keep that equivalence: a character reference is at least two
|
|
12362
|
+
// characters longer than what it decodes to, so equal lengths mean nothing
|
|
12363
|
+
// decoded, while unequal ones mean the node was merged or foster-parented
|
|
12364
|
+
// and its range no longer describes `data`. The tail is checked separately
|
|
12365
|
+
// as the one place concatenation can change tokenization — a `<`, or a `&`
|
|
12366
|
+
// still open on a reference, fuses with the next sibling once a comment
|
|
12367
|
+
// between them is dropped. A CR is the rewrite the length test cannot see,
|
|
12368
|
+
// preprocessing mapping a lone one to LF, so it is excluded by name.
|
|
12481
12369
|
if (
|
|
12482
12370
|
raw.length === data.length &&
|
|
12483
12371
|
!_tailFusesWithNextTag(raw) &&
|
|
@@ -12533,12 +12421,12 @@ let _visitors = EMPTY_VISITORS;
|
|
|
12533
12421
|
/** @type {PrintContext | undefined} */
|
|
12534
12422
|
let _writer;
|
|
12535
12423
|
|
|
12536
|
-
//
|
|
12537
|
-
// `nextSibling` descend,
|
|
12538
|
-
//
|
|
12539
|
-
//
|
|
12540
|
-
//
|
|
12541
|
-
//
|
|
12424
|
+
// WHY: an iterative depth-first walk over the link columns — `firstChild` and
|
|
12425
|
+
// `nextSibling` descend, `parent` ascends — so arbitrarily deep markup cannot
|
|
12426
|
+
// overflow the call stack, where the old recursive walk died at ~10⁵ nesting. A
|
|
12427
|
+
// `<template>`'s content fragment is visited before the element's children, and
|
|
12428
|
+
// ascending out of it continues with those: the fragment is never in a sibling
|
|
12429
|
+
// chain, so its `_nodeNextSiblings` is 0.
|
|
12542
12430
|
|
|
12543
12431
|
/**
|
|
12544
12432
|
* Fire a node's `enter` visitors; true when the walk may descend.
|
|
@@ -12567,13 +12455,13 @@ const _enterNode = (node) => {
|
|
|
12567
12455
|
return !skip;
|
|
12568
12456
|
};
|
|
12569
12457
|
|
|
12570
|
-
//
|
|
12571
|
-
// The printer composes a node from its children's text, so the store
|
|
12572
|
-
// node's until the root is taken — several times the output on a
|
|
12573
|
-
// size. A node whose text is exactly `open + children + close`
|
|
12574
|
-
// opener as it opens and its closer as it exits,
|
|
12575
|
-
// as it finishes
|
|
12576
|
-
//
|
|
12458
|
+
// WHY: printing in pieces.
|
|
12459
|
+
// The printer composes a node from its children's text, so the store would
|
|
12460
|
+
// hold every node's until the root is taken — several times the output on a
|
|
12461
|
+
// document of any size. A node whose text is exactly `open + children + close`
|
|
12462
|
+
// instead emits its opener as it opens and its closer as it exits, with each
|
|
12463
|
+
// child going straight out as it finishes, so the store only ever holds the
|
|
12464
|
+
// subtrees that cannot. Indexed by print depth, which is the open path.
|
|
12577
12465
|
/** @type {number} depth of the node the printing walk is on (maintained by `_walkRun`) */
|
|
12578
12466
|
let _printDepth = 0;
|
|
12579
12467
|
/** @type {number[]} 1 while the node at that depth prints in pieces */
|
|
@@ -12604,6 +12492,7 @@ const _emitPiece = (text) => {
|
|
|
12604
12492
|
const c = text.charCodeAt(0);
|
|
12605
12493
|
if (c === 0x09 || c === 0x0a || c === 0x0c || c === 0x0d || c === 0x20) {
|
|
12606
12494
|
w.setPending(held - 1, `<${_printPendingNames[held - 1]}>`);
|
|
12495
|
+
_materializedPendingSlot = held - 1;
|
|
12607
12496
|
}
|
|
12608
12497
|
_printPendingNames.length = 0;
|
|
12609
12498
|
w.flushPending();
|
|
@@ -12641,6 +12530,7 @@ const _openStreamed = (node, depth, descending) => {
|
|
|
12641
12530
|
if (_streamImplied === "") {
|
|
12642
12531
|
_emitPiece(_streamOpen);
|
|
12643
12532
|
} else {
|
|
12533
|
+
_printCloseIfOpened[depth] = _streamCloseIfOpened;
|
|
12644
12534
|
_printPendingSlot[depth] = w.pushPending("");
|
|
12645
12535
|
_printPendingNames.push(_streamImplied);
|
|
12646
12536
|
}
|
|
@@ -12693,8 +12583,14 @@ const _exitNode = (node) => {
|
|
|
12693
12583
|
if (_printOpen[depth] === 1) {
|
|
12694
12584
|
// Printed in pieces: its opener went out as it opened and its children as
|
|
12695
12585
|
// they finished, so only its closer is left.
|
|
12696
|
-
_emitPiece(_printClose[depth]);
|
|
12697
12586
|
const slot = _printPendingSlot[depth];
|
|
12587
|
+
// Its start tag printed after all, so the end tag is no longer the one a
|
|
12588
|
+
// transparent element leaves behind.
|
|
12589
|
+
_emitPiece(
|
|
12590
|
+
slot !== -1 && slot === _materializedPendingSlot
|
|
12591
|
+
? _printCloseIfOpened[depth]
|
|
12592
|
+
: _printClose[depth]
|
|
12593
|
+
);
|
|
12698
12594
|
// Nothing inside it printed, so its held-back opener never had to.
|
|
12699
12595
|
if (slot !== -1 && _writer.isPending(slot)) {
|
|
12700
12596
|
_printPendingNames.pop();
|
|
@@ -12729,8 +12625,8 @@ const _firstInner = (node) => {
|
|
|
12729
12625
|
return 0;
|
|
12730
12626
|
};
|
|
12731
12627
|
|
|
12732
|
-
//
|
|
12733
|
-
//
|
|
12628
|
+
// WHY: the streaming walk.
|
|
12629
|
+
// It follows the parser's open element stack: everything under the
|
|
12734
12630
|
// deepest *entered* element is finished markup, so it is visited and its node
|
|
12735
12631
|
// ids recycled, and peak storage is the open subtree rather than the whole
|
|
12736
12632
|
// document — the HTML analog of the CSS grammar's per-top-level-rule recycling.
|
|
@@ -12763,12 +12659,12 @@ const _entered = [];
|
|
|
12763
12659
|
let _skipFrom = -1;
|
|
12764
12660
|
/** set when `open` was spliced mid-stack, so the next sync scans it in full */
|
|
12765
12661
|
let _openSpliced = false;
|
|
12766
|
-
//
|
|
12767
|
-
// entered
|
|
12768
|
-
//
|
|
12662
|
+
// WHY: set when a mid-stack `open.splice` removes an element the walk already
|
|
12663
|
+
// entered, such as a `</form>` with content still open inside it. The element
|
|
12664
|
+
// leaves the stack without its subtree ending, so the stack no longer describes
|
|
12769
12665
|
// what has finished and reconciling against it would close and re-enter live
|
|
12770
|
-
// elements. Rare enough to
|
|
12771
|
-
//
|
|
12666
|
+
// elements. Rare enough to stop streaming for the rest of the parse: what is
|
|
12667
|
+
// visited stays visited, and the remainder is walked at EOF.
|
|
12772
12668
|
let _streamHalted = false;
|
|
12773
12669
|
/** set by `popOpen`: the open stack changed, so the walk has work at token end */
|
|
12774
12670
|
let _openStackChanged = false;
|
|
@@ -12776,11 +12672,11 @@ let _openStackChanged = false;
|
|
|
12776
12672
|
// earns its walk setup. Closing an entered element always flushes, so this only
|
|
12777
12673
|
// bounds how much an un-entered subtree holds — a few KB of columns.
|
|
12778
12674
|
const _FLUSH_BATCH = 4096;
|
|
12779
|
-
//
|
|
12780
|
-
// only pays once the columns are big enough that not growing them
|
|
12781
|
-
// per-token sync, and a page
|
|
12782
|
-
// Chosen so the streamed cap below stays under
|
|
12783
|
-
// keeping
|
|
12675
|
+
// WHY: below this many nodes a document is walked once at EOF instead.
|
|
12676
|
+
// Recycling only pays once the columns are big enough that not growing them
|
|
12677
|
+
// beats the per-token sync, and a page never reaching that must not carry the
|
|
12678
|
+
// cost. Chosen so the streamed cap below stays under
|
|
12679
|
+
// `_COLUMN_SHRINK_CAPACITY`, keeping the columns reusable by the next parse.
|
|
12784
12680
|
const _STREAM_MIN_NODES = 49152;
|
|
12785
12681
|
|
|
12786
12682
|
/**
|
|
@@ -12974,9 +12870,8 @@ const _streamSync = () => {
|
|
|
12974
12870
|
|
|
12975
12871
|
let i = _enteredPrefix();
|
|
12976
12872
|
// Close: everything entered below the divergence has finished. A descendant
|
|
12977
|
-
// of a `skipChildren()` element was tracked without being entered, so it
|
|
12978
|
-
// not exit either —
|
|
12979
|
-
// itself and nothing below it.
|
|
12873
|
+
// of a `skipChildren()` element was tracked without being entered, so it does
|
|
12874
|
+
// not exit either — only the skipped element itself does.
|
|
12980
12875
|
for (let k = _entered.length - 1; k >= i; k--) {
|
|
12981
12876
|
const el = _entered[k];
|
|
12982
12877
|
const belowSkip = _skipFrom !== -1 && k > _skipFrom;
|
|
@@ -13032,12 +12927,12 @@ const _streamFinish = (root) => {
|
|
|
13032
12927
|
* @param {HtmlProcessOptions} options process options
|
|
13033
12928
|
*/
|
|
13034
12929
|
const grammar = (input, visitors, writer, options) => {
|
|
13035
|
-
//
|
|
13036
|
-
//
|
|
13037
|
-
//
|
|
13038
|
-
//
|
|
13039
|
-
//
|
|
13040
|
-
//
|
|
12930
|
+
// WHY: the walk's state — visitors, print context, open path, AST columns —
|
|
12931
|
+
// is module-scoped so the tokenizer's call sites stay monomorphic, which means
|
|
12932
|
+
// one parse at a time: a visitor starting another would take this one's state
|
|
12933
|
+
// over and release its columns on the way out. Nesting the other language is
|
|
12934
|
+
// fine, a `<style>`'s CSS having module state of its own, and an
|
|
12935
|
+
// `<iframe srcdoc>` is a separate module webpack parses on its own.
|
|
13041
12936
|
if (_parsing) {
|
|
13042
12937
|
throw new Error(
|
|
13043
12938
|
"html syntax: process() is already running; a visitor cannot start another HTML parse"
|
|
@@ -13160,6 +13055,8 @@ const grammar = (input, visitors, writer, options) => {
|
|
|
13160
13055
|
_printClose.length = 0;
|
|
13161
13056
|
_printPendingNames.length = 0;
|
|
13162
13057
|
_printPendingSlot.length = 0;
|
|
13058
|
+
_printCloseIfOpened.length = 0;
|
|
13059
|
+
_materializedPendingSlot = -1;
|
|
13163
13060
|
// Hoisting these to module scope outlives the call, so drop the caller's
|
|
13164
13061
|
// visitors and print context here — they used to fall away with `grammar`.
|
|
13165
13062
|
_visitors = EMPTY_VISITORS;
|
|
@@ -13197,9 +13094,8 @@ const RIGHT_PARENTHESIS = ")".charCodeAt(0);
|
|
|
13197
13094
|
const SMALL_LETTER_W = "w".charCodeAt(0);
|
|
13198
13095
|
const SMALL_LETTER_X = "x".charCodeAt(0);
|
|
13199
13096
|
const SMALL_LETTER_H = "h".charCodeAt(0);
|
|
13200
|
-
//
|
|
13201
|
-
//
|
|
13202
|
-
// whole remaining input per call (which made srcset parsing quadratic).
|
|
13097
|
+
// Not `\s`, which matches a non-breaking space. Sticky so `collectCharacters`
|
|
13098
|
+
// matches at an offset rather than slicing per call, which was quadratic.
|
|
13203
13099
|
// eslint-disable-next-line no-control-regex
|
|
13204
13100
|
const LEADING_SPACES_REGEXP = /[ \t\n\r\u000C]+/y;
|
|
13205
13101
|
// eslint-disable-next-line no-control-regex
|
|
@@ -13208,10 +13104,8 @@ const LEADING_COMMAS_OR_SPACES_REGEXP = /[, \t\n\r\u000C]+/y;
|
|
|
13208
13104
|
const LEADING_NOT_SPACES = /[^ \t\n\r\u000C]+/y;
|
|
13209
13105
|
const TRAILING_COMMAS_REGEXP = /[,]+$/;
|
|
13210
13106
|
const NON_NEGATIVE_INTEGER_REGEXP = /^\d+$/;
|
|
13211
|
-
//
|
|
13212
|
-
//
|
|
13213
|
-
// According to spec tests any decimal point must be followed by a digit.
|
|
13214
|
-
// No leading plus sign is allowed.)
|
|
13107
|
+
// A signed or unsigned integer or decimal, with or without an exponent, holding
|
|
13108
|
+
// at least one digit, no leading plus, and a digit after any decimal point.
|
|
13215
13109
|
// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
|
|
13216
13110
|
const FLOATING_POINT_REGEXP =
|
|
13217
13111
|
/^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/;
|
|
@@ -13400,10 +13294,8 @@ const parseSrcset = (input) => {
|
|
|
13400
13294
|
if (state === "in descriptor") {
|
|
13401
13295
|
// Do the following, depending on the value of charCode:
|
|
13402
13296
|
|
|
13403
|
-
// Space
|
|
13404
|
-
//
|
|
13405
|
-
// descriptors and let current descriptor be the empty string.
|
|
13406
|
-
// Set state to after descriptor.
|
|
13297
|
+
// Space: append a non-empty current descriptor to descriptors and empty
|
|
13298
|
+
// it, then set state to after descriptor.
|
|
13407
13299
|
if (isSpace(charCode)) {
|
|
13408
13300
|
if (descriptorStart !== -1) {
|
|
13409
13301
|
descriptors.push(input.slice(descriptorStart, position));
|
|
@@ -13411,10 +13303,9 @@ const parseSrcset = (input) => {
|
|
|
13411
13303
|
state = "after descriptor";
|
|
13412
13304
|
}
|
|
13413
13305
|
}
|
|
13414
|
-
//
|
|
13415
|
-
//
|
|
13416
|
-
//
|
|
13417
|
-
// labeled descriptor parser.
|
|
13306
|
+
// `,`: Advance position to the next character in input. If current
|
|
13307
|
+
// descriptor is not empty, append current descriptor to descriptors. Jump
|
|
13308
|
+
// to the step labeled descriptor parser.
|
|
13418
13309
|
else if (charCode === COMMA) {
|
|
13419
13310
|
position += 1;
|
|
13420
13311
|
|
|
@@ -13696,10 +13587,9 @@ let _currentParent = null;
|
|
|
13696
13587
|
*/
|
|
13697
13588
|
/* eslint-enable jsdoc/require-template */
|
|
13698
13589
|
|
|
13699
|
-
// AST field-access seam
|
|
13700
|
-
//
|
|
13701
|
-
//
|
|
13702
|
-
// `HtmlNodeRef`; results are valid until the next `parseHtml` call.
|
|
13590
|
+
// AST field-access seam, mirroring the CSS parser's `A`: every field a consumer
|
|
13591
|
+
// reads goes through an accessor here, so the node representation can change
|
|
13592
|
+
// underneath. `n` is an `HtmlNodeRef`, valid until the next `parseHtml`.
|
|
13703
13593
|
const A = {
|
|
13704
13594
|
// === path position (rebound by the walk before every visitor call) ===
|
|
13705
13595
|
/**
|