shithub: opossum

Download patch

ref: cdae93dae8b27ebf26cca981be01088e01e9f865
parent: 3ce4cfc9371c16b2ac4ac167668122fca13cc850
author: Philip Silva <philip.silva@protonmail.com>
date: Mon Dec 28 08:53:33 EST 2020

merge exec and export

--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@
 ~*
 .DS_Store
 /cmd/browse/browse
+main.js
--- a/browser/experimental.go
+++ b/browser/experimental.go
@@ -3,7 +3,6 @@
 import (
 	"fmt"
 	"image"
-	"strings"
 	"opossum/domino"
 	"opossum/nodes"
 	"time"
@@ -137,42 +136,16 @@
 	})
 }
 
-func processJS(htm string) (resHtm string, err error) {
-	_ = strings.Replace(htm, "window.", "", -1)
-	d := domino.NewDomino(htm)
-	d.Start()
-	if err = d.ExecInlinedScripts(); err != nil {
-		return "", fmt.Errorf("exec <script>s: %w", err)
-	}
-	time.Sleep(time.Second)
-	resHtm, changed, err := d.TrackChanges()
-	log.Infof("processJS: changes = %v", changed)
-	d.Stop()
-	return
-}
-
 func processJS2(d *domino.Domino, doc *nodes.Node, scripts []string) (resHtm string, err error) {
-	code := ""
+	initialized := false
 	for _, script := range scripts {
-		code += `
-			try {
-		` + script + `;
-		` + fmt.Sprintf(`
-			console.log('==============');
-			console.log('Success!');
-			console.log('==============');
-		`) + `
-			} catch(e) {
-				console.log('==============');
-				console.log('Catch:');
-				console.log(e);
-				console.log('==============');
-			}
-		`
+		if _, err := d.Exec/*6*/(script, !initialized); err == nil {
+			initialized = true
+		} else {
+			log.Errorf("exec <script>: %v", err)
+		}
 	}
-	if err = d.Exec/*6*/(code); err != nil {
-		return "", fmt.Errorf("exec <script>s: %w", err)
-	}
+
 	time.Sleep(time.Second)
 	resHtm, changed, err := d.TrackChanges()
 	if err != nil {
--- a/domino/domino.go
+++ b/domino/domino.go
@@ -20,8 +20,8 @@
 var DebugDumpJS *bool
 
 type Domino struct {
+	initialized bool
 	loop       *eventloop.EventLoop
-	vm         *goja.Runtime
 	html       string
 	outputHtml string
 	domChanged chan int
@@ -56,7 +56,6 @@
 		yx := strings.Split(yxStart, ":")
 		y, _ := strconv.Atoi(yx[0])
 		x, _ := strconv.Atoi(yx[1])
-		log.Printf("line %v, column %v", y, x)
 		lines := strings.Split(script, "\n")
 
 		if wholeLine := lines[y-1]; len(wholeLine) > 100 {
@@ -81,18 +80,19 @@
 	}
 }
 
-func (d *Domino) Exec(script string) (err error) {
+func (d *Domino) Exec(script string, initial bool) (res string, err error) {
+	if !initial && !d.initialized {
+		initial = true
+	}
 	script = strings.Replace(script, "const ", "var ", -1)
 	script = strings.Replace(script, "let ", "var ", -1)
 	script = strings.Replace(script, "<!--", "", -1)
 	SCRIPT := `
-	    global = {};
-	    //global.__domino_frozen__ = true; // Must precede any require('domino')
-	    var domino = require('domino-lib/index');
-	    var Element = domino.impl.Element; // etc
+		global = {};
+		//global.__domino_frozen__ = true; // Must precede any require('domino')
+		var domino = require('domino-lib/index');
+		var Element = domino.impl.Element; // etc
 
-	    // JSDOM also knows the style tag
-	    // https://github.com/jsdom/jsdom/issues/2485
 		Object.assign(this, domino.createWindow(s.html, 'http://example.com'));
 		window = this;
 		window.parent = window;
@@ -102,54 +102,57 @@
 		window.location.href = 'http://example.com';
 		navigator = {};
 		HTMLElement = domino.impl.HTMLElement;
-	    // Fire DOMContentLoaded
-	    // to trigger $(document)readfy!!!!!!!
-	    document.close();
+		// Fire DOMContentLoaded to trigger $(document).ready(..)
+		document.close();
 	` + script
+	if !initial {
+		SCRIPT = script
+	}
 	if *DebugDumpJS {
 		ioutil.WriteFile("main.js", []byte(SCRIPT), 0644)
 	}
 
-	ready := make(chan int)
+	ready := make(chan goja.Value)
 	go func() {
 		d.loop.RunOnLoop(func(vm *goja.Runtime) {
 			log.Printf("RunOnLoop")
-			registry := require.NewRegistry(
-				require.WithGlobalFolders(".", ".."),
-			)
-			console.Enable(vm)
-			req := registry.Enable(vm)
-			_ = req
+			if initial {
+				registry := require.NewRegistry(
+					require.WithGlobalFolders(".", ".."),
+				)
+				console.Enable(vm)
+				req := registry.Enable(vm)
+				_ = req
 
-			vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
-			type S struct {
-				Buf  string `json:"buf"`
-				HTML string `json:"html"`
-			}
-			d.vm = vm
+				vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
+				type S struct {
+					Buf  string `json:"buf"`
+					HTML string `json:"html"`
+				}
 
-			vm.Set("s", S{
-				HTML: d.html,
-				Buf:  "yolo",
-			})
-			_, err := vm.RunString(SCRIPT)
+				vm.Set("s", S{
+					HTML: d.html,
+					Buf:  "yolo",
+				})
+			}
+			vv, err := vm.RunString(SCRIPT)
 			if err != nil {
 				log.Printf("run program: %v", err)
 				IntrospectError(err, script)
 			}
-			ready <- 1
+			ready <- vv
 		})
 	}()
-	<-ready
+	v := <-ready
 	<-time.After(10 * time.Millisecond)
-	//res = fmt.Sprintf("%v", v.Export())
-	if _, _, err = d.TrackChanges(); err != nil {
-		return fmt.Errorf("track changes: %w", err)
+	if v != nil {
+		res = v.String()
 	}
+	if err == nil { d.initialized=true }
 	return
 }
 
-func (d *Domino) Exec6(script string) (err error) {
+func (d *Domino) Exec6(script string) (res string, err error) {
 	babel.Init(4) // Setup 4 transformers (can be any number > 0)
 	r, err := babel.Transform(strings.NewReader(script), map[string]interface{}{
 		"plugins": []string{
@@ -158,67 +161,40 @@
 		},
 	})
 	if err != nil {
-		return fmt.Errorf("babel: %v", err)
+		return "", fmt.Errorf("babel: %v", err)
 	}
 	buf, err := ioutil.ReadAll(r)
 	if err != nil {
-		return fmt.Errorf("read all: %v", err)
+		return "", fmt.Errorf("read all: %v", err)
 	}
-	return d.Exec(string(buf))
+	return d.Exec(string(buf), true)
 }
 
-func (d *Domino) Export(expr string) (res string, err error) {
-	var v goja.Value
-	ch := make(chan int, 1)
-
-	d.loop.RunOnLoop(func(vm *goja.Runtime) {
-		v, err = vm.RunString(expr)
-		ch <- 1
-	})
-
-	<-ch
-
-	if err != nil {
-		return "", fmt.Errorf("export: %w", err)
-	}
-	if v != nil {
-		res = fmt.Sprintf("%v", v.Export())
-	}
-
-	return
-}
-
 // TriggerClick, and return the result html
 // ...then HTML5 parse it, diff the node tree
 // (probably faster and cleaner than anything else)
 func (d *Domino) TriggerClick(selector string) (newHTML string, ok bool, err error) {
-	var res goja.Value
-	ch := make(chan int, 1)
+	res, err := d.Exec(`
+		var sel = '` + selector + `';
+		var el = document.querySelector(sel);
 
-	d.loop.RunOnLoop(func(vm *goja.Runtime) {
-		res, err = vm.RunString(`
-			var sel = '` + selector + `';
-			console.log('sel=');
-			console.log(sel);
-			var sell = document.querySelector(sel);
-			if (sell._listeners && sell._listeners.click) {
-				var selfn = sell.click.bind(sell);
-				if (selfn) {
-					selfn();
-				}
-				!!selfn;
-			} else {
-				false;
-			}
-		`)
-		ch <- 1
-	})
-	<- ch
+		console.log('query ' + sel);
 
+		if (el._listeners && el._listeners.click) {
+			var fn = el.click.bind(el);
 
-	ok = fmt.Sprintf("%v", res) == "true"
+			if (fn) {
+				console.log('  call click handler...');
+				fn();
+			}
 
-	if ok {
+			!!fn;
+		} else {
+			false;
+		}
+	`, false)
+
+	if ok = res == "true"; ok {
 		newHTML, ok, err = d.TrackChanges()
 	}
 
@@ -227,20 +203,20 @@
 
 // Put change into html (e.g. from input field mutation)
 func (d *Domino) PutAttr(selector, attr, val string) (ok bool, err error) {
-	res, err := d.vm.RunString(`
+	res, err := d.Exec(`
 		var sel = '` + selector + `';
-		var sell = document.querySelector(sel);
-		sell.attr('` + attr + `', '` + val + `');
-		!!sell;
-	`)
+		var el = document.querySelector(sel);
+		el.attr('` + attr + `', '` + val + `');
+		!!el;
+	`, false)
 
-	ok = fmt.Sprintf("%v", res) == "true"
+	ok = res == "true"
 
 	return
 }
 
 func (d *Domino) TrackChanges() (html string, changed bool, err error) {
-	html, err = d.Export("document.querySelector('html').innerHTML;")
+	html, err = d.Exec("document.querySelector('html').innerHTML;", false)
 	if err != nil {
 		return
 	}
@@ -247,30 +223,6 @@
 	changed = d.outputHtml != html
 	d.outputHtml = html
 	return
-}
-
-// https://stackoverflow.com/a/26716182
-// TODO: eval is evil
-func (d *Domino) ExecInlinedScripts() (err error) {
-	return d.Exec(`
-	navigator = {};
-
-    var scripts = Array.prototype.slice.call(document.getElementsByTagName("script"));
-    for (var i = 0; i < scripts.length; i++) {
-        if (scripts[i].src != "") {
-            var tag = document.createElement("script");
-            tag.src = scripts[i].src;
-            document.getElementsByTagName("head")[0].appendChild(tag);
-        }
-        else {
-        	try {
-            	eval.call(window, scripts[i].innerHTML);
-            } catch(e) {
-            	console.log(e);
-            }
-        }
-    }
-	`)
 }
 
 func Srcs(doc *nodes.Node) (srcs []string) {
--- a/domino/domino_test.go
+++ b/domino/domino_test.go
@@ -23,25 +23,36 @@
 func TestSimple(t *testing.T) {
 	d := NewDomino(simpleHTML)
 	d.Start()
-	script := `
-	console.log('Hello!!');
-	var numberOne = 1;
+	s := `
+	var state = 'empty';
+	var a = 1;
+	b = 2;
 	`
-	err := d.Exec(script)
+	_, err := d.Exec(s, true)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	res, err := d.Export("numberOne+1")
-	t.Logf("res=%v", res)
+	s2 := `
+	(function() {
+		if (state !== 'empty') throw new Exception(state);
+
+		state = a + b;
+	})()
+	var a = 1;
+	b = 2;
+	`
+	_, err = d.Exec(s2, false)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	if res != "2" {
-		t.Fatal()
-	}
 	d.Stop()
 }
 
+func TestGlobals(t *testing.T) {
+	d := NewDomino(simpleHTML)
+	d.Start()
+}
+
 func TestJQuery(t *testing.T) {
 	buf, err := ioutil.ReadFile("jquery-3.5.1.js")
 	if err != nil {
@@ -66,11 +77,11 @@
 	var numberOne = 1;
 	`
 	_=buf
-	err = d.Exec(string(buf) + ";" + script)
+	_, err = d.Exec(string(buf) + ";" + script, true)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	res, err := d.Export("numberOne+1")
+	res, err := d.Exec("numberOne+1", false)
 	t.Logf("res=%v", res)
 	if err != nil {
 		t.Fatalf("%v", err)
@@ -111,13 +122,13 @@
 	`
 	d := NewDomino(simpleHTML)
 	d.Start()
-	err = d.Exec(SCRIPT)
+	_, err = d.Exec(SCRIPT, true)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
 
 	time.Sleep(2 * time.Second)
-	res, err := d.Export("$('h1').html()")
+	res, err := d.Exec("$('h1').html()", false)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
@@ -148,13 +159,13 @@
 	`
 	d := NewDomino(simpleHTML)
 	d.Start()
-	err = d.Exec(SCRIPT)
+	_, err = d.Exec(SCRIPT, true)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
 
 	//time.Sleep(2 * time.Second)
-	res, err := d.Export("$('h1').html()")
+	res, err := d.Exec("$('h1').html()", false)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
@@ -161,6 +172,10 @@
 	if res != "Hello" {
 		t.Fatalf(res)
 	}
+
+	if _, _, err = d.TrackChanges(); err != nil {
+		t.Fatalf(err.Error())
+	}
 	_, changed, err := d.TriggerClick("h1")
 	if err != nil {
 		t.Fatalf(err.Error())
@@ -168,7 +183,7 @@
 	if changed {
 		t.Fatal()
 	}
-	res, err = d.Export("clicked")
+	res, err = d.Exec("clicked", false)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
@@ -207,13 +222,13 @@
 	`
 	d := NewDomino(simpleHTML)
 	d.Start()
-	err = d.Exec(SCRIPT)
+	_, err = d.Exec(SCRIPT, true)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
 
 	time.Sleep(2 * time.Second)
-	res, err := d.Export("$('h1').html()")
+	res, err := d.Exec("$('h1').html()", false)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
@@ -221,7 +236,7 @@
 		t.Fatalf(res)
 	}*/
 	_=res
-	res, err = d.Export("$('h1').html('minor updates :-)'); $('h1').html();")
+	res, err = d.Exec("$('h1').html('minor updates :-)'); $('h1').html();", false)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
@@ -233,10 +248,14 @@
 func TestTrackChanges(t *testing.T) {
 	d := NewDomino(simpleHTML)
 	d.Start()
-	err := d.Exec(``)
+	_, err := d.Exec(``, true)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
+	// 0th time: init
+	if _, _, err = d.TrackChanges(); err != nil {
+		t.Fatalf(err.Error())
+	}
 	// 1st time: no change
 	html, changed, err := d.TrackChanges()
 	if err != nil {
@@ -259,7 +278,7 @@
 	if changed == true {
 		t.Fatal()
 	}
-	_, err = d.Export("document.getElementById('title').innerHTML='new title'; true;")
+	_, err = d.Exec("document.getElementById('title').innerHTML='new title'; true;", false)
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
@@ -280,7 +299,7 @@
 	d.Stop()
 }
 
-func TestExecInlinedScripts(t *testing.T) {
+/*func TestExecInlinedScripts(t *testing.T) {
 	const h = `
 	<html>
 	<body>
@@ -305,9 +324,9 @@
 		t.Fatalf(res)
 	}
 	d.Stop()
-}
+}*/
 
-func TestWindowEqualsGlobal(t *testing.T) {
+/*func TestWindowEqualsGlobal(t *testing.T) {
 	const h = `
 	<html>
 	<body>
@@ -347,7 +366,7 @@
 		t.Fatalf(res)
 	}
 	d.Stop()
-}
+}*/
 
 func TestES6(t *testing.T) {
 	d := NewDomino(simpleHTML)
@@ -356,11 +375,11 @@
 	console.log('Hello!!');
 	const numberOne = 1;
 	`
-	err := d.Exec6(script)
+	_, err := d.Exec6(script)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	res, err := d.Export("numberOne+1")
+	res, err := d.Exec("numberOne+1", false)
 	t.Logf("res=%v", res)
 	if err != nil {
 		t.Fatalf("%v", err)
@@ -377,11 +396,11 @@
 	script := `
 	console.log('Hello!!')
 	`
-	err := d.Exec(script)
+	_, err := d.Exec(script, true)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
-	res, err := d.Export("window === window.parent")
+	res, err := d.Exec("window === window.parent", false)
 	t.Logf("res=%v", res)
 	if err != nil {
 		t.Fatalf("%v", err)
--- a/domino/main.js
+++ /dev/null
@@ -1,23 +1,0 @@
-
-	    global = {};
-	    //global.__domino_frozen__ = true; // Must precede any require('domino')
-	    var domino = require('domino-lib/index');
-	    var Element = domino.impl.Element; // etc
-
-	    // JSDOM also knows the style tag
-	    // https://github.com/jsdom/jsdom/issues/2485
-		Object.assign(this, domino.createWindow(s.html, 'http://example.com'));
-		window = this;
-		window.parent = window;
-		window.top = window;
-		window.self = window;
-		addEventListener = function() {};
-		window.location.href = 'http://example.com';
-		navigator = {};
-		HTMLElement = domino.impl.HTMLElement;
-	    // Fire DOMContentLoaded
-	    // to trigger $(document)readfy!!!!!!!
-	    document.close();
-	
-	console.log('Hello!!')
-	
\ No newline at end of file