shithub: mycel

Download patch

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)