From daede43653e744a65a4b77c3864c0b1b4aff65ee Mon Sep 17 00:00:00 2001 From: Mathias Picker Date: Wed, 6 Jul 2022 19:06:48 +0200 Subject: [PATCH] fixed attribute value bottleneck in parser --- src/compiler/parse/state/tag.ts | 19 +++++++++++-------- .../input-crazy-value-attribute/_config.js | 10 ++++++++++ .../input-crazy-value-attribute/main.svelte | 1 + .../_config.js | 2 +- .../main.svelte | 0 .../textarea-crazy-value-attribute/_config.js | 10 ++++++++++ .../main.svelte | 1 + 7 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 test/runtime/samples/input-crazy-value-attribute/_config.js create mode 100644 test/runtime/samples/input-crazy-value-attribute/main.svelte rename test/runtime/samples/{svg-crazy-attributes => svg-crazy-attribute-value}/_config.js (81%) rename test/runtime/samples/{svg-crazy-attributes => svg-crazy-attribute-value}/main.svelte (100%) create mode 100644 test/runtime/samples/textarea-crazy-value-attribute/_config.js create mode 100644 test/runtime/samples/textarea-crazy-value-attribute/main.svelte diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index efce375b7e..219e14ca33 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -9,6 +9,9 @@ import read_script from '../read/script'; import read_style from '../read/style'; import { closing_tag_omitted, decode_character_references } from '../utils/html'; +// characters equivalent to using \s in regex +const whitespace_characters = ['\r', '\n', '\t', '\f', '\v', ' ', '\u00a0', '\u1680', '\u2000-', '\u200a', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000', '\ufeff']; + // eslint-disable-next-line no-useless-escape const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; @@ -77,6 +80,7 @@ export default function tag(parser: Parser) { const name = read_tag_name(parser); + if (meta_tags.has(name)) { const slug = meta_tags.get(name).toLowerCase(); if (is_closing_tag) { @@ -109,6 +113,7 @@ export default function tag(parser: Parser) { : name === 'title' && parent_is_head(parser.stack) ? 'Title' : name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; + const element: TemplateNode = { start, end: null, // filled in later @@ -160,8 +165,9 @@ export default function tag(parser: Parser) { }; } + const unique_names: Set = new Set(); - + let attribute; while ((attribute = read_attribute(parser, unique_names))) { element.attributes.push(attribute); @@ -363,6 +369,7 @@ function read_attribute(parser: Parser, unique_names: Set) { parser.error(parser_errors.unexpected_token('='), parser.index); } + if (type) { const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|'); @@ -455,6 +462,7 @@ function get_directive_type(name: string): DirectiveType { function read_attribute_value(parser: Parser) { const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null; + if (quote_mark && parser.eat(quote_mark)) { return [{ start: parser.index - 1, @@ -465,15 +473,10 @@ function read_attribute_value(parser: Parser) { }]; } - const regex = ( - quote_mark === "'" ? /'/ : - quote_mark === '"' ? /"/ : - /(\/>|[\s"'=<>`])/ - ); - let value; try { - value = read_sequence(parser, () => !!parser.match_regex(regex)); + const characters = quote_mark ? [quote_mark] : [...whitespace_characters, '/>', '"', '"', '=', '<', '>', '`']; + value = read_sequence(parser, () => characters.some(c => parser.match(c))); } catch (error) { if (error.code === 'parse-error') { // if the attribute value didn't close + self-closing tag diff --git a/test/runtime/samples/input-crazy-value-attribute/_config.js b/test/runtime/samples/input-crazy-value-attribute/_config.js new file mode 100644 index 0000000000..415497c670 --- /dev/null +++ b/test/runtime/samples/input-crazy-value-attribute/_config.js @@ -0,0 +1,10 @@ +export default { + skip_if_hydrate_from_ssr: true, + skip_if_hydrate: true, + skip_if_ssr: true, + + test({ assert, target }) { + const input = target.querySelector('input'); + assert.equal(input.value.length > 100_000, true); + } +}; diff --git a/test/runtime/samples/input-crazy-value-attribute/main.svelte b/test/runtime/samples/input-crazy-value-attribute/main.svelte new file mode 100644 index 0000000000..a066c8e0ec --- /dev/null +++ b/test/runtime/samples/input-crazy-value-attribute/main.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/svg-crazy-attributes/_config.js b/test/runtime/samples/svg-crazy-attribute-value/_config.js similarity index 81% rename from test/runtime/samples/svg-crazy-attributes/_config.js rename to test/runtime/samples/svg-crazy-attribute-value/_config.js index 7968763b51..91e0ca490b 100644 --- a/test/runtime/samples/svg-crazy-attributes/_config.js +++ b/test/runtime/samples/svg-crazy-attribute-value/_config.js @@ -5,6 +5,6 @@ export default { test({ assert, target }) { const image = target.querySelector('image'); - assert.equal(image.tagName, "IMAGE"); + assert.equal(image.tagName, 'IMAGE'); } }; diff --git a/test/runtime/samples/svg-crazy-attributes/main.svelte b/test/runtime/samples/svg-crazy-attribute-value/main.svelte similarity index 100% rename from test/runtime/samples/svg-crazy-attributes/main.svelte rename to test/runtime/samples/svg-crazy-attribute-value/main.svelte diff --git a/test/runtime/samples/textarea-crazy-value-attribute/_config.js b/test/runtime/samples/textarea-crazy-value-attribute/_config.js new file mode 100644 index 0000000000..e0f6a960f0 --- /dev/null +++ b/test/runtime/samples/textarea-crazy-value-attribute/_config.js @@ -0,0 +1,10 @@ +export default { + skip_if_hydrate_from_ssr: true, + skip_if_hydrate: true, + skip_if_ssr: true, + + test({ assert, target }) { + const textarea = target.querySelector('textarea'); + assert.equal(textarea.value.length > 100_000, true); + } +}; diff --git a/test/runtime/samples/textarea-crazy-value-attribute/main.svelte b/test/runtime/samples/textarea-crazy-value-attribute/main.svelte new file mode 100644 index 0000000000..bf330bd852 --- /dev/null +++ b/test/runtime/samples/textarea-crazy-value-attribute/main.svelte @@ -0,0 +1 @@ + \ No newline at end of file