LibWeb: Forward non-synthetic pseudo-elt accesses to relevant element
This adds a new class `ElementReferencePseudoElement` which forwards any accesses to the referenced element for "element-reference" pseudo elements. This allows us to, for instance: - Access the underlying `ComputedProperties` in getComputedStyle, which includes inline style. - Store and access `CustomPropertyData` - Access the layout node for "resolved value" computation - Apply animations from `update_animated_style_if_needed` We register these with the originating element when calling `set_associated_shadow_host_pseudo_element`, this requires us to append the element to the tree beforehand since we need to be able to get the shadow host.
This commit is contained in:
parent
507380d8c5
commit
199e82fc73
18 changed files with 210 additions and 17 deletions
|
|
@ -3568,6 +3568,21 @@ void Element::set_computed_properties(Optional<CSS::PseudoElement> pseudo_elemen
|
|||
computed_properties_changed();
|
||||
}
|
||||
|
||||
void Element::set_associated_shadow_host_pseudo_element(CSS::PseudoElement type)
|
||||
{
|
||||
VERIFY(CSS::is_element_reference_pseudo_element(type));
|
||||
|
||||
auto& root = this->root();
|
||||
VERIFY(is<ShadowRoot>(root));
|
||||
|
||||
auto& shadow_root = as<ShadowRoot>(root);
|
||||
VERIFY(shadow_root.host());
|
||||
|
||||
shadow_root.host()->register_element_reference_pseudo_element(type, *this);
|
||||
|
||||
m_associated_shadow_host_pseudo_element = type;
|
||||
}
|
||||
|
||||
Optional<SyntheticPseudoElement&> Element::get_synthetic_pseudo_element(CSS::PseudoElement type) const
|
||||
{
|
||||
VERIFY(is_synthetic_pseudo_element(type));
|
||||
|
|
@ -3596,6 +3611,25 @@ Optional<PseudoElement&> Element::get_pseudo_element(CSS::PseudoElement type) co
|
|||
return *(pseudo_element.value());
|
||||
}
|
||||
|
||||
void Element::register_element_reference_pseudo_element(CSS::PseudoElement type, GC::Ref<Element> element)
|
||||
{
|
||||
VERIFY(CSS::is_element_reference_pseudo_element(type));
|
||||
|
||||
if (!m_pseudo_element_data)
|
||||
m_pseudo_element_data = make<PseudoElementData>();
|
||||
|
||||
m_pseudo_element_data->set(type, heap().allocate<ElementReferencePseudoElement>(element));
|
||||
}
|
||||
|
||||
void Element::clear_element_reference_pseudo_elements()
|
||||
{
|
||||
if (!m_pseudo_element_data)
|
||||
return;
|
||||
|
||||
for (auto i = to_underlying(CSS::first_element_reference_pseudo_element); i <= to_underlying(CSS::last_element_reference_pseudo_element); ++i)
|
||||
m_pseudo_element_data->remove(static_cast<CSS::PseudoElement>(i));
|
||||
}
|
||||
|
||||
SyntheticPseudoElement& Element::ensure_synthetic_pseudo_element(CSS::PseudoElement type) const
|
||||
{
|
||||
if (!m_pseudo_element_data)
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ public:
|
|||
CSS::RequiredInvalidationAfterStyleChange recompute_inherited_style();
|
||||
|
||||
Optional<CSS::PseudoElement> associated_shadow_host_pseudo_element() const { return m_associated_shadow_host_pseudo_element; }
|
||||
void set_associated_shadow_host_pseudo_element(Optional<CSS::PseudoElement> pseudo_element) { m_associated_shadow_host_pseudo_element = move(pseudo_element); }
|
||||
void set_associated_shadow_host_pseudo_element(CSS::PseudoElement pseudo_element);
|
||||
|
||||
GC::Ptr<Layout::NodeWithStyle> layout_node();
|
||||
GC::Ptr<Layout::NodeWithStyle const> layout_node() const;
|
||||
|
|
@ -662,6 +662,7 @@ protected:
|
|||
GC::Ptr<HTML::CustomElementDefinition> custom_element_definition() const { return m_custom_element_definition; }
|
||||
|
||||
void play_or_cancel_animations_after_display_property_change();
|
||||
void clear_element_reference_pseudo_elements();
|
||||
|
||||
private:
|
||||
FlyString make_html_uppercased_qualified_name() const;
|
||||
|
|
@ -700,6 +701,7 @@ private:
|
|||
|
||||
using PseudoElementData = HashMap<CSS::PseudoElement, GC::Ref<PseudoElement>>;
|
||||
mutable OwnPtr<PseudoElementData> m_pseudo_element_data;
|
||||
void register_element_reference_pseudo_element(CSS::PseudoElement type, GC::Ref<Element> element);
|
||||
SyntheticPseudoElement& ensure_synthetic_pseudo_element(CSS::PseudoElement) const;
|
||||
void clear_synthetic_pseudo_element_layout_nodes();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <LibWeb/CSS/ComputedProperties.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/DOM/PseudoElement.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ namespace Web::DOM {
|
|||
GC_DEFINE_ALLOCATOR(PseudoElement);
|
||||
GC_DEFINE_ALLOCATOR(SyntheticPseudoElement);
|
||||
GC_DEFINE_ALLOCATOR(SyntheticPseudoElementTreeNode);
|
||||
GC_DEFINE_ALLOCATOR(ElementReferencePseudoElement);
|
||||
|
||||
void SyntheticPseudoElement::visit_edges(JS::Cell::Visitor& visitor)
|
||||
{
|
||||
|
|
@ -43,4 +45,29 @@ void SyntheticPseudoElement::set_counters_set(OwnPtr<CSS::CountersSet>&& counter
|
|||
m_counters_set = move(counters_set);
|
||||
}
|
||||
|
||||
GC::Ptr<Layout::NodeWithStyle> ElementReferencePseudoElement::layout_node() const
|
||||
{
|
||||
return m_referenced_element->layout_node();
|
||||
}
|
||||
|
||||
GC::Ptr<Layout::NodeWithStyle> ElementReferencePseudoElement::unsafe_layout_node() const
|
||||
{
|
||||
return m_referenced_element->unsafe_layout_node();
|
||||
}
|
||||
|
||||
GC::Ptr<CSS::ComputedProperties> ElementReferencePseudoElement::computed_properties() const
|
||||
{
|
||||
return m_referenced_element->computed_properties({});
|
||||
}
|
||||
|
||||
RefPtr<CSS::CustomPropertyData const> ElementReferencePseudoElement::custom_property_data() const
|
||||
{
|
||||
return m_referenced_element->custom_property_data({});
|
||||
}
|
||||
|
||||
void ElementReferencePseudoElement::set_custom_property_data(RefPtr<CSS::CustomPropertyData const> value)
|
||||
{
|
||||
m_referenced_element->set_custom_property_data({}, move(value));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,4 +78,32 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
class WEB_API ElementReferencePseudoElement : public PseudoElement {
|
||||
GC_CELL(ElementReferencePseudoElement, PseudoElement);
|
||||
GC_DECLARE_ALLOCATOR(ElementReferencePseudoElement);
|
||||
|
||||
ElementReferencePseudoElement(GC::Ref<Element> referenced_element)
|
||||
: m_referenced_element(referenced_element)
|
||||
{
|
||||
}
|
||||
|
||||
GC::Ptr<Layout::NodeWithStyle> layout_node() const override;
|
||||
GC::Ptr<Layout::NodeWithStyle> unsafe_layout_node() const override;
|
||||
|
||||
GC::Ptr<CSS::ComputedProperties> computed_properties() const override;
|
||||
|
||||
RefPtr<CSS::CustomPropertyData const> custom_property_data() const override;
|
||||
void set_custom_property_data(RefPtr<CSS::CustomPropertyData const> value) override;
|
||||
|
||||
protected:
|
||||
virtual void visit_edges(JS::Cell::Visitor& visitor) override
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_referenced_element);
|
||||
}
|
||||
|
||||
private:
|
||||
GC::Ref<Element> m_referenced_element;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,8 +242,8 @@ WebIDL::ExceptionOr<void> HTMLDetailsElement::create_shadow_tree_if_needed()
|
|||
|
||||
// The second child element is a slot that is expected to take the details element's remaining descendants, if any.
|
||||
auto descendants_slot = TRY(DOM::create_element(document(), HTML::TagNames::slot, Namespace::HTML));
|
||||
descendants_slot->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::DetailsContent);
|
||||
MUST(shadow_root->append_child(descendants_slot));
|
||||
descendants_slot->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::DetailsContent);
|
||||
|
||||
// The third child element is either a link or style element with the following styles for the default summary:
|
||||
auto style = TRY(DOM::create_element(document(), HTML::TagNames::style, Namespace::HTML));
|
||||
|
|
|
|||
|
|
@ -1170,8 +1170,8 @@ void HTMLInputElement::create_text_input_shadow_tree()
|
|||
MUST(m_inner_text_element->append_child(*m_text_node));
|
||||
|
||||
m_placeholder_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
m_placeholder_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::Placeholder);
|
||||
MUST(element->append_child(*m_placeholder_element));
|
||||
m_placeholder_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::Placeholder);
|
||||
|
||||
m_placeholder_text_node = realm().create<DOM::Text>(document(), Utf16String::from_utf8(placeholder()));
|
||||
MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
|
||||
|
|
@ -1295,6 +1295,7 @@ void HTMLInputElement::create_file_input_shadow_tree()
|
|||
shadow_root->set_user_agent_internal(true);
|
||||
|
||||
m_file_button = DOM::create_element(document(), HTML::TagNames::button, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
|
||||
MUST(shadow_root->append_child(*m_file_button));
|
||||
m_file_button->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::FileSelectorButton);
|
||||
|
||||
m_file_label = DOM::create_element(document(), HTML::TagNames::label, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
|
||||
|
|
@ -1311,7 +1312,6 @@ void HTMLInputElement::create_file_input_shadow_tree()
|
|||
|
||||
update_file_input_shadow_tree();
|
||||
|
||||
MUST(shadow_root->append_child(*m_file_button));
|
||||
MUST(shadow_root->append_child(*m_file_label));
|
||||
|
||||
set_shadow_root(shadow_root);
|
||||
|
|
@ -1342,16 +1342,16 @@ void HTMLInputElement::create_range_input_shadow_tree()
|
|||
set_shadow_root(shadow_root);
|
||||
|
||||
m_slider_runnable_track = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
m_slider_runnable_track->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderTrack);
|
||||
MUST(shadow_root->append_child(*m_slider_runnable_track));
|
||||
m_slider_runnable_track->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderTrack);
|
||||
|
||||
m_slider_progress_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
m_slider_progress_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderFill);
|
||||
MUST(m_slider_runnable_track->append_child(*m_slider_progress_element));
|
||||
m_slider_progress_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderFill);
|
||||
|
||||
m_slider_thumb = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
m_slider_thumb->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderThumb);
|
||||
MUST(m_slider_runnable_track->append_child(*m_slider_thumb));
|
||||
m_slider_thumb->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderThumb);
|
||||
|
||||
update_slider_shadow_tree_elements();
|
||||
|
||||
|
|
@ -1620,6 +1620,7 @@ void HTMLInputElement::type_attribute_changed(TypeAttributeState old_state, Type
|
|||
|
||||
// 4. Update the element's rendering and behavior to the new state's.
|
||||
m_type = new_state;
|
||||
clear_element_reference_pseudo_elements();
|
||||
set_shadow_root(nullptr);
|
||||
create_shadow_tree_if_needed();
|
||||
|
||||
|
|
|
|||
|
|
@ -187,12 +187,12 @@ void HTMLMeterElement::create_shadow_tree_if_needed()
|
|||
set_shadow_root(shadow_root);
|
||||
|
||||
auto meter_bar_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
meter_bar_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderTrack);
|
||||
MUST(shadow_root->append_child(*meter_bar_element));
|
||||
meter_bar_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderTrack);
|
||||
|
||||
m_meter_value_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
m_meter_value_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderFill);
|
||||
MUST(meter_bar_element->append_child(*m_meter_value_element));
|
||||
m_meter_value_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderFill);
|
||||
update_meter_value_element();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,12 +112,12 @@ void HTMLProgressElement::create_shadow_tree_if_needed()
|
|||
set_shadow_root(shadow_root);
|
||||
|
||||
auto progress_bar_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
progress_bar_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderTrack);
|
||||
MUST(shadow_root->append_child(*progress_bar_element));
|
||||
progress_bar_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderTrack);
|
||||
|
||||
m_progress_value_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
m_progress_value_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderFill);
|
||||
MUST(progress_bar_element->append_child(*m_progress_value_element));
|
||||
m_progress_value_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::SliderFill);
|
||||
update_progress_value_element();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -358,8 +358,8 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed()
|
|||
MUST(m_inner_text_element->append_child(*m_text_node));
|
||||
|
||||
m_placeholder_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
|
||||
m_placeholder_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::Placeholder);
|
||||
MUST(element->append_child(*m_placeholder_element));
|
||||
m_placeholder_element->set_associated_shadow_host_pseudo_element(CSS::PseudoElement::Placeholder);
|
||||
|
||||
m_placeholder_text_node = realm().create<DOM::Text>(document(), Utf16String::from_utf8(get_attribute_value(HTML::AttributeNames::placeholder)));
|
||||
MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<style>
|
||||
details::details-content {
|
||||
background-color: green;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
<details open>
|
||||
<summary></summary>
|
||||
<div></div>
|
||||
</details>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<!doctype html>
|
||||
<html class="reftest-wait">
|
||||
<link rel="match" href="../expected/web-animation-details-content-pseudo-element-ref.html" />
|
||||
<style>
|
||||
details::details-content {
|
||||
display: block;
|
||||
background-color: red;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
<details open id="foo">
|
||||
<summary></summary>
|
||||
<div></div>
|
||||
</details>
|
||||
<script>
|
||||
getComputedStyle(foo, "::details-content").width;
|
||||
|
||||
const animation = foo.animate(
|
||||
{ backgroundColor: ["green", "green"] },
|
||||
{ duration: 1000, pseudoElement: "::details-content" }
|
||||
);
|
||||
|
||||
animation.currentTime = 500;
|
||||
animation.pause();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
document.documentElement.classList.remove("reftest-wait");
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
details-content open content-visibility: visible
|
||||
details-content closed content-visibility: hidden
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
before change: rgb(0, 128, 0)
|
||||
after change: rgb(128, 128, 128)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
color: rgb(0, 0, 0)
|
||||
opacity: 1
|
||||
font-weight: 400
|
||||
inherited color: rgb(0, 0, 0)
|
||||
nested color: rgb(0, 0, 0)
|
||||
color: rgb(0, 0, 255)
|
||||
opacity: 0.5
|
||||
font-weight: 900
|
||||
inherited color: rgb(0, 128, 0)
|
||||
nested color: rgb(0, 0, 255)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
input placeholder 'letter-spacing: 0': normal
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<!doctype html>
|
||||
<details id="details" open>
|
||||
<summary>summary</summary>
|
||||
</details>
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
// Force a layout update
|
||||
getComputedStyle(details, "::details-content").width;
|
||||
|
||||
println(
|
||||
`details-content open content-visibility: ${
|
||||
getComputedStyle(details, "::details-content").contentVisibility
|
||||
}`
|
||||
);
|
||||
|
||||
details.removeAttribute("open");
|
||||
|
||||
println(
|
||||
`details-content closed content-visibility: ${
|
||||
getComputedStyle(details, "::details-content").contentVisibility
|
||||
}`
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<style>
|
||||
input[type="text"]::placeholder {
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
<input id="target" type="text" placeholder="placeholder" />
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
println(`before change: ${getComputedStyle(target, "::placeholder").color}`);
|
||||
target.type = "file";
|
||||
println(`after change: ${getComputedStyle(target, "::placeholder").color}`);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<style>
|
||||
input::placeholder {
|
||||
letter-spacing: 0;
|
||||
}
|
||||
</style>
|
||||
<input id="input" placeholder="placeholder" />
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
println(`input placeholder 'letter-spacing: 0': ${getComputedStyle(input, "::placeholder").letterSpacing}`);
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue