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)
--
⑨