diff --git a/Base/res/html/misc/first-of-type.html b/Base/res/html/misc/first-of-type.html
new file mode 100644
index 00000000000..9749825aebd
--- /dev/null
+++ b/Base/res/html/misc/first-of-type.html
@@ -0,0 +1,13 @@
+
+
+This `div is first!
+This nested `span` is first!
+This nested `em` is first, but this nested `em` is last!
+This nested `span` gets styled!
+This `b` qualifies!
+This is the final `div`.
+
diff --git a/Base/res/html/misc/welcome.html b/Base/res/html/misc/welcome.html
index ee2d4c55c95..bf826530c42 100644
--- a/Base/res/html/misc/welcome.html
+++ b/Base/res/html/misc/welcome.html
@@ -38,6 +38,7 @@ span#loadtime {
This page loaded in ms
Some small test pages:
+ - CSS :first-of-type selector
- background image with repetition rules
- link elements with background box placed with z-index
- contenteditable
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
index 365ed0eee24..a64bcfbae89 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
@@ -554,6 +554,8 @@ public:
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Empty;
} else if (pseudo_name.equals_ignoring_case("root")) {
simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Root;
+ } else if (pseudo_name.equals_ignoring_case("first-of-type")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstOfType;
} else if (pseudo_name.equals_ignoring_case("before")) {
simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
} else if (pseudo_name.equals_ignoring_case("after")) {
diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h
index d5dae94f98d..e9d00852060 100644
--- a/Userland/Libraries/LibWeb/CSS/Selector.h
+++ b/Userland/Libraries/LibWeb/CSS/Selector.h
@@ -54,6 +54,7 @@ public:
OnlyChild,
Empty,
Root,
+ FirstOfType,
};
PseudoClass pseudo_class { PseudoClass::None };
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
index a1db87f78fe..96c0743f895 100644
--- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
@@ -90,6 +90,12 @@ static bool matches(const CSS::Selector::SimpleSelector& component, const DOM::E
if (!is(element))
return false;
break;
+ case CSS::Selector::SimpleSelector::PseudoClass::FirstOfType:
+ for (auto* sibling = element.previous_element_sibling(); sibling; sibling = sibling->previous_element_sibling()) {
+ if (sibling->tag_name() == element.tag_name())
+ return false;
+ }
+ break;
}
switch (component.attribute_match_type) {
diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp
index 1be93503a7a..cdb5297fb82 100644
--- a/Userland/Libraries/LibWeb/Dump.cpp
+++ b/Userland/Libraries/LibWeb/Dump.cpp
@@ -359,6 +359,9 @@ void dump_selector(StringBuilder& builder, const CSS::Selector& selector)
case CSS::Selector::SimpleSelector::PseudoClass::Root:
pseudo_class_description = "Root";
break;
+ case CSS::Selector::SimpleSelector::PseudoClass::FirstOfType:
+ pseudo_class_description = "FirstOfType";
+ break;
case CSS::Selector::SimpleSelector::PseudoClass::Focus:
pseudo_class_description = "Focus";
break;