diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp
index f0a877a84b..bd25760637 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.cpp
@@ -9,12 +9,15 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
#include
@@ -225,7 +228,7 @@ void HTMLElement::parse_attribute(FlyString const& name, String const& value)
}
// https://html.spec.whatwg.org/multipage/interaction.html#focus-update-steps
-static void run_focus_update_steps(Vector> old_chain, Vector> new_chain, DOM::Node& new_focus_target)
+static void run_focus_update_steps(Vector> old_chain, Vector> new_chain, DOM::Node* new_focus_target)
{
// 1. If the last entry in old chain and the last entry in new chain are the same,
// pop the last entry from old chain and the last entry from new chain and redo this step.
@@ -405,7 +408,83 @@ static void run_focusing_steps(DOM::Node* new_focus_target, DOM::Node* fallback_
auto new_chain = focus_chain(new_focus_target);
// 8. Run the focus update steps with old chain, new chain, and new focus target respectively.
- run_focus_update_steps(old_chain, new_chain, *new_focus_target);
+ run_focus_update_steps(old_chain, new_chain, new_focus_target);
+}
+
+// https://html.spec.whatwg.org/multipage/interaction.html#unfocusing-steps
+static void run_unfocusing_steps(DOM::Node* old_focus_target)
+{
+ // NOTE: The unfocusing steps do not always result in the focus changing, even when applied to the currently focused
+ // area of a top-level browsing context. For example, if the currently focused area of a top-level browsing context
+ // is a viewport, then it will usually keep its focus regardless until another focusable area is explicitly focused
+ // with the focusing steps.
+
+ auto is_shadow_host = [](DOM::Node* node) {
+ return is(node) && static_cast(node)->shadow_root() != nullptr;
+ };
+
+ // 1. If old focus target is a shadow host whose shadow root's delegates focus is true, and old focus target's
+ // shadow root is a shadow-including inclusive ancestor of the currently focused area of a top-level browsing
+ // context's DOM anchor, then set old focus target to that currently focused area of a top-level browsing
+ // context.
+ if (is_shadow_host(old_focus_target)) {
+ auto* shadow_root = static_cast(old_focus_target)->shadow_root();
+ if (shadow_root->delegates_focus()) {
+ auto& top_level_browsing_context = old_focus_target->document().browsing_context()->top_level_browsing_context();
+ if (auto currently_focused_area = top_level_browsing_context.currently_focused_area()) {
+ if (shadow_root->is_shadow_including_ancestor_of(*currently_focused_area)) {
+ old_focus_target = currently_focused_area;
+ }
+ }
+ }
+ }
+
+ // FIXME: 2. If old focus target is inert, then return.
+
+ // FIXME: 3. If old focus target is an area element and one of its shapes is the currently focused area of a
+ // top-level browsing context, or, if old focus target is an element with one or more scrollable regions, and one
+ // of them is the currently focused area of a top-level browsing context, then let old focus target be that
+ // currently focused area of a top-level browsing context.
+
+ // NOTE: HTMLAreaElement is currently missing the shapes property
+
+ auto& top_level_browsing_context = old_focus_target->document().browsing_context()->top_level_browsing_context();
+
+ // 4. Let old chain be the current focus chain of the top-level browsing context in which old focus target finds itself.
+ auto old_chain = focus_chain(top_level_browsing_context.currently_focused_area());
+
+ // 5. If old focus target is not one of the entries in old chain, then return.
+ for (auto& node : old_chain) {
+ if (old_focus_target != node) {
+ return;
+ }
+ }
+
+ // 6. If old focus target is not a focusable area, then return.
+ if (!old_focus_target->is_focusable())
+ return;
+
+ // 7. Let topDocument be old chain's last entry.
+ auto* top_document = verify_cast(old_chain.last().ptr());
+
+ // 8. If topDocument's browsing context has system focus, then run the focusing steps for topDocument's viewport.
+ if (top_document->browsing_context()->system_visibility_state() == VisibilityState::Visible) {
+ // FIXME: run the focusing steps for topDocument's viewport (??)
+ } else {
+ // FIXME: Otherwise, apply any relevant platform-specific conventions for removing system focus from
+ // topDocument's browsing context, and run the focus update steps with old chain, an empty list, and null
+ // respectively.
+
+ // What? It already doesn't have system focus, what possible platform-specific conventions are there?
+
+ run_focus_update_steps(old_chain, {}, nullptr);
+ }
+
+ // FIXME: When the currently focused area of a top-level browsing context is somehow unfocused without another
+ // element being explicitly focused in its stead, the user agent must immediately run the unfocusing steps for that
+ // object.
+
+ // What? How are we supposed to detect when something is "somehow unfocused without another element being explicitly focused"?
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-focus
@@ -480,4 +559,13 @@ void HTMLElement::click()
m_click_in_progress = false;
}
+// https://html.spec.whatwg.org/multipage/interaction.html#dom-blur
+void HTMLElement::blur()
+{
+ // The blur() method, when invoked, should run the unfocusing steps for the element on which the method was called.
+ run_unfocusing_steps(this);
+
+ // User agents may selectively or uniformly ignore calls to this method for usability reasons.
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.h b/Userland/Libraries/LibWeb/HTML/HTMLElement.h
index 68fdb4c1ff..72097c8462 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.h
@@ -44,6 +44,8 @@ public:
void click();
+ void blur();
+
bool fire_a_synthetic_pointer_event(FlyString const& type, DOM::Element& target, bool not_trusted);
// https://html.spec.whatwg.org/multipage/forms.html#category-label
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLElement.idl
index 9abdbdd538..81ac8c4597 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.idl
@@ -15,6 +15,8 @@ interface HTMLElement : Element {
// FIXME: Support the optional FocusOptions parameter.
undefined focus();
+ undefined blur();
+
[LegacyNullToEmptyString] attribute DOMString innerText;
readonly attribute long offsetTop;