ref: 069daee6347a509dab3c109291cfd35956229b91
parent: e1e71dec97f88d1fdefd5615368485c373c75f79
author: Philip Silva <philip.silva@protonmail.com>
date: Fri Jan 28 17:27:56 EST 2022
Process css specificity
--- a/browser/website.go
+++ b/browser/website.go
@@ -164,6 +164,7 @@
}
func cssSrcs(f opossum.Fetcher, doc *html.Node) (csss []string) {
+ // TODO: keep order of inline and referenced files
cssHrefs := style.Hrefs(doc)
inlines := make([]string, 0, 3)
ntAll := nodes.NewNodeTree(doc, style.Map{}, make(map[*html.Node]style.Map), nil)
--- a/style/css.go
+++ b/style/css.go
@@ -3,6 +3,7 @@
import (
"bytes"
"fmt"
+ "github.com/andybalholm/cascadia"
"github.com/psilva261/opossum"
"github.com/psilva261/opossum/logger"
"github.com/tdewolff/parse/v2"
@@ -31,9 +32,10 @@
}
type Declaration struct {
- Important bool
- Prop string
- Val string
+ Important bool
+ Specificity cascadia.Specificity
+ Prop string
+ Val string
}
func Preprocess(s string) (bs []byte, ct opossum.ContentType, imports []string, err error) {
--- a/style/stylesheets.go
+++ b/style/stylesheets.go
@@ -51,7 +51,7 @@
display: block;
}
-a[href] {
+*[href] {
color: blue;
margin-right: 2px;
}
@@ -138,11 +138,17 @@
}
func smaller(d, dd Declaration) bool {
- return dd.Important
+ if dd.Important {
+ return true
+ } else if d.Important {
+ return false
+ } else {
+ return d.Specificity.Less(dd.Specificity)
+ }
}
-func compile(v string) (cs cascadia.Selector, err error) {
- return cascadia.Compile(v)
+func compile(v string) (cs cascadia.SelectorGroup, err error) {
+ return cascadia.ParseGroup(v)
}
func FetchNodeRules(doc *html.Node, cssText string, windowWidth int) (m map[*html.Node][]Rule, rVars map[string]string, err error) {
@@ -153,23 +159,37 @@
return nil, nil, fmt.Errorf("parse: %w", err)
}
processRule := func(m map[*html.Node][]Rule, r Rule) (err error) {
- for _, sel := range r.Selectors {
+ for i, sel := range r.Selectors {
if sel.Val == ":root" {
for _, d := range r.Declarations {
rVars[d.Prop] = d.Val
}
}
- cs, err := compile(sel.Val)
+ csg, err := compile(sel.Val)
if err != nil {
log.Printf("cssSel compile %v: %v", sel.Val, err)
continue
}
+ var cs cascadia.Sel
+ if n := len(csg); n == 1 {
+ cs = csg[0]
+ } else {
+ log.Errorf("csg len %v", n)
+ }
for _, el := range cascadia.QueryAll(doc, cs) {
existing, ok := m[el]
if !ok {
existing = make([]Rule, 0, 3)
}
- existing = append(existing, r)
+ var sr Rule
+ sr = r
+ sr.Selectors = []Selector{r.Selectors[i]}
+ for j := range sr.Declarations {
+ sr.Declarations[j].Specificity[0] = cs.Specificity()[0]
+ sr.Declarations[j].Specificity[1] = cs.Specificity()[1]
+ sr.Declarations[j].Specificity[2] = cs.Specificity()[2]
+ }
+ existing = append(existing, sr)
m[el] = existing
}
}
@@ -289,11 +309,14 @@
res.Declarations[k] = v
}
// overwrite with higher prio child props
- for k, v := range ccs.Declarations {
- if v.Val == "inherit" {
+ for k, d := range ccs.Declarations {
+ if d.Val == "inherit" {
continue
}
- res.Declarations[k] = v
+ if exist, ok := res.Declarations[k]; ok && smaller(d, exist) {
+ continue
+ }
+ res.Declarations[k] = d
}
return
--- a/style/stylesheets_test.go
+++ b/style/stylesheets_test.go
@@ -95,6 +95,11 @@
if r.Declarations[0].Important {
importantFound = true
}
+ for _, d := range r.Declarations {
+ if d.Specificity[0] != 0 || d.Specificity[1] != 0 || d.Specificity[2] != 1 {
+ t.Fail()
+ }
+ }
}
if !importantFound {
t.Fail()
@@ -182,6 +187,34 @@
t.Logf("m=%+v", m)
}
+func TestMergeNodeMaps(t *testing.T) {
+ nodeMap := make(map[*html.Node]Map)
+ data := `<p>
+ <a class="link" href="http://example.com">Test</a>
+ </p>`
+ doc, err := html.Parse(strings.NewReader(data))
+ if err != nil {
+ t.Fail()
+ }
+ a := grep(doc, "a")
+ m, err := FetchNodeMap(doc, AddOnCSS, 1024)
+ if err != nil {
+ t.Fail()
+ }
+ MergeNodeMaps(nodeMap, m)
+ if nodeMap[a].Css("color") != "blue" {
+ t.Fatalf("%v", nodeMap[a])
+ }
+ m2, err := FetchNodeMap(doc, `.link { color: red; }`, 1024)
+ if err != nil {
+ t.Fail()
+ }
+ MergeNodeMaps(nodeMap, m2)
+ if nodeMap[a].Css("color") != "red" {
+ t.Fatalf("%v", nodeMap[a])
+ }
+}
+
func TestNewMapStyle(t *testing.T) {
htms := []string{
`<h2 style="color: green;">a header</h2>`,
@@ -262,6 +295,52 @@
child.Declarations["font-size"] = Declaration{
Prop: "font-size",
Val: "inherit",
+ }
+
+ res := parent.ApplyChildStyle(child, true)
+ if v := res.Declarations["font-size"].Val; v != "12pt" {
+ t.Fatalf(v)
+ }
+}
+
+func TestApplyChildStyleInherit3(t *testing.T) {
+ parent := Map{
+ Declarations: make(map[string]Declaration),
+ }
+ child := Map{
+ Declarations: make(map[string]Declaration),
+ }
+ parent.Declarations["font-size"] = Declaration{
+ Prop: "font-size",
+ Val: "12pt",
+ }
+ child.Declarations["font-size"] = Declaration{
+ Prop: "font-size",
+ Val: "13pt",
+ }
+
+ res := parent.ApplyChildStyle(child, true)
+ if v := res.Declarations["font-size"].Val; v != "13pt" {
+ t.Fatalf(v)
+ }
+}
+
+func TestApplyChildStyleInherit4(t *testing.T) {
+ parent := Map{
+ Declarations: make(map[string]Declaration),
+ }
+ child := Map{
+ Declarations: make(map[string]Declaration),
+ }
+ parent.Declarations["font-size"] = Declaration{
+ Prop: "font-size",
+ Val: "12pt",
+ Specificity: [3]int{0, 2, 0},
+ }
+ child.Declarations["font-size"] = Declaration{
+ Prop: "font-size",
+ Val: "13pt",
+ Specificity: [3]int{0, 1, 0},
}
res := parent.ApplyChildStyle(child, true)