mirror of
https://github.com/RGBCube/serenity
synced 2025-10-24 18:52:35 +00:00
Currently, every public DOM::Element method which changes an attribute
fires this handler itself. This was missed in commit 720f7ba, so any
user of that API would not fire the internal handler.
To fix this, and prevent any missing invocations in the future, invoke
the handler from from Attr::handle_attribute_changes. This method is
reached for all attribute changes, including adding/removing attributes.
This ensures the handler will always be fired, and reduces the footprint
of this ad-hoc behavior.
Note that our ad-hoc handler is not the "attribute change steps" noted
by the spec. Those are overridden only by a couple of specific elements,
e.g. HTMLSlotElement. However, we could easily make our ad-hoc handler
hook into those steps in the future.
117 lines
4.3 KiB
C++
117 lines
4.3 KiB
C++
/*
|
||
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
|
||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <LibWeb/Bindings/Intrinsics.h>
|
||
#include <LibWeb/DOM/Attr.h>
|
||
#include <LibWeb/DOM/Document.h>
|
||
#include <LibWeb/DOM/Element.h>
|
||
#include <LibWeb/DOM/MutationType.h>
|
||
#include <LibWeb/DOM/StaticNodeList.h>
|
||
#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
|
||
|
||
namespace Web::DOM {
|
||
|
||
JS::NonnullGCPtr<Attr> Attr::create(Document& document, DeprecatedFlyString local_name, DeprecatedString value, Element* owner_element)
|
||
{
|
||
return document.heap().allocate<Attr>(document.realm(), document, QualifiedName(move(local_name), {}, {}), move(value), owner_element);
|
||
}
|
||
|
||
JS::NonnullGCPtr<Attr> Attr::create(Document& document, QualifiedName qualified_name, DeprecatedString value, Element* owner_element)
|
||
{
|
||
return document.heap().allocate<Attr>(document.realm(), document, move(qualified_name), move(value), owner_element);
|
||
}
|
||
|
||
JS::NonnullGCPtr<Attr> Attr::clone(Document& document)
|
||
{
|
||
return *heap().allocate<Attr>(realm(), document, m_qualified_name, m_value, nullptr);
|
||
}
|
||
|
||
Attr::Attr(Document& document, QualifiedName qualified_name, DeprecatedString value, Element* owner_element)
|
||
: Node(document, NodeType::ATTRIBUTE_NODE)
|
||
, m_qualified_name(move(qualified_name))
|
||
, m_value(move(value))
|
||
, m_owner_element(owner_element)
|
||
{
|
||
}
|
||
|
||
void Attr::initialize(JS::Realm& realm)
|
||
{
|
||
Base::initialize(realm);
|
||
set_prototype(&Bindings::ensure_web_prototype<Bindings::AttrPrototype>(realm, "Attr"));
|
||
}
|
||
|
||
void Attr::visit_edges(Cell::Visitor& visitor)
|
||
{
|
||
Base::visit_edges(visitor);
|
||
visitor.visit(m_owner_element.ptr());
|
||
}
|
||
|
||
Element* Attr::owner_element()
|
||
{
|
||
return m_owner_element.ptr();
|
||
}
|
||
|
||
Element const* Attr::owner_element() const
|
||
{
|
||
return m_owner_element.ptr();
|
||
}
|
||
|
||
void Attr::set_owner_element(Element* owner_element)
|
||
{
|
||
m_owner_element = owner_element;
|
||
}
|
||
|
||
// https://dom.spec.whatwg.org/#set-an-existing-attribute-value
|
||
void Attr::set_value(DeprecatedString value)
|
||
{
|
||
// 1. If attribute’s element is null, then set attribute’s value to value.
|
||
if (!owner_element()) {
|
||
m_value = move(value);
|
||
}
|
||
// 2. Otherwise, change attribute to value.
|
||
else {
|
||
change_attribute(move(value));
|
||
}
|
||
}
|
||
|
||
// https://dom.spec.whatwg.org/#concept-element-attributes-change
|
||
void Attr::change_attribute(DeprecatedString value)
|
||
{
|
||
// 1. Let oldValue be attribute’s value.
|
||
auto old_value = move(m_value);
|
||
|
||
// 2. Set attribute’s value to value.
|
||
m_value = move(value);
|
||
|
||
// 3. Handle attribute changes for attribute with attribute’s element, oldValue, and value.
|
||
handle_attribute_changes(*owner_element(), old_value, m_value);
|
||
}
|
||
|
||
// https://dom.spec.whatwg.org/#handle-attribute-changes
|
||
void Attr::handle_attribute_changes(Element& element, DeprecatedString const& old_value, DeprecatedString const& new_value)
|
||
{
|
||
// 1. Queue a mutation record of "attributes" for element with attribute’s local name, attribute’s namespace, oldValue, « », « », null, and null.
|
||
element.queue_mutation_record(MutationType::attributes, local_name(), namespace_uri(), old_value, {}, {}, nullptr, nullptr);
|
||
|
||
// 2. If element is custom, then enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attribute’s local name, oldValue, newValue, and attribute’s namespace.
|
||
if (element.is_custom()) {
|
||
auto& vm = this->vm();
|
||
|
||
JS::MarkedVector<JS::Value> arguments { vm.heap() };
|
||
arguments.append(JS::PrimitiveString::create(vm, local_name()));
|
||
arguments.append(old_value.is_null() ? JS::js_null() : JS::PrimitiveString::create(vm, old_value));
|
||
arguments.append(new_value.is_null() ? JS::js_null() : JS::PrimitiveString::create(vm, new_value));
|
||
arguments.append(namespace_uri().is_null() ? JS::js_null() : JS::PrimitiveString::create(vm, namespace_uri()));
|
||
|
||
element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::attributeChangedCallback, move(arguments));
|
||
}
|
||
|
||
// 3. Run the attribute change steps with element, attribute’s local name, oldValue, newValue, and attribute’s namespace.
|
||
element.run_attribute_change_steps(local_name(), old_value, new_value, namespace_uri());
|
||
}
|
||
|
||
}
|