ref: 2d8c5c0443f3d663243b167d97e569e27ab97ad4
parent: dd52020bb30338f9eb23910e2fe09a6667dcafcd
author: Philip Silva <[email protected]>
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>