diff --git a/Libraries/LibWeb/DOM/Node.cpp b/Libraries/LibWeb/DOM/Node.cpp index 4ad49e47cd3..b02f7c2431a 100644 --- a/Libraries/LibWeb/DOM/Node.cpp +++ b/Libraries/LibWeb/DOM/Node.cpp @@ -2110,6 +2110,17 @@ bool Node::is_default_namespace(Optional namespace_) const return default_namespace == namespace_; } +bool Node::is_inert() const +{ + if (auto* html_element = as_if(*this)) + return html_element->is_inert(); + + if (auto* enclosing_html_element = this->enclosing_html_element()) + return enclosing_html_element->is_inert(); + + return false; +} + // https://dom.spec.whatwg.org/#in-a-document-tree bool Node::in_a_document_tree() const { diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index 3c99efa05e0..3b9e369b94c 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -499,6 +499,8 @@ public: Optional lookup_prefix(Optional namespace_) const; bool is_default_namespace(Optional namespace_) const; + bool is_inert() const; + protected: Node(JS::Realm&, Document&, NodeType); Node(Document&, NodeType); diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index db33dcae809..b5c4e8c17c3 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -639,6 +639,12 @@ void HTMLElement::attribute_changed(FlyString const& name, Optional cons // Having an invalid value maps to the "inherit" state. m_content_editable_state = ContentEditableState::Inherit; } + } else if (name == HTML::AttributeNames::inert) { + // https://html.spec.whatwg.org/multipage/interaction.html#the-inert-attribute + // The inert attribute is a boolean attribute that indicates, by its presence, that the element and all its flat tree descendants which don't otherwise escape inertness + // (such as modal dialogs) are to be made inert by the user agent. + auto is_inert = value.has_value(); + set_subtree_inertness(is_inert); } // 1. If namespace is not null, or localName is not the name of an event handler content attribute on element, then return. @@ -673,6 +679,18 @@ void HTMLElement::attribute_changed(FlyString const& name, Optional cons }(); } +void HTMLElement::set_subtree_inertness(bool is_inert) +{ + set_inert(is_inert); + for_each_in_subtree_of_type([&](auto& html_element) { + if (html_element.has_attribute(HTML::AttributeNames::inert)) + return TraversalDecision::SkipChildrenAndContinue; + // FIXME: Exclude elements that should escape inertness. + html_element.set_inert(is_inert); + return TraversalDecision::Continue; + }); +} + WebIDL::ExceptionOr HTMLElement::cloned(Web::DOM::Node& copy, bool clone_children) const { TRY(Base::cloned(copy, clone_children)); @@ -684,6 +702,9 @@ void HTMLElement::inserted() { Base::inserted(); HTMLOrSVGElement::inserted(); + + if (auto* parent_html_element = first_ancestor_of_type(); parent_html_element && parent_html_element->is_inert() && !has_attribute(HTML::AttributeNames::inert)) + set_subtree_inertness(true); } // https://html.spec.whatwg.org/multipage/webappapis.html#fire-a-synthetic-pointer-event @@ -1826,6 +1847,14 @@ void HTMLElement::removed_from(Node* old_parent, Node& old_root) // If removedNode's popover attribute is not in the no popover state, then run the hide popover algorithm given removedNode, false, false, false, and true. if (popover().has_value()) MUST(hide_popover(FocusPreviousElement::No, FireEvents::No, ThrowExceptions::No, IgnoreDomState::Yes)); + + if (old_parent) { + auto* parent_html_element = as_if(old_parent); + if (!parent_html_element) + parent_html_element = old_parent->first_ancestor_of_type(); + if (parent_html_element && parent_html_element->is_inert() && !has_attribute(HTML::AttributeNames::inert)) + set_subtree_inertness(false); + } } // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel diff --git a/Libraries/LibWeb/HTML/HTMLElement.h b/Libraries/LibWeb/HTML/HTMLElement.h index 1053e05b6d5..636f629d17b 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Libraries/LibWeb/HTML/HTMLElement.h @@ -149,6 +149,8 @@ public: static void hide_all_popovers_until(Variant, GC::Ptr> endpoint, FocusPreviousElement focus_previous_element, FireEvents fire_events); static GC::Ptr topmost_popover_ancestor(GC::Ptr new_popover_or_top_layer_element, Vector> const& popover_list, GC::Ptr invoker, IsPopover is_popover); + bool is_inert() const { return m_inert; } + protected: HTMLElement(DOM::Document&, DOM::QualifiedName); @@ -169,6 +171,9 @@ protected: // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#implicitly-potentially-render-blocking virtual bool is_implicitly_potentially_render_blocking() const { return false; } + void set_inert(bool inert) { m_inert = inert; } + void set_subtree_inertness(bool is_inert); + private: virtual bool is_html_element() const final { return true; } @@ -200,6 +205,8 @@ private: // https://html.spec.whatwg.org/multipage/interaction.html#click-in-progress-flag bool m_click_in_progress { false }; + bool m_inert { false }; + // Popover API // https://html.spec.whatwg.org/multipage/popover.html#popover-visibility-state