diff --git a/Base/res/html/misc/last-of-type.html b/Base/res/html/misc/last-of-type.html
new file mode 100644
index 00000000000..0d08ce80cf8
--- /dev/null
+++ b/Base/res/html/misc/last-of-type.html
@@ -0,0 +1,12 @@
+
+
+ This `div` is first.
+ This nested `span` is last!
+ This nested `em` is first, but this nested `em` is last!
+ 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 bf826530c42..da883339fe1 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 :last-of-type selector
- CSS :first-of-type selector
- background image with repetition rules
- link elements with background box placed with z-index
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
index a64bcfbae89..f231f2a2b43 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/DeprecatedCSSParser.cpp
@@ -556,6 +556,8 @@ public:
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("last-of-type")) {
+ simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastOfType;
} 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 e9d00852060..9964f7a9724 100644
--- a/Userland/Libraries/LibWeb/CSS/Selector.h
+++ b/Userland/Libraries/LibWeb/CSS/Selector.h
@@ -55,6 +55,7 @@ public:
Empty,
Root,
FirstOfType,
+ LastOfType,
};
PseudoClass pseudo_class { PseudoClass::None };
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
index 96c0743f895..491a1aebc78 100644
--- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
@@ -96,6 +96,12 @@ static bool matches(const CSS::Selector::SimpleSelector& component, const DOM::E
return false;
}
break;
+ case CSS::Selector::SimpleSelector::PseudoClass::LastOfType:
+ for (auto* sibling = element.next_element_sibling(); sibling; sibling = sibling->next_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 cdb5297fb82..a9f721dfef3 100644
--- a/Userland/Libraries/LibWeb/Dump.cpp
+++ b/Userland/Libraries/LibWeb/Dump.cpp
@@ -362,6 +362,9 @@ void dump_selector(StringBuilder& builder, const CSS::Selector& selector)
case CSS::Selector::SimpleSelector::PseudoClass::FirstOfType:
pseudo_class_description = "FirstOfType";
break;
+ case CSS::Selector::SimpleSelector::PseudoClass::LastOfType:
+ pseudo_class_description = "LastOfType";
+ break;
case CSS::Selector::SimpleSelector::PseudoClass::Focus:
pseudo_class_description = "Focus";
break;