diff --git a/go.mod b/go.mod
index f302ca6ab4f48c1b0d9c115e8e969e9a7ae9fb69..f7ef22234c3d4a440a7a0eef74d15066cafad403 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module git.autistici.org/ale/crawl
 go 1.15
 
 require (
-	github.com/PuerkitoBio/goquery v1.7.1
+	github.com/PuerkitoBio/goquery v1.8.0
 	github.com/PuerkitoBio/purell v0.1.0
 	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
 	github.com/google/go-cmp v0.5.6
diff --git a/go.sum b/go.sum
index b977e7259b9c323c68db509c2cad1e58a8a4b8b4..de34829985a3e3c4acaeeb6c2a6ba3be8e2eeec3 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP
 github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
 github.com/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4=
 github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY=
+github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
+github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
 github.com/PuerkitoBio/purell v0.0.0-20180310210909-975f53781597 h1:1H3FyRw7YsqIty9WHPOVEGJaFJ1sfGVZ3PPDUw3ob2w=
 github.com/PuerkitoBio/purell v0.0.0-20180310210909-975f53781597/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/purell v0.1.0 h1:N8Bcc53nei5frgNYgAKo93qMUVdU5LUGHCBv8efdVcM=
@@ -12,6 +14,8 @@ github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRy
 github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
 github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
+github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
+github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
@@ -48,6 +52,8 @@ golang.org/x/net v0.0.0-20190926025831-c00fd9afed17 h1:qPnAdmjNA41t3QBTx2mFGf/SD
 golang.org/x/net v0.0.0-20190926025831-c00fd9afed17/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/vendor/github.com/PuerkitoBio/goquery/.travis.yml b/vendor/github.com/PuerkitoBio/goquery/.travis.yml
deleted file mode 100644
index 8430c86a2a505815865340e25f933dd4d709318f..0000000000000000000000000000000000000000
--- a/vendor/github.com/PuerkitoBio/goquery/.travis.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-arch:
-    - amd64
-    - ppc64le
-language: go
-
-go:
-    - 1.7.x
-    - 1.8.x
-    - 1.9.x
-    - 1.10.x
-    - 1.11.x
-    - 1.12.x
-    - 1.13.x
-    - 1.14.x
-    - 1.15.x
-    - tip
-
-jobs:
- exclude:
-    - arch: ppc64le
-      go: 1.7.x
-    - arch: ppc64le
-      go: 1.8.x
-    - arch: ppc64le
-      go: 1.9.x
-    - arch: ppc64le
-      go: 1.10.x
-    - arch: ppc64le
-      go: 1.11.x
-    - arch: ppc64le
-      go: 1.12.x
diff --git a/vendor/github.com/PuerkitoBio/goquery/README.md b/vendor/github.com/PuerkitoBio/goquery/README.md
index 6bb185c2cad3e4d0434ccd057c12ead9f6e8d2d4..775223401dd38af858ef7184ed4b0994cce0dab4 100644
--- a/vendor/github.com/PuerkitoBio/goquery/README.md
+++ b/vendor/github.com/PuerkitoBio/goquery/README.md
@@ -1,7 +1,6 @@
 # goquery - a little like that j-thing, only in Go
 
-[![builds.sr.ht status](https://builds.sr.ht/~mna/goquery/commits/fedora.yml.svg)](https://builds.sr.ht/~mna/goquery/commits/fedora.yml?)
-[![build status](https://secure.travis-ci.org/PuerkitoBio/goquery.svg?branch=master)](http://travis-ci.org/PuerkitoBio/goquery)
+[![Build Status](https://github.com/PuerkitoBio/goquery/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/PuerkitoBio/goquery/actions)
 [![Go Reference](https://pkg.go.dev/badge/github.com/PuerkitoBio/goquery.svg)](https://pkg.go.dev/github.com/PuerkitoBio/goquery)
 [![Sourcegraph Badge](https://sourcegraph.com/github.com/PuerkitoBio/goquery/-/badge.svg)](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge)
 
@@ -41,6 +40,7 @@ Please note that because of the net/html dependency, goquery requires Go1.1+ and
 
 **Note that goquery's API is now stable, and will not break.**
 
+*    **2021-10-25 (v1.8.0)** : Add `Render` function to render a `Selection` to an `io.Writer` (thanks [@anthonygedeon](https://github.com/anthonygedeon)).
 *    **2021-07-11 (v1.7.1)** : Update go.mod dependencies and add dependabot config (thanks [@jauderho](https://github.com/jauderho)).
 *    **2021-06-14 (v1.7.0)** : Add `Single` and `SingleMatcher` functions to optimize first-match selection (thanks [@gdollardollar](https://github.com/gdollardollar)).
 *    **2021-01-11 (v1.6.1)** : Fix panic when calling `{Prepend,Append,Set}Html` on a `Selection` that contains non-Element nodes.
diff --git a/vendor/github.com/PuerkitoBio/goquery/go.mod b/vendor/github.com/PuerkitoBio/goquery/go.mod
index 3af3b154db61ca88cce58faa90678cc16f8062c2..4b5a30963a39fdcf6e9afd73fbf298801340fa81 100644
--- a/vendor/github.com/PuerkitoBio/goquery/go.mod
+++ b/vendor/github.com/PuerkitoBio/goquery/go.mod
@@ -1,8 +1,8 @@
 module github.com/PuerkitoBio/goquery
 
 require (
-	github.com/andybalholm/cascadia v1.2.0
-	golang.org/x/net v0.0.0-20210614182718-04defd469f4e
+	github.com/andybalholm/cascadia v1.3.1
+	golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
 )
 
 go 1.13
diff --git a/vendor/github.com/PuerkitoBio/goquery/go.sum b/vendor/github.com/PuerkitoBio/goquery/go.sum
index 13b128d4e55f0a7d99f041bc7ae03dd3b46ed6d1..167f12d2db63d97a0dbb16fead68d9478eea6378 100644
--- a/vendor/github.com/PuerkitoBio/goquery/go.sum
+++ b/vendor/github.com/PuerkitoBio/goquery/go.sum
@@ -1,8 +1,7 @@
-github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
-github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
-golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
+github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
diff --git a/vendor/github.com/PuerkitoBio/goquery/utilities.go b/vendor/github.com/PuerkitoBio/goquery/utilities.go
index 3e11b1db1b42401308a8feda729ec3ef5ce133dd..6d243cdd63ad55f3fbb728d864fe9e6a5eaf8a02 100644
--- a/vendor/github.com/PuerkitoBio/goquery/utilities.go
+++ b/vendor/github.com/PuerkitoBio/goquery/utilities.go
@@ -2,6 +2,7 @@ package goquery
 
 import (
 	"bytes"
+	"io"
 
 	"golang.org/x/net/html"
 )
@@ -50,13 +51,24 @@ func nodeName(node *html.Node) string {
 	case html.ElementNode, html.DoctypeNode:
 		return node.Data
 	default:
-		if node.Type >= 0 && int(node.Type) < len(nodeNames) {
+		if int(node.Type) < len(nodeNames) {
 			return nodeNames[node.Type]
 		}
 		return ""
 	}
 }
 
+// Render renders the html of the first element from selector and writes it to
+// the writer.  It behaves the same as OuterHtml but writes to w instead of
+// returning the string.
+func Render(w io.Writer, s *Selection) error {
+	if s.Length() == 0 {
+		return nil
+	}
+	n := s.Get(0)
+	return html.Render(w, n)
+}
+
 // OuterHtml returns the outer HTML rendering of the first item in
 // the selection - that is, the HTML including the first element's
 // tag and attributes.
@@ -66,12 +78,7 @@ func nodeName(node *html.Node) string {
 // a property provided by the DOM).
 func OuterHtml(s *Selection) (string, error) {
 	var buf bytes.Buffer
-
-	if s.Length() == 0 {
-		return "", nil
-	}
-	n := s.Get(0)
-	if err := html.Render(&buf, n); err != nil {
+	if err := Render(&buf, s); err != nil {
 		return "", err
 	}
 	return buf.String(), nil
diff --git a/vendor/github.com/andybalholm/cascadia/go.mod b/vendor/github.com/andybalholm/cascadia/go.mod
index 51a330b50004eb7d7d749c5e9b8c233fad107621..ee72d3fe0c8d726656b1fc83fa206a8e98dd1e74 100644
--- a/vendor/github.com/andybalholm/cascadia/go.mod
+++ b/vendor/github.com/andybalholm/cascadia/go.mod
@@ -1,5 +1,5 @@
 module github.com/andybalholm/cascadia
 
-require golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01
+go 1.16
 
-go 1.13
+require golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
diff --git a/vendor/github.com/andybalholm/cascadia/go.sum b/vendor/github.com/andybalholm/cascadia/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..0f4194c577d5cad37b4e58de9303e66984630c7a
--- /dev/null
+++ b/vendor/github.com/andybalholm/cascadia/go.sum
@@ -0,0 +1,7 @@
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/vendor/github.com/andybalholm/cascadia/parser.go b/vendor/github.com/andybalholm/cascadia/parser.go
index c40a39fed16e1e0a8c365fc3dc59955ae46dc7ad..f654c0c7a81dd6e08c41dec98a2b4a0a418906a3 100644
--- a/vendor/github.com/andybalholm/cascadia/parser.go
+++ b/vendor/github.com/andybalholm/cascadia/parser.go
@@ -36,7 +36,7 @@ func (p *parser) parseEscape() (result string, err error) {
 		for i = start; i < start+6 && i < len(p.s) && hexDigit(p.s[i]); i++ {
 			// empty
 		}
-		v, _ := strconv.ParseUint(p.s[start:i], 16, 21)
+		v, _ := strconv.ParseUint(p.s[start:i], 16, 64)
 		if len(p.s) > i {
 			switch p.s[i] {
 			case '\r':
@@ -409,6 +409,19 @@ func (p *parser) parseAttributeSelector() (attrSelector, error) {
 	if p.i >= len(p.s) {
 		return attrSelector{}, errors.New("unexpected EOF in attribute selector")
 	}
+
+	// check if the attribute contains an ignore case flag
+	ignoreCase := false
+	if p.s[p.i] == 'i' || p.s[p.i] == 'I' {
+		ignoreCase = true
+		p.i++
+	}
+
+	p.skipWhitespace()
+	if p.i >= len(p.s) {
+		return attrSelector{}, errors.New("unexpected EOF in attribute selector")
+	}
+
 	if p.s[p.i] != ']' {
 		return attrSelector{}, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
 	}
@@ -416,15 +429,17 @@ func (p *parser) parseAttributeSelector() (attrSelector, error) {
 
 	switch op {
 	case "=", "!=", "~=", "|=", "^=", "$=", "*=", "#=":
-		return attrSelector{key: key, val: val, operation: op, regexp: rx}, nil
+		return attrSelector{key: key, val: val, operation: op, regexp: rx, insensitive: ignoreCase}, nil
 	default:
 		return attrSelector{}, fmt.Errorf("attribute operator %q is not supported", op)
 	}
 }
 
-var errExpectedParenthesis = errors.New("expected '(' but didn't find it")
-var errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it")
-var errUnmatchedParenthesis = errors.New("unmatched '('")
+var (
+	errExpectedParenthesis        = errors.New("expected '(' but didn't find it")
+	errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it")
+	errUnmatchedParenthesis       = errors.New("unmatched '('")
+)
 
 // parsePseudoclassSelector parses a pseudoclass selector like :not(p) or a pseudo-element
 // For backwards compatibility, both ':' and '::' prefix are allowed for pseudo-elements.
@@ -552,6 +567,37 @@ func (p *parser) parsePseudoclassSelector() (out Sel, pseudoElement string, err
 		out = emptyElementPseudoClassSelector{}
 	case "root":
 		out = rootPseudoClassSelector{}
+	case "link":
+		out = linkPseudoClassSelector{}
+	case "lang":
+		if !p.consumeParenthesis() {
+			return out, "", errExpectedParenthesis
+		}
+		if p.i == len(p.s) {
+			return out, "", errUnmatchedParenthesis
+		}
+		val, err := p.parseIdentifier()
+		if err != nil {
+			return out, "", err
+		}
+		val = strings.ToLower(val)
+		p.skipWhitespace()
+		if p.i >= len(p.s) {
+			return out, "", errors.New("unexpected EOF in pseudo selector")
+		}
+		if !p.consumeClosingParenthesis() {
+			return out, "", errExpectedClosingParenthesis
+		}
+		out = langPseudoClassSelector{lang: val}
+	case "enabled":
+		out = enabledPseudoClassSelector{}
+	case "disabled":
+		out = disabledPseudoClassSelector{}
+	case "checked":
+		out = checkedPseudoClassSelector{}
+	case "visited", "hover", "active", "focus", "target":
+		// Not applicable in a static context: never match.
+		out = neverMatchSelector{value: ":" + name}
 	case "after", "backdrop", "before", "cue", "first-letter", "first-line", "grammar-error", "marker", "placeholder", "selection", "spelling-error":
 		return nil, name, nil
 	default:
@@ -714,6 +760,9 @@ func (p *parser) parseSimpleSelectorSequence() (Sel, error) {
 	case '*':
 		// It's the universal selector. Just skip over it, since it doesn't affect the meaning.
 		p.i++
+		if p.i+2 < len(p.s) && p.s[p.i:p.i+2] == "|*" { // other version of universal selector
+			p.i += 2
+		}
 	case '#', '.', '[', ':':
 		// There's no type selector. Wait to process the other till the main loop.
 	default:
diff --git a/vendor/github.com/andybalholm/cascadia/pseudo_classes.go b/vendor/github.com/andybalholm/cascadia/pseudo_classes.go
new file mode 100644
index 0000000000000000000000000000000000000000..3986b22cd4e27097da7d7a662f809a8949530346
--- /dev/null
+++ b/vendor/github.com/andybalholm/cascadia/pseudo_classes.go
@@ -0,0 +1,474 @@
+package cascadia
+
+import (
+	"bytes"
+	"fmt"
+	"regexp"
+	"strings"
+
+	"golang.org/x/net/html"
+	"golang.org/x/net/html/atom"
+)
+
+// This file implements the pseudo classes selectors,
+// which share the implementation of PseudoElement() and Specificity()
+
+type abstractPseudoClass struct{}
+
+func (s abstractPseudoClass) Specificity() Specificity {
+	return Specificity{0, 1, 0}
+}
+
+func (c abstractPseudoClass) PseudoElement() string {
+	return ""
+}
+
+type relativePseudoClassSelector struct {
+	name  string // one of "not", "has", "haschild"
+	match SelectorGroup
+}
+
+func (s relativePseudoClassSelector) Match(n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+	switch s.name {
+	case "not":
+		// matches elements that do not match a.
+		return !s.match.Match(n)
+	case "has":
+		//  matches elements with any descendant that matches a.
+		return hasDescendantMatch(n, s.match)
+	case "haschild":
+		// matches elements with a child that matches a.
+		return hasChildMatch(n, s.match)
+	default:
+		panic(fmt.Sprintf("unsupported relative pseudo class selector : %s", s.name))
+	}
+}
+
+// hasChildMatch returns whether n has any child that matches a.
+func hasChildMatch(n *html.Node, a Matcher) bool {
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		if a.Match(c) {
+			return true
+		}
+	}
+	return false
+}
+
+// hasDescendantMatch performs a depth-first search of n's descendants,
+// testing whether any of them match a. It returns true as soon as a match is
+// found, or false if no match is found.
+func hasDescendantMatch(n *html.Node, a Matcher) bool {
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		if a.Match(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) {
+			return true
+		}
+	}
+	return false
+}
+
+// Specificity returns the specificity of the most specific selectors
+// in the pseudo-class arguments.
+// See https://www.w3.org/TR/selectors/#specificity-rules
+func (s relativePseudoClassSelector) Specificity() Specificity {
+	var max Specificity
+	for _, sel := range s.match {
+		newSpe := sel.Specificity()
+		if max.Less(newSpe) {
+			max = newSpe
+		}
+	}
+	return max
+}
+
+func (c relativePseudoClassSelector) PseudoElement() string {
+	return ""
+}
+
+type containsPseudoClassSelector struct {
+	abstractPseudoClass
+	value string
+	own   bool
+}
+
+func (s containsPseudoClassSelector) Match(n *html.Node) bool {
+	var text string
+	if s.own {
+		// matches nodes that directly contain the given text
+		text = strings.ToLower(nodeOwnText(n))
+	} else {
+		// matches nodes that contain the given text.
+		text = strings.ToLower(nodeText(n))
+	}
+	return strings.Contains(text, s.value)
+}
+
+type regexpPseudoClassSelector struct {
+	abstractPseudoClass
+	regexp *regexp.Regexp
+	own    bool
+}
+
+func (s regexpPseudoClassSelector) Match(n *html.Node) bool {
+	var text string
+	if s.own {
+		// matches nodes whose text directly matches the specified regular expression
+		text = nodeOwnText(n)
+	} else {
+		// matches nodes whose text matches the specified regular expression
+		text = nodeText(n)
+	}
+	return s.regexp.MatchString(text)
+}
+
+// writeNodeText writes the text contained in n and its descendants to b.
+func writeNodeText(n *html.Node, b *bytes.Buffer) {
+	switch n.Type {
+	case html.TextNode:
+		b.WriteString(n.Data)
+	case html.ElementNode:
+		for c := n.FirstChild; c != nil; c = c.NextSibling {
+			writeNodeText(c, b)
+		}
+	}
+}
+
+// nodeText returns the text contained in n and its descendants.
+func nodeText(n *html.Node) string {
+	var b bytes.Buffer
+	writeNodeText(n, &b)
+	return b.String()
+}
+
+// nodeOwnText returns the contents of the text nodes that are direct
+// children of n.
+func nodeOwnText(n *html.Node) string {
+	var b bytes.Buffer
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		if c.Type == html.TextNode {
+			b.WriteString(c.Data)
+		}
+	}
+	return b.String()
+}
+
+type nthPseudoClassSelector struct {
+	abstractPseudoClass
+	a, b         int
+	last, ofType bool
+}
+
+func (s nthPseudoClassSelector) Match(n *html.Node) bool {
+	if s.a == 0 {
+		if s.last {
+			return simpleNthLastChildMatch(s.b, s.ofType, n)
+		} else {
+			return simpleNthChildMatch(s.b, s.ofType, n)
+		}
+	}
+	return nthChildMatch(s.a, s.b, s.last, s.ofType, n)
+}
+
+// nthChildMatch implements :nth-child(an+b).
+// If last is true, implements :nth-last-child instead.
+// If ofType is true, implements :nth-of-type instead.
+func nthChildMatch(a, b int, last, ofType bool, n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+
+	parent := n.Parent
+	if parent == nil {
+		return false
+	}
+
+	if parent.Type == html.DocumentNode {
+		return false
+	}
+
+	i := -1
+	count := 0
+	for c := parent.FirstChild; c != nil; c = c.NextSibling {
+		if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
+			continue
+		}
+		count++
+		if c == n {
+			i = count
+			if !last {
+				break
+			}
+		}
+	}
+
+	if i == -1 {
+		// This shouldn't happen, since n should always be one of its parent's children.
+		return false
+	}
+
+	if last {
+		i = count - i + 1
+	}
+
+	i -= b
+	if a == 0 {
+		return i == 0
+	}
+
+	return i%a == 0 && i/a >= 0
+}
+
+// simpleNthChildMatch implements :nth-child(b).
+// If ofType is true, implements :nth-of-type instead.
+func simpleNthChildMatch(b int, ofType bool, n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+
+	parent := n.Parent
+	if parent == nil {
+		return false
+	}
+
+	if parent.Type == html.DocumentNode {
+		return false
+	}
+
+	count := 0
+	for c := parent.FirstChild; c != nil; c = c.NextSibling {
+		if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
+			continue
+		}
+		count++
+		if c == n {
+			return count == b
+		}
+		if count >= b {
+			return false
+		}
+	}
+	return false
+}
+
+// simpleNthLastChildMatch implements :nth-last-child(b).
+// If ofType is true, implements :nth-last-of-type instead.
+func simpleNthLastChildMatch(b int, ofType bool, n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+
+	parent := n.Parent
+	if parent == nil {
+		return false
+	}
+
+	if parent.Type == html.DocumentNode {
+		return false
+	}
+
+	count := 0
+	for c := parent.LastChild; c != nil; c = c.PrevSibling {
+		if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
+			continue
+		}
+		count++
+		if c == n {
+			return count == b
+		}
+		if count >= b {
+			return false
+		}
+	}
+	return false
+}
+
+type onlyChildPseudoClassSelector struct {
+	abstractPseudoClass
+	ofType bool
+}
+
+// Match implements :only-child.
+// If `ofType` is true, it implements :only-of-type instead.
+func (s onlyChildPseudoClassSelector) Match(n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+
+	parent := n.Parent
+	if parent == nil {
+		return false
+	}
+
+	if parent.Type == html.DocumentNode {
+		return false
+	}
+
+	count := 0
+	for c := parent.FirstChild; c != nil; c = c.NextSibling {
+		if (c.Type != html.ElementNode) || (s.ofType && c.Data != n.Data) {
+			continue
+		}
+		count++
+		if count > 1 {
+			return false
+		}
+	}
+
+	return count == 1
+}
+
+type inputPseudoClassSelector struct {
+	abstractPseudoClass
+}
+
+// Matches input, select, textarea and button elements.
+func (s inputPseudoClassSelector) Match(n *html.Node) bool {
+	return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button")
+}
+
+type emptyElementPseudoClassSelector struct {
+	abstractPseudoClass
+}
+
+// Matches empty elements.
+func (s emptyElementPseudoClassSelector) Match(n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		switch c.Type {
+		case html.ElementNode:
+			return false
+		case html.TextNode:
+			if strings.TrimSpace(nodeText(c)) == "" {
+				continue
+			} else {
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
+type rootPseudoClassSelector struct {
+	abstractPseudoClass
+}
+
+// Match implements :root
+func (s rootPseudoClassSelector) Match(n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+	if n.Parent == nil {
+		return false
+	}
+	return n.Parent.Type == html.DocumentNode
+}
+
+func hasAttr(n *html.Node, attr string) bool {
+	return matchAttribute(n, attr, func(string) bool { return true })
+}
+
+type linkPseudoClassSelector struct {
+	abstractPseudoClass
+}
+
+// Match implements :link
+func (s linkPseudoClassSelector) Match(n *html.Node) bool {
+	return (n.DataAtom == atom.A || n.DataAtom == atom.Area || n.DataAtom == atom.Link) && hasAttr(n, "href")
+}
+
+type langPseudoClassSelector struct {
+	abstractPseudoClass
+	lang string
+}
+
+func (s langPseudoClassSelector) Match(n *html.Node) bool {
+	own := matchAttribute(n, "lang", func(val string) bool {
+		return val == s.lang || strings.HasPrefix(val, s.lang+"-")
+	})
+	if n.Parent == nil {
+		return own
+	}
+	return own || s.Match(n.Parent)
+}
+
+type enabledPseudoClassSelector struct {
+	abstractPseudoClass
+}
+
+func (s enabledPseudoClassSelector) Match(n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+	switch n.DataAtom {
+	case atom.A, atom.Area, atom.Link:
+		return hasAttr(n, "href")
+	case atom.Optgroup, atom.Menuitem, atom.Fieldset:
+		return !hasAttr(n, "disabled")
+	case atom.Button, atom.Input, atom.Select, atom.Textarea, atom.Option:
+		return !hasAttr(n, "disabled") && !inDisabledFieldset(n)
+	}
+	return false
+}
+
+type disabledPseudoClassSelector struct {
+	abstractPseudoClass
+}
+
+func (s disabledPseudoClassSelector) Match(n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+	switch n.DataAtom {
+	case atom.Optgroup, atom.Menuitem, atom.Fieldset:
+		return hasAttr(n, "disabled")
+	case atom.Button, atom.Input, atom.Select, atom.Textarea, atom.Option:
+		return hasAttr(n, "disabled") || inDisabledFieldset(n)
+	}
+	return false
+}
+
+func hasLegendInPreviousSiblings(n *html.Node) bool {
+	for s := n.PrevSibling; s != nil; s = s.PrevSibling {
+		if s.DataAtom == atom.Legend {
+			return true
+		}
+	}
+	return false
+}
+
+func inDisabledFieldset(n *html.Node) bool {
+	if n.Parent == nil {
+		return false
+	}
+	if n.Parent.DataAtom == atom.Fieldset && hasAttr(n.Parent, "disabled") &&
+		(n.DataAtom != atom.Legend || hasLegendInPreviousSiblings(n)) {
+		return true
+	}
+	return inDisabledFieldset(n.Parent)
+}
+
+type checkedPseudoClassSelector struct {
+	abstractPseudoClass
+}
+
+func (s checkedPseudoClassSelector) Match(n *html.Node) bool {
+	if n.Type != html.ElementNode {
+		return false
+	}
+	switch n.DataAtom {
+	case atom.Input, atom.Menuitem:
+		return hasAttr(n, "checked") && matchAttribute(n, "type", func(val string) bool {
+			t := toLowerASCII(val)
+			return t == "checkbox" || t == "radio"
+		})
+	case atom.Option:
+		return hasAttr(n, "selected")
+	}
+	return false
+}
diff --git a/vendor/github.com/andybalholm/cascadia/selector.go b/vendor/github.com/andybalholm/cascadia/selector.go
index e2a6dc4be16f99b89c54fbb94d1c609db571307b..87549be23958dfc5948fb4ea20ac998c02ea8d8a 100644
--- a/vendor/github.com/andybalholm/cascadia/selector.go
+++ b/vendor/github.com/andybalholm/cascadia/selector.go
@@ -1,7 +1,6 @@
 package cascadia
 
 import (
-	"bytes"
 	"fmt"
 	"regexp"
 	"strings"
@@ -232,7 +231,7 @@ type classSelector struct {
 // Matches elements by class attribute.
 func (t classSelector) Match(n *html.Node) bool {
 	return matchAttribute(n, "class", func(s string) bool {
-		return matchInclude(t.class, s)
+		return matchInclude(t.class, s, false)
 	})
 }
 
@@ -266,6 +265,7 @@ func (c idSelector) PseudoElement() string {
 type attrSelector struct {
 	key, val, operation string
 	regexp              *regexp.Regexp
+	insensitive         bool
 }
 
 // Matches elements by attribute value.
@@ -274,20 +274,20 @@ func (t attrSelector) Match(n *html.Node) bool {
 	case "":
 		return matchAttribute(n, t.key, func(string) bool { return true })
 	case "=":
-		return matchAttribute(n, t.key, func(s string) bool { return s == t.val })
+		return matchAttribute(n, t.key, func(s string) bool { return matchInsensitiveValue(s, t.val, t.insensitive) })
 	case "!=":
-		return attributeNotEqualMatch(t.key, t.val, n)
+		return attributeNotEqualMatch(t.key, t.val, n, t.insensitive)
 	case "~=":
 		// matches elements where the attribute named key is a whitespace-separated list that includes val.
-		return matchAttribute(n, t.key, func(s string) bool { return matchInclude(t.val, s) })
+		return matchAttribute(n, t.key, func(s string) bool { return matchInclude(t.val, s, t.insensitive) })
 	case "|=":
-		return attributeDashMatch(t.key, t.val, n)
+		return attributeDashMatch(t.key, t.val, n, t.insensitive)
 	case "^=":
-		return attributePrefixMatch(t.key, t.val, n)
+		return attributePrefixMatch(t.key, t.val, n, t.insensitive)
 	case "$=":
-		return attributeSuffixMatch(t.key, t.val, n)
+		return attributeSuffixMatch(t.key, t.val, n, t.insensitive)
 	case "*=":
-		return attributeSubstringMatch(t.key, t.val, n)
+		return attributeSubstringMatch(t.key, t.val, n, t.insensitive)
 	case "#=":
 		return attributeRegexMatch(t.key, t.regexp, n)
 	default:
@@ -295,6 +295,17 @@ func (t attrSelector) Match(n *html.Node) bool {
 	}
 }
 
+// matches elements where we ignore (or not) the case of the attribute value
+// the user attribute is the value set by the user to match elements
+// the real attribute is the attribute value found in the code parsed
+func matchInsensitiveValue(userAttr string, realAttr string, ignoreCase bool) bool {
+	if ignoreCase {
+		return strings.EqualFold(userAttr, realAttr)
+	}
+	return userAttr == realAttr
+
+}
+
 // matches elements where the attribute named key satisifes the function f.
 func matchAttribute(n *html.Node, key string, f func(string) bool) bool {
 	if n.Type != html.ElementNode {
@@ -310,12 +321,12 @@ func matchAttribute(n *html.Node, key string, f func(string) bool) bool {
 
 // attributeNotEqualMatch matches elements where
 // the attribute named key does not have the value val.
-func attributeNotEqualMatch(key, val string, n *html.Node) bool {
+func attributeNotEqualMatch(key, val string, n *html.Node, ignoreCase bool) bool {
 	if n.Type != html.ElementNode {
 		return false
 	}
 	for _, a := range n.Attr {
-		if a.Key == key && a.Val == val {
+		if a.Key == key && matchInsensitiveValue(a.Val, val, ignoreCase) {
 			return false
 		}
 	}
@@ -323,13 +334,13 @@ func attributeNotEqualMatch(key, val string, n *html.Node) bool {
 }
 
 // returns true if s is a whitespace-separated list that includes val.
-func matchInclude(val, s string) bool {
+func matchInclude(val string, s string, ignoreCase bool) bool {
 	for s != "" {
 		i := strings.IndexAny(s, " \t\r\n\f")
 		if i == -1 {
-			return s == val
+			return matchInsensitiveValue(s, val, ignoreCase)
 		}
-		if s[:i] == val {
+		if matchInsensitiveValue(s[:i], val, ignoreCase) {
 			return true
 		}
 		s = s[i+1:]
@@ -338,16 +349,16 @@ func matchInclude(val, s string) bool {
 }
 
 //  matches elements where the attribute named key equals val or starts with val plus a hyphen.
-func attributeDashMatch(key, val string, n *html.Node) bool {
+func attributeDashMatch(key, val string, n *html.Node, ignoreCase bool) bool {
 	return matchAttribute(n, key,
 		func(s string) bool {
-			if s == val {
+			if matchInsensitiveValue(s, val, ignoreCase) {
 				return true
 			}
 			if len(s) <= len(val) {
 				return false
 			}
-			if s[:len(val)] == val && s[len(val)] == '-' {
+			if matchInsensitiveValue(s[:len(val)], val, ignoreCase) && s[len(val)] == '-' {
 				return true
 			}
 			return false
@@ -356,36 +367,45 @@ func attributeDashMatch(key, val string, n *html.Node) bool {
 
 // attributePrefixMatch returns a Selector that matches elements where
 // the attribute named key starts with val.
-func attributePrefixMatch(key, val string, n *html.Node) bool {
+func attributePrefixMatch(key, val string, n *html.Node, ignoreCase bool) bool {
 	return matchAttribute(n, key,
 		func(s string) bool {
 			if strings.TrimSpace(s) == "" {
 				return false
 			}
+			if ignoreCase {
+				return strings.HasPrefix(strings.ToLower(s), strings.ToLower(val))
+			}
 			return strings.HasPrefix(s, val)
 		})
 }
 
 // attributeSuffixMatch matches elements where
 // the attribute named key ends with val.
-func attributeSuffixMatch(key, val string, n *html.Node) bool {
+func attributeSuffixMatch(key, val string, n *html.Node, ignoreCase bool) bool {
 	return matchAttribute(n, key,
 		func(s string) bool {
 			if strings.TrimSpace(s) == "" {
 				return false
 			}
+			if ignoreCase {
+				return strings.HasSuffix(strings.ToLower(s), strings.ToLower(val))
+			}
 			return strings.HasSuffix(s, val)
 		})
 }
 
 // attributeSubstringMatch matches nodes where
 // the attribute named key contains val.
-func attributeSubstringMatch(key, val string, n *html.Node) bool {
+func attributeSubstringMatch(key, val string, n *html.Node, ignoreCase bool) bool {
 	return matchAttribute(n, key,
 		func(s string) bool {
 			if strings.TrimSpace(s) == "" {
 				return false
 			}
+			if ignoreCase {
+				return strings.Contains(strings.ToLower(s), strings.ToLower(val))
+			}
 			return strings.Contains(s, val)
 		})
 }
@@ -407,394 +427,22 @@ func (c attrSelector) PseudoElement() string {
 	return ""
 }
 
-// ---------------- Pseudo class selectors ----------------
-// we use severals concrete types of pseudo-class selectors
-
-type relativePseudoClassSelector struct {
-	name  string // one of "not", "has", "haschild"
-	match SelectorGroup
-}
-
-func (s relativePseudoClassSelector) Match(n *html.Node) bool {
-	if n.Type != html.ElementNode {
-		return false
-	}
-	switch s.name {
-	case "not":
-		// matches elements that do not match a.
-		return !s.match.Match(n)
-	case "has":
-		//  matches elements with any descendant that matches a.
-		return hasDescendantMatch(n, s.match)
-	case "haschild":
-		// matches elements with a child that matches a.
-		return hasChildMatch(n, s.match)
-	default:
-		panic(fmt.Sprintf("unsupported relative pseudo class selector : %s", s.name))
-	}
-}
-
-// hasChildMatch returns whether n has any child that matches a.
-func hasChildMatch(n *html.Node, a Matcher) bool {
-	for c := n.FirstChild; c != nil; c = c.NextSibling {
-		if a.Match(c) {
-			return true
-		}
-	}
-	return false
-}
-
-// hasDescendantMatch performs a depth-first search of n's descendants,
-// testing whether any of them match a. It returns true as soon as a match is
-// found, or false if no match is found.
-func hasDescendantMatch(n *html.Node, a Matcher) bool {
-	for c := n.FirstChild; c != nil; c = c.NextSibling {
-		if a.Match(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) {
-			return true
-		}
-	}
-	return false
-}
-
-// Specificity returns the specificity of the most specific selectors
-// in the pseudo-class arguments.
-// See https://www.w3.org/TR/selectors/#specificity-rules
-func (s relativePseudoClassSelector) Specificity() Specificity {
-	var max Specificity
-	for _, sel := range s.match {
-		newSpe := sel.Specificity()
-		if max.Less(newSpe) {
-			max = newSpe
-		}
-	}
-	return max
-}
-
-func (c relativePseudoClassSelector) PseudoElement() string {
-	return ""
-}
+// see pseudo_classes.go for pseudo classes selectors
 
-type containsPseudoClassSelector struct {
-	own   bool
+// on a static context, some selectors can't match anything
+type neverMatchSelector struct {
 	value string
 }
 
-func (s containsPseudoClassSelector) Match(n *html.Node) bool {
-	var text string
-	if s.own {
-		// matches nodes that directly contain the given text
-		text = strings.ToLower(nodeOwnText(n))
-	} else {
-		// matches nodes that contain the given text.
-		text = strings.ToLower(nodeText(n))
-	}
-	return strings.Contains(text, s.value)
-}
-
-func (s containsPseudoClassSelector) Specificity() Specificity {
-	return Specificity{0, 1, 0}
-}
-
-func (c containsPseudoClassSelector) PseudoElement() string {
-	return ""
-}
-
-type regexpPseudoClassSelector struct {
-	own    bool
-	regexp *regexp.Regexp
-}
-
-func (s regexpPseudoClassSelector) Match(n *html.Node) bool {
-	var text string
-	if s.own {
-		// matches nodes whose text directly matches the specified regular expression
-		text = nodeOwnText(n)
-	} else {
-		// matches nodes whose text matches the specified regular expression
-		text = nodeText(n)
-	}
-	return s.regexp.MatchString(text)
-}
-
-// writeNodeText writes the text contained in n and its descendants to b.
-func writeNodeText(n *html.Node, b *bytes.Buffer) {
-	switch n.Type {
-	case html.TextNode:
-		b.WriteString(n.Data)
-	case html.ElementNode:
-		for c := n.FirstChild; c != nil; c = c.NextSibling {
-			writeNodeText(c, b)
-		}
-	}
-}
-
-// nodeText returns the text contained in n and its descendants.
-func nodeText(n *html.Node) string {
-	var b bytes.Buffer
-	writeNodeText(n, &b)
-	return b.String()
-}
-
-// nodeOwnText returns the contents of the text nodes that are direct
-// children of n.
-func nodeOwnText(n *html.Node) string {
-	var b bytes.Buffer
-	for c := n.FirstChild; c != nil; c = c.NextSibling {
-		if c.Type == html.TextNode {
-			b.WriteString(c.Data)
-		}
-	}
-	return b.String()
-}
-
-func (s regexpPseudoClassSelector) Specificity() Specificity {
-	return Specificity{0, 1, 0}
-}
-
-func (c regexpPseudoClassSelector) PseudoElement() string {
-	return ""
-}
-
-type nthPseudoClassSelector struct {
-	a, b         int
-	last, ofType bool
-}
-
-func (s nthPseudoClassSelector) Match(n *html.Node) bool {
-	if s.a == 0 {
-		if s.last {
-			return simpleNthLastChildMatch(s.b, s.ofType, n)
-		} else {
-			return simpleNthChildMatch(s.b, s.ofType, n)
-		}
-	}
-	return nthChildMatch(s.a, s.b, s.last, s.ofType, n)
-}
-
-// nthChildMatch implements :nth-child(an+b).
-// If last is true, implements :nth-last-child instead.
-// If ofType is true, implements :nth-of-type instead.
-func nthChildMatch(a, b int, last, ofType bool, n *html.Node) bool {
-	if n.Type != html.ElementNode {
-		return false
-	}
-
-	parent := n.Parent
-	if parent == nil {
-		return false
-	}
-
-	if parent.Type == html.DocumentNode {
-		return false
-	}
-
-	i := -1
-	count := 0
-	for c := parent.FirstChild; c != nil; c = c.NextSibling {
-		if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
-			continue
-		}
-		count++
-		if c == n {
-			i = count
-			if !last {
-				break
-			}
-		}
-	}
-
-	if i == -1 {
-		// This shouldn't happen, since n should always be one of its parent's children.
-		return false
-	}
-
-	if last {
-		i = count - i + 1
-	}
-
-	i -= b
-	if a == 0 {
-		return i == 0
-	}
-
-	return i%a == 0 && i/a >= 0
-}
-
-// simpleNthChildMatch implements :nth-child(b).
-// If ofType is true, implements :nth-of-type instead.
-func simpleNthChildMatch(b int, ofType bool, n *html.Node) bool {
-	if n.Type != html.ElementNode {
-		return false
-	}
-
-	parent := n.Parent
-	if parent == nil {
-		return false
-	}
-
-	if parent.Type == html.DocumentNode {
-		return false
-	}
-
-	count := 0
-	for c := parent.FirstChild; c != nil; c = c.NextSibling {
-		if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
-			continue
-		}
-		count++
-		if c == n {
-			return count == b
-		}
-		if count >= b {
-			return false
-		}
-	}
-	return false
-}
-
-// simpleNthLastChildMatch implements :nth-last-child(b).
-// If ofType is true, implements :nth-last-of-type instead.
-func simpleNthLastChildMatch(b int, ofType bool, n *html.Node) bool {
-	if n.Type != html.ElementNode {
-		return false
-	}
-
-	parent := n.Parent
-	if parent == nil {
-		return false
-	}
-
-	if parent.Type == html.DocumentNode {
-		return false
-	}
-
-	count := 0
-	for c := parent.LastChild; c != nil; c = c.PrevSibling {
-		if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
-			continue
-		}
-		count++
-		if c == n {
-			return count == b
-		}
-		if count >= b {
-			return false
-		}
-	}
+func (s neverMatchSelector) Match(n *html.Node) bool {
 	return false
 }
 
-// Specificity for nth-child pseudo-class.
-// Does not support a list of selectors
-func (s nthPseudoClassSelector) Specificity() Specificity {
-	return Specificity{0, 1, 0}
-}
-
-func (c nthPseudoClassSelector) PseudoElement() string {
-	return ""
-}
-
-type onlyChildPseudoClassSelector struct {
-	ofType bool
-}
-
-// Match implements :only-child.
-// If `ofType` is true, it implements :only-of-type instead.
-func (s onlyChildPseudoClassSelector) Match(n *html.Node) bool {
-	if n.Type != html.ElementNode {
-		return false
-	}
-
-	parent := n.Parent
-	if parent == nil {
-		return false
-	}
-
-	if parent.Type == html.DocumentNode {
-		return false
-	}
-
-	count := 0
-	for c := parent.FirstChild; c != nil; c = c.NextSibling {
-		if (c.Type != html.ElementNode) || (s.ofType && c.Data != n.Data) {
-			continue
-		}
-		count++
-		if count > 1 {
-			return false
-		}
-	}
-
-	return count == 1
-}
-
-func (s onlyChildPseudoClassSelector) Specificity() Specificity {
-	return Specificity{0, 1, 0}
-}
-
-func (c onlyChildPseudoClassSelector) PseudoElement() string {
-	return ""
-}
-
-type inputPseudoClassSelector struct{}
-
-// Matches input, select, textarea and button elements.
-func (s inputPseudoClassSelector) Match(n *html.Node) bool {
-	return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button")
-}
-
-func (s inputPseudoClassSelector) Specificity() Specificity {
-	return Specificity{0, 1, 0}
-}
-
-func (c inputPseudoClassSelector) PseudoElement() string {
-	return ""
-}
-
-type emptyElementPseudoClassSelector struct{}
-
-// Matches empty elements.
-func (s emptyElementPseudoClassSelector) Match(n *html.Node) bool {
-	if n.Type != html.ElementNode {
-		return false
-	}
-
-	for c := n.FirstChild; c != nil; c = c.NextSibling {
-		switch c.Type {
-		case html.ElementNode, html.TextNode:
-			return false
-		}
-	}
-
-	return true
-}
-
-func (s emptyElementPseudoClassSelector) Specificity() Specificity {
-	return Specificity{0, 1, 0}
-}
-
-func (c emptyElementPseudoClassSelector) PseudoElement() string {
-	return ""
-}
-
-type rootPseudoClassSelector struct{}
-
-// Match implements :root
-func (s rootPseudoClassSelector) Match(n *html.Node) bool {
-	if n.Type != html.ElementNode {
-		return false
-	}
-	if n.Parent == nil {
-		return false
-	}
-	return n.Parent.Type == html.DocumentNode
-}
-
-func (s rootPseudoClassSelector) Specificity() Specificity {
-	return Specificity{0, 1, 0}
+func (s neverMatchSelector) Specificity() Specificity {
+	return Specificity{0, 0, 0}
 }
 
-func (c rootPseudoClassSelector) PseudoElement() string {
+func (c neverMatchSelector) PseudoElement() string {
 	return ""
 }
 
diff --git a/vendor/github.com/andybalholm/cascadia/serialize.go b/vendor/github.com/andybalholm/cascadia/serialize.go
index f15b07952775715c7dbbf8887449dfb7eaf563ac..61acf04e1c69470888485381ea95c1195d908f18 100644
--- a/vendor/github.com/andybalholm/cascadia/serialize.go
+++ b/vendor/github.com/andybalholm/cascadia/serialize.go
@@ -2,21 +2,35 @@ package cascadia
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 )
 
 // implements the reverse operation Sel -> string
 
+var specialCharReplacer *strings.Replacer
+
+func init() {
+	var pairs []string
+	for _, s := range ",!\"#$%&'()*+ -./:;<=>?@[\\]^`{|}~" {
+		pairs = append(pairs, string(s), "\\"+string(s))
+	}
+	specialCharReplacer = strings.NewReplacer(pairs...)
+}
+
+// espace special CSS char
+func escape(s string) string { return specialCharReplacer.Replace(s) }
+
 func (c tagSelector) String() string {
 	return c.tag
 }
 
 func (c idSelector) String() string {
-	return "#" + c.id
+	return "#" + escape(c.id)
 }
 
 func (c classSelector) String() string {
-	return "." + c.class
+	return "." + escape(c.class)
 }
 
 func (c attrSelector) String() string {
@@ -26,12 +40,20 @@ func (c attrSelector) String() string {
 	} else if c.operation != "" {
 		val = fmt.Sprintf(`"%s"`, val)
 	}
-	return fmt.Sprintf(`[%s%s%s]`, c.key, c.operation, val)
+
+	ignoreCase := ""
+
+	if c.insensitive {
+		ignoreCase = " i"
+	}
+
+	return fmt.Sprintf(`[%s%s%s%s]`, c.key, c.operation, val, ignoreCase)
 }
 
 func (c relativePseudoClassSelector) String() string {
 	return fmt.Sprintf(":%s(%s)", c.name, c.match.String())
 }
+
 func (c containsPseudoClassSelector) String() string {
 	s := "contains"
 	if c.own {
@@ -39,6 +61,7 @@ func (c containsPseudoClassSelector) String() string {
 	}
 	return fmt.Sprintf(`:%s("%s")`, s, c.value)
 }
+
 func (c regexpPseudoClassSelector) String() string {
 	s := "matches"
 	if c.own {
@@ -46,6 +69,7 @@ func (c regexpPseudoClassSelector) String() string {
 	}
 	return fmt.Sprintf(":%s(%s)", s, c.regexp.String())
 }
+
 func (c nthPseudoClassSelector) String() string {
 	if c.a == 0 && c.b == 1 { // special cases
 		s := ":first-"
@@ -70,24 +94,56 @@ func (c nthPseudoClassSelector) String() string {
 	case [2]bool{false, false}:
 		name = "nth-child"
 	}
-	return fmt.Sprintf(":%s(%dn+%d)", name, c.a, c.b)
+	s := fmt.Sprintf("+%d", c.b)
+	if c.b < 0 { // avoid +-8 invalid syntax
+		s = strconv.Itoa(c.b)
+	}
+	return fmt.Sprintf(":%s(%dn%s)", name, c.a, s)
 }
+
 func (c onlyChildPseudoClassSelector) String() string {
 	if c.ofType {
 		return ":only-of-type"
 	}
 	return ":only-child"
 }
+
 func (c inputPseudoClassSelector) String() string {
 	return ":input"
 }
+
 func (c emptyElementPseudoClassSelector) String() string {
 	return ":empty"
 }
+
 func (c rootPseudoClassSelector) String() string {
 	return ":root"
 }
 
+func (c linkPseudoClassSelector) String() string {
+	return ":link"
+}
+
+func (c langPseudoClassSelector) String() string {
+	return fmt.Sprintf(":lang(%s)", c.lang)
+}
+
+func (c neverMatchSelector) String() string {
+	return c.value
+}
+
+func (c enabledPseudoClassSelector) String() string {
+	return ":enabled"
+}
+
+func (c disabledPseudoClassSelector) String() string {
+	return ":disabled"
+}
+
+func (c checkedPseudoClassSelector) String() string {
+	return ":checked"
+}
+
 func (c compoundSelector) String() string {
 	if len(c.selectors) == 0 && c.pseudoElement == "" {
 		return "*"
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 1d2f65d959c5e6ab9366434b068613fa89ec512d..2d68b824ba4a9f118df47f69713950efdbdd982a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,4 +1,4 @@
-# github.com/PuerkitoBio/goquery v1.7.1
+# github.com/PuerkitoBio/goquery v1.8.0
 ## explicit
 github.com/PuerkitoBio/goquery
 # github.com/PuerkitoBio/purell v0.1.0
@@ -6,7 +6,7 @@ github.com/PuerkitoBio/goquery
 github.com/PuerkitoBio/purell
 # github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
 ## explicit
-# github.com/andybalholm/cascadia v1.2.0
+# github.com/andybalholm/cascadia v1.3.1
 github.com/andybalholm/cascadia
 # github.com/golang/snappy v0.0.1
 github.com/golang/snappy
@@ -39,6 +39,6 @@ github.com/syndtr/goleveldb/leveldb/table
 github.com/syndtr/goleveldb/leveldb/util
 # golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
 ## explicit
-# golang.org/x/net v0.0.0-20210614182718-04defd469f4e
+# golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
 golang.org/x/net/html
 golang.org/x/net/html/atom