2021-07-12 10:12:47 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2021, Max Wipfli <max.wipfli@serenityos.org>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <LibTest/TestCase.h>
|
|
|
|
|
|
2023-02-08 23:02:46 -03:00
|
|
|
#include <LibCore/File.h>
|
2021-07-12 10:12:47 -03:00
|
|
|
#include <LibWeb/HTML/Parser/HTMLTokenizer.h>
|
|
|
|
|
|
|
|
|
|
using Tokenizer = Web::HTML::HTMLTokenizer;
|
|
|
|
|
using Token = Web::HTML::HTMLToken;
|
|
|
|
|
|
|
|
|
|
#define BEGIN_ENUMERATION(tokens) \
|
|
|
|
|
auto current_token = (tokens).begin(); \
|
2021-07-15 17:23:06 -03:00
|
|
|
[[maybe_unused]] Token* last_token;
|
2021-07-12 10:12:47 -03:00
|
|
|
|
|
|
|
|
#define END_ENUMERATION() \
|
|
|
|
|
EXPECT(current_token.is_end());
|
|
|
|
|
|
2021-07-15 17:23:06 -03:00
|
|
|
#define NEXT_TOKEN() \
|
|
|
|
|
last_token = &*current_token; \
|
2021-07-12 10:12:47 -03:00
|
|
|
++current_token;
|
|
|
|
|
|
2023-08-24 18:01:19 -03:00
|
|
|
#define EXPECT_START_TAG_TOKEN(_tag_name, start_column, end_column) \
|
|
|
|
|
EXPECT_EQ(current_token->type(), Token::Type::StartTag); \
|
|
|
|
|
EXPECT_EQ(current_token->tag_name(), #_tag_name); \
|
|
|
|
|
EXPECT_EQ(current_token->start_position().column, start_column); \
|
|
|
|
|
EXPECT_EQ(current_token->end_position().column, end_column); \
|
2021-07-12 10:12:47 -03:00
|
|
|
NEXT_TOKEN();
|
|
|
|
|
|
2023-08-24 18:01:19 -03:00
|
|
|
#define EXPECT_END_TAG_TOKEN(_tag_name, start_column, end_column) \
|
|
|
|
|
EXPECT_EQ(current_token->type(), Token::Type::EndTag); \
|
|
|
|
|
EXPECT_EQ(current_token->tag_name(), #_tag_name); \
|
|
|
|
|
EXPECT_EQ(current_token->start_position().column, start_column); \
|
|
|
|
|
EXPECT_EQ(current_token->end_position().column, end_column); \
|
2021-07-12 10:12:47 -03:00
|
|
|
NEXT_TOKEN();
|
|
|
|
|
|
|
|
|
|
#define EXPECT_END_OF_FILE_TOKEN() \
|
|
|
|
|
EXPECT_EQ(current_token->type(), Token::Type::EndOfFile); \
|
|
|
|
|
NEXT_TOKEN();
|
|
|
|
|
|
|
|
|
|
#define EXPECT_CHARACTER_TOKEN(character) \
|
|
|
|
|
EXPECT_EQ(current_token->type(), Token::Type::Character); \
|
|
|
|
|
EXPECT_EQ(current_token->code_point(), (u32)(character)); \
|
|
|
|
|
NEXT_TOKEN();
|
|
|
|
|
|
2022-07-11 14:32:29 -03:00
|
|
|
#define EXPECT_CHARACTER_TOKENS(string) \
|
|
|
|
|
for (auto c : #string##sv) { \
|
|
|
|
|
EXPECT_CHARACTER_TOKEN(c); \
|
2021-07-12 10:12:47 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define EXPECT_COMMENT_TOKEN() \
|
|
|
|
|
EXPECT_EQ(current_token->type(), Token::Type::Comment); \
|
|
|
|
|
NEXT_TOKEN();
|
|
|
|
|
|
|
|
|
|
#define EXPECT_DOCTYPE_TOKEN() \
|
|
|
|
|
EXPECT_EQ(current_token->type(), Token::Type::DOCTYPE); \
|
|
|
|
|
NEXT_TOKEN();
|
|
|
|
|
|
2023-08-24 18:43:05 -03:00
|
|
|
#define EXPECT_TAG_TOKEN_ATTRIBUTE(name, attribute_value, name_start_column, name_end_column, value_start_column, value_end_column) \
|
|
|
|
|
VERIFY(last_token); \
|
2023-10-07 19:42:00 -03:00
|
|
|
auto name##_attr = last_token->raw_attribute(#name##_fly_string); \
|
2023-08-24 18:43:05 -03:00
|
|
|
VERIFY(name##_attr.has_value()); \
|
|
|
|
|
EXPECT_EQ(name##_attr->value, attribute_value); \
|
|
|
|
|
EXPECT_EQ(name##_attr->name_start_position.column, name_start_column); \
|
|
|
|
|
EXPECT_EQ(name##_attr->name_end_position.column, name_end_column); \
|
|
|
|
|
EXPECT_EQ(name##_attr->value_start_position.column, value_start_column); \
|
|
|
|
|
EXPECT_EQ(name##_attr->value_end_position.column, value_end_column);
|
2021-07-12 10:12:47 -03:00
|
|
|
|
|
|
|
|
#define EXPECT_TAG_TOKEN_ATTRIBUTE_COUNT(count) \
|
2021-07-15 17:23:06 -03:00
|
|
|
VERIFY(last_token); \
|
2021-07-14 18:53:11 -03:00
|
|
|
EXPECT_EQ(last_token->attribute_count(), (size_t)(count));
|
2021-07-12 10:12:47 -03:00
|
|
|
|
2021-11-10 20:55:02 -03:00
|
|
|
static Vector<Token> run_tokenizer(StringView input)
|
2021-07-12 10:12:47 -03:00
|
|
|
{
|
|
|
|
|
Vector<Token> tokens;
|
|
|
|
|
Tokenizer tokenizer { input, "UTF-8"sv };
|
|
|
|
|
while (true) {
|
|
|
|
|
auto maybe_token = tokenizer.next_token();
|
|
|
|
|
if (!maybe_token.has_value())
|
|
|
|
|
break;
|
|
|
|
|
tokens.append(maybe_token.release_value());
|
|
|
|
|
}
|
|
|
|
|
return tokens;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FIXME: It's not very nice to rely on the format of HTMLToken::to_string() to stay the same.
|
|
|
|
|
static u32 hash_tokens(Vector<Token> const& tokens)
|
|
|
|
|
{
|
|
|
|
|
StringBuilder builder;
|
|
|
|
|
for (auto& token : tokens)
|
2023-11-04 19:45:55 -03:00
|
|
|
builder.append(token.to_string());
|
2021-07-12 10:12:47 -03:00
|
|
|
return (u32)builder.string_view().hash();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(empty)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer(""sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
|
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(basic)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer("<html><head></head><body></body></html>"sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(html, 1u, 5u);
|
|
|
|
|
EXPECT_START_TAG_TOKEN(head, 7u, 11u);
|
|
|
|
|
EXPECT_END_TAG_TOKEN(head, 14u, 18u);
|
|
|
|
|
EXPECT_START_TAG_TOKEN(body, 20u, 24u);
|
|
|
|
|
EXPECT_END_TAG_TOKEN(body, 27u, 31u);
|
|
|
|
|
EXPECT_END_TAG_TOKEN(html, 34u, 38u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(basic_with_text)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer("<p>This is some text.</p>"sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(p, 1u, 2u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_CHARACTER_TOKENS(This is some text.);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_END_TAG_TOKEN(p, 23u, 24u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(unquoted_attributes)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer("<p foo=bar>"sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(p, 1u, 10u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE_COUNT(1);
|
2023-08-24 18:43:05 -03:00
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(foo, "bar", 3u, 6u, 7u, 10u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(single_quoted_attributes)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer("<p foo='bar'>"sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(p, 1u, 12u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE_COUNT(1);
|
2023-08-24 18:43:05 -03:00
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(foo, "bar", 3u, 6u, 7u, 12u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(double_quoted_attributes)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer("<p foo=\"bar\">"sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(p, 1u, 12u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE_COUNT(1);
|
2023-08-24 18:43:05 -03:00
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(foo, "bar", 3u, 6u, 7u, 12u);
|
|
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(valueless_attribute)
|
|
|
|
|
{
|
|
|
|
|
auto tokens = run_tokenizer("<p foo>"sv);
|
|
|
|
|
BEGIN_ENUMERATION(tokens);
|
|
|
|
|
EXPECT_START_TAG_TOKEN(p, 1u, 6u);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE_COUNT(1);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(foo, "", 3u, 6u, 0u, 0u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(multiple_attributes)
|
|
|
|
|
{
|
2023-08-24 18:43:05 -03:00
|
|
|
auto tokens = run_tokenizer("<p foo=\"bar\" baz=foobar biz foo2=\"bar2\">"sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
2023-08-24 18:43:05 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(p, 1u, 39u);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE_COUNT(4);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(foo, "bar", 3u, 6u, 7u, 12u);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(baz, "foobar", 13u, 16u, 17u, 23u);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(biz, "", 24u, 27u, 0u, 0u);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(foo2, "bar2", 28u, 32u, 33u, 39u);
|
2021-07-24 19:15:47 -03:00
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(character_reference_in_attribute)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer("<p foo=a&b bar='a&b' baz=\"a&b\">"sv);
|
2021-07-24 19:15:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(p, 1u, 43u);
|
2021-07-24 19:15:47 -03:00
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE_COUNT(3);
|
2023-08-24 18:43:05 -03:00
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(foo, "a&b", 3u, 6u, 7u, 14u);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(bar, "a&b", 15u, 18u, 19u, 28u);
|
|
|
|
|
EXPECT_TAG_TOKEN_ATTRIBUTE(baz, "a&b", 29u, 32u, 33u, 43u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
LibWeb: Replace the HTML tokenizer with Rust
Replace the C++ HTML tokenizer with a Rust implementation behind the
existing HTMLTokenizer API.
Keep the parser-facing integration points for streaming input,
insertion points, document.write(), EOF insertion, parser aborts,
speculative parser input, and last start tag tracking. The generated
FFI handle stays an implementation detail of HTMLTokenizer, so callers
keep a single tokenizer class.
Preserve duplicate attributes through FFI so C++ token normalization can
record the duplicate-attribute signal used by CSP nonce checks. Keep
bulk tag-name and attribute scans capped at the active insertion point
so streamed parser input is spliced at the right offset.
Use generated DAFSA tables for named character references and intern
common tag and attribute names to reduce FFI marshalling overhead. This
also fixes attribute name source positions, nested old insertion points,
and aborted fast-path handling.
TestHTMLTokenizer covers duplicate attributes and insertion points in
fast tag-name, attribute-name, and quoted-value scans. A CSP text test
covers duplicate nonce attributes on parser-created script elements.
The tokenizer dump fixtures still match, TestHTMLTokenizer passes, and
the full release test-web run passes with 6981 tests and 226 skipped.
2026-05-15 10:13:43 -03:00
|
|
|
TEST_CASE(duplicate_attributes_are_reported)
|
|
|
|
|
{
|
|
|
|
|
auto tokens = run_tokenizer("<script nonce=x nonce=y></script>"sv);
|
|
|
|
|
auto& token = tokens.first();
|
|
|
|
|
EXPECT_EQ(token.type(), Token::Type::StartTag);
|
|
|
|
|
EXPECT(token.had_duplicate_attribute());
|
|
|
|
|
EXPECT_EQ(token.attribute_count(), 1u);
|
|
|
|
|
|
|
|
|
|
auto nonce = token.raw_attribute("nonce"_fly_string);
|
|
|
|
|
VERIFY(nonce.has_value());
|
|
|
|
|
EXPECT_EQ(nonce->value, "x");
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-04 01:26:49 -03:00
|
|
|
TEST_CASE(named_character_reference)
|
|
|
|
|
{
|
|
|
|
|
auto tokens = run_tokenizer("⋶¬it;&cz"sv);
|
|
|
|
|
BEGIN_ENUMERATION(tokens);
|
|
|
|
|
EXPECT_CHARACTER_TOKEN(0x22F6); // ⋶
|
|
|
|
|
EXPECT_CHARACTER_TOKEN(0xAC); // ¬ (backtracked from ¬it)
|
|
|
|
|
EXPECT_CHARACTER_TOKENS(it);
|
|
|
|
|
EXPECT_CHARACTER_TOKEN(';');
|
|
|
|
|
EXPECT_CHARACTER_TOKENS(&cz); // invalid
|
|
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
LibWeb: Fix numeric character reference at EOF leaking its last digit
Previously, if the NumericCharacterReferenceEnd state was reached when
current_input_character was None, then the
DONT_CONSUME_NEXT_INPUT_CHARACTER macro would restore back before the
EOF, and allow the next state (after the SWITCH_TO_RETURN_STATE) to
proceed with the last digit of the numeric character reference.
For example, with something like `ї`, before this commit the
output would incorrectly be `<code point with the value 1111>1` instead
of just `<code point with the value 1111>`.
Instead of putting the `if (current_input_character.has_value())` check
inside NumericCharacterReferenceEnd directly, it was instead added to
DONT_CONSUME_NEXT_INPUT_CHARACTER, because all usages of the macro
benefit from this check, even if the other existing usage sites don't
exhibit any bugs without it:
- In MarkupDeclarationOpen, if the current_input_character is EOF, then
the previous character is always `!`, so restoring and then checking
forward for strings like `--`, `DOCTYPE`, etc won't match and the
BogusComment state will run one extra time (once for `!` and once
for EOF) with no practical consequences. With the `has_value()` check,
BogusComment will only run once with EOF.
- In AfterDOCTYPEName, ConsumeNextResult::RanOutOfCharacters can only
occur when stopping at the insertion point, and because of how
the code is structured, it is guaranteed that current_input_character
is either `P` or `S`, so the `has_value()` check is irrelevant.
2024-12-20 11:05:37 -03:00
|
|
|
TEST_CASE(numeric_character_reference)
|
|
|
|
|
{
|
|
|
|
|
auto tokens = run_tokenizer("ї"sv);
|
|
|
|
|
BEGIN_ENUMERATION(tokens);
|
|
|
|
|
EXPECT_CHARACTER_TOKEN(1111);
|
|
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-20 11:17:13 -03:00
|
|
|
TEST_CASE(hex_character_reference)
|
|
|
|
|
{
|
|
|
|
|
auto tokens = run_tokenizer("ꄫZ"sv);
|
|
|
|
|
BEGIN_ENUMERATION(tokens);
|
|
|
|
|
EXPECT_CHARACTER_TOKEN(0xA12B);
|
|
|
|
|
EXPECT_CHARACTER_TOKEN('Z');
|
|
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-12 10:12:47 -03:00
|
|
|
TEST_CASE(comment)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer("<p><!-- This is a comment --></p>"sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(p, 1u, 2u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_COMMENT_TOKEN();
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_END_TAG_TOKEN(p, 31u, 32u);
|
2021-07-12 10:12:47 -03:00
|
|
|
EXPECT_END_OF_FILE_TOKEN();
|
|
|
|
|
END_ENUMERATION();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(doctype)
|
|
|
|
|
{
|
2022-07-11 14:32:29 -03:00
|
|
|
auto tokens = run_tokenizer("<!DOCTYPE html><html></html>"sv);
|
2021-07-12 10:12:47 -03:00
|
|
|
BEGIN_ENUMERATION(tokens);
|
|
|
|
|
EXPECT_DOCTYPE_TOKEN();
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_START_TAG_TOKEN(html, 16u, 20u);
|
|
|
|
|
EXPECT_END_TAG_TOKEN(html, 23u, 27u);
|
2021-07-12 10:12:47 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: This relies on the format of HTMLToken::to_string() staying the same.
|
|
|
|
|
// If that changes, or something is added to the test HTML, the hash needs to be adjusted.
|
|
|
|
|
TEST_CASE(regression)
|
|
|
|
|
{
|
2023-01-09 18:29:39 -03:00
|
|
|
StringView path = "tokenizer-test.html"sv;
|
|
|
|
|
|
2023-02-08 23:02:46 -03:00
|
|
|
auto file = MUST(Core::File::open(path, Core::File::OpenMode::Read));
|
2022-03-10 11:02:40 -03:00
|
|
|
auto file_size = MUST(file->size());
|
|
|
|
|
auto content = MUST(ByteBuffer::create_uninitialized(file_size));
|
2023-03-01 13:24:50 -03:00
|
|
|
MUST(file->read_until_filled(content.bytes()));
|
2023-12-16 11:19:34 -03:00
|
|
|
ByteString file_contents { content.bytes() };
|
2021-07-12 10:12:47 -03:00
|
|
|
auto tokens = run_tokenizer(file_contents);
|
|
|
|
|
u32 hash = hash_tokens(tokens);
|
2023-08-24 18:01:19 -03:00
|
|
|
EXPECT_EQ(hash, 3657343287u);
|
2021-07-12 10:12:47 -03:00
|
|
|
}
|
2025-11-06 04:23:01 -03:00
|
|
|
|
|
|
|
|
TEST_CASE(ambiguous_ampersand_offset)
|
|
|
|
|
{
|
|
|
|
|
auto tokens = run_tokenizer("&a"sv);
|
|
|
|
|
auto& token = tokens.first();
|
|
|
|
|
EXPECT_EQ(token.type(), Token::Type::Character);
|
|
|
|
|
EXPECT_EQ(token.start_position().line, 0u);
|
|
|
|
|
EXPECT_EQ(token.start_position().column, 1u);
|
|
|
|
|
}
|
LibWeb: Replace the HTML tokenizer with Rust
Replace the C++ HTML tokenizer with a Rust implementation behind the
existing HTMLTokenizer API.
Keep the parser-facing integration points for streaming input,
insertion points, document.write(), EOF insertion, parser aborts,
speculative parser input, and last start tag tracking. The generated
FFI handle stays an implementation detail of HTMLTokenizer, so callers
keep a single tokenizer class.
Preserve duplicate attributes through FFI so C++ token normalization can
record the duplicate-attribute signal used by CSP nonce checks. Keep
bulk tag-name and attribute scans capped at the active insertion point
so streamed parser input is spliced at the right offset.
Use generated DAFSA tables for named character references and intern
common tag and attribute names to reduce FFI marshalling overhead. This
also fixes attribute name source positions, nested old insertion points,
and aborted fast-path handling.
TestHTMLTokenizer covers duplicate attributes and insertion points in
fast tag-name, attribute-name, and quoted-value scans. A CSP text test
covers duplicate nonce attributes on parser-created script elements.
The tokenizer dump fixtures still match, TestHTMLTokenizer passes, and
the full release test-web run passes with 6981 tests and 226 skipped.
2026-05-15 10:13:43 -03:00
|
|
|
|
|
|
|
|
TEST_CASE(insertion_point_inside_fast_tag_name)
|
|
|
|
|
{
|
|
|
|
|
Tokenizer tokenizer;
|
|
|
|
|
tokenizer.update_insertion_point();
|
|
|
|
|
tokenizer.insert_input_at_insertion_point("<abc"sv);
|
|
|
|
|
tokenizer.append_to_input_stream("def>"sv);
|
|
|
|
|
|
|
|
|
|
EXPECT(!tokenizer.next_token(Tokenizer::StopAtInsertionPoint::Yes).has_value());
|
|
|
|
|
EXPECT(tokenizer.is_insertion_point_reached());
|
|
|
|
|
EXPECT_EQ(tokenizer.unparsed_input(), "def>"sv);
|
|
|
|
|
|
|
|
|
|
tokenizer.insert_input_at_insertion_point("x"sv);
|
|
|
|
|
tokenizer.undefine_insertion_point();
|
|
|
|
|
tokenizer.close_input_stream();
|
|
|
|
|
|
|
|
|
|
auto token = tokenizer.next_token();
|
|
|
|
|
VERIFY(token.has_value());
|
|
|
|
|
EXPECT_EQ(token->type(), Token::Type::StartTag);
|
|
|
|
|
EXPECT_EQ(token->tag_name(), "abcxdef");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(insertion_point_inside_fast_attribute_name)
|
|
|
|
|
{
|
|
|
|
|
Tokenizer tokenizer;
|
|
|
|
|
tokenizer.update_insertion_point();
|
|
|
|
|
tokenizer.insert_input_at_insertion_point("<p abc"sv);
|
|
|
|
|
tokenizer.append_to_input_stream("def=value>"sv);
|
|
|
|
|
|
|
|
|
|
EXPECT(!tokenizer.next_token(Tokenizer::StopAtInsertionPoint::Yes).has_value());
|
|
|
|
|
EXPECT(tokenizer.is_insertion_point_reached());
|
|
|
|
|
EXPECT_EQ(tokenizer.unparsed_input(), "def=value>"sv);
|
|
|
|
|
|
|
|
|
|
tokenizer.insert_input_at_insertion_point("x"sv);
|
|
|
|
|
tokenizer.undefine_insertion_point();
|
|
|
|
|
tokenizer.close_input_stream();
|
|
|
|
|
|
|
|
|
|
auto token = tokenizer.next_token();
|
|
|
|
|
VERIFY(token.has_value());
|
|
|
|
|
EXPECT_EQ(token->type(), Token::Type::StartTag);
|
|
|
|
|
EXPECT_EQ(token->attribute_count(), 1u);
|
|
|
|
|
|
|
|
|
|
auto attribute = token->raw_attribute("abcxdef"_fly_string);
|
|
|
|
|
VERIFY(attribute.has_value());
|
|
|
|
|
EXPECT_EQ(attribute->value, "value");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_CASE(insertion_point_inside_fast_quoted_attribute_value)
|
|
|
|
|
{
|
|
|
|
|
Tokenizer tokenizer;
|
|
|
|
|
tokenizer.update_insertion_point();
|
|
|
|
|
tokenizer.insert_input_at_insertion_point("<p a=\"abc"sv);
|
|
|
|
|
tokenizer.append_to_input_stream("def\">"sv);
|
|
|
|
|
|
|
|
|
|
EXPECT(!tokenizer.next_token(Tokenizer::StopAtInsertionPoint::Yes).has_value());
|
|
|
|
|
EXPECT(tokenizer.is_insertion_point_reached());
|
|
|
|
|
EXPECT_EQ(tokenizer.unparsed_input(), "def\">"sv);
|
|
|
|
|
|
|
|
|
|
tokenizer.insert_input_at_insertion_point("x"sv);
|
|
|
|
|
tokenizer.undefine_insertion_point();
|
|
|
|
|
tokenizer.close_input_stream();
|
|
|
|
|
|
|
|
|
|
auto token = tokenizer.next_token();
|
|
|
|
|
VERIFY(token.has_value());
|
|
|
|
|
EXPECT_EQ(token->type(), Token::Type::StartTag);
|
|
|
|
|
|
|
|
|
|
auto attribute = token->raw_attribute("a"_fly_string);
|
|
|
|
|
VERIFY(attribute.has_value());
|
|
|
|
|
EXPECT_EQ(attribute->value, "abcxdef");
|
|
|
|
|
}
|