LibWeb: Refactor DOM parsing APIs

Multiple APIs have moved from the DOM Parsing and Serialization spec to
HTML.

Updates spec URLs and comments.

Delete InnerHTML file:
- Make parse_fragment a member of Element, matching serialize_fragment
on Node.
- Move inner_html_setter inline into Element and ShadowRoot as per the
spec.

Add FIXME to Range.idl for Trusted Types createContextualFragment

(cherry picked from commit 9171c3518358cd2d146ffbd7582e4c1247a1daa7)
This commit is contained in:
Luke Warlow 2024-06-25 19:51:18 +01:00 committed by Nico Weber
parent d586afbf73
commit 1739111d17
9 changed files with 119 additions and 153 deletions

View file

@ -1,8 +1,5 @@
source_set("DOMParsing") { source_set("DOMParsing") {
configs += [ "//Userland/Libraries/LibWeb:configs" ] configs += [ "//Userland/Libraries/LibWeb:configs" ]
deps = [ "//Userland/Libraries/LibWeb:all_generated" ] deps = [ "//Userland/Libraries/LibWeb:all_generated" ]
sources = [ sources = [ "XMLSerializer.cpp" ]
"InnerHTML.cpp",
"XMLSerializer.cpp",
]
} }

View file

@ -191,7 +191,6 @@ set(SOURCES
DOM/Text.cpp DOM/Text.cpp
DOM/TreeWalker.cpp DOM/TreeWalker.cpp
DOM/XMLDocument.cpp DOM/XMLDocument.cpp
DOMParsing/InnerHTML.cpp
DOMParsing/XMLSerializer.cpp DOMParsing/XMLSerializer.cpp
DOMURL/DOMURL.cpp DOMURL/DOMURL.cpp
DOMURL/URLSearchParams.cpp DOMURL/URLSearchParams.cpp

View file

@ -28,7 +28,6 @@
#include <LibWeb/DOM/NamedNodeMap.h> #include <LibWeb/DOM/NamedNodeMap.h>
#include <LibWeb/DOM/ShadowRoot.h> #include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOM/Text.h> #include <LibWeb/DOM/Text.h>
#include <LibWeb/DOMParsing/InnerHTML.h>
#include <LibWeb/Geometry/DOMRect.h> #include <LibWeb/Geometry/DOMRect.h>
#include <LibWeb/Geometry/DOMRectList.h> #include <LibWeb/Geometry/DOMRectList.h>
#include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/BrowsingContext.h>
@ -49,6 +48,7 @@
#include <LibWeb/HTML/HTMLSlotElement.h> #include <LibWeb/HTML/HTMLSlotElement.h>
#include <LibWeb/HTML/HTMLStyleElement.h> #include <LibWeb/HTML/HTMLStyleElement.h>
#include <LibWeb/HTML/HTMLTableElement.h> #include <LibWeb/HTML/HTMLTableElement.h>
#include <LibWeb/HTML/HTMLTemplateElement.h>
#include <LibWeb/HTML/HTMLTextAreaElement.h> #include <LibWeb/HTML/HTMLTextAreaElement.h>
#include <LibWeb/HTML/Numbers.h> #include <LibWeb/HTML/Numbers.h>
#include <LibWeb/HTML/Parser/HTMLParser.h> #include <LibWeb/HTML/Parser/HTMLParser.h>
@ -754,13 +754,38 @@ WebIDL::ExceptionOr<DOM::Element const*> Element::closest(StringView selectors)
return nullptr; return nullptr;
} }
WebIDL::ExceptionOr<void> Element::set_inner_html(StringView markup) // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-element-innerhtml
WebIDL::ExceptionOr<void> Element::set_inner_html(StringView value)
{ {
TRY(DOMParsing::inner_html_setter(*this, markup)); // FIXME: 1. Let compliantString be the result of invoking the Get Trusted Type compliant string algorithm with TrustedHTML, this's relevant global object, the given value, "Element innerHTML", and "script".
// 2. Let context be this.
DOM::Node* context = this;
// 3. Let fragment be the result of invoking the fragment parsing algorithm steps with context and compliantString. FIXME: Use compliantString.
auto fragment = TRY(verify_cast<Element>(*context).parse_fragment(value));
// 4. If context is a template element, then set context to the template element's template contents (a DocumentFragment).
if (is<HTML::HTMLTemplateElement>(*context))
context = verify_cast<HTML::HTMLTemplateElement>(*context).content();
// 5. Replace all with fragment within context.
context->replace_all(fragment);
// NOTE: We don't invalidate style & layout for <template> elements since they don't affect rendering.
if (!is<HTML::HTMLTemplateElement>(*context)) {
context->set_needs_style_update(true);
if (context->is_connected()) {
// NOTE: Since the DOM has changed, we have to rebuild the layout tree.
context->document().invalidate_layout();
}
}
return {}; return {};
} }
// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-element-innerhtml
WebIDL::ExceptionOr<String> Element::inner_html() const WebIDL::ExceptionOr<String> Element::inner_html() const
{ {
return serialize_fragment(DOMParsing::RequireWellFormed::Yes); return serialize_fragment(DOMParsing::RequireWellFormed::Yes);
@ -1462,6 +1487,32 @@ bool Element::is_actually_disabled() const
return false; return false;
} }
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-parsing-algorithm-steps
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOM::DocumentFragment>> Element::parse_fragment(StringView markup)
{
// 1. Let algorithm be the HTML fragment parsing algorithm.
auto algorithm = HTML::HTMLParser::parse_html_fragment;
// FIXME: 2. If context's node document is an XML document, then set algorithm to the XML fragment parsing algorithm.
if (document().is_xml_document()) {
dbgln("FIXME: Handle fragment parsing of XML documents");
}
// 3. Let new children be the result of invoking algorithm given markup, with context set to context.
auto new_children = algorithm(*this, markup);
// 4. Let fragment be a new DocumentFragment whose node document is context's node document.
auto fragment = realm().heap().allocate<DOM::DocumentFragment>(realm(), document());
// 5. Append each Node in new children to fragment (in tree order).
for (auto& child : new_children) {
// I don't know if this can throw here, but let's be safe.
(void)TRY(fragment->append_child(*child));
}
return fragment;
}
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-element-outerhtml // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-element-outerhtml
WebIDL::ExceptionOr<String> Element::outer_html() const WebIDL::ExceptionOr<String> Element::outer_html() const
{ {
@ -1471,23 +1522,25 @@ WebIDL::ExceptionOr<String> Element::outer_html() const
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-element-outerhtml // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-element-outerhtml
WebIDL::ExceptionOr<void> Element::set_outer_html(String const& value) WebIDL::ExceptionOr<void> Element::set_outer_html(String const& value)
{ {
// 1. Let parent be this's parent. // 1. FIXME: Let compliantString be the result of invoking the Get Trusted Type compliant string algorithm with TrustedHTML, this's relevant global object, the given value, "Element outerHTML", and "script".
// 2. Let parent be this's parent.
auto* parent = this->parent(); auto* parent = this->parent();
// 2. If parent is null, return. There would be no way to obtain a reference to the nodes created even if the remaining steps were run. // 3. If parent is null, return. There would be no way to obtain a reference to the nodes created even if the remaining steps were run.
if (!parent) if (!parent)
return {}; return {};
// 3. If parent is a Document, throw a "NoModificationAllowedError" DOMException. // 4. If parent is a Document, throw a "NoModificationAllowedError" DOMException.
if (parent->is_document()) if (parent->is_document())
return WebIDL::NoModificationAllowedError::create(realm(), "Cannot set outer HTML on document"_fly_string); return WebIDL::NoModificationAllowedError::create(realm(), "Cannot set outer HTML on document"_fly_string);
// 4. If parent is a DocumentFragment, set parent to the result of creating an element given this's node document, body, and the HTML namespace. // 5. If parent is a DocumentFragment, set parent to the result of creating an element given this's node document, body, and the HTML namespace.
if (parent->is_document_fragment()) if (parent->is_document_fragment())
parent = TRY(create_element(document(), HTML::TagNames::body, Namespace::HTML)); parent = TRY(create_element(document(), HTML::TagNames::body, Namespace::HTML));
// 5. Let fragment be the result of invoking the fragment parsing algorithm steps given parent and the given value. // 6. Let fragment be the result of invoking the fragment parsing algorithm steps given parent and compliantString. FIXME: Use compliantString.
auto fragment = TRY(DOMParsing::parse_fragment(value, verify_cast<Element>(*parent))); auto fragment = TRY(verify_cast<Element>(*parent).parse_fragment(value));
// 6. Replace this with fragment within this's parent. // 6. Replace this with fragment within this's parent.
TRY(parent->replace_child(fragment, *this)); TRY(parent->replace_child(fragment, *this));
@ -1538,7 +1591,7 @@ WebIDL::ExceptionOr<void> Element::insert_adjacent_html(String const& position,
} }
// 4. Let fragment be the result of invoking the fragment parsing algorithm steps with context and string. // 4. Let fragment be the result of invoking the fragment parsing algorithm steps with context and string.
auto fragment = TRY(DOMParsing::parse_fragment(string, verify_cast<Element>(*context))); auto fragment = TRY(verify_cast<Element>(*context).parse_fragment(string));
// 5. Use the first matching item from this list: // 5. Use the first matching item from this list:

View file

@ -184,6 +184,8 @@ public:
CSS::StyleSheetList& document_or_shadow_root_style_sheets(); CSS::StyleSheetList& document_or_shadow_root_style_sheets();
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOM::DocumentFragment>> parse_fragment(StringView markup);
WebIDL::ExceptionOr<String> inner_html() const; WebIDL::ExceptionOr<String> inner_html() const;
WebIDL::ExceptionOr<void> set_inner_html(StringView); WebIDL::ExceptionOr<void> set_inner_html(StringView);

View file

@ -18,7 +18,6 @@
#include <LibWeb/DOM/ProcessingInstruction.h> #include <LibWeb/DOM/ProcessingInstruction.h>
#include <LibWeb/DOM/Range.h> #include <LibWeb/DOM/Range.h>
#include <LibWeb/DOM/Text.h> #include <LibWeb/DOM/Text.h>
#include <LibWeb/DOMParsing/InnerHTML.h>
#include <LibWeb/Geometry/DOMRect.h> #include <LibWeb/Geometry/DOMRect.h>
#include <LibWeb/Geometry/DOMRectList.h> #include <LibWeb/Geometry/DOMRectList.h>
#include <LibWeb/HTML/HTMLHtmlElement.h> #include <LibWeb/HTML/HTMLHtmlElement.h>
@ -1178,57 +1177,48 @@ JS::NonnullGCPtr<Geometry::DOMRect> Range::get_bounding_client_rect() const
return Geometry::DOMRect::construct_impl(realm(), 0, 0, 0, 0).release_value_but_fixme_should_propagate_errors(); return Geometry::DOMRect::construct_impl(realm(), 0, 0, 0, 0).release_value_but_fixme_should_propagate_errors();
} }
// https://w3c.github.io/DOM-Parsing/#dom-range-createcontextualfragment // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-range-createcontextualfragment
WebIDL::ExceptionOr<JS::NonnullGCPtr<DocumentFragment>> Range::create_contextual_fragment(String const& fragment) WebIDL::ExceptionOr<JS::NonnullGCPtr<DocumentFragment>> Range::create_contextual_fragment(String const& string)
{ {
// 1. Let node be the context object's start node. // FIXME: Let compliantString be the result of invoking the Get Trusted Type compliant string algorithm with TrustedHTML, this's relevant global object, string, and "Range createContextualFragment".
// 2. Let node be this's start node.
JS::NonnullGCPtr<Node> node = *start_container(); JS::NonnullGCPtr<Node> node = *start_container();
// Let element be as follows, depending on node's interface: // 3. Let element be null.
JS::GCPtr<Element> element; JS::GCPtr<Element> element = nullptr;
switch (static_cast<NodeType>(node->node_type())) {
case NodeType::DOCUMENT_NODE:
case NodeType::DOCUMENT_FRAGMENT_NODE:
element = nullptr;
break;
case NodeType::ELEMENT_NODE:
element = static_cast<DOM::Element&>(*node);
break;
case NodeType::TEXT_NODE:
case NodeType::COMMENT_NODE:
element = node->parent_element();
break;
case NodeType::DOCUMENT_TYPE_NODE:
case NodeType::PROCESSING_INSTRUCTION_NODE:
// [DOM4] prevents this case.
VERIFY_NOT_REACHED();
default:
VERIFY_NOT_REACHED();
}
// 2. If either element is null or the following are all true: auto node_type = static_cast<NodeType>(node->node_type());
// 4. If node implements Element, set element to node.
if (node_type == NodeType::ELEMENT_NODE)
element = static_cast<DOM::Element&>(*node);
// 5. Otherwise, if node implements Text or Comment node, set element to node's parent element.
else if (first_is_one_of(node_type, NodeType::TEXT_NODE, NodeType::COMMENT_NODE))
element = node->parent_element();
// 6. If either element is null or all of the following are true:
// - element's node document is an HTML document, // - element's node document is an HTML document,
// - element's local name is "html", and // - element's local name is "html"; and
// - element's namespace is the HTML namespace; // - element's namespace is the HTML namespace;
if (!element || is<HTML::HTMLHtmlElement>(*element)) { if (!element || is<HTML::HTMLHtmlElement>(*element)) {
// let element be a new Element with // then set element to the result of creating an element given this's node document,
// - "body" as its local name, // body, and the HTML namespace.
// - The HTML namespace as its namespace, and
// - The context object's node document as its node document.
element = TRY(DOM::create_element(node->document(), HTML::TagNames::body, Namespace::HTML)); element = TRY(DOM::create_element(node->document(), HTML::TagNames::body, Namespace::HTML));
} }
// 3. Let fragment node be the result of invoking the fragment parsing algorithm with fragment as markup, and element as the context element. // 7. Let fragment node be the result of invoking the fragment parsing algorithm steps with element and compliantString. FIXME: Use compliantString.
auto fragment_node = TRY(DOMParsing::parse_fragment(fragment, *element)); auto fragment_node = TRY(element->parse_fragment(string));
// 4. Unmark all scripts in fragment node as "already started" and as "parser-inserted". // 8. For each script of fragment node's script element descendants:
fragment_node->for_each_in_subtree_of_type<HTML::HTMLScriptElement>([&](HTML::HTMLScriptElement& script_element) { fragment_node->for_each_in_subtree_of_type<HTML::HTMLScriptElement>([&](HTML::HTMLScriptElement& script_element) {
// 8.1 Set scripts already started to false.
script_element.unmark_as_already_started({}); script_element.unmark_as_already_started({});
// 8.2 Set scripts parser document to null.
script_element.unmark_as_parser_inserted({}); script_element.unmark_as_parser_inserted({});
return TraversalDecision::Continue; return TraversalDecision::Continue;
}); });
// 5. Return the value of fragment node. // 5. Return fragment node.
return fragment_node; return fragment_node;
} }

View file

@ -45,7 +45,8 @@ interface Range : AbstractRange {
stringifier; stringifier;
// Extensions from the DOM Parsing specification: // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-range-createcontextualfragment
[CEReactions, NewObject] DocumentFragment createContextualFragment(DOMString fragment); // FIXME: [CEReactions, NewObject] DocumentFragment createContextualFragment((TrustedHTML or DOMString) string);
[CEReactions, NewObject] DocumentFragment createContextualFragment(DOMString string);
}; };

View file

@ -9,7 +9,7 @@
#include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h> #include <LibWeb/DOM/Event.h>
#include <LibWeb/DOM/ShadowRoot.h> #include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOMParsing/InnerHTML.h> #include <LibWeb/HTML/HTMLTemplateElement.h>
#include <LibWeb/HTML/Parser/HTMLParser.h> #include <LibWeb/HTML/Parser/HTMLParser.h>
#include <LibWeb/Layout/BlockContainer.h> #include <LibWeb/Layout/BlockContainer.h>
@ -61,16 +61,35 @@ EventTarget* ShadowRoot::get_parent(Event const& event)
return host(); return host();
} }
// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-shadowroot-innerhtml
WebIDL::ExceptionOr<String> ShadowRoot::inner_html() const WebIDL::ExceptionOr<String> ShadowRoot::inner_html() const
{ {
return serialize_fragment(DOMParsing::RequireWellFormed::Yes); return serialize_fragment(DOMParsing::RequireWellFormed::Yes);
} }
// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-shadowroot-innerhtml
WebIDL::ExceptionOr<void> ShadowRoot::set_inner_html(StringView markup) WebIDL::ExceptionOr<void> ShadowRoot::set_inner_html(StringView value)
{ {
TRY(DOMParsing::inner_html_setter(*this, markup)); // FIXME: 1. Let compliantString be the result of invoking the Get Trusted Type compliant string algorithm with TrustedHTML, this's relevant global object, the given value, "ShadowRoot innerHTML", and "script".
// 2. Let context be this's host.
auto context = this->host();
// 3. Let fragment be the result of invoking the fragment parsing algorithm steps with context and compliantString. FIXME: Use compliantString instead of markup.
auto fragment = TRY(verify_cast<Element>(*context).parse_fragment(value));
// 4. Replace all with fragment within this.
this->replace_all(fragment);
// NOTE: We don't invalidate style & layout for <template> elements since they don't affect rendering.
if (!is<HTML::HTMLTemplateElement>(*this)) {
this->set_needs_style_update(true);
if (this->is_connected()) {
// NOTE: Since the DOM has changed, we have to rebuild the layout tree.
this->document().invalidate_layout();
}
}
set_needs_style_update(true); set_needs_style_update(true);
return {}; return {};

View file

@ -1,74 +0,0 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Heap/Heap.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentFragment.h>
#include <LibWeb/DOMParsing/InnerHTML.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::DOMParsing {
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-parsing-algorithm-steps
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOM::DocumentFragment>> parse_fragment(StringView markup, DOM::Element& context_element)
{
auto& realm = context_element.realm();
// 1. Let algorithm be the HTML fragment parsing algorithm.
auto algorithm = HTML::HTMLParser::parse_html_fragment;
// FIXME: 2. If context's node document is an XML document, then set algorithm to the XML fragment parsing algorithm.
if (context_element.document().is_xml_document()) {
dbgln("FIXME: Handle fragment parsing of XML documents");
}
// 3. Let new children be the result of invoking algorithm given markup, with context set to context.
auto new_children = algorithm(context_element, markup);
// 4. Let fragment be a new DocumentFragment whose node document is context's node document.
auto fragment = realm.heap().allocate<DOM::DocumentFragment>(realm, context_element.document());
// 5. Append each Node in new children to fragment (in tree order).
for (auto& child : new_children) {
// I don't know if this can throw here, but let's be safe.
(void)TRY(fragment->append_child(*child));
}
return fragment;
}
// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml
WebIDL::ExceptionOr<void> inner_html_setter(JS::NonnullGCPtr<DOM::Node> context_object, StringView value)
{
// 1. Let context element be the context object's host if the context object is a ShadowRoot object, or the context object otherwise.
// (This is handled in Element and ShadowRoot)
JS::NonnullGCPtr<DOM::Element> context_element = is<DOM::ShadowRoot>(*context_object) ? *verify_cast<DOM::ShadowRoot>(*context_object).host() : verify_cast<DOM::Element>(*context_object);
// 2. Let fragment be the result of invoking the fragment parsing algorithm with the new value as markup, and with context element.
auto fragment = TRY(parse_fragment(value, context_element));
// 3. If the context object is a template element, then let context object be the template's template contents (a DocumentFragment).
if (is<HTML::HTMLTemplateElement>(*context_object))
context_object = verify_cast<HTML::HTMLTemplateElement>(*context_object).content();
// 4. Replace all with fragment within the context object.
context_object->replace_all(fragment);
// NOTE: We don't invalidate style & layout for <template> elements since they don't affect rendering.
if (!is<HTML::HTMLTemplateElement>(*context_object)) {
context_object->set_needs_style_update(true);
if (context_object->is_connected()) {
// NOTE: Since the DOM has changed, we have to rebuild the layout tree.
context_object->document().invalidate_layout();
}
}
return {};
}
}

View file

@ -1,21 +0,0 @@
/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/HTMLTemplateElement.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::DOMParsing {
// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml
WebIDL::ExceptionOr<void> inner_html_setter(JS::NonnullGCPtr<DOM::Node> context_object, StringView value);
WebIDL::ExceptionOr<JS::NonnullGCPtr<DOM::DocumentFragment>> parse_fragment(StringView markup, DOM::Element& context_element);
}