shithub: opossum

Download patch

ref: 2d8c5c0443f3d663243b167d97e569e27ab97ad4
parent: dd52020bb30338f9eb23910e2fe09a6667dcafcd
author: Philip Silva <philip.silva@protonmail.com>
date: Sun Jan 24 07:40:13 EST 2021

use github.com/andybalholm/cascadia

--- a/browser/browser.go
+++ b/browser/browser.go
@@ -28,7 +28,6 @@
 )
 
 const debugPrintHtml = false
-const stashElements = true
 const experimentalUseSlicedDrawing = false
 
 const EnterKey = 10
@@ -217,15 +216,6 @@
 		return nil
 	}
 
-	if stashElements {
-		existingEl, ok := ui.(*Element)
-		if ok && existingEl != nil && n == existingEl.n {
-			return &Element{
-				UI: existingEl.UI,
-				n: existingEl.n,
-			}
-		}
-	}
 	return &Element{
 		UI: ui,
 		n: n,
--- a/browser/browser_test.go
+++ b/browser/browser_test.go
@@ -1,6 +1,7 @@
 package browser
 
 import (
+	"fmt"
 	"github.com/mjl-/duit"
 	"golang.org/x/net/html"
 	"net/http"
@@ -177,18 +178,13 @@
 	}
 }
 
-func TestInlining(t *testing.T) {
-	htm := `
-		<body>
-			<span id="outer">(<a href="http://example.com"><span>example.com</span></a></span>
-		</body>
-	`
+func digestHtm(htm string) (nt *nodes.Node, boxed *Element, err error) {
 	doc, err := html.ParseWithOptions(
 		strings.NewReader(string(htm)),
 		html.ParseOptionEnableScripting(false),
 	)
 	if err != nil {
-		t.Fatalf(err.Error())
+		return nil, nil, fmt.Errorf("parse html: %w", err)
 	}
 	body := grep(doc, "body")
 	b := &Browser{}
@@ -196,16 +192,44 @@
 	browser = b
 	u, err := url.Parse("https://example.com")
 	if err != nil {
-		log.Fatalf("parse: %v", err)
+		return nil, nil, fmt.Errorf("parse url: %w", err)
 	}
 	b.History.Push(u)
 	nm, err := style.FetchNodeMap(doc, style.AddOnCSS, 1280)
 	if err != nil {
-		log.Fatalf("FetchNodeMap: %v", err)
+		return nil, nil, fmt.Errorf("FetchNodeMap: %w", err)
 	}
-	nt := nodes.NewNodeTree(body, style.Map{}, nm, nil)
-	boxed := NodeToBox(0, b, nt)
 
+	nt = nodes.NewNodeTree(body, style.Map{}, nm, nil)
+	boxed = NodeToBox(0, b, nt)
+
+	return
+}
+
+func explodeRow(e *Element) (cols []*duit.Kid, ok bool) {
+	for {
+		el, ok := e.UI.(*Element)
+		if ok {
+			e = el
+		} else {
+			break
+		}
+	}
+	el := e.UI.(*duit.Box)
+	return el.Kids, true
+}
+
+func TestInlining(t *testing.T) {
+	htm := `
+		<body>
+			<span id="outer">(<a href="http://example.com"><span>example.com</span></a></span>
+		</body>
+	`
+	nt, boxed, err := digestHtm(htm)
+	if err != nil {
+		t.Fatalf("digest: %v", err)
+	}
+
 	// 1. nodes are row-like
 	outerSpan := nt.Find("span")
 	if outerSpan.Attr("id") != "outer" || len(outerSpan.Children) != 2 || outerSpan.IsFlex() {
@@ -221,14 +245,58 @@
 	}
 
 	// 2. Elements are row-like
-	box := boxed.UI.(*Element).UI.(*duit.Box)
-	if len(box.Kids) != 2 {
-		t.Errorf("box: %+v", box)
+	kids, ok := explodeRow(boxed)
+	if !ok || len(kids) != 2 {
+		t.Errorf("boxed: %+v", boxed)
 	}
-	bel := box.Kids[0].UI.(*Element)
-	ael := box.Kids[1].UI.(*Element)
+	bel := kids[0].UI.(*Element)
+	ael := 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)
+	}
+}
+
+func TestInlining2(t *testing.T) {
+	htm := `
+		<body>
+			<span id="outer">
+				<span id="sp1">[</span>
+				<a href="http://example.com">edit</a>
+				<span id="sp2">]</span>
+			</span>
+		</body>
+	`
+	nt, boxed, err := digestHtm(htm)
+	if err != nil {
+		t.Fatalf("digest: %v", err)
+	}
+
+	// 1. nodes are row-like
+	outerSpan := nt.Find("span")
+	if outerSpan.Attr("id") != "outer" || len(outerSpan.Children) != 7 || outerSpan.IsFlex() {
+		t.Errorf("node: %+v", outerSpan)
+	}
+	bracket := outerSpan.Children[0]
+	if /*bracket.Data() != "(" || */!bracket.IsInline() {
+		t.Errorf("bracket, is inline: %v %+v %+v", bracket.IsInline(), bracket, bracket.Data())
+	}
+	sp1 := outerSpan.Children[1]
+	if sp1.Data() != "span" || !sp1.IsInline() {
+		t.Errorf("sp1, is inline: %v, %+v %+v", sp1.IsInline(), sp1, sp1.Data())
+	}
+
+	// 2. Elements are row-like
+	kids, ok := explodeRow(boxed)
+	if !ok || len(kids) != 3 {
+		t.Errorf("boxed: %+v, kids: %+v", boxed, kids)
+	}
+	sel := kids[0].UI.(*Element)
+	ael := kids[1].UI.(*Element)
+	if sel.n.Data() != "span" {
+		t.Errorf("sel: %+v", sel)
 	}
 	if ael.n.Data() != "a" {
 		t.Errorf("ael: %+v %+v", ael, ael.n)
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@
 
 require (
 	9fans.net/go v0.0.0-00010101000000-000000000000
-	github.com/PuerkitoBio/goquery v1.6.0 // indirect
+	github.com/andybalholm/cascadia v1.1.0
 	github.com/chris-ramon/douceur v0.2.1-0.20160603235419-f3463056cd52
 	github.com/dop251/goja v0.0.0-20201107160812-7545ac6de48a
 	github.com/dop251/goja_nodejs v0.0.0-20200811150831-9bc458b4bbeb
@@ -18,7 +18,6 @@
 	github.com/jvatic/goja-babel v0.0.0-20200102152603-63c66b7c796a
 	github.com/mjl-/duit v0.0.0-20200330125617-580cb0b2843f
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
-	github.com/psilva261/css v0.1.0
 	github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564
 	github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9
 	golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,3 @@
-github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94=
-github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
 github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/chris-ramon/douceur v0.2.1-0.20160603235419-f3463056cd52 h1:xJWyi77j4VQwdeo6bO3wQSQ7o7yVwEM0ZvwXpyKHZZ8=
@@ -28,8 +26,6 @@
 github.com/mjl-/duit v0.0.0-20200330125617-580cb0b2843f/go.mod h1:OlRagobzQ97GoM+WaQ5kyzdyts952BFYsuY5bMyv9tw=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
-github.com/psilva261/css v0.1.0 h1:lzQtXIUKSdH7s6Vi4SeOt1YnTWFY2O/H//rujTGcu10=
-github.com/psilva261/css v0.1.0/go.mod h1:3jVsGoeXcAQqOOyDYqJe4p3bFgR1IXdAilbe2V4WMbE=
 github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
 github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
 github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
@@ -42,8 +38,6 @@
 golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20191108045252-6f6bbb1828be/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
--- a/img/img_test.go
+++ b/img/img_test.go
@@ -27,15 +27,13 @@
 }
 
 func TestSvg(t *testing.T) {
-	xml := `
-		<svg fill="currentColor" height="24" viewBox="0 0 24 24" width="24">
-		</svg>
-	`
+       xml := `
+               <svg fill="currentColor" height="24" viewBox="0 0 24 24" width="24">
+               </svg>
+       `
 
-	_, err := Svg(xml, 0, 0)
-	if err != nil {
-		t.Fatalf(err.Error())
-	}
+       _, err := Svg(xml, 0, 0)
+       if err != nil {
+               t.Fatalf(err.Error())
+       }
 }
-
-
--- a/nodes/nodes.go
+++ b/nodes/nodes.go
@@ -2,6 +2,7 @@
 
 import (
 	"bytes"
+	//"fmt"
 	"golang.org/x/net/html"
 	"github.com/chris-ramon/douceur/css"
 	"github.com/psilva261/opossum/logger"
@@ -30,20 +31,30 @@
 //
 // 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) {
+	//fmt.Printf("<%v %v>\n", doc.Data, doc.Attr)
+	//fmt.Printf("       nodeMap=%+v\n", nodeMap)
 	ncs := style.Map{
 		Declarations: make(map[string]css.Declaration),
 	}
+	//fmt.Printf("     ps=%+v\n", ps)
 	ncs = ps.ApplyChildStyle(ncs, false)
+	//fmt.Printf("     ncs=%+v\n", ncs)
+
 	// add from matching selectors
 	// (keep only inheriting properties from parent node)
 	if m, ok := nodeMap[doc]; ok {
+		//fmt.Printf("     m=%+v\n", m)
 		ncs = ncs.ApplyChildStyle(m, false)
+	} else {
+		//fmt.Printf("     no nodeMap entry\n")
 	}
+	//fmt.Printf("     ncs=%+v\n", ncs)
 
 	// add style attribute
 	// (keep all properties that already match)
 	styleAttr := style.NewMap(doc)
 	ncs = ncs.ApplyChildStyle(styleAttr, true)
+	//fmt.Printf("     ncs=%+v\n", ncs)
 
 	data := doc.Data
 	if doc.Type == html.ElementNode {
--- a/style/stylesheets.go
+++ b/style/stylesheets.go
@@ -3,9 +3,9 @@
 import (
 	"9fans.net/go/draw"
 	"fmt"
+	"github.com/andybalholm/cascadia"
 	"github.com/chris-ramon/douceur/css"
 	"github.com/chris-ramon/douceur/parser"
-	cssSel "github.com/psilva261/css"
 	"github.com/mjl-/duit"
 	"golang.org/x/image/colornames"
 	"golang.org/x/net/html"
@@ -34,16 +34,23 @@
 var WindowHeight = 1080
 
 const AddOnCSS = `
-a, span, i, tt, b {
+/* https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements */
+a, abbr, acronym, audio, b, bdi, bdo, big, br, button, canvas, cite, code, data, datalist, del, dfn, em, embed, i, iframe, img, input, ins, kbd, label, map, mark, meter, noscript, object, output, picture, progress, q, ruby, s, samp, script, select, slot, small, span, strong, sub, sup, svg, template, textarea, time, u, tt, var, video, wbr {
   display: inline;
 }
 
+/* non-HTML5 elements: https://www.w3schools.com/tags/ref_byfunc.asp */
+font, strike, tt {
+  display: inline;
+}
+
 button, textarea, input, select {
   display: inline-block;
 }
 
-h1, h2, h3, h4, h5, h6, div, center, frame, frameset, p, ul, menu, pre, dir, dl, dd, dt {
-	display: block;
+/* https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements */
+address, article, aside, blockquote, details, dialog, dd, div, dl, dt, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, li, main, nav, ol, p, pre, section, table, ul {
+  display: block;
 }
 
 a {
@@ -118,6 +125,7 @@
 	if err != nil {
 		return nil, fmt.Errorf("fetch rules: %w", err)
 	}
+	//fmt.Printf("mr=%+v", mr)
 	m = make(map[*html.Node]Map)
 	for n, rs := range mr {
 		ds := make(map[string]css.Declaration)
@@ -146,12 +154,14 @@
 	}
 	processRule := func(m map[*html.Node][]*css.Rule, r *css.Rule) (err error) {
 		for _, sel := range r.Selectors {
-			cs, err := cssSel.Compile(sel.Value)
+			cs, err := cascadia.Compile(sel.Value)
 			if err != nil {
 				log.Printf("cssSel compile %v: %v", sel.Value, err)
 				continue
 			}
-			for _, el := range cs.Select(doc) {
+			//fmt.Printf("cs=%+v\n", cs)
+			for _, el := range cascadia.QueryAll(doc, cs) {
+				//fmt.Printf("el==%+v\n", el)
 				existing, ok := m[el]
 				if !ok {
 					existing = make([]*css.Rule, 0, 3)
@@ -222,7 +232,7 @@
 		} else if a.Key == "height" || a.Key == "width" {
 			v := a.Val
 
-			if !strings.HasSuffix(v, "%") {
+			if !strings.HasSuffix(v, "%") && !strings.HasSuffix(v, "px") {
 				v += "px"
 			}
 
@@ -492,6 +502,9 @@
 		if strings.HasSuffix(l, suffix) {
 			if s = strings.TrimSuffix(l, suffix); s != "" {
 				f, err = strconv.ParseFloat(s, 64)
+				if err != nil {
+					return 0, "", fmt.Errorf("error parsing '%v': %w", l, err)
+				}
 			}
 			unit = suffix
 			break
--- a/style/stylesheets_test.go
+++ b/style/stylesheets_test.go
@@ -116,6 +116,57 @@
 	}
 }
 
+func TestFetchNodeRules2(t *testing.T) {
+	data := `<h2 id="outer">
+				<h2 id="sp1">[</h2>
+				<h2 id="sp2">]</h2>
+			</h2>`
+	doc, err := html.Parse(strings.NewReader(data))
+	if err != nil {
+		t.Fail()
+	}
+	m, err := FetchNodeRules(doc, AddOnCSS, 1024)
+	if err != nil {
+		t.Fail()
+	}
+	t.Logf("m=%+v", m)
+
+	var outer *html.Node
+	var sp1 *html.Node
+	var sp2 *html.Node
+
+	var f func(n *html.Node)
+	f = func(n *html.Node) {
+		if len(n.Attr) == 1 {
+			switch n.Attr[0].Val {
+			case "outer":
+				outer = n
+			case "sp1":
+				sp1 = n
+			case "sp2":
+				sp2 = n
+			}
+		}
+		for c := n.FirstChild; c != nil; c = c.NextSibling {
+			f(c)
+		}
+	}
+	f(doc)
+
+	/*t.Logf("outer=%+v", outer)
+	t.Logf("sp1=%+v", sp1)
+	t.Logf("sp2=%+v", sp2)*/
+
+	for _, n := range []*html.Node{outer, sp1, sp2} {
+		_, ok := m[n]
+		if !ok {
+			t.Errorf("not found: %+v", n)
+		} else {
+			t.Logf("success: %+v", n)
+		}
+	}
+}
+
 func TestFetchNodeMap(t *testing.T) {
 	data := `<p>
       		<h2 id="foo">a header</h2>