diff --git a/Tests/LibWeb/Text/expected/HTML/Element-checkVisibility.txt b/Tests/LibWeb/Text/expected/HTML/Element-checkVisibility.txt
new file mode 100644
index 00000000000..4d3b83b28bf
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/HTML/Element-checkVisibility.txt
@@ -0,0 +1,8 @@
+ display-none visible: false
+content-visibility-parent visible: true
+content-visibility-child visible: false
+opacity-hidden visible: false
+opacity-visible visible: true
+visibility-hidden visible: false
+visibility-visible visible: true
+content-visibility-auto-hidden visible: true
\ No newline at end of file
diff --git a/Tests/LibWeb/Text/input/HTML/Element-checkVisibility.html b/Tests/LibWeb/Text/input/HTML/Element-checkVisibility.html
new file mode 100644
index 00000000000..cae83bd0445
--- /dev/null
+++ b/Tests/LibWeb/Text/input/HTML/Element-checkVisibility.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp
index 0134a1f9155..d055fbd0e56 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Element.cpp
@@ -2344,6 +2344,54 @@ void Element::scroll_by(HTML::ScrollToOptions options)
scroll(options);
}
+// https://drafts.csswg.org/cssom-view-1/#dom-element-checkvisibility
+bool Element::check_visibility(Optional options)
+{
+ // NOTE: Ensure that layout is up-to-date before looking at metrics.
+ document().update_layout();
+
+ // 1. If this does not have an associated box, return false.
+ if (!paintable_box())
+ return false;
+
+ // 2. If an ancestor of this in the flat tree has content-visibility: hidden, return false.
+ for (auto* element = parent_element(); element; element = element->parent_element()) {
+ if (element->computed_css_values()->content_visibility() == CSS::ContentVisibility::Hidden)
+ return false;
+ }
+
+ // AD-HOC: Since the rest of the steps use the options, we can return early if we haven't been given any options.
+ if (!options.has_value())
+ return true;
+
+ // 3. If either the opacityProperty or the checkOpacity dictionary members of options are true, and this, or an ancestor of this in the flat tree, has a computed opacity value of 0, return false.
+ if (options->opacity_property || options->check_opacity) {
+ for (auto* element = this; element; element = element->parent_element()) {
+ if (element->computed_css_values()->opacity() == 0.0f)
+ return false;
+ }
+ }
+
+ // 4. If either the visibilityProperty or the checkVisibilityCSS dictionary members of options are true, and this is invisible, return false.
+ if (options->visibility_property || options->check_visibility_css) {
+ if (computed_css_values()->visibility() == CSS::Visibility::Hidden)
+ return false;
+ }
+
+ // 5. If the contentVisibilityAuto dictionary member of options is true and an ancestor of this in the flat tree skips its contents due to content-visibility: auto, return false.
+ // FIXME: Currently we do not skip any content if content-visibility is auto: https://drafts.csswg.org/css-contain-2/#proximity-to-the-viewport
+ auto const skipped_contents_due_to_content_visibility_auto = false;
+ if (options->content_visibility_auto && skipped_contents_due_to_content_visibility_auto) {
+ for (auto* element = this; element; element = element->parent_element()) {
+ if (element->computed_css_values()->content_visibility() == CSS::ContentVisibility::Auto)
+ return false;
+ }
+ }
+
+ // 6. Return true.
+ return true;
+}
+
bool Element::id_reference_exists(String const& id_reference) const
{
return document().get_element_by_id(id_reference);
diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h
index 1d92498b83e..e546feb2d21 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.h
+++ b/Userland/Libraries/LibWeb/DOM/Element.h
@@ -48,6 +48,15 @@ struct ScrollIntoViewOptions : public HTML::ScrollOptions {
Bindings::ScrollLogicalPosition inline_ { Bindings::ScrollLogicalPosition::Nearest };
};
+// https://drafts.csswg.org/cssom-view-1/#dictdef-checkvisibilityoptions
+struct CheckVisibilityOptions {
+ bool check_opacity = false;
+ bool check_visibility_css = false;
+ bool content_visibility_auto = false;
+ bool opacity_property = false;
+ bool visibility_property = false;
+};
+
// https://html.spec.whatwg.org/multipage/custom-elements.html#upgrade-reaction
// An upgrade reaction, which will upgrade the custom element and contains a custom element definition; or
struct CustomElementUpgradeReaction {
@@ -354,6 +363,8 @@ public:
void scroll_by(HTML::ScrollToOptions);
void scroll_by(double x, double y);
+ bool check_visibility(Optional);
+
void register_intersection_observer(Badge, IntersectionObserver::IntersectionObserverRegistration);
void unregister_intersection_observer(Badge, JS::NonnullGCPtr);
IntersectionObserver::IntersectionObserverRegistration& get_intersection_observer_registration(Badge, IntersectionObserver::IntersectionObserver const&);
diff --git a/Userland/Libraries/LibWeb/DOM/Element.idl b/Userland/Libraries/LibWeb/DOM/Element.idl
index e1e63943809..1e9a8769a60 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.idl
+++ b/Userland/Libraries/LibWeb/DOM/Element.idl
@@ -20,6 +20,15 @@ dictionary ScrollIntoViewOptions : ScrollOptions {
ScrollLogicalPosition inline = "nearest";
};
+// https://drafts.csswg.org/cssom-view-1/#dictdef-checkvisibilityoptions
+dictionary CheckVisibilityOptions {
+ boolean checkOpacity = false;
+ boolean checkVisibilityCSS = false;
+ boolean contentVisibilityAuto = false;
+ boolean opacityProperty = false;
+ boolean visibilityProperty = false;
+};
+
// https://dom.spec.whatwg.org/#element
[Exposed=Window]
interface Element : Node {
@@ -74,7 +83,7 @@ interface Element : Node {
DOMRectList getClientRects();
DOMRect getBoundingClientRect();
- [FIXME] boolean checkVisibility(optional CheckVisibilityOptions options = {});
+ boolean checkVisibility(optional CheckVisibilityOptions options = {});
undefined scrollIntoView(optional (boolean or ScrollIntoViewOptions) arg = {});
undefined scroll(optional ScrollToOptions options = {});