diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index 9f420bb5c1..583689cfbc 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -244,16 +245,15 @@ NonnullRefPtr Element::computed_style() return properties; } -void Element::set_inner_html(StringView markup) +ExceptionOr Element::set_inner_html(String const& markup) { - auto new_children = HTML::HTMLDocumentParser::parse_html_fragment(*this, markup); - remove_all_children(); - while (!new_children.is_empty()) { - append_child(new_children.take_first()); - } + auto result = DOMParsing::InnerHTML::inner_html_setter(*this, markup); + if (result.is_exception()) + return result.exception(); set_needs_style_update(true); document().invalidate_layout(); + return {}; } // https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index 4d54e2ab57..e1eb9369e4 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -81,9 +81,8 @@ public: NonnullRefPtr style_for_bindings(); - // FIXME: innerHTML also appears on shadow roots. https://w3c.github.io/DOM-Parsing/#dom-innerhtml String inner_html() const; - void set_inner_html(StringView); + ExceptionOr set_inner_html(String const&); bool is_focused() const; virtual bool is_focusable() const { return false; } diff --git a/Userland/Libraries/LibWeb/DOM/Element.idl b/Userland/Libraries/LibWeb/DOM/Element.idl index 536c4120e9..031e8d94d4 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.idl +++ b/Userland/Libraries/LibWeb/DOM/Element.idl @@ -13,7 +13,9 @@ interface Element : Node { HTMLCollection getElementsByTagName(DOMString tagName); HTMLCollection getElementsByClassName(DOMString className); - [LegacyNullToEmptyString] attribute DOMString innerHTML; + // FIXME: This should come from a InnerHTML mixin. + [LegacyNullToEmptyString, CEReactions] attribute DOMString innerHTML; + [Reflect] attribute DOMString id; [Reflect=class] attribute DOMString className; diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.cpp b/Userland/Libraries/LibWeb/DOM/ShadowRoot.cpp index 7043ce8242..675a8a2df2 100644 --- a/Userland/Libraries/LibWeb/DOM/ShadowRoot.cpp +++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.cpp @@ -4,8 +4,10 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include +#include #include namespace Web::DOM { @@ -33,4 +35,22 @@ RefPtr ShadowRoot::create_layout_node() return adopt_ref(*new Layout::BlockBox(document(), this, CSS::ComputedValues {})); } +// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml +String ShadowRoot::inner_html() const +{ + return serialize_fragment(/* FIXME: Providing true for the require well-formed flag (which may throw) */); +} + +// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml +ExceptionOr ShadowRoot::set_inner_html(String const& markup) +{ + auto result = DOMParsing::InnerHTML::inner_html_setter(*this, markup); + if (result.is_exception()) + return result.exception(); + + set_needs_style_update(true); + document().invalidate_layout(); + return {}; +} + } diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h index 22cc1a1db7..12532a8eda 100644 --- a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h +++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h @@ -28,6 +28,9 @@ public: // NOTE: This is intended for the JS bindings. String mode() const { return m_closed ? "closed" : "open"; } + String inner_html() const; + ExceptionOr set_inner_html(String const&); + private: // ^Node virtual FlyString node_name() const override { return "#shadow-root"; } diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.idl b/Userland/Libraries/LibWeb/DOM/ShadowRoot.idl index 320ca53e80..ea228c9aa8 100644 --- a/Userland/Libraries/LibWeb/DOM/ShadowRoot.idl +++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.idl @@ -3,4 +3,7 @@ interface ShadowRoot : DocumentFragment { readonly attribute DOMString mode; readonly attribute Element host; + // FIXME: This should come from a InnerHTML mixin. + [LegacyNullToEmptyString] attribute DOMString innerHTML; + }; diff --git a/Userland/Libraries/LibWeb/DOMParsing/Algorithms.h b/Userland/Libraries/LibWeb/DOMParsing/Algorithms.h new file mode 100644 index 0000000000..0a04654bba --- /dev/null +++ b/Userland/Libraries/LibWeb/DOMParsing/Algorithms.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::DOMParsing { + +// https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm +static DOM::ExceptionOr> parse_fragment(String const& markup, DOM::Element& context_element) +{ + // FIXME: Handle XML documents. + + auto new_children = HTML::HTMLDocumentParser::parse_html_fragment(context_element, markup); + auto fragment = make_ref_counted(context_element.document()); + + for (auto& child : new_children) { + // I don't know if this can throw here, but let's be safe. + auto result = fragment->append_child(child); + if (result.is_exception()) + return result.exception(); + } + + return fragment; +} + +} diff --git a/Userland/Libraries/LibWeb/DOMParsing/InnerHTML.h b/Userland/Libraries/LibWeb/DOMParsing/InnerHTML.h new file mode 100644 index 0000000000..ef95a21e4d --- /dev/null +++ b/Userland/Libraries/LibWeb/DOMParsing/InnerHTML.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::DOMParsing::InnerHTML { + +// https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml +static DOM::ExceptionOr inner_html_setter(NonnullRefPtr context_object, String const& 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) + NonnullRefPtr context_element = is(*context_object) ? *verify_cast(*context_object).host() : verify_cast(*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_or_exception = parse_fragment(value, context_element); + if (fragment_or_exception.is_exception()) + return fragment_or_exception.exception(); + auto fragment = fragment_or_exception.release_value(); + + // 3. If the context object is a template element, then let context object be the template's template contents (a DocumentFragment). + if (is(*context_object)) + context_object = verify_cast(*context_object).content(); + + // 4. Replace all with fragment within the context object. + context_object->replace_all(fragment); + + return {}; +} + +}