shithub: opossum

Download patch

ref: b0f057869b6c4fe492224a134c83403469e24f14
parent: 833decef052e263ed371d55774d4598e1bd17016
author: Philip Silva <philip.silva@protonmail.com>
date: Fri Jan 22 17:11:34 EST 2021

css property inheritance

--- a/browser/browser.go
+++ b/browser/browser.go
@@ -160,7 +160,7 @@
 			if err != nil {
 				return nil, fmt.Errorf("read image %v: %v", xml, err)
 			}
-			
+
 			goto img_elem
 		} else {
 			return nil, fmt.Errorf("img svg %v: %v", xml, err)
@@ -219,7 +219,7 @@
 
 	if stashElements {
 		existingEl, ok := ui.(*Element)
-		if ok && existingEl != nil {
+		if ok && existingEl != nil && n == existingEl.n {
 			return &Element{
 				UI: existingEl.UI,
 				n: existingEl.n,
@@ -424,6 +424,14 @@
 }
 
 func (el *Element) Mouse(dui *duit.DUI, self *duit.Kid, m draw.Mouse, origM draw.Mouse, orig image.Point) (r duit.Result) {
+	if m.Buttons == 2 {
+		if el == nil {
+			log.Infof("inspect nil element")
+		} else {
+			log.Infof("inspect el %+v %+v %+v", el, el.n, el.UI)
+		}
+	}
+
 	if el == nil {
 		return
 	}
@@ -572,7 +580,6 @@
 		}
 	}
 	flushCurrentRow()
-
 	if len(rows) == 0 {
 		return nil
 	} else if len(rows) == 1 {
--- a/browser/browser_test.go
+++ b/browser/browser_test.go
@@ -20,6 +20,7 @@
 	ExperimentalJsInsecure = &js
 	logger.Init()
 	SetLogger(&logger.Logger{})
+	style.Init(nil, &logger.Logger{})
 }
 
 type item struct {
@@ -160,5 +161,63 @@
 	})
 	if numInputs != 1 {
 		t.Fail()
+	}
+}
+
+func TestInlining(t *testing.T) {
+	htm := `
+		<body>
+			<span id="outer">(<a href="http://example.com"><span>example.com</span></a></span>
+		</body>
+	`
+	doc, err := html.ParseWithOptions(
+		strings.NewReader(string(htm)),
+		html.ParseOptionEnableScripting(false),
+	)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	body := grep(doc, "body")
+	b := &Browser{}
+	b.client = &http.Client{}
+	browser = b
+	u, err := url.Parse("https://example.com")
+	if err != nil {
+		log.Fatalf("parse: %v", err)
+	}
+	b.History.Push(u)
+	nm, err := style.FetchNodeMap(doc, style.AddOnCSS, 1280)
+	if err != nil {
+		log.Fatalf("FetchNodeMap: %v", err)
+	}
+	nt := nodes.NewNodeTree(body, style.Map{}, nm, nil)
+	boxed := NodeToBox(0, b, nt)
+
+	// 1. nodes are row-like
+	outerSpan := nt.Find("span")
+	if outerSpan.Attr("id") != "outer" || len(outerSpan.Children) != 2 || outerSpan.IsFlex() {
+		t.Fatalf(" node")
+	}
+	bracket := outerSpan.Children[0]
+	if bracket.Data() != "(" || !bracket.IsInline() {
+		t.Errorf("bracket, is inline: %v", bracket.IsInline())
+	}
+	a := outerSpan.Children[1]
+	if a.Data() != "a" || !a.IsInline() {
+		t.Errorf("a, is inline: %v, %+v %+v", a.IsInline(), a, nil)
+	}
+
+	// 2. Elements are row-like
+	box := boxed.UI.(*Element).UI.(*duit.Box)
+	if len(box.Kids) != 2 {
+		t.Errorf("box: %+v", box)
+	}
+	bel := box.Kids[0].UI.(*Element)
+	ael := box.Kids[1].UI.(*Element)
+	if bel.n.Data() != "(" {
+		t.Errorf("bel: %+v", bel)
+	}
+	if ael.n.Data() != "a" {
+		t.Errorf("ael: %+v %+v", ael, ael.n)
 	}
 }
--- a/nodes/nodes.go
+++ b/nodes/nodes.go
@@ -3,6 +3,7 @@
 import (
 	"bytes"
 	"golang.org/x/net/html"
+	"github.com/chris-ramon/douceur/css"
 	"github.com/psilva261/opossum/logger"
 	"github.com/psilva261/opossum/style"
 	"strings"
@@ -27,13 +28,23 @@
 
 // NewNodeTree propagates the cascading styles to the leaves
 //
-// First applies the global style and at the end the local style attribute's style is attached.
-func NewNodeTree(doc *html.Node, cs style.Map, nodeMap map[*html.Node]style.Map, parent *Node) (n *Node) {
-	ncs := cs
+// First applies the parent style and at the end the local style attribute's style is attached.
+func NewNodeTree(doc *html.Node, ps style.Map, nodeMap map[*html.Node]style.Map, parent *Node) (n *Node) {
+	ncs := style.Map{
+		Declarations: make(map[string]css.Declaration),
+	}
+	ncs = ps.ApplyChildStyle(ncs, false)
+	// add from matching selectors
+	// (keep only inheriting properties from parent node)
 	if m, ok := nodeMap[doc]; ok {
-		ncs = ncs.ApplyChildStyle(m)
+		ncs = ncs.ApplyChildStyle(m, false)
 	}
-	ncs = ncs.ApplyChildStyle(style.NewMap(doc))
+
+	// add style attribute
+	// (keep all properties that already match)
+	styleAttr := style.NewMap(doc)
+	ncs = ncs.ApplyChildStyle(styleAttr, true)
+
 	data := doc.Data
 	if doc.Type == html.ElementNode {
 		data = strings.ToLower(data)
@@ -51,6 +62,13 @@
 	if doc.Type == html.TextNode {
 
 		n.Text = filterText(doc.Data)
+		n.Map = style.Map{
+			Declarations: make(map[string]css.Declaration),
+		}
+		n.Map.Declarations["display"] = css.Declaration{
+			Property: "display",
+			Value: "inline",
+		}
 	}
 	i := 0
 	for c := doc.FirstChild; c != nil; c = c.NextSibling {
@@ -233,9 +251,9 @@
 			},
 		}
 	}
-	
+
 	n.Children[0].Text = t
 	n.Children[0].DomSubtree.Data = t
-	
+
 	return
 }
--- a/nodes/nodes_test.go
+++ b/nodes/nodes_test.go
@@ -47,3 +47,31 @@
 		t.Fatalf(s)
 	}
 }
+
+func TestNewNodeTree(t *testing.T) {
+	buf := strings.NewReader(`
+	<html>
+		<body style="width: 900px; height: 700px; font-size: 12px">
+			<p>
+				<b style="height: 100px;">bold stuff</b>
+			</p>
+		</body>
+	</html>`)
+	doc, err := html.Parse(buf)
+	if err != nil { t.Fatalf(err.Error()) }
+	n := NewNodeTree(doc, style.Map{}, make(map[*html.Node]style.Map), nil)
+	body := n.Find("body")
+	bodyW := body.Map.Css("width")
+	bodyH := body.Map.Css("height")
+	bodyF := body.Map.Css("font-size")
+	if bodyW != "900px" || bodyH != "700px"/* || bodyF != "12px"*/  {
+		t.Fatalf("<%v> w=%v h=%v f=%v", body.Data(), bodyW, bodyH, bodyF)
+	}
+	b := n.Find("b")
+	bW := b.Map.Css("width")
+	bH := b.Map.Css("height")
+	bF := b.Map.Css("font-size")
+	if bW != "" || bH != "100px"/* || bF != "12px"*/  {
+		t.Fatalf("<%v> w=%v h=%v f=%v", b.Data(), bW, bH, bF)
+	}
+}
--- a/style/stylesheets.go
+++ b/style/stylesheets.go
@@ -42,7 +42,7 @@
   display: inline-block;
 }
 
-h1, h2, h3, h4. h5, h6, div, center, frame, frameset, p, ul, menu, pre, dir, dl, dd, dt {
+h1, h2, h3, h4, h5, h6, div, center, frame, frameset, p, ul, menu, pre, dir, dl, dd, dt {
 	display: block;
 }
 
@@ -109,7 +109,7 @@
 		// "zero" valued Map if it doesn't exist yet
 		initial := m[n]
 
-		m[n] = initial.ApplyChildStyle(mp)
+		m[n] = initial.ApplyChildStyle(mp, true)
 	}
 }
 
@@ -241,40 +241,24 @@
 	return s
 }
 
-func (cs Map) ApplyChildStyle(ccs Map) (res Map) {
+func (cs Map) ApplyChildStyle(ccs Map, copyAll bool) (res Map) {
 	res.Declarations = make(map[string]css.Declaration)
 
 	for k, v := range cs.Declarations {
-		res.Declarations[k] = v
-	}
-	// overwrite with higher prio child props
-	for k, v := range ccs.Declarations {
 		switch k {
-		/*case "height", "width":
-			parentL, ok := res.Declarations[k]
-			if ok && strings.HasSuffix(v.Value, "%") && strings.HasSuffix(parentL.Value, "px") {
-				parentLNum, err := strconv.Atoi(strings.TrimSuffix(parentL.Value, "px"))
-				if err != nil {
-					log.Errorf("atoi: %v", err)
-					continue
-				}
-				percentNum, err := strconv.ParseFloat(strings.TrimSuffix(v.Value, "%"), 64)
-				if err != nil {
-					log.Errorf("atoi: %v", err)
-					continue
-				}
-				prod := int(percentNum * float64(parentLNum) / 100.0)
-				res.Declarations[k] = css.Declaration{
-					Property: k,
-					Value: fmt.Sprintf("%vpx", prod),
-				}
+		// https://www.w3.org/TR/CSS21/propidx.html
+		case "azimuth", "border-collapse", "border-spacing", "caption-side", "color", "cursor", "direction", "elevation", "empty-cells", "font-family", "font-size", "font-style", "font-variant", "font-weight", "font", "letter-spacing", "line-height", "list-style-image", "list-style-position", "list-style-type", "list-style", "orphans", "pitch-range", "pitch", "quotes", "richness", "speak-header", "speak-numeral", "speak-punctuation", "speak", "speech-rate", "stress", "text-align", "text-indent", "text-transform", "visibility", "voice-family", "volume", "white-space", "widows", "word-spacing":
+		default:
+			if !copyAll {
 				continue
 			}
-			fallthrough*/
-		default:
-			res.Declarations[k] = v
 		}
+		res.Declarations[k] = v
 	}
+	// overwrite with higher prio child props
+	for k, v := range ccs.Declarations {
+		res.Declarations[k] = v
+	}
 
 	return
 }
@@ -528,7 +512,9 @@
 		return f, unit, fmt.Errorf("unknown suffix: %v", l)
 	}
 
-	f = float64(dui.Scale(int(f)))
+	if dui != nil {
+		f = float64(dui.Scale(int(f)))
+	}
 
 	return
 }
--- a/style/stylesheets_test.go
+++ b/style/stylesheets_test.go
@@ -103,7 +103,7 @@
 		}
 
 		if w == 400 {
-			_ =m[body][0] 
+			_ =m[body][0]
 			if m[body][0].Declarations[0].Value != "lightblue" {
 				t.Fail()
 			}
@@ -133,6 +133,18 @@
 	t.Logf("m=%+v", m)
 }
 
+func TestSmaller(t *testing.T) {
+	d := css.Declaration{
+		Important: false,
+	}
+	dd := css.Declaration{
+		Important: true,
+	}
+	if !smaller(d, dd) {
+		t.Fail()
+	}
+}
+
 func TestApplyChildStyleInherit(t *testing.T) {
 	parent := Map{
 		Declarations: make(map[string]css.Declaration),
@@ -145,30 +157,29 @@
 		Declarations: make(map[string]css.Declaration),
 	}
 
-	res := parent.ApplyChildStyle(child)
+	res := parent.ApplyChildStyle(child, true)
 	if v := res.Declarations["height"].Value; v != "80px" {
 		t.Fatalf(v)
 	}
 }
 
-/*func TestApplyChildStyleMultiply(t *testing.T) {
-	parent := Map{
-		Declarations: make(map[string]css.Declaration),
+func TestLength(t *testing.T) {
+	lpx := map[string]float64{
+		"auto": 0.0,
+		"inherit": 0.0,
+		"17px": 17.0,
+		"10em": 110.0,
+		"10vw": 128.0,
+		"10vh": 108.0,
+		"10%": 0,
 	}
-	parent.Declarations["height"] = css.Declaration{
-		Property: "height",
-		Value:    "80px",
+	for l, px := range lpx {
+		f, _, err := length(l)
+		if err != nil {
+			t.Fatalf("%v: %v", l, err)
+		}
+		if f != px {
+			t.Fatalf("expected %v but got %v", px, f)
+		}
 	}
-	child := Map{
-		Declarations: make(map[string]css.Declaration),
-	}
-	child.Declarations["height"] = css.Declaration{
-		Property: "height",
-		Value:    "50%",
-	}
-
-	res := parent.ApplyChildStyle(child)
-	if v := res.Declarations["height"].Value; v != "40px" {
-		t.Fatalf(v)
-	}
-}*/
+}