mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 13:36:13 +03:00
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:
parent
55e1ab88ad
commit
9171c35183
Notes:
sideshowbarker
2024-07-17 08:42:05 +09:00
Author: https://github.com/lukewarlow Commit: https://github.com/LadybirdBrowser/ladybird/commit/9171c35183 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/280
@ -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" ]
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
};
|
||||
|
@ -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 {};
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user