mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-14 12:23:43 +00:00
143 lines
3.6 KiB
Go
143 lines
3.6 KiB
Go
// Experimental adapter only; this is not a production engine switch.
|
|
package evaluation
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
production "github.com/GopeedLab/gopeed/pkg/download/engine"
|
|
"github.com/MoeForge/moejs"
|
|
"github.com/MoeForge/moejs/eventloop"
|
|
"github.com/MoeForge/moejs/webapi"
|
|
)
|
|
|
|
type Runtime interface {
|
|
Eval(string) (any, error)
|
|
Close()
|
|
}
|
|
|
|
type Goja struct{ *production.Engine }
|
|
|
|
func (g *Goja) Eval(s string) (any, error) { return g.RunString(s) }
|
|
|
|
type Moe struct {
|
|
rt *moejs.Runtime
|
|
loop *eventloop.Loop
|
|
api *webapi.API
|
|
}
|
|
|
|
func readHostScript(path string) (string, error) {
|
|
b, err := os.ReadFile(filepath.Join("..", "..", "pkg", "download", "engine", path))
|
|
return string(b), err
|
|
}
|
|
|
|
func New(name string) (Runtime, error) { return newRuntime(name, false) }
|
|
|
|
func newRuntime(name string, network bool) (Runtime, error) {
|
|
if name == "goja" {
|
|
return &Goja{production.NewEngine(nil)}, nil
|
|
}
|
|
if name != "moejs" && name != "moejs-frozen" {
|
|
return nil, fmt.Errorf("unknown engine %s", name)
|
|
}
|
|
// Preserve goja's mutable-builtin contract; shared builtins are a separate probe.
|
|
rt := moejs.NewRuntime(moejs.Options{MutableIntrinsics: name != "moejs-frozen", MaxDynamicSource: -1})
|
|
loop, err := eventloop.New(rt, eventloop.Options{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts := webapi.Options{}
|
|
if network {
|
|
opts.Client = &http.Client{Timeout: 20 * time.Second}
|
|
}
|
|
api, err := webapi.Install(loop, opts)
|
|
if err != nil {
|
|
loop.Terminate()
|
|
return nil, err
|
|
}
|
|
m := &Moe{rt, loop, api}
|
|
if err = loop.Start(); err != nil {
|
|
m.Close()
|
|
return nil, err
|
|
}
|
|
scripts := []string{"globalThis.global = globalThis; globalThis.window = globalThis; globalThis.location = new URL('http://localhost');"}
|
|
for _, path := range []string{"polyfill/out/index.js", "inject/xhr/xhr.js"} {
|
|
s, e := readHostScript(path)
|
|
if e != nil {
|
|
m.Close()
|
|
return nil, e
|
|
}
|
|
scripts = append(scripts, s)
|
|
}
|
|
for _, s := range scripts {
|
|
if _, e := m.Eval(s); e != nil {
|
|
m.Close()
|
|
return nil, e
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func (m *Moe) Close() { m.api.Close(); m.loop.Terminate() }
|
|
func (m *Moe) Eval(source string) (any, error) {
|
|
// Compile each evaluation, matching production Engine.RunString.
|
|
script, err := moejs.CompileScript("evaluation.js", source)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
type result struct {
|
|
value any
|
|
err error
|
|
}
|
|
ch := make(chan result, 1)
|
|
ok := m.loop.RunOnLoop(func(rt *moejs.Runtime) {
|
|
v, e := rt.RunScript(script)
|
|
if e != nil {
|
|
ch <- result{err: e}
|
|
return
|
|
}
|
|
if _, _, promise := moejs.PromiseResult(v); promise {
|
|
then, e := rt.Get(v, "then")
|
|
if e != nil {
|
|
ch <- result{err: e}
|
|
return
|
|
}
|
|
fulfilled := rt.Function("fulfilled", 1, func(_ *moejs.Realm, _ moejs.Value, args []moejs.Value) (moejs.Value, error) {
|
|
out, e := rt.ToGo(moejs.Arg(args, 0))
|
|
ch <- result{out, e}
|
|
return moejs.Undefined(), nil
|
|
})
|
|
rejected := rt.Function("rejected", 1, func(_ *moejs.Realm, _ moejs.Value, args []moejs.Value) (moejs.Value, error) {
|
|
v := moejs.Arg(args, 0)
|
|
stack, e := rt.Get(v, "stack")
|
|
message := v.String()
|
|
if e == nil && !stack.IsUndefined() {
|
|
message = stack.String()
|
|
}
|
|
ch <- result{err: fmt.Errorf("%s", message)}
|
|
return moejs.Undefined(), nil
|
|
})
|
|
_, e = rt.CallFunction(then, v, fulfilled, rejected)
|
|
if e != nil {
|
|
ch <- result{err: e}
|
|
}
|
|
return
|
|
}
|
|
out, e := rt.ToGo(v)
|
|
ch <- result{out, e}
|
|
})
|
|
if !ok {
|
|
return nil, fmt.Errorf("loop terminated")
|
|
}
|
|
select {
|
|
case r := <-ch:
|
|
return r.value, r.err
|
|
case <-time.After(30 * time.Second):
|
|
m.rt.Interrupt("evaluation timeout")
|
|
return nil, fmt.Errorf("evaluation timed out")
|
|
}
|
|
}
|