diff --git a/Tests/LibWeb/Ref/css-any-link-selector-ref.html b/Tests/LibWeb/Ref/css-any-link-selector-ref.html
new file mode 100644
index 0000000000..c52dc9d82a
--- /dev/null
+++ b/Tests/LibWeb/Ref/css-any-link-selector-ref.html
@@ -0,0 +1 @@
+Link
diff --git a/Tests/LibWeb/Ref/css-any-link-selector.html b/Tests/LibWeb/Ref/css-any-link-selector.html
new file mode 100644
index 0000000000..bc20a79168
--- /dev/null
+++ b/Tests/LibWeb/Ref/css-any-link-selector.html
@@ -0,0 +1,6 @@
+
+Link
diff --git a/Tests/LibWeb/Ref/css-local-link-selector-ref.html b/Tests/LibWeb/Ref/css-local-link-selector-ref.html
new file mode 100644
index 0000000000..44d033ca80
--- /dev/null
+++ b/Tests/LibWeb/Ref/css-local-link-selector-ref.html
@@ -0,0 +1,2 @@
+Local
+Not local
diff --git a/Tests/LibWeb/Ref/css-local-link-selector.html b/Tests/LibWeb/Ref/css-local-link-selector.html
new file mode 100644
index 0000000000..e9fbbdfd6f
--- /dev/null
+++ b/Tests/LibWeb/Ref/css-local-link-selector.html
@@ -0,0 +1,7 @@
+
+Local
+Not local
diff --git a/Tests/LibWeb/Ref/manifest.json b/Tests/LibWeb/Ref/manifest.json
index 09c36b5436..60d30ac5b2 100644
--- a/Tests/LibWeb/Ref/manifest.json
+++ b/Tests/LibWeb/Ref/manifest.json
@@ -5,8 +5,10 @@
"square-flex.html": "square-ref.html",
"separate-borders-inline-table.html": "separate-borders-ref.html",
"opacity-stacking.html": "opacity-stacking-ref.html",
+ "css-any-link-selector.html": "css-any-link-selector-ref.html",
"css-gradient-currentcolor.html": "css-gradient-currentcolor-ref.html",
"css-lang-selector.html": "css-lang-selector-ref.html",
+ "css-local-link-selector.html": "css-local-link-selector-ref.html",
"css-gradients.html": "css-gradients-ref.html",
"svg-symbol.html": "svg-symbol-ref.html",
"svg-gradient-spreadMethod.html": "svg-gradient-spreadMethod-ref.html",
diff --git a/Userland/Libraries/LibWeb/CSS/PseudoClasses.json b/Userland/Libraries/LibWeb/CSS/PseudoClasses.json
index 5d2826acc6..25f68d921d 100644
--- a/Userland/Libraries/LibWeb/CSS/PseudoClasses.json
+++ b/Userland/Libraries/LibWeb/CSS/PseudoClasses.json
@@ -2,6 +2,9 @@
"active": {
"argument": ""
},
+ "any-link": {
+ "argument": ""
+ },
"buffering": {
"argument": ""
},
@@ -62,6 +65,9 @@
"link": {
"argument": ""
},
+ "local-link": {
+ "argument": ""
+ },
"muted": {
"argument": ""
},
diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
index 2d16da1ccf..e7a25e9d7a 100644
--- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
+++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
@@ -213,7 +213,24 @@ static inline bool matches_pseudo_class(CSS::Selector::SimpleSelector::PseudoCla
{
switch (pseudo_class.type) {
case CSS::PseudoClass::Link:
+ case CSS::PseudoClass::AnyLink:
+ // NOTE: AnyLink should match whether the link is visited or not, so if we ever start matching
+ // :visited, we'll need to handle these differently.
return matches_link_pseudo_class(element);
+ case CSS::PseudoClass::LocalLink: {
+ // The :local-link pseudo-class allows authors to style hyperlinks based on the users current location
+ // within a site. It represents an element that is the source anchor of a hyperlink whose target’s
+ // absolute URL matches the element’s own document URL. If the hyperlink’s target includes a fragment
+ // URL, then the fragment URL of the current URL must also match; if it does not, then the fragment
+ // URL portion of the current URL is not taken into account in the comparison.
+ if (!matches_link_pseudo_class(element))
+ return false;
+ auto document_url = element.document().url();
+ AK::URL target_url = element.document().parse_url(element.attribute(HTML::AttributeNames::href));
+ if (target_url.fragment().has_value())
+ return document_url.equals(target_url, AK::URL::ExcludeFragment::No);
+ return document_url.equals(target_url, AK::URL::ExcludeFragment::Yes);
+ }
case CSS::PseudoClass::Visited:
// FIXME: Maybe match this selector sometimes?
return false;