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
This commit is contained in:
Luke Warlow 2024-06-25 19:51:18 +01:00 committed by Andreas Kling
parent 55e1ab88ad
commit 9171c35183
Notes: sideshowbarker 2024-07-17 08:42:05 +09:00
9 changed files with 119 additions and 153 deletions

View file

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

View file

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

View file

@ -28,7 +28,6 @@
#include <LibWeb/DOM/NamedNodeMap.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/DOMParsing/InnerHTML.h>
#include <LibWeb/Geometry/DOMRect.h>
#include <LibWeb/Geometry/DOMRectList.h>
#include <LibWeb/HTML/BrowsingContext.h>
@ -49,6 +48,7 @@
#include <LibWeb/HTML/HTMLSlotElement.h>
#include <LibWeb/HTML/HTMLStyleElement.h>
#include <LibWeb/HTML/HTMLTableElement.h>
#include <LibWeb/HTML/HTMLTemplateElement.h>
#include <LibWeb/HTML/HTMLTextAreaElement.h>
#include <LibWeb/HTML/Numbers.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
@ -754,13 +754,38 @@ WebIDL::ExceptionOr<DOM::Element const*> Element::closest(StringView selectors)
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 {};
}
// 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
{
return serialize_fragment(DOMParsing::RequireWellFormed::Yes);
@ -1462,6 +1487,32 @@ bool Element::is_actually_disabled() const
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
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
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();
// 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)
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())
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())
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.
auto fragment = TRY(DOMParsing::parse_fragment(value, verify_cast<Element>(*parent)));
// 6. Let fragment be the result of invoking the fragment parsing algorithm steps given parent and compliantString. FIXME: Use compliantString.
auto fragment = TRY(verify_cast<Element>(*parent).parse_fragment(value));
// 6. Replace this with fragment within this's parent.
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.
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:

View file

@ -184,6 +184,8 @@ public:
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<void> set_inner_html(StringView);

View file

@ -18,7 +18,6 @@
#include <LibWeb/DOM/ProcessingInstruction.h>
#include <LibWeb/DOM/Range.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/DOMParsing/InnerHTML.h>
#include <LibWeb/Geometry/DOMRect.h>
#include <LibWeb/Geometry/DOMRectList.h>
#include <LibWeb/HTML/HTMLHtmlElement.h>
@ -1179,57 +1178,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();
}
// https://w3c.github.io/DOM-Parsing/#dom-range-createcontextualfragment
WebIDL::ExceptionOr<JS::NonnullGCPtr<DocumentFragment>> Range::create_contextual_fragment(String const& fragment)
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-range-createcontextualfragment
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();
// Let element be as follows, depending on node's interface:
JS::GCPtr<Element> element;
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();
}
// 3. Let element be null.
JS::GCPtr<Element> element = nullptr;
// 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 local name is "html", and
// - element's local name is "html"; and
// - element's namespace is the HTML namespace;
if (!element || is<HTML::HTMLHtmlElement>(*element)) {
// let element be a new Element with
// - "body" as its local name,
// - The HTML namespace as its namespace, and
// - The context object's node document as its node document.
// then set element to the result of creating an element given this's node document,
// body, and the HTML namespace.
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.
auto fragment_node = TRY(DOMParsing::parse_fragment(fragment, *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(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) {
// 8.1 Set scripts already started to false.
script_element.unmark_as_already_started({});
// 8.2 Set scripts parser document to null.
script_element.unmark_as_parser_inserted({});
return TraversalDecision::Continue;
});
// 5. Return the value of fragment node.
// 5. Return fragment node.
return fragment_node;
}

View file

@ -45,7 +45,8 @@ interface Range : AbstractRange {
stringifier;
// Extensions from the DOM Parsing specification:
[CEReactions, NewObject] DocumentFragment createContextualFragment(DOMString fragment);
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-range-createcontextualfragment
// 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/Event.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOMParsing/InnerHTML.h>
#include <LibWeb/HTML/HTMLTemplateElement.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
#include <LibWeb/Layout/BlockContainer.h>
@ -61,16 +61,35 @@ EventTarget* ShadowRoot::get_parent(Event const& event)
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
{
return serialize_fragment(DOMParsing::RequireWellFormed::Yes);
}
// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml
WebIDL::ExceptionOr<void> ShadowRoot::set_inner_html(StringView markup)
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-shadowroot-innerhtml
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);
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);
}