shithub: mycel

Download patch

ref: c4be6e0dd0ca49ab1e1535c37888435e6411bb16
parent: 09b964472704c60ee0aa9a34d00e82316b94f952
author: Philip Silva <philip.silva@protonmail.com>
date: Sun Apr 6 04:04:08 EDT 2025

js object

--- a/browser/browser.go
+++ b/browser/browser.go
@@ -820,7 +820,7 @@
 		q := el.n.QueryRef()
 		var res string
 		var err error
-		res, consumed, err = js.TriggerClick(q)
+		res, consumed, err = el.b.js.TriggerClick(q)
 		if err != nil {
 			log.Errorf("trigger click %v: %v", q, err)
 		} else if consumed {
@@ -1519,6 +1519,7 @@
 
 	history.History
 	dui      *duit.DUI
+	js       *js.JS
 	Website  *Website
 	loading  bool
 	client   *http.Client
--- a/browser/experimental.go
+++ b/browser/experimental.go
@@ -2,14 +2,15 @@
 
 import (
 	"fmt"
+	"github.com/psilva261/mycel"
 	"github.com/psilva261/mycel/js"
 	"github.com/psilva261/mycel/logger"
 )
 
-func processJS2() (resHtm string, changed bool, err error) {
-	resHtm, changed, err = js.Start()
+func processJS2(f  mycel.Fetcher) (s *js.JS, resHtm string, changed bool, err error) {
+	s, resHtm, changed, err = js.Start(f)
 	if err != nil {
-		return "", false, fmt.Errorf("start: %w", err)
+		return nil, "", false, fmt.Errorf("start: %w", err)
 	}
 	log.Printf("processJS: changed = %v", changed)
 	return
--- a/browser/experimental_test.go
+++ b/browser/experimental_test.go
@@ -18,7 +18,6 @@
 
 func init() {
 	log.Debug = true
-	js.SetFetcher(&TestFetcher{})
 	style.Init(nil)
 	go fs.Srv9p()
 }
@@ -46,6 +45,7 @@
 	if testing.Short() {
 		t.Skip("skipping test in short mode.")
 	}
+	f := &TestFetcher{}
 	h := `
 	<html>
 	<body>
@@ -72,8 +72,8 @@
 	}
 	fs.SetDOM(nt)
 	fs.Update("", h, nil, scripts)
-	js.Start()
-	h, _, err = processJS2()
+	js.Start(f)
+	s, h, _, err := processJS2(f)
 	if err != nil {
 		t.Errorf(err.Error())
 	}
@@ -81,5 +81,5 @@
 	if !strings.Contains(h, `<body style="display: none; ">`) {
 		t.Fail()
 	}
-	js.Stop()
+	s.Stop()
 }
--- a/browser/website.go
+++ b/browser/website.go
@@ -82,6 +82,12 @@
 	// state. During subsequent calls from click handlers that state is kept.
 	var scripts []string
 	if ExperimentalJsInsecure && layouting != ClickRelayout {
+		var (
+			jsProcessed string
+			changed bool
+			err error
+		)
+
 		log.Printf("3rd pass")
 		nt := nodes.NewNodeTree(doc, style.Map{}, nodeMap, nil)
 		jsSrcs := js.Srcs(nt)
@@ -104,9 +110,8 @@
 		fs.Update(f.Origin().String(), htm, csss, scripts)
 		fs.SetDOM(nt)
 		log.Infof("JS pipeline start")
-		js.Stop()
-		js.SetFetcher(f)
-		jsProcessed, changed, err := processJS2()
+		w.b.js.Stop()
+		w.b.js, jsProcessed, changed, err = processJS2(f)
 		if changed && err == nil {
 			htm = jsProcessed
 			if debugPrintHtml {
--- a/cmd/mycel/main.go
+++ b/cmd/mycel/main.go
@@ -341,7 +341,7 @@
 }
 
 func finalize() {
-	js.Stop()
+	js.StopAll()
 	os.Exit(1)
 }
 
--- a/js/js.go
+++ b/js/js.go
@@ -13,6 +13,7 @@
 	"os"
 	"os/exec"
 	"strings"
+	"sync"
 	"time"
 )
 
@@ -19,21 +20,19 @@
 var timeout = 60 * time.Second
 
 var (
-	fetcher mycel.Fetcher
+	instances sync.Map
+)
 
+type JS struct {
 	service string
 	cmd *exec.Cmd
 	cancel  context.CancelFunc
-)
-
-func SetFetcher(f mycel.Fetcher) {
-	fetcher = f
 }
 
-func call(fn, cmd string, args ...string) (resp string, err error) {
+func (js *JS) call(fn, cmd string, args ...string) (resp string, err error) {
 	var rwc io.ReadWriteCloser
 	for t := 100*time.Millisecond; t < 5*time.Second; t *= 2 {
-		rwc, err = callSparkleCtl()
+		rwc, err = js.callSparkleCtl()
 		if err == nil {
 			break
 		}
@@ -57,28 +56,32 @@
 }
 
 // Start with pre-defined scripts
-func Start(scripts ...string) (resHtm string, changed bool, err error) {
-	service = fmt.Sprintf("sparkle.%d", os.Getpid())
+func Start(f mycel.Fetcher, scripts ...string) (js *JS, resHtm string, changed bool, err error) {
+	js = &JS{
+		service: fmt.Sprintf("sparkle.%d", os.Getpid()),
+	}
 	args := make([]string, 0, len(scripts)+2)
 	if log.Debug {
 		args = append(args, "-v")
 	}
-	args = append(args, "-s", service)
+	args = append(args, "-s", js.service)
 	log.Infof("Start sparklefs")
 
 	var ctx context.Context
-	ctx, cancel = context.WithCancel(fetcher.Ctx())
-	cmd = exec.CommandContext(ctx, "sparklefs", args...)
-	cmd.Stderr = os.Stderr
+	ctx, js.cancel = context.WithCancel(f.Ctx())
+	js.cmd = exec.CommandContext(ctx, "sparklefs", args...)
+	js.cmd.Stderr = os.Stderr
 
 	log.Infof("cmd.Start...")
-	if err = cmd.Start(); err != nil {
-		return "", false, fmt.Errorf("cmd start: %w", err)
+	if err = js.cmd.Start(); err != nil {
+		return nil, "", false, fmt.Errorf("cmd start: %w", err)
 	}
 
-	resp, err := call("ctl", "start")
+	instances.Store(js, js)
+
+	resp, err := js.call("ctl", "start")
 	if err != nil {
-		return "", false, fmt.Errorf("call start: %v", err)
+		return nil, "", false, fmt.Errorf("call start: %v", err)
 	}
 
 	if resp != "" {
@@ -89,26 +92,36 @@
 	return
 }
 
-func Stop() {
+func StopAll() {
+	instances.Range(func(k, _ any) bool {
+		js := k.(*JS)
+		js.Stop()
+		instances.Delete(js)
+		return true
+	})
+}
+
+func (js *JS) Stop() {
 	log.Infof("Stop sparklefs")
-	hangup()
-	if cancel != nil {
+	js.hangup()
+	if js.cancel != nil {
 		log.Infof("cancel()")
-		cancel()
-		cancel = nil
-		if cmd != nil {
+		js.cancel()
+		js.cancel = nil
+		if js.cmd != nil {
 			// Prevent Zombie processes after stopping
-			cmd.Wait()
-			cmd = nil
+			js.cmd.Wait()
+			js.cmd = nil
 		}
 	}
+	instances.Delete(js)
 }
 
 // TriggerClick, and return the result html
 // ...then HTML5 parse it, diff the node tree
 // (probably faster and cleaner than anything else)
-func TriggerClick(selector string) (newHTML string, ok bool, err error) {
-	newHTML, err = call("ctl", "click", selector)
+func (js *JS) TriggerClick(selector string) (newHTML string, ok bool, err error) {
+	newHTML, err = js.call("ctl", "click", selector)
 	ok = newHTML != "" && err == nil
 	return
 }
--- a/js/js_plan9.go
+++ b/js/js_plan9.go
@@ -5,8 +5,8 @@
 	"os"
 )
 
-func hangup() {}
+func (js *JS) hangup() {}
 
-func callSparkleCtl() (rwc io.ReadWriteCloser, err error) {
+func (js *JS) callSparkleCtl() (rwc io.ReadWriteCloser, err error) {
 	return os.OpenFile("/mnt/sparkle/ctl", os.O_RDWR, 0600)
 }
--- a/js/js_test.go
+++ b/js/js_test.go
@@ -26,7 +26,6 @@
 
 func init() {
 	log.Debug = true
-	SetFetcher(&TestFetcher{})
 	go fs.Srv9p()
 	<-time.After(2*time.Second)
 }
@@ -51,6 +50,7 @@
 }
 
 func TestJQueryHide(t *testing.T) {
+	f := &TestFetcher{}
 	if testing.Short() {
 		t.Skip("skipping test in short mode.")
 	}
@@ -73,7 +73,7 @@
 	fs.SetDOM(nt)
 	fs.Update("", simpleHTML, nil, []string{string(buf), script})
 
-	resHtm, changed, err := Start(string(buf), script)
+	svc, resHtm, changed, err := Start(f, string(buf), script)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -91,5 +91,5 @@
 	if v := nt.Find("h1").Css("display"); v != "none" {
 		t.Fatalf("%v", v)
 	}
-	Stop()
+	svc.Stop()
 }
--- a/js/js_unix.go
+++ b/js/js_unix.go
@@ -16,9 +16,9 @@
 	fsys *client.Fsys
 )
 
-func dial() (err error) {
+func (js *JS) dial() (err error) {
 	log.Infof("Init...")
-	conn, err := client.DialService(service)
+	conn, err := client.DialService(js.service)
 	if err != nil {
 		return fmt.Errorf("dial: %v", err)
 	}
@@ -34,7 +34,7 @@
 	return
 }
 
-func hangup() {
+func (js *JS) hangup() {
 	if fsys != nil {
 		fsys = nil
 	}
@@ -44,9 +44,9 @@
 	}
 }
 
-func callSparkleCtl() (rwc io.ReadWriteCloser, err error) {
+func (js *JS) callSparkleCtl() (rwc io.ReadWriteCloser, err error) {
 	if fsys == nil {
-		if err := dial(); err != nil {
+		if err := js.dial(); err != nil {
 			return nil, fmt.Errorf("dial: %v", err)
 		}
 	}
--