LibWeb: Complete Rust HTML tree construction
Finish the Rust implementation of the spec tree-construction algorithms needed by the LibWeb test suite. Add the remaining table modes, foster parenting, scope helpers, adoption agency handling, ruby/list/form and select cases, frameset state, foreign-content edge cases, and parser host callbacks. Preserve behavior that depends on the C++ DOM integration, including parser-created custom element reactions, fragment quirks mode, arbitrary fragment namespaces, template fragment mode, fragment form ownership, MathML annotation-xml boundaries, contextual fragment scripts, parser script source positions, document.close() parser state, void-element insertion, and duplicate attribute tracking. Add focused tests for the parser edge cases that are easy to regress at the boundary between the Rust tree builder and the C++ DOM host.
This commit is contained in:
parent
de12062515
commit
54879bc916
26 changed files with 3402 additions and 361 deletions
|
|
@ -984,9 +984,17 @@ WebIDL::ExceptionOr<void> Document::close()
|
|||
// 4. Insert an explicit "EOF" character at the end of the parser's input stream.
|
||||
m_parser->tokenizer().insert_eof();
|
||||
|
||||
auto finish_script_created_parser = [this] {
|
||||
m_parser->tokenizer().undefine_insertion_point();
|
||||
m_parser->pop_all_open_elements();
|
||||
|
||||
// AD-HOC: This ensures that a load event is fired if the node navigable's container is an iframe.
|
||||
completely_finish_loading();
|
||||
};
|
||||
|
||||
// 5. If there is a pending parsing-blocking script, then return.
|
||||
if (has_pending_parsing_blocking_script()) {
|
||||
m_parser->set_post_parse_action([this] { completely_finish_loading(); });
|
||||
m_parser->set_post_parse_action(move(finish_script_created_parser));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -995,12 +1003,11 @@ WebIDL::ExceptionOr<void> Document::close()
|
|||
|
||||
// run() may have paused on a blocking script (e.g. from document.write inside an inline script).
|
||||
if (has_pending_parsing_blocking_script()) {
|
||||
m_parser->set_post_parse_action([this] { completely_finish_loading(); });
|
||||
m_parser->set_post_parse_action(move(finish_script_created_parser));
|
||||
return {};
|
||||
}
|
||||
|
||||
// AD-HOC: This ensures that a load event is fired if the node navigable's container is an iframe.
|
||||
completely_finish_loading();
|
||||
finish_script_created_parser();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,11 @@ void HTMLScriptElement::execute_script()
|
|||
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
|
||||
void HTMLScriptElement::prepare_script()
|
||||
{
|
||||
// AD-HOC: Don't prepare scripts while in the temporary document used for fragment parsing. They will be prepared
|
||||
// when inserted into a real document, unless fragment parsing already marked them as already started.
|
||||
if (document().is_temporary_document_for_fragment_parsing())
|
||||
return;
|
||||
|
||||
// 1. If el's already started is true, then return.
|
||||
if (m_already_started)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/SourceLocation.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <AK/Utf32View.h>
|
||||
#include <LibTextCodec/Decoder.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
|
|
@ -23,6 +24,7 @@
|
|||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/ElementFactory.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/NamedNodeMap.h>
|
||||
#include <LibWeb/DOM/ProcessingInstruction.h>
|
||||
#include <LibWeb/DOM/QualifiedName.h>
|
||||
#include <LibWeb/DOM/ShadowRoot.h>
|
||||
|
|
@ -69,7 +71,36 @@ static inline void log_parse_error(SourceLocation const& location = SourceLocati
|
|||
}
|
||||
|
||||
static DOM::Node& node_from_html_parser_ffi(size_t);
|
||||
static HTMLParser& parser_from_html_parser_ffi(void*);
|
||||
static RustFfiHtmlNamespace namespace_to_html_parser_ffi(Optional<FlyString> const&);
|
||||
static RustFfiHtmlAttributeNamespace attribute_namespace_to_html_parser_ffi(Optional<FlyString> const&);
|
||||
static RustFfiHtmlQuirksMode quirks_mode_to_html_parser_ffi(DOM::QuirksMode);
|
||||
|
||||
extern "C" void ladybird_html_parser_log_parse_error(void*, u8 const*, size_t);
|
||||
extern "C" void ladybird_html_parser_stop_parsing(void*);
|
||||
extern "C" bool ladybird_html_parser_parse_errors_enabled();
|
||||
extern "C" void ladybird_html_parser_visit_node(void*, size_t);
|
||||
extern "C" size_t ladybird_html_parser_document_node(void*);
|
||||
extern "C" size_t ladybird_html_parser_document_html_element(void*);
|
||||
extern "C" void ladybird_html_parser_set_document_quirks_mode(void*, RustFfiHtmlQuirksMode);
|
||||
extern "C" size_t ladybird_html_parser_create_document_type(void*, u8 const*, size_t, u8 const*, size_t, u8 const*, size_t);
|
||||
extern "C" size_t ladybird_html_parser_create_comment(void*, u8 const*, size_t);
|
||||
extern "C" void ladybird_html_parser_insert_text(size_t, size_t, u8 const*, size_t);
|
||||
extern "C" void ladybird_html_parser_add_missing_attribute(size_t, u8 const*, size_t, u8 const*, size_t);
|
||||
extern "C" void ladybird_html_parser_remove_node(size_t);
|
||||
extern "C" void ladybird_html_parser_handle_element_popped(size_t);
|
||||
extern "C" void ladybird_html_parser_prepare_svg_script(void*, size_t, size_t);
|
||||
extern "C" void ladybird_html_parser_set_script_source_line(void*, size_t, size_t);
|
||||
extern "C" void ladybird_html_parser_mark_script_already_started(void*, size_t);
|
||||
extern "C" size_t ladybird_html_parser_parent_node(size_t);
|
||||
extern "C" size_t ladybird_html_parser_create_element(void*, size_t, RustFfiHtmlNamespace, u8 const*, size_t, u8 const*, size_t, RustFfiHtmlParserAttribute const*, size_t, bool, size_t, bool);
|
||||
extern "C" void ladybird_html_parser_append_child(size_t, size_t);
|
||||
extern "C" void ladybird_html_parser_insert_node(size_t, size_t, size_t, bool);
|
||||
extern "C" void ladybird_html_parser_move_all_children(size_t, size_t);
|
||||
extern "C" size_t ladybird_html_parser_template_content(size_t);
|
||||
extern "C" size_t ladybird_html_parser_attach_declarative_shadow_root(size_t, RustFfiHtmlShadowRootMode, RustFfiHtmlSlotAssignmentMode, bool, bool, bool, bool);
|
||||
extern "C" void ladybird_html_parser_set_template_content(size_t, size_t);
|
||||
extern "C" bool ladybird_html_parser_allows_declarative_shadow_roots(size_t);
|
||||
|
||||
Optional<HTMLParserBackend> html_parser_backend_from_string(StringView backend)
|
||||
{
|
||||
|
|
@ -256,6 +287,8 @@ void HTMLParser::visit_edges(Cell::Visitor& visitor)
|
|||
m_stack_of_open_elements.visit_edges(visitor);
|
||||
m_list_of_active_formatting_elements.visit_edges(visitor);
|
||||
m_tokenizer.visit_edges(visitor);
|
||||
if (m_rust_parser)
|
||||
rust_html_parser_visit_edges(m_rust_parser, &visitor);
|
||||
}
|
||||
|
||||
void HTMLParser::initialize(JS::Realm& realm)
|
||||
|
|
@ -276,14 +309,27 @@ void HTMLParser::run(HTMLTokenizer::StopAtInsertionPoint stop_at_insertion_point
|
|||
m_rust_parser,
|
||||
m_tokenizer.ffi_handle({}),
|
||||
this,
|
||||
m_scripting_mode != ParserScriptingMode::Disabled,
|
||||
stop_at_insertion_point == HTMLTokenizer::StopAtInsertionPoint::Yes);
|
||||
if (result == RustFfiHtmlParserRunResult::Ok)
|
||||
break;
|
||||
|
||||
VERIFY(result == RustFfiHtmlParserRunResult::ExecuteScript);
|
||||
auto script = rust_html_parser_take_pending_script(m_rust_parser);
|
||||
VERIFY(script);
|
||||
process_script_end_tag_from_rust_parser(as<HTMLScriptElement>(node_from_html_parser_ffi(script)));
|
||||
if (result == RustFfiHtmlParserRunResult::ExecuteScript) {
|
||||
auto script = rust_html_parser_take_pending_script(m_rust_parser);
|
||||
VERIFY(script);
|
||||
process_script_end_tag_from_rust_parser(as<HTMLScriptElement>(node_from_html_parser_ffi(script)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result == RustFfiHtmlParserRunResult::ExecuteSvgScript) {
|
||||
auto script = rust_html_parser_take_pending_svg_script(m_rust_parser);
|
||||
VERIFY(script);
|
||||
if (process_svg_script_end_tag_from_rust_parser(as<SVG::SVGScriptElement>(node_from_html_parser_ffi(script))))
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
m_tokenizer.parser_did_run({});
|
||||
|
|
@ -358,9 +404,22 @@ void HTMLParser::run(URL::URL const& url, HTMLTokenizer::StopAtInsertionPoint st
|
|||
run_until_completion(stop_at_insertion_point);
|
||||
}
|
||||
|
||||
void HTMLParser::pop_all_open_elements()
|
||||
{
|
||||
if (m_backend == HTMLParserBackend::Rust) {
|
||||
rust_html_parser_pop_all_open_elements(m_rust_parser);
|
||||
return;
|
||||
}
|
||||
|
||||
while (!m_stack_of_open_elements.is_empty())
|
||||
(void)m_stack_of_open_elements.pop();
|
||||
}
|
||||
|
||||
void HTMLParser::configure_element_created_by_rust_parser(DOM::Element& element)
|
||||
{
|
||||
if (element.local_name() == HTML::TagNames::link && element.namespace_uri() == Namespace::HTML) {
|
||||
// AD-HOC: Let <link> elements know which document they were originally parsed for.
|
||||
// This is used for the render-blocking logic.
|
||||
auto& link_element = as<HTMLLinkElement>(element);
|
||||
link_element.set_parser_document({}, document());
|
||||
link_element.set_was_enabled_when_created_by_parser({}, !element.has_attribute(HTML::AttributeNames::disabled));
|
||||
|
|
@ -380,9 +439,11 @@ void HTMLParser::configure_element_created_by_rust_parser(DOM::Element& element)
|
|||
|
||||
GC::Ref<DOM::Element> HTMLParser::create_element_for_rust_parser(HTMLToken const& token, Optional<FlyString> const& namespace_, DOM::Node& intended_parent, bool had_duplicate_attribute, GC::Ptr<HTMLFormElement> form_element, bool has_template_element_on_stack)
|
||||
{
|
||||
TemporaryChange<GC::Ptr<HTMLFormElement>> suppress_cpp_form_element { m_form_element, {} };
|
||||
auto element = create_element_for(token, namespace_, intended_parent);
|
||||
configure_element_created_by_rust_parser(element);
|
||||
|
||||
// AD-HOC: See AD-HOC comment on Element.m_had_duplicate_attribute_during_tokenization about why this is done.
|
||||
if (had_duplicate_attribute)
|
||||
element->set_had_duplicate_attribute_during_tokenization({});
|
||||
|
||||
|
|
@ -464,6 +525,82 @@ bool HTMLParser::process_script_end_tag_from_rust_parser(HTMLScriptElement& scri
|
|||
return m_parser_pause_flag;
|
||||
}
|
||||
|
||||
void HTMLParser::prepare_svg_script_for_rust_parser(SVG::SVGScriptElement& script, size_t source_line_number)
|
||||
{
|
||||
// AD-HOC: For SVG script elements, set the parser-inserted flag before the element is inserted into the DOM.
|
||||
// Otherwise inserted()/attribute_changed() would invoke process_the_script_element() with the flag still unset
|
||||
// and bypass the parser-blocking fetch handling.
|
||||
//
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#scripting-mode
|
||||
// The Fragment scripting mode treats parser-inserted scripts as if they were not parser-inserted, allowing, for
|
||||
// example, executing scripts when applying a fragment created by createContextualFragment().
|
||||
if (m_scripting_mode != ParserScriptingMode::Fragment)
|
||||
script.set_parser_inserted({});
|
||||
script.set_source_line_number({}, source_line_number);
|
||||
}
|
||||
|
||||
void HTMLParser::set_script_source_line_from_rust_parser(DOM::Element& element, size_t source_line_number)
|
||||
{
|
||||
if (auto* html_script_element = as_if<HTML::HTMLScriptElement>(element)) {
|
||||
html_script_element->set_source_line_number({}, source_line_number);
|
||||
return;
|
||||
}
|
||||
if (auto* svg_script_element = as_if<SVG::SVGScriptElement>(element))
|
||||
svg_script_element->set_source_line_number({}, source_line_number);
|
||||
}
|
||||
|
||||
void HTMLParser::mark_script_already_started_from_rust_parser(HTMLScriptElement& script)
|
||||
{
|
||||
script.set_already_started(Badge<HTMLParser> {}, true);
|
||||
}
|
||||
|
||||
void HTMLParser::stop_parsing_from_rust_parser()
|
||||
{
|
||||
stop_parsing();
|
||||
}
|
||||
|
||||
bool HTMLParser::process_svg_script_end_tag_from_rust_parser(SVG::SVGScriptElement& script)
|
||||
{
|
||||
// Let the old insertion point have the same value as the current insertion point.
|
||||
m_tokenizer.store_old_insertion_point();
|
||||
|
||||
// Let the insertion point be just before the next input character.
|
||||
m_tokenizer.update_insertion_point();
|
||||
|
||||
// Increment the parser's script nesting level by one.
|
||||
increment_script_nesting_level();
|
||||
|
||||
// Set the parser pause flag to true.
|
||||
m_parser_pause_flag = true;
|
||||
|
||||
// Non-standard: Make sure the <script> element has up-to-date text content before processing the script.
|
||||
flush_character_insertions();
|
||||
|
||||
// If the active speculative HTML parser is null and the user agent supports SVG, then Process the SVG script element according to the SVG rules. [SVG]
|
||||
// The active speculative HTML parser is null here.
|
||||
script.process_the_script_element();
|
||||
|
||||
// Decrement the parser's script nesting level by one.
|
||||
decrement_script_nesting_level();
|
||||
|
||||
// If the parser's script nesting level is zero, then set the parser pause flag to false.
|
||||
if (script_nesting_level() == 0)
|
||||
m_parser_pause_flag = false;
|
||||
|
||||
// Let the insertion point have the value of the old insertion point.
|
||||
m_tokenizer.restore_old_insertion_point();
|
||||
|
||||
// If the SVG script registered itself as a pending parsing-blocking script (external fetch in flight),
|
||||
// pause the parser and schedule a resume check. The parser will resume from
|
||||
// resume_after_parser_blocking_script when the fetch completes.
|
||||
if (document().pending_parsing_blocking_svg_script()) {
|
||||
m_parser_pause_flag = true;
|
||||
schedule_resume_check();
|
||||
}
|
||||
|
||||
return m_parser_pause_flag;
|
||||
}
|
||||
|
||||
void HTMLParser::run_until_completion(HTMLTokenizer::StopAtInsertionPoint stop_at_insertion_point)
|
||||
{
|
||||
m_post_parse_action = [this] { the_end(*m_document, this); };
|
||||
|
|
@ -520,10 +657,8 @@ void HTMLParser::the_end(GC::Ref<DOM::Document> document, GC::Ptr<HTMLParser> pa
|
|||
document->update_readiness(HTML::DocumentReadyState::Interactive);
|
||||
|
||||
// 4. Pop all the nodes off the stack of open elements.
|
||||
if (parser) {
|
||||
while (!parser->m_stack_of_open_elements.is_empty())
|
||||
(void)parser->m_stack_of_open_elements.pop();
|
||||
}
|
||||
if (parser)
|
||||
parser->pop_all_open_elements();
|
||||
|
||||
// AD-HOC: Skip remaining steps when there's no browsing context.
|
||||
// This happens when parsing HTML via DOMParser or similar mechanisms.
|
||||
|
|
@ -5289,14 +5424,18 @@ WebIDL::ExceptionOr<Vector<GC::Root<DOM::Node>>> HTMLParser::parse_html_fragment
|
|||
// 9. Set the parser's scripting mode to scriptingMode.
|
||||
if (context_element.document().is_scripting_disabled())
|
||||
scripting_mode = HTML::ParserScriptingMode::Disabled;
|
||||
auto parser = HTMLParser::create(*temp_document, markup, scripting_mode, "utf-8"sv);
|
||||
auto backend = default_html_parser_backend();
|
||||
|
||||
auto parser = HTMLParser::create(*temp_document, markup, scripting_mode, "utf-8"sv, backend);
|
||||
parser->m_context_element = context_element;
|
||||
parser->m_parsing_fragment = true;
|
||||
|
||||
// 10. Set the state of the HTML parser's tokenization stage as follows, switching on the context element:
|
||||
bool const context_element_is_html = context_element.namespace_uri() == Namespace::HTML;
|
||||
// - title
|
||||
// - textarea
|
||||
if (context_element.local_name().is_one_of(HTML::TagNames::title, HTML::TagNames::textarea)) {
|
||||
if (context_element_is_html
|
||||
&& context_element.local_name().is_one_of(HTML::TagNames::title, HTML::TagNames::textarea)) {
|
||||
// Switch the tokenizer to the RCDATA state.
|
||||
parser->m_tokenizer.switch_to({}, HTMLTokenizer::State::RCDATA);
|
||||
}
|
||||
|
|
@ -5305,23 +5444,24 @@ WebIDL::ExceptionOr<Vector<GC::Root<DOM::Node>>> HTMLParser::parse_html_fragment
|
|||
// - iframe
|
||||
// - noembed
|
||||
// - noframes
|
||||
else if (context_element.local_name().is_one_of(HTML::TagNames::style, HTML::TagNames::xmp, HTML::TagNames::iframe, HTML::TagNames::noembed, HTML::TagNames::noframes)) {
|
||||
else if (context_element_is_html
|
||||
&& context_element.local_name().is_one_of(HTML::TagNames::style, HTML::TagNames::xmp, HTML::TagNames::iframe, HTML::TagNames::noembed, HTML::TagNames::noframes)) {
|
||||
// Switch the tokenizer to the RAWTEXT state.
|
||||
parser->m_tokenizer.switch_to({}, HTMLTokenizer::State::RAWTEXT);
|
||||
}
|
||||
// - script
|
||||
else if (context_element.local_name().is_one_of(HTML::TagNames::script)) {
|
||||
else if (context_element_is_html && context_element.local_name().is_one_of(HTML::TagNames::script)) {
|
||||
// Switch the tokenizer to the script data state.
|
||||
parser->m_tokenizer.switch_to({}, HTMLTokenizer::State::ScriptData);
|
||||
}
|
||||
// - noscript
|
||||
else if (context_element.local_name().is_one_of(HTML::TagNames::noscript)) {
|
||||
else if (context_element_is_html && context_element.local_name().is_one_of(HTML::TagNames::noscript)) {
|
||||
// If scripting mode is not Disabled, switch the tokenizer to the RAWTEXT state. Otherwise, leave the tokenizer in the data state.
|
||||
if (scripting_mode != HTML::ParserScriptingMode::Disabled)
|
||||
parser->m_tokenizer.switch_to({}, HTMLTokenizer::State::RAWTEXT);
|
||||
}
|
||||
// - plaintext
|
||||
else if (context_element.local_name().is_one_of(HTML::TagNames::plaintext)) {
|
||||
else if (context_element_is_html && context_element.local_name().is_one_of(HTML::TagNames::plaintext)) {
|
||||
// Switch the tokenizer to the PLAINTEXT state.
|
||||
parser->m_tokenizer.switch_to({}, HTMLTokenizer::State::PLAINTEXT);
|
||||
}
|
||||
|
|
@ -5354,16 +5494,48 @@ WebIDL::ExceptionOr<Vector<GC::Root<DOM::Node>>> HTMLParser::parse_html_fragment
|
|||
// 17. Set the HTML parser's form element pointer to the nearest node to context that is a form element
|
||||
// (going straight up the ancestor chain, and including the element itself, if it is a form element), if any.
|
||||
// (If there is no such form element, the form element pointer keeps its initial value, null.)
|
||||
parser->m_form_element = context_element.first_ancestor_of_type<HTMLFormElement>();
|
||||
parser->m_form_element = as_if<HTMLFormElement>(context_element);
|
||||
if (!parser->m_form_element)
|
||||
parser->m_form_element = context_element.first_ancestor_of_type<HTMLFormElement>();
|
||||
|
||||
if (parser->m_backend == HTMLParserBackend::Rust) {
|
||||
auto context_local_name = context_element.local_name().bytes_as_string_view();
|
||||
auto context_namespace = context_element.namespace_uri();
|
||||
auto context_namespace_ffi = namespace_to_html_parser_ffi(context_namespace);
|
||||
StringView context_namespace_uri;
|
||||
if (context_namespace_ffi == RustFfiHtmlNamespace::Other && context_namespace.has_value())
|
||||
context_namespace_uri = context_namespace->bytes_as_string_view();
|
||||
Vector<RustFfiHtmlParserAttribute> context_attributes;
|
||||
if (auto attributes = context_element.attributes()) {
|
||||
context_attributes.ensure_capacity(attributes->length());
|
||||
for (size_t i = 0; i < attributes->length(); ++i) {
|
||||
auto const* attribute = attributes->item(i);
|
||||
auto local_name = attribute->local_name().bytes_as_string_view();
|
||||
auto value = attribute->value().bytes_as_string_view();
|
||||
auto prefix = attribute->prefix().map([](auto const& prefix) { return prefix.bytes_as_string_view(); });
|
||||
context_attributes.unchecked_append({
|
||||
reinterpret_cast<u8 const*>(local_name.characters_without_null_termination()),
|
||||
local_name.length(),
|
||||
prefix.has_value() ? reinterpret_cast<u8 const*>(prefix->characters_without_null_termination()) : nullptr,
|
||||
prefix.has_value() ? prefix->length() : 0,
|
||||
attribute_namespace_to_html_parser_ffi(attribute->namespace_uri()),
|
||||
reinterpret_cast<u8 const*>(value.characters_without_null_termination()),
|
||||
value.length(),
|
||||
});
|
||||
}
|
||||
}
|
||||
rust_html_parser_begin_fragment(
|
||||
parser->m_rust_parser,
|
||||
reinterpret_cast<size_t>(root.ptr()),
|
||||
namespace_to_html_parser_ffi(context_element.namespace_uri()),
|
||||
reinterpret_cast<size_t>(&context_element),
|
||||
context_namespace_ffi,
|
||||
reinterpret_cast<u8 const*>(context_namespace_uri.characters_without_null_termination()),
|
||||
context_namespace_uri.length(),
|
||||
reinterpret_cast<u8 const*>(context_local_name.characters_without_null_termination()),
|
||||
context_local_name.length(),
|
||||
context_attributes.data(),
|
||||
context_attributes.size(),
|
||||
quirks_mode_to_html_parser_ffi(temp_document->mode()),
|
||||
parser->m_form_element ? reinterpret_cast<size_t>(parser->m_form_element.ptr()) : 0);
|
||||
}
|
||||
|
||||
|
|
@ -6061,8 +6233,7 @@ void HTMLParser::abort()
|
|||
m_document->update_readiness(DocumentReadyState::Interactive);
|
||||
|
||||
// 4. Pop all the nodes off the stack of open elements.
|
||||
while (!m_stack_of_open_elements.is_empty())
|
||||
m_stack_of_open_elements.pop();
|
||||
pop_all_open_elements();
|
||||
|
||||
// 5. Update the current document readiness to "complete".
|
||||
m_document->update_readiness(DocumentReadyState::Complete);
|
||||
|
|
@ -6114,7 +6285,30 @@ static String string_from_html_parser_ffi(u8 const* ptr, size_t len)
|
|||
return MUST(String::from_utf8(html_parser_ffi_string_view(ptr, len)));
|
||||
}
|
||||
|
||||
static Optional<FlyString> namespace_from_html_parser_ffi(RustFfiHtmlNamespace namespace_)
|
||||
extern "C" void ladybird_html_parser_log_parse_error(void* parser, u8 const* message_ptr, size_t message_len)
|
||||
{
|
||||
(void)parser_from_html_parser_ffi(parser);
|
||||
dbgln_if(HTML_PARSER_DEBUG, "Rust parser parse error: {}", html_parser_ffi_string_view(message_ptr, message_len));
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_stop_parsing(void* parser)
|
||||
{
|
||||
parser_from_html_parser_ffi(parser).stop_parsing_from_rust_parser();
|
||||
}
|
||||
|
||||
extern "C" bool ladybird_html_parser_parse_errors_enabled()
|
||||
{
|
||||
return HTML_PARSER_DEBUG;
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_visit_node(void* visitor, size_t node)
|
||||
{
|
||||
if (node == 0)
|
||||
return;
|
||||
static_cast<GC::Cell::Visitor*>(visitor)->visit(node_from_html_parser_ffi(node));
|
||||
}
|
||||
|
||||
static Optional<FlyString> namespace_from_html_parser_ffi(RustFfiHtmlNamespace namespace_, u8 const* namespace_uri_ptr, size_t namespace_uri_len)
|
||||
{
|
||||
switch (namespace_) {
|
||||
case RustFfiHtmlNamespace::Html:
|
||||
|
|
@ -6123,6 +6317,10 @@ static Optional<FlyString> namespace_from_html_parser_ffi(RustFfiHtmlNamespace n
|
|||
return Namespace::MathML;
|
||||
case RustFfiHtmlNamespace::Svg:
|
||||
return Namespace::SVG;
|
||||
case RustFfiHtmlNamespace::Other:
|
||||
if (namespace_uri_len == 0)
|
||||
return {};
|
||||
return fly_string_from_html_parser_ffi(namespace_uri_ptr, namespace_uri_len);
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
|
@ -6138,17 +6336,38 @@ static Optional<FlyString> attribute_namespace_from_html_parser_ffi(RustFfiHtmlA
|
|||
return Namespace::XML;
|
||||
case RustFfiHtmlAttributeNamespace::Xmlns:
|
||||
return Namespace::XMLNS;
|
||||
case RustFfiHtmlAttributeNamespace::Other:
|
||||
// Only fragment context attributes use this sentinel; parser-created attributes do not cross this path with
|
||||
// arbitrary namespace URIs.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static RustFfiHtmlAttributeNamespace attribute_namespace_to_html_parser_ffi(Optional<FlyString> const& namespace_)
|
||||
{
|
||||
if (namespace_ == Namespace::XLink)
|
||||
return RustFfiHtmlAttributeNamespace::XLink;
|
||||
if (namespace_ == Namespace::XML)
|
||||
return RustFfiHtmlAttributeNamespace::Xml;
|
||||
if (namespace_ == Namespace::XMLNS)
|
||||
return RustFfiHtmlAttributeNamespace::Xmlns;
|
||||
if (namespace_.has_value())
|
||||
return RustFfiHtmlAttributeNamespace::Other;
|
||||
return RustFfiHtmlAttributeNamespace::None;
|
||||
}
|
||||
|
||||
static RustFfiHtmlNamespace namespace_to_html_parser_ffi(Optional<FlyString> const& namespace_)
|
||||
{
|
||||
if (!namespace_.has_value())
|
||||
return RustFfiHtmlNamespace::Other;
|
||||
if (namespace_ == Namespace::HTML)
|
||||
return RustFfiHtmlNamespace::Html;
|
||||
if (namespace_ == Namespace::MathML)
|
||||
return RustFfiHtmlNamespace::MathMl;
|
||||
if (namespace_ == Namespace::SVG)
|
||||
return RustFfiHtmlNamespace::Svg;
|
||||
return RustFfiHtmlNamespace::Html;
|
||||
return RustFfiHtmlNamespace::Other;
|
||||
}
|
||||
|
||||
static DOM::QuirksMode quirks_mode_from_html_parser_ffi(RustFfiHtmlQuirksMode mode)
|
||||
|
|
@ -6164,6 +6383,19 @@ static DOM::QuirksMode quirks_mode_from_html_parser_ffi(RustFfiHtmlQuirksMode mo
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static RustFfiHtmlQuirksMode quirks_mode_to_html_parser_ffi(DOM::QuirksMode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case DOM::QuirksMode::No:
|
||||
return RustFfiHtmlQuirksMode::No;
|
||||
case DOM::QuirksMode::Limited:
|
||||
return RustFfiHtmlQuirksMode::Limited;
|
||||
case DOM::QuirksMode::Yes:
|
||||
return RustFfiHtmlQuirksMode::Yes;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static HTMLParser& parser_from_html_parser_ffi(void* parser)
|
||||
{
|
||||
VERIFY(parser);
|
||||
|
|
@ -6181,9 +6413,19 @@ extern "C" size_t ladybird_html_parser_document_node(void* parser)
|
|||
return reinterpret_cast<size_t>(&parser_from_html_parser_ffi(parser).document());
|
||||
}
|
||||
|
||||
extern "C" size_t ladybird_html_parser_document_html_element(void* parser)
|
||||
{
|
||||
auto* html_element = parser_from_html_parser_ffi(parser).document().document_element();
|
||||
if (!html_element || !is<HTMLHtmlElement>(*html_element))
|
||||
return 0;
|
||||
return reinterpret_cast<size_t>(html_element);
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_set_document_quirks_mode(void* parser, RustFfiHtmlQuirksMode mode)
|
||||
{
|
||||
parser_from_html_parser_ffi(parser).document().set_quirks_mode(quirks_mode_from_html_parser_ffi(mode));
|
||||
auto& document = parser_from_html_parser_ffi(parser).document();
|
||||
if (!document.parser_cannot_change_the_mode())
|
||||
document.set_quirks_mode(quirks_mode_from_html_parser_ffi(mode));
|
||||
}
|
||||
|
||||
extern "C" size_t ladybird_html_parser_create_document_type(void* parser, u8 const* name_ptr, size_t name_len, u8 const* public_id_ptr, size_t public_id_len, u8 const* system_id_ptr, size_t system_id_len)
|
||||
|
|
@ -6230,7 +6472,54 @@ extern "C" void ladybird_html_parser_insert_text(size_t parent, size_t before, u
|
|||
MUST(parent_node.append_child(*text));
|
||||
}
|
||||
|
||||
extern "C" size_t ladybird_html_parser_create_element(void* parser, size_t intended_parent, RustFfiHtmlNamespace namespace_, u8 const* local_name_ptr, size_t local_name_len, RustFfiHtmlParserAttribute const* attributes, size_t attribute_count, bool had_duplicate_attribute, size_t form_element, bool has_template_element_on_stack)
|
||||
extern "C" void ladybird_html_parser_add_missing_attribute(size_t element, u8 const* local_name_ptr, size_t local_name_len, u8 const* value_ptr, size_t value_len)
|
||||
{
|
||||
auto& dom_element = as<DOM::Element>(node_from_html_parser_ffi(element));
|
||||
auto local_name = fly_string_from_html_parser_ffi(local_name_ptr, local_name_len);
|
||||
if (dom_element.has_attribute(local_name))
|
||||
return;
|
||||
dom_element.append_attribute(local_name, string_from_html_parser_ffi(value_ptr, value_len));
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_remove_node(size_t node)
|
||||
{
|
||||
node_from_html_parser_ffi(node).remove(true);
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_handle_element_popped(size_t element)
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/form-elements.html#the-option-element
|
||||
// When an option element is popped off the stack of open elements of an HTML parser or XML parser,
|
||||
// the user agent must run maybe clone an option into selectedcontent given the option element.
|
||||
// AD-HOC: The Rust tree builder flushes buffered text before invoking this hook, so the option's content is
|
||||
// up-to-date before cloning.
|
||||
if (auto* option_element = as_if<HTML::HTMLOptionElement>(node_from_html_parser_ffi(element)))
|
||||
MUST(option_element->maybe_clone_into_selectedcontent());
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_prepare_svg_script(void* parser, size_t element, size_t source_line_number)
|
||||
{
|
||||
parser_from_html_parser_ffi(parser).prepare_svg_script_for_rust_parser(as<SVG::SVGScriptElement>(node_from_html_parser_ffi(element)), source_line_number);
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_set_script_source_line(void* parser, size_t element, size_t source_line_number)
|
||||
{
|
||||
parser_from_html_parser_ffi(parser).set_script_source_line_from_rust_parser(as<DOM::Element>(node_from_html_parser_ffi(element)), source_line_number);
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_mark_script_already_started(void* parser, size_t element)
|
||||
{
|
||||
if (auto* script = as_if<HTMLScriptElement>(node_from_html_parser_ffi(element)))
|
||||
parser_from_html_parser_ffi(parser).mark_script_already_started_from_rust_parser(*script);
|
||||
}
|
||||
|
||||
extern "C" size_t ladybird_html_parser_parent_node(size_t node)
|
||||
{
|
||||
auto* parent = node_from_html_parser_ffi(node).parent();
|
||||
return reinterpret_cast<size_t>(parent);
|
||||
}
|
||||
|
||||
extern "C" size_t ladybird_html_parser_create_element(void* parser, size_t intended_parent, RustFfiHtmlNamespace namespace_, u8 const* namespace_uri_ptr, size_t namespace_uri_len, u8 const* local_name_ptr, size_t local_name_len, RustFfiHtmlParserAttribute const* attributes, size_t attribute_count, bool had_duplicate_attribute, size_t form_element, bool has_template_element_on_stack)
|
||||
{
|
||||
auto& html_parser = parser_from_html_parser_ffi(parser);
|
||||
auto local_name = fly_string_from_html_parser_ffi(local_name_ptr, local_name_len);
|
||||
|
|
@ -6253,7 +6542,7 @@ extern "C" size_t ladybird_html_parser_create_element(void* parser, size_t inten
|
|||
GC::Ptr<HTMLFormElement> form_element_ptr;
|
||||
if (form_element)
|
||||
form_element_ptr = as<HTMLFormElement>(node_from_html_parser_ffi(form_element));
|
||||
auto element = html_parser.create_element_for_rust_parser(token, namespace_from_html_parser_ffi(namespace_), intended_parent_node, had_duplicate_attribute, form_element_ptr, has_template_element_on_stack);
|
||||
auto element = html_parser.create_element_for_rust_parser(token, namespace_from_html_parser_ffi(namespace_, namespace_uri_ptr, namespace_uri_len), intended_parent_node, had_duplicate_attribute, form_element_ptr, has_template_element_on_stack);
|
||||
|
||||
return reinterpret_cast<size_t>(element.ptr());
|
||||
}
|
||||
|
|
@ -6263,6 +6552,35 @@ extern "C" void ladybird_html_parser_append_child(size_t parent, size_t child)
|
|||
MUST(node_from_html_parser_ffi(parent).append_child(node_from_html_parser_ffi(child)));
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_insert_node(size_t parent, size_t before, size_t child, bool queue_custom_element_reactions)
|
||||
{
|
||||
auto& parent_node = node_from_html_parser_ffi(parent);
|
||||
auto& child_node = node_from_html_parser_ffi(child);
|
||||
auto* child_element = as_if<DOM::Element>(child_node);
|
||||
if (queue_custom_element_reactions && child_element)
|
||||
relevant_similar_origin_window_agent(*child_element).custom_element_reactions_stack.element_queue_stack.append({});
|
||||
|
||||
if (!before) {
|
||||
MUST(parent_node.append_child(child_node));
|
||||
} else {
|
||||
auto& before_node = node_from_html_parser_ffi(before);
|
||||
parent_node.insert_before(child_node, &before_node, false);
|
||||
}
|
||||
|
||||
if (queue_custom_element_reactions && child_element) {
|
||||
auto queue = relevant_similar_origin_window_agent(*child_element).custom_element_reactions_stack.element_queue_stack.take_last();
|
||||
Bindings::invoke_custom_element_reactions(queue);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ladybird_html_parser_move_all_children(size_t from, size_t to)
|
||||
{
|
||||
auto& from_node = node_from_html_parser_ffi(from);
|
||||
auto& to_node = node_from_html_parser_ffi(to);
|
||||
for (auto& child : from_node.children_as_vector())
|
||||
MUST(to_node.append_child(from_node.remove_child(*child).release_value()));
|
||||
}
|
||||
|
||||
extern "C" size_t ladybird_html_parser_template_content(size_t element)
|
||||
{
|
||||
auto& template_element = as<HTMLTemplateElement>(node_from_html_parser_ffi(element));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@
|
|||
#include <LibWeb/MimeSniff/MimeType.h>
|
||||
#include <LibWeb/Platform/Timer.h>
|
||||
|
||||
namespace Web::SVG {
|
||||
|
||||
class SVGScriptElement;
|
||||
|
||||
}
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
class HTMLScriptElement;
|
||||
|
|
@ -72,6 +78,7 @@ public:
|
|||
void run(HTMLTokenizer::StopAtInsertionPoint = HTMLTokenizer::StopAtInsertionPoint::No);
|
||||
void run(URL::URL const&, HTMLTokenizer::StopAtInsertionPoint = HTMLTokenizer::StopAtInsertionPoint::No);
|
||||
void run_until_completion(HTMLTokenizer::StopAtInsertionPoint = HTMLTokenizer::StopAtInsertionPoint::No);
|
||||
void pop_all_open_elements();
|
||||
|
||||
static void the_end(GC::Ref<DOM::Document>, GC::Ptr<HTMLParser> = nullptr);
|
||||
|
||||
|
|
@ -102,7 +109,12 @@ public:
|
|||
|
||||
void configure_element_created_by_rust_parser(DOM::Element&);
|
||||
GC::Ref<DOM::Element> create_element_for_rust_parser(HTMLToken const&, Optional<FlyString> const& namespace_, DOM::Node& intended_parent, bool had_duplicate_attribute, GC::Ptr<HTMLFormElement>, bool has_template_element_on_stack);
|
||||
void prepare_svg_script_for_rust_parser(SVG::SVGScriptElement&, size_t source_line_number);
|
||||
void set_script_source_line_from_rust_parser(DOM::Element&, size_t source_line_number);
|
||||
void mark_script_already_started_from_rust_parser(HTMLScriptElement&);
|
||||
void stop_parsing_from_rust_parser();
|
||||
bool process_script_end_tag_from_rust_parser(HTMLScriptElement&);
|
||||
bool process_svg_script_end_tag_from_rust_parser(SVG::SVGScriptElement&);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#abort-a-parser
|
||||
void abort();
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ public:
|
|||
|
||||
void set_start_position(Badge<HTMLTokenizer>, Position start_position) { m_start_position = start_position; }
|
||||
void set_end_position(Badge<HTMLTokenizer>, Position end_position) { m_end_position = end_position; }
|
||||
void set_had_duplicate_attribute(Badge<HTMLTokenizer>) { m_had_duplicate_attribute = true; }
|
||||
|
||||
void normalize_attributes();
|
||||
bool had_duplicate_attribute() const { return m_had_duplicate_attribute; }
|
||||
|
|
|
|||
|
|
@ -167,6 +167,8 @@ Optional<HTMLToken> HTMLTokenizer::next_token(StopAtInsertionPoint stop_at_inser
|
|||
token.add_attribute(move(attribute));
|
||||
}
|
||||
token.normalize_attributes();
|
||||
if (ffi.had_duplicate_attribute)
|
||||
token.set_had_duplicate_attribute({});
|
||||
break;
|
||||
}
|
||||
case HTMLToken::Type::Comment:
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ pub struct RustFfiToken {
|
|||
pub token_type: u8,
|
||||
pub code_point: u32,
|
||||
pub self_closing: bool,
|
||||
pub had_duplicate_attribute: bool,
|
||||
|
||||
/// If nonzero, an interned tag-name id (1-based index into
|
||||
/// `interned_names::INTERNED_TAG_NAMES`). When set, `tag_name_ptr` /
|
||||
|
|
@ -101,6 +102,7 @@ impl Default for RustFfiToken {
|
|||
token_type: TokenType::Invalid as u8,
|
||||
code_point: 0,
|
||||
self_closing: false,
|
||||
had_duplicate_attribute: false,
|
||||
tag_name_id: 0,
|
||||
tag_name_ptr: ptr::null(),
|
||||
tag_name_len: 0,
|
||||
|
|
@ -304,10 +306,11 @@ fn next_token_slow(
|
|||
tag_name,
|
||||
tag_name_id,
|
||||
self_closing,
|
||||
had_duplicate_attribute: _,
|
||||
had_duplicate_attribute,
|
||||
attributes,
|
||||
} => {
|
||||
out.self_closing = self_closing;
|
||||
out.had_duplicate_attribute = had_duplicate_attribute;
|
||||
// Tokenizer already resolved intern ids, so we trust tag_name_id.
|
||||
out.tag_name_id = tag_name_id;
|
||||
if tag_name_id == 0 {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -66,6 +66,7 @@ pub enum TokenPayload {
|
|||
tag_name: String,
|
||||
tag_name_id: u16,
|
||||
self_closing: bool,
|
||||
// AD-HOC: See AD-HOC comment on Element.m_had_duplicate_attribute_during_tokenization about why this is tracked.
|
||||
had_duplicate_attribute: bool,
|
||||
attributes: Vec<Attribute>,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
after parser insertion: constructor,connected
|
||||
at DOMContentLoaded: constructor,connected
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
connected
|
||||
written parent: body
|
||||
host child count: 3
|
||||
body children: reentrant-writer,span
|
||||
|
|
@ -0,0 +1 @@
|
|||
document.write after document.close reopens document: true
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
BackCompat
|
||||
<p><table><tbody><tr><td>cell</td></tr></tbody></table></p>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
HTML split start tag line: 17
|
||||
SVG split start tag line: 19
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
option end tag closes option before p: true
|
||||
optgroup end tag closes optgroup before p: true
|
||||
MathML annotation-xml keeps breakout HTML inside form: true
|
||||
annotation-xml innerHTML child namespace: true
|
||||
namespaced annotation-xml encoding attribute stays foreign: true
|
||||
namespace-only annotation-xml encoding attribute stays foreign: true
|
||||
custom namespace innerHTML child namespace: true
|
||||
template innerHTML starts in template mode for table rows: true
|
||||
form innerHTML uses context as form pointer: true
|
||||
annotation-xml contextual fragment child namespace: true
|
||||
HTML script from contextual fragment executes when applied: true
|
||||
SVG script from contextual fragment executes when applied: true
|
||||
SVG title innerHTML tokenizes in data state: true
|
||||
SVG title innerHTML treats title end tag as markup: true
|
||||
SVG title contextual fragment creates HTML child: true
|
||||
SVG desc innerHTML ignores frameset start tag: true
|
||||
prefixed SVG desc innerHTML ignores frameset start tag: true
|
||||
param start tag skips active formatting reconstruction: true
|
||||
source start tag skips active formatting reconstruction: true
|
||||
track start tag skips active formatting reconstruction: true
|
||||
adoption agency fallback after marker closes stale span: true
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
svg write parent: body
|
||||
body children: svg,span,script
|
||||
|
|
@ -2,8 +2,7 @@ Harness status: OK
|
|||
|
||||
Found 66 tests
|
||||
|
||||
63 Pass
|
||||
3 Fail
|
||||
66 Pass
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 4917b7458e1fff6c5cb21d7baf6863cc9550c61e
|
||||
Pass html5lib_innerHTML_foreign-fragment.html b6d19b8ccacd2fde354df002b856f649ae91b20e
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 0c2411aa96ee023941778adaa11977890b232dc3
|
||||
|
|
@ -11,7 +10,7 @@ Pass html5lib_innerHTML_foreign-fragment.html af0d0fc82bcd7e5ba5bc40f781701552b1
|
|||
Pass html5lib_innerHTML_foreign-fragment.html 0135b05656c198b96a9e0f94333aa2c0190ec795
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 60d4a82dede2a297d6306278a19897d021075c6e
|
||||
Pass html5lib_innerHTML_foreign-fragment.html f862d10d81a600b69e7fabd1474ca854ce08cca7
|
||||
Fail html5lib_innerHTML_foreign-fragment.html 5d1db05a97609488e6749ff191294713aec9a90f
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 5d1db05a97609488e6749ff191294713aec9a90f
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 8804aa42daebb5ff2ab0015c6e89d8e40e7a8610
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 34b599e68117799324663b39aa3ba469bffb2dcb
|
||||
Pass html5lib_innerHTML_foreign-fragment.html cc2199d299947f304e204c867bed2c7e910d50cc
|
||||
|
|
@ -48,8 +47,8 @@ Pass html5lib_innerHTML_foreign-fragment.html b4c43a4fcdaa1a4c753674c4f92987b70d
|
|||
Pass html5lib_innerHTML_foreign-fragment.html 7021fb0933e92112b94ee54b923efb6bc71e3b07
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 7c4b1614e2180b6649f3b02cf5c4a9d735166e1b
|
||||
Pass html5lib_innerHTML_foreign-fragment.html bdeacb9250263776c63c2c7f731717c389bdc34c
|
||||
Fail html5lib_innerHTML_foreign-fragment.html 2c46c15bdba5835b0f2f0e9eb5bc8566047b7d6d
|
||||
Fail html5lib_innerHTML_foreign-fragment.html 8dfcfbf823ad6c7b6f7b81efc352f29b4e41e8be
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 2c46c15bdba5835b0f2f0e9eb5bc8566047b7d6d
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 8dfcfbf823ad6c7b6f7b81efc352f29b4e41e8be
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 74a8a40517c6fe110f0c71af7efb56d459ea8227
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 0c1782eb0f62f616627f0132729d6a194f8b7546
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 9dc5a819afe33d6babc04edc8f92cb8045f0f006
|
||||
|
|
@ -69,4 +68,4 @@ Pass html5lib_innerHTML_foreign-fragment.html ce74a1ba339d07982908cc088c9057957a
|
|||
Pass html5lib_innerHTML_foreign-fragment.html b941cd3ca955b1025061b0ff0cda775f0edd16bc
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 69fb90a251264e4e80762fa9acecd2c0bffc0c4c
|
||||
Pass html5lib_innerHTML_foreign-fragment.html f856588390b813aafc272f42800d31ba9a4844e6
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 4c871c875e73e61adb24de1d18fad01363982e21
|
||||
Pass html5lib_innerHTML_foreign-fragment.html 4c871c875e73e61adb24de1d18fad01363982e21
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ Harness status: OK
|
|||
|
||||
Found 41 tests
|
||||
|
||||
39 Pass
|
||||
2 Fail
|
||||
41 Pass
|
||||
Pass html5lib_webkit02.html f50b8c15847159a6d2c6ecc2bd1e4a944ba5aae6
|
||||
Pass html5lib_webkit02.html 326328ea805a2ebdde707e08567713f88a4cf8ab
|
||||
Pass html5lib_webkit02.html 05138397908cfdad69a3bfe5da5a06098320b504
|
||||
|
|
@ -43,5 +42,5 @@ Pass html5lib_webkit02.html a6c50b1f6bfbe3c55102d8cad0950d0b68cc6729
|
|||
Pass html5lib_webkit02.html 411f313a1b92ac7be549c41ee6758f952dc2dced
|
||||
Pass html5lib_webkit02.html 84467597648753feeb78793e2cc9196bc75857c2
|
||||
Pass html5lib_webkit02.html ae6f2e0a014f620269920ceb12660ec708236846
|
||||
Fail html5lib_webkit02.html fd7aea4db6702879b9f8f410b0400d9300ae9c05
|
||||
Fail html5lib_webkit02.html bad5cceffaffe98e3a1522be5f7df3e3e179d500
|
||||
Pass html5lib_webkit02.html fd7aea4db6702879b9f8f410b0400d9300ae9c05
|
||||
Pass html5lib_webkit02.html bad5cceffaffe98e3a1522be5f7df3e3e179d500
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ Harness status: OK
|
|||
|
||||
Found 41 tests
|
||||
|
||||
39 Pass
|
||||
2 Fail
|
||||
41 Pass
|
||||
Pass html5lib_webkit02.html f50b8c15847159a6d2c6ecc2bd1e4a944ba5aae6
|
||||
Pass html5lib_webkit02.html 326328ea805a2ebdde707e08567713f88a4cf8ab
|
||||
Pass html5lib_webkit02.html 05138397908cfdad69a3bfe5da5a06098320b504
|
||||
|
|
@ -43,5 +42,5 @@ Pass html5lib_webkit02.html a6c50b1f6bfbe3c55102d8cad0950d0b68cc6729
|
|||
Pass html5lib_webkit02.html 411f313a1b92ac7be549c41ee6758f952dc2dced
|
||||
Pass html5lib_webkit02.html 84467597648753feeb78793e2cc9196bc75857c2
|
||||
Pass html5lib_webkit02.html ae6f2e0a014f620269920ceb12660ec708236846
|
||||
Fail html5lib_webkit02.html fd7aea4db6702879b9f8f410b0400d9300ae9c05
|
||||
Fail html5lib_webkit02.html bad5cceffaffe98e3a1522be5f7df3e3e179d500
|
||||
Pass html5lib_webkit02.html fd7aea4db6702879b9f8f410b0400d9300ae9c05
|
||||
Pass html5lib_webkit02.html bad5cceffaffe98e3a1522be5f7df3e3e179d500
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
let reactions = [];
|
||||
customElements.define("parser-insertion-reaction", class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
reactions.push("constructor");
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
reactions.push("connected");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<parser-insertion-reaction></parser-insertion-reaction>
|
||||
<script>
|
||||
let reactionsAfterParserInsertion = reactions.join(",");
|
||||
test(() => {
|
||||
println(`after parser insertion: ${reactionsAfterParserInsertion}`);
|
||||
println(`at DOMContentLoaded: ${reactions.join(",")}`);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
let events = [];
|
||||
customElements.define("reentrant-writer", class extends HTMLElement {
|
||||
connectedCallback() {
|
||||
events.push("connected");
|
||||
document.write("<span id='written'>written</span>");
|
||||
}
|
||||
});
|
||||
document.write("<reentrant-writer id='host'></reentrant-writer>");
|
||||
</script>
|
||||
<script>
|
||||
test(() => {
|
||||
let host = document.getElementById("host");
|
||||
let written = document.getElementById("written");
|
||||
println(events.join(","));
|
||||
println(`written parent: ${written.parentElement.localName}`);
|
||||
println(`host child count: ${host.childNodes.length}`);
|
||||
let bodyChildren = Array.from(document.body.children)
|
||||
.filter(element => element.id !== "out")
|
||||
.map(element => element.localName);
|
||||
println(`body children: ${bodyChildren.join(",")}`);
|
||||
});
|
||||
</script>
|
||||
18
Tests/LibWeb/Text/input/HTML/document-write-after-close.html
Normal file
18
Tests/LibWeb/Text/input/HTML/document-write-after-close.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
let iframe = document.createElement("iframe");
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
let frameDocument = iframe.contentDocument;
|
||||
frameDocument.open();
|
||||
frameDocument.write("<p id=closed>closed</p>");
|
||||
frameDocument.close();
|
||||
frameDocument.write("<span id=after>after</span>");
|
||||
|
||||
let reopened = frameDocument.getElementById("closed") === null
|
||||
&& frameDocument.getElementById("after") !== null;
|
||||
println(`document.write after document.close reopens document: ${reopened}`);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE quirks>
|
||||
<script src="../include.js"></script>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
test(() => {
|
||||
println(document.compatMode);
|
||||
container.innerHTML = "<p><table><tr><td>cell</td></tr></table>";
|
||||
println(container.innerHTML);
|
||||
});
|
||||
</script>
|
||||
19
Tests/LibWeb/Text/input/HTML/parser-script-source-lines.html
Normal file
19
Tests/LibWeb/Text/input/HTML/parser-script-source-lines.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
removeTestErrorHandler();
|
||||
let labels = ["HTML", "SVG"];
|
||||
let lines = [];
|
||||
window.onerror = (message, source, lineno) => {
|
||||
lines.push(`${labels[lines.length]} split start tag line: ${lineno}`);
|
||||
return true;
|
||||
};
|
||||
test(() => {
|
||||
for (let line of lines)
|
||||
println(line);
|
||||
});
|
||||
</script>
|
||||
<script
|
||||
>throw new Error("HTML split start tag");</script>
|
||||
<svg><script
|
||||
>throw new Error("SVG split start tag");</script></svg>
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
|
||||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||||
|
||||
let container = document.createElement("div");
|
||||
container.innerHTML = "<option id=option><annotation-xml><img></option><p id=after></p>";
|
||||
let option = container.querySelector("#option");
|
||||
let after = container.querySelector("#after");
|
||||
println(`option end tag closes option before p: ${after.parentElement === container && !option.contains(after)}`);
|
||||
|
||||
container.innerHTML = "<optgroup id=optgroup><annotation-xml><img></optgroup><p id=after></p>";
|
||||
let optgroup = container.querySelector("#optgroup");
|
||||
after = container.querySelector("#after");
|
||||
println(`optgroup end tag closes optgroup before p: ${after.parentElement === container && !optgroup.contains(after)}`);
|
||||
|
||||
container.innerHTML = "<form id=form><math><annotation-xml></form><b id=bold>bold</b>";
|
||||
let form = container.querySelector("#form");
|
||||
let bold = container.querySelector("#bold");
|
||||
println(`MathML annotation-xml keeps breakout HTML inside form: ${bold.closest("form") === form}`);
|
||||
|
||||
let annotation = document.createElementNS(MATHML_NS, "annotation-xml");
|
||||
annotation.setAttribute("encoding", "text/html");
|
||||
annotation.innerHTML = "<span></span>";
|
||||
println(`annotation-xml innerHTML child namespace: ${annotation.firstElementChild.namespaceURI === HTML_NS}`);
|
||||
|
||||
let namespacedAnnotation = document.createElementNS(MATHML_NS, "annotation-xml");
|
||||
namespacedAnnotation.setAttributeNS("urn:ladybird:test", "x:encoding", "text/html");
|
||||
namespacedAnnotation.innerHTML = "<foo></foo>";
|
||||
println(`namespaced annotation-xml encoding attribute stays foreign: ${namespacedAnnotation.firstElementChild.namespaceURI === MATHML_NS}`);
|
||||
|
||||
let namespaceOnlyAnnotation = document.createElementNS(MATHML_NS, "annotation-xml");
|
||||
namespaceOnlyAnnotation.setAttributeNS("urn:ladybird:test", "encoding", "text/html");
|
||||
namespaceOnlyAnnotation.innerHTML = "<foo></foo>";
|
||||
println(`namespace-only annotation-xml encoding attribute stays foreign: ${namespaceOnlyAnnotation.firstElementChild.namespaceURI === MATHML_NS}`);
|
||||
|
||||
const CUSTOM_NS = "urn:ladybird:test";
|
||||
let custom = document.createElementNS(CUSTOM_NS, "x");
|
||||
custom.innerHTML = "<y></y>";
|
||||
println(`custom namespace innerHTML child namespace: ${custom.firstElementChild.namespaceURI === CUSTOM_NS}`);
|
||||
|
||||
let template = document.createElement("template");
|
||||
template.innerHTML = "<tr><td id=template-cell>x</td></tr>";
|
||||
let templateCell = template.content.querySelector("#template-cell");
|
||||
println(`template innerHTML starts in template mode for table rows: ${templateCell?.parentElement?.localName === "tr" && templateCell.textContent === "x"}`);
|
||||
|
||||
let formFragmentContext = document.createElement("form");
|
||||
formFragmentContext.innerHTML = "<form id=inner-form><input id=form-input></form><span id=form-after></span>";
|
||||
let formInput = formFragmentContext.querySelector("#form-input");
|
||||
let formAfter = formFragmentContext.querySelector("#form-after");
|
||||
println(`form innerHTML uses context as form pointer: ${formFragmentContext.querySelector("#inner-form") === null && formInput?.parentElement === formFragmentContext && formAfter?.parentElement === formFragmentContext}`);
|
||||
|
||||
let range = document.createRange();
|
||||
range.selectNodeContents(annotation);
|
||||
let fragment = range.createContextualFragment("<span></span>");
|
||||
println(`annotation-xml contextual fragment child namespace: ${fragment.firstElementChild.namespaceURI === HTML_NS}`);
|
||||
|
||||
window.htmlFragmentScriptRan = false;
|
||||
range.selectNodeContents(document.body);
|
||||
fragment = range.createContextualFragment("<script>window.htmlFragmentScriptRan = true;<\/script>");
|
||||
document.body.appendChild(fragment);
|
||||
println(`HTML script from contextual fragment executes when applied: ${window.htmlFragmentScriptRan}`);
|
||||
document.body.lastElementChild.remove();
|
||||
|
||||
window.svgFragmentScriptRan = false;
|
||||
range.selectNodeContents(document.body);
|
||||
fragment = range.createContextualFragment("<svg><script>window.svgFragmentScriptRan = true;<\/script></svg>");
|
||||
document.body.appendChild(fragment);
|
||||
println(`SVG script from contextual fragment executes when applied: ${window.svgFragmentScriptRan}`);
|
||||
document.body.lastElementChild.remove();
|
||||
|
||||
let svgTitle = document.createElementNS(SVG_NS, "title");
|
||||
svgTitle.innerHTML = "<div id=svg-title-div></div>";
|
||||
let svgTitleDiv = svgTitle.querySelector("#svg-title-div");
|
||||
println(`SVG title innerHTML tokenizes in data state: ${svgTitleDiv?.namespaceURI === HTML_NS && svgTitle.childNodes.length === 1}`);
|
||||
|
||||
svgTitle.innerHTML = "</title>X";
|
||||
println(`SVG title innerHTML treats title end tag as markup: ${svgTitle.childNodes.length === 1 && svgTitle.textContent === "X"}`);
|
||||
|
||||
range.selectNodeContents(svgTitle);
|
||||
fragment = range.createContextualFragment("<figure></figure>");
|
||||
println(`SVG title contextual fragment creates HTML child: ${fragment.firstElementChild?.namespaceURI === HTML_NS && fragment.firstElementChild?.localName === "figure"}`);
|
||||
|
||||
let svgDesc = document.createElementNS(SVG_NS, "desc");
|
||||
svgDesc.innerHTML = "<frameset>X";
|
||||
println(`SVG desc innerHTML ignores frameset start tag: ${svgDesc.childNodes.length === 1 && svgDesc.textContent === "X"}`);
|
||||
|
||||
let prefixedSvgDesc = document.createElementNS(SVG_NS, "svg:desc");
|
||||
prefixedSvgDesc.innerHTML = "<frameset>X";
|
||||
println(`prefixed SVG desc innerHTML ignores frameset start tag: ${prefixedSvgDesc.childNodes.length === 1 && prefixedSvgDesc.textContent === "X"}`);
|
||||
|
||||
for (const tagName of ["param", "source", "track"]) {
|
||||
container.innerHTML = `<p><b>text</p><${tagName} id=void-element><span>after`;
|
||||
let voidElement = container.querySelector("#void-element");
|
||||
println(`${tagName} start tag skips active formatting reconstruction: ${voidElement.parentElement === container && !voidElement.closest("b")}`);
|
||||
}
|
||||
|
||||
container.innerHTML = "<applet><b><b><b><b></b></b></b><span id=stale></b><p id=after></p>";
|
||||
let stale = container.querySelector("#stale");
|
||||
after = container.querySelector("#after");
|
||||
println(`adoption agency fallback after marker closes stale span: ${!stale.contains(after) && !after.closest("b")}`);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../include.js"></script>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<script>
|
||||
document.write("<span id='from-svg'>svg</span>");
|
||||
</script>
|
||||
</svg>
|
||||
<script>
|
||||
test(() => {
|
||||
let fromSVG = document.getElementById("from-svg");
|
||||
println(`svg write parent: ${fromSVG.parentElement.localName}`);
|
||||
let bodyChildren = Array.from(document.body.children)
|
||||
.filter(element => element.id !== "out")
|
||||
.map(element => element.localName);
|
||||
println(`body children: ${bodyChildren.join(",")}`);
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue