mirror of https://github.com/golang/go.git
syscall/js: garbage collect references to JavaScript values
The js.Value struct now contains a pointer, so a finalizer can determine if the value is not referenced by Go any more. Unfortunately this breaks Go's == operator with js.Value. This change adds a new Equal method to check for the equality of two Values. This is a breaking change. The == operator is now disallowed to not silently break code. Additionally the helper methods IsUndefined, IsNull and IsNaN got added. Fixes #35111 Change-Id: I58a50ca18f477bf51a259c668a8ba15bfa76c955 Reviewed-on: https://go-review.googlesource.com/c/go/+/203600 Run-TryBot: Richard Musiol <neelance@gmail.com> Reviewed-by: Cherry Zhang <cherryyz@google.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
063d0f11e5
commit
54e6ba6724
|
|
@ -205,26 +205,31 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ref = this._refs.get(v);
|
let id = this._ids.get(v);
|
||||||
if (ref === undefined) {
|
if (id === undefined) {
|
||||||
ref = this._values.length;
|
id = this._idPool.pop();
|
||||||
this._values.push(v);
|
if (id === undefined) {
|
||||||
this._refs.set(v, ref);
|
id = this._values.length;
|
||||||
|
}
|
||||||
|
this._values[id] = v;
|
||||||
|
this._goRefCounts[id] = 0;
|
||||||
|
this._ids.set(v, id);
|
||||||
}
|
}
|
||||||
let typeFlag = 0;
|
this._goRefCounts[id]++;
|
||||||
|
let typeFlag = 1;
|
||||||
switch (typeof v) {
|
switch (typeof v) {
|
||||||
case "string":
|
case "string":
|
||||||
typeFlag = 1;
|
|
||||||
break;
|
|
||||||
case "symbol":
|
|
||||||
typeFlag = 2;
|
typeFlag = 2;
|
||||||
break;
|
break;
|
||||||
case "function":
|
case "symbol":
|
||||||
typeFlag = 3;
|
typeFlag = 3;
|
||||||
break;
|
break;
|
||||||
|
case "function":
|
||||||
|
typeFlag = 4;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||||
this.mem.setUint32(addr, ref, true);
|
this.mem.setUint32(addr, id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadSlice = (addr) => {
|
const loadSlice = (addr) => {
|
||||||
|
|
@ -263,7 +268,9 @@
|
||||||
this.exited = true;
|
this.exited = true;
|
||||||
delete this._inst;
|
delete this._inst;
|
||||||
delete this._values;
|
delete this._values;
|
||||||
delete this._refs;
|
delete this._goRefCounts;
|
||||||
|
delete this._ids;
|
||||||
|
delete this._idPool;
|
||||||
this.exit(code);
|
this.exit(code);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -323,6 +330,18 @@
|
||||||
crypto.getRandomValues(loadSlice(sp + 8));
|
crypto.getRandomValues(loadSlice(sp + 8));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// func finalizeRef(v ref)
|
||||||
|
"syscall/js.finalizeRef": (sp) => {
|
||||||
|
const id = this.mem.getUint32(sp + 8, true);
|
||||||
|
this._goRefCounts[id]--;
|
||||||
|
if (this._goRefCounts[id] === 0) {
|
||||||
|
const v = this._values[id];
|
||||||
|
this._values[id] = null;
|
||||||
|
this._ids.delete(v);
|
||||||
|
this._idPool.push(id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// func stringVal(value string) ref
|
// func stringVal(value string) ref
|
||||||
"syscall/js.stringVal": (sp) => {
|
"syscall/js.stringVal": (sp) => {
|
||||||
storeValue(sp + 24, loadString(sp + 8));
|
storeValue(sp + 24, loadString(sp + 8));
|
||||||
|
|
@ -462,7 +481,7 @@
|
||||||
async run(instance) {
|
async run(instance) {
|
||||||
this._inst = instance;
|
this._inst = instance;
|
||||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||||
this._values = [ // TODO: garbage collection
|
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||||
NaN,
|
NaN,
|
||||||
0,
|
0,
|
||||||
null,
|
null,
|
||||||
|
|
@ -471,8 +490,10 @@
|
||||||
global,
|
global,
|
||||||
this,
|
this,
|
||||||
];
|
];
|
||||||
this._refs = new Map();
|
this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id
|
||||||
this.exited = false;
|
this._ids = new Map(); // mapping from JS values to reference ids
|
||||||
|
this._idPool = []; // unused ids that have been garbage collected
|
||||||
|
this.exited = false; // whether the Go program has exited
|
||||||
|
|
||||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||||
let offset = 4096;
|
let offset = 4096;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ const jsFetchCreds = "js.fetch:credentials"
|
||||||
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters
|
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters
|
||||||
const jsFetchRedirect = "js.fetch:redirect"
|
const jsFetchRedirect = "js.fetch:redirect"
|
||||||
|
|
||||||
var useFakeNetwork = js.Global().Get("fetch") == js.Undefined()
|
var useFakeNetwork = js.Global().Get("fetch").IsUndefined()
|
||||||
|
|
||||||
// RoundTrip implements the RoundTripper interface using the WHATWG Fetch API.
|
// RoundTrip implements the RoundTripper interface using the WHATWG Fetch API.
|
||||||
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||||
|
|
@ -50,7 +50,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ac := js.Global().Get("AbortController")
|
ac := js.Global().Get("AbortController")
|
||||||
if ac != js.Undefined() {
|
if !ac.IsUndefined() {
|
||||||
// Some browsers that support WASM don't necessarily support
|
// Some browsers that support WASM don't necessarily support
|
||||||
// the AbortController. See
|
// the AbortController. See
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/AbortController#Browser_compatibility.
|
// https://developer.mozilla.org/en-US/docs/Web/API/AbortController#Browser_compatibility.
|
||||||
|
|
@ -74,7 +74,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||||
opt.Set("redirect", h)
|
opt.Set("redirect", h)
|
||||||
req.Header.Del(jsFetchRedirect)
|
req.Header.Del(jsFetchRedirect)
|
||||||
}
|
}
|
||||||
if ac != js.Undefined() {
|
if !ac.IsUndefined() {
|
||||||
opt.Set("signal", ac.Get("signal"))
|
opt.Set("signal", ac.Get("signal"))
|
||||||
}
|
}
|
||||||
headers := js.Global().Get("Headers").New()
|
headers := js.Global().Get("Headers").New()
|
||||||
|
|
@ -132,7 +132,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||||
var body io.ReadCloser
|
var body io.ReadCloser
|
||||||
// The body is undefined when the browser does not support streaming response bodies (Firefox),
|
// The body is undefined when the browser does not support streaming response bodies (Firefox),
|
||||||
// and null in certain error cases, i.e. when the request is blocked because of CORS settings.
|
// and null in certain error cases, i.e. when the request is blocked because of CORS settings.
|
||||||
if b != js.Undefined() && b != js.Null() {
|
if !b.IsUndefined() && !b.IsNull() {
|
||||||
body = &streamReader{stream: b.Call("getReader")}
|
body = &streamReader{stream: b.Call("getReader")}
|
||||||
} else {
|
} else {
|
||||||
// Fall back to using ArrayBuffer
|
// Fall back to using ArrayBuffer
|
||||||
|
|
@ -168,7 +168,7 @@ func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||||
respPromise.Call("then", success, failure)
|
respPromise.Call("then", success, failure)
|
||||||
select {
|
select {
|
||||||
case <-req.Context().Done():
|
case <-req.Context().Done():
|
||||||
if ac != js.Undefined() {
|
if !ac.IsUndefined() {
|
||||||
// Abort the Fetch request
|
// Abort the Fetch request
|
||||||
ac.Call("abort")
|
ac.Call("abort")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -259,7 +259,7 @@ func Lchown(path string, uid, gid int) error {
|
||||||
if err := checkPath(path); err != nil {
|
if err := checkPath(path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if jsFS.Get("lchown") == js.Undefined() {
|
if jsFS.Get("lchown").IsUndefined() {
|
||||||
// fs.lchown is unavailable on Linux until Node.js 10.6.0
|
// fs.lchown is unavailable on Linux until Node.js 10.6.0
|
||||||
// TODO(neelance): remove when we require at least this Node.js version
|
// TODO(neelance): remove when we require at least this Node.js version
|
||||||
return ENOSYS
|
return ENOSYS
|
||||||
|
|
@ -497,7 +497,7 @@ func fsCall(name string, args ...interface{}) (js.Value, error) {
|
||||||
var res callResult
|
var res callResult
|
||||||
|
|
||||||
if len(args) >= 1 { // on Node.js 8, fs.utimes calls the callback without any arguments
|
if len(args) >= 1 { // on Node.js 8, fs.utimes calls the callback without any arguments
|
||||||
if jsErr := args[0]; jsErr != js.Null() {
|
if jsErr := args[0]; !jsErr.IsNull() {
|
||||||
res.err = mapJSError(jsErr)
|
res.err = mapJSError(jsErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build js,wasm
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
var JSGo = jsGo
|
||||||
|
|
@ -64,7 +64,7 @@ func init() {
|
||||||
|
|
||||||
func handleEvent() {
|
func handleEvent() {
|
||||||
cb := jsGo.Get("_pendingEvent")
|
cb := jsGo.Get("_pendingEvent")
|
||||||
if cb == Null() {
|
if cb.IsNull() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsGo.Set("_pendingEvent", Null())
|
jsGo.Set("_pendingEvent", Null())
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
package js
|
package js
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ import (
|
||||||
// The JavaScript value "undefined" is represented by the value 0.
|
// The JavaScript value "undefined" is represented by the value 0.
|
||||||
// A JavaScript number (64-bit float, except 0 and NaN) is represented by its IEEE 754 binary representation.
|
// A JavaScript number (64-bit float, except 0 and NaN) is represented by its IEEE 754 binary representation.
|
||||||
// All other values are represented as an IEEE 754 binary representation of NaN with bits 0-31 used as
|
// All other values are represented as an IEEE 754 binary representation of NaN with bits 0-31 used as
|
||||||
// an ID and bits 32-33 used to differentiate between string, symbol, function and object.
|
// an ID and bits 32-34 used to differentiate between string, symbol, function and object.
|
||||||
type ref uint64
|
type ref uint64
|
||||||
|
|
||||||
// nanHead are the upper 32 bits of a ref which are set if the value is not encoded as an IEEE 754 number (see above).
|
// nanHead are the upper 32 bits of a ref which are set if the value is not encoded as an IEEE 754 number (see above).
|
||||||
|
|
@ -33,21 +34,45 @@ type Wrapper interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value represents a JavaScript value. The zero value is the JavaScript value "undefined".
|
// Value represents a JavaScript value. The zero value is the JavaScript value "undefined".
|
||||||
|
// Values can be checked for equality with the Equal method.
|
||||||
type Value struct {
|
type Value struct {
|
||||||
ref ref
|
_ [0]func() // uncomparable; to make == not compile
|
||||||
|
ref ref // identifies a JavaScript value, see ref type
|
||||||
|
gcPtr *ref // used to trigger the finalizer when the Value is not referenced any more
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// the type flags need to be in sync with wasm_exec.js
|
||||||
|
typeFlagNone = iota
|
||||||
|
typeFlagObject
|
||||||
|
typeFlagString
|
||||||
|
typeFlagSymbol
|
||||||
|
typeFlagFunction
|
||||||
|
)
|
||||||
|
|
||||||
// JSValue implements Wrapper interface.
|
// JSValue implements Wrapper interface.
|
||||||
func (v Value) JSValue() Value {
|
func (v Value) JSValue() Value {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeValue(v ref) Value {
|
func makeValue(r ref) Value {
|
||||||
return Value{ref: v}
|
var gcPtr *ref
|
||||||
|
typeFlag := (r >> 32) & 7
|
||||||
|
if (r>>32)&nanHead == nanHead && typeFlag != typeFlagNone {
|
||||||
|
gcPtr = new(ref)
|
||||||
|
*gcPtr = r
|
||||||
|
runtime.SetFinalizer(gcPtr, func(p *ref) {
|
||||||
|
finalizeRef(*p)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value{ref: r, gcPtr: gcPtr}
|
||||||
}
|
}
|
||||||
|
|
||||||
func predefValue(id uint32) Value {
|
func finalizeRef(r ref)
|
||||||
return Value{ref: nanHead<<32 | ref(id)}
|
|
||||||
|
func predefValue(id uint32, typeFlag byte) Value {
|
||||||
|
return Value{ref: (nanHead|ref(typeFlag))<<32 | ref(id)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func floatValue(f float64) Value {
|
func floatValue(f float64) Value {
|
||||||
|
|
@ -73,28 +98,48 @@ func (e Error) Error() string {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
valueUndefined = Value{ref: 0}
|
valueUndefined = Value{ref: 0}
|
||||||
valueNaN = predefValue(0)
|
valueNaN = predefValue(0, typeFlagNone)
|
||||||
valueZero = predefValue(1)
|
valueZero = predefValue(1, typeFlagNone)
|
||||||
valueNull = predefValue(2)
|
valueNull = predefValue(2, typeFlagNone)
|
||||||
valueTrue = predefValue(3)
|
valueTrue = predefValue(3, typeFlagNone)
|
||||||
valueFalse = predefValue(4)
|
valueFalse = predefValue(4, typeFlagNone)
|
||||||
valueGlobal = predefValue(5)
|
valueGlobal = predefValue(5, typeFlagObject)
|
||||||
jsGo = predefValue(6) // instance of the Go class in JavaScript
|
jsGo = predefValue(6, typeFlagObject) // instance of the Go class in JavaScript
|
||||||
|
|
||||||
objectConstructor = valueGlobal.Get("Object")
|
objectConstructor = valueGlobal.Get("Object")
|
||||||
arrayConstructor = valueGlobal.Get("Array")
|
arrayConstructor = valueGlobal.Get("Array")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Equal reports whether v and w are equal according to JavaScript's === operator.
|
||||||
|
func (v Value) Equal(w Value) bool {
|
||||||
|
return v.ref == w.ref && v.ref != valueNaN.ref
|
||||||
|
}
|
||||||
|
|
||||||
// Undefined returns the JavaScript value "undefined".
|
// Undefined returns the JavaScript value "undefined".
|
||||||
func Undefined() Value {
|
func Undefined() Value {
|
||||||
return valueUndefined
|
return valueUndefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsUndefined reports whether v is the JavaScript value "undefined".
|
||||||
|
func (v Value) IsUndefined() bool {
|
||||||
|
return v.ref == valueUndefined.ref
|
||||||
|
}
|
||||||
|
|
||||||
// Null returns the JavaScript value "null".
|
// Null returns the JavaScript value "null".
|
||||||
func Null() Value {
|
func Null() Value {
|
||||||
return valueNull
|
return valueNull
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNull reports whether v is the JavaScript value "null".
|
||||||
|
func (v Value) IsNull() bool {
|
||||||
|
return v.ref == valueNull.ref
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNaN reports whether v is the JavaScript value "NaN".
|
||||||
|
func (v Value) IsNaN() bool {
|
||||||
|
return v.ref == valueNaN.ref
|
||||||
|
}
|
||||||
|
|
||||||
// Global returns the JavaScript global object, usually "window" or "global".
|
// Global returns the JavaScript global object, usually "window" or "global".
|
||||||
func Global() Value {
|
func Global() Value {
|
||||||
return valueGlobal
|
return valueGlobal
|
||||||
|
|
@ -232,16 +277,18 @@ func (v Value) Type() Type {
|
||||||
if v.isNumber() {
|
if v.isNumber() {
|
||||||
return TypeNumber
|
return TypeNumber
|
||||||
}
|
}
|
||||||
typeFlag := v.ref >> 32 & 3
|
typeFlag := (v.ref >> 32) & 7
|
||||||
switch typeFlag {
|
switch typeFlag {
|
||||||
case 1:
|
case typeFlagObject:
|
||||||
|
return TypeObject
|
||||||
|
case typeFlagString:
|
||||||
return TypeString
|
return TypeString
|
||||||
case 2:
|
case typeFlagSymbol:
|
||||||
return TypeSymbol
|
return TypeSymbol
|
||||||
case 3:
|
case typeFlagFunction:
|
||||||
return TypeFunction
|
return TypeFunction
|
||||||
default:
|
default:
|
||||||
return TypeObject
|
panic("bad type flag")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,7 +298,9 @@ func (v Value) Get(p string) Value {
|
||||||
if vType := v.Type(); !vType.isObject() {
|
if vType := v.Type(); !vType.isObject() {
|
||||||
panic(&ValueError{"Value.Get", vType})
|
panic(&ValueError{"Value.Get", vType})
|
||||||
}
|
}
|
||||||
return makeValue(valueGet(v.ref, p))
|
r := makeValue(valueGet(v.ref, p))
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueGet(v ref, p string) ref
|
func valueGet(v ref, p string) ref
|
||||||
|
|
@ -262,7 +311,10 @@ func (v Value) Set(p string, x interface{}) {
|
||||||
if vType := v.Type(); !vType.isObject() {
|
if vType := v.Type(); !vType.isObject() {
|
||||||
panic(&ValueError{"Value.Set", vType})
|
panic(&ValueError{"Value.Set", vType})
|
||||||
}
|
}
|
||||||
valueSet(v.ref, p, ValueOf(x).ref)
|
xv := ValueOf(x)
|
||||||
|
valueSet(v.ref, p, xv.ref)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
runtime.KeepAlive(xv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueSet(v ref, p string, x ref)
|
func valueSet(v ref, p string, x ref)
|
||||||
|
|
@ -274,6 +326,7 @@ func (v Value) Delete(p string) {
|
||||||
panic(&ValueError{"Value.Delete", vType})
|
panic(&ValueError{"Value.Delete", vType})
|
||||||
}
|
}
|
||||||
valueDelete(v.ref, p)
|
valueDelete(v.ref, p)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueDelete(v ref, p string)
|
func valueDelete(v ref, p string)
|
||||||
|
|
@ -284,7 +337,9 @@ func (v Value) Index(i int) Value {
|
||||||
if vType := v.Type(); !vType.isObject() {
|
if vType := v.Type(); !vType.isObject() {
|
||||||
panic(&ValueError{"Value.Index", vType})
|
panic(&ValueError{"Value.Index", vType})
|
||||||
}
|
}
|
||||||
return makeValue(valueIndex(v.ref, i))
|
r := makeValue(valueIndex(v.ref, i))
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueIndex(v ref, i int) ref
|
func valueIndex(v ref, i int) ref
|
||||||
|
|
@ -295,17 +350,23 @@ func (v Value) SetIndex(i int, x interface{}) {
|
||||||
if vType := v.Type(); !vType.isObject() {
|
if vType := v.Type(); !vType.isObject() {
|
||||||
panic(&ValueError{"Value.SetIndex", vType})
|
panic(&ValueError{"Value.SetIndex", vType})
|
||||||
}
|
}
|
||||||
valueSetIndex(v.ref, i, ValueOf(x).ref)
|
xv := ValueOf(x)
|
||||||
|
valueSetIndex(v.ref, i, xv.ref)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
runtime.KeepAlive(xv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueSetIndex(v ref, i int, x ref)
|
func valueSetIndex(v ref, i int, x ref)
|
||||||
|
|
||||||
func makeArgs(args []interface{}) []ref {
|
func makeArgs(args []interface{}) ([]Value, []ref) {
|
||||||
argVals := make([]ref, len(args))
|
argVals := make([]Value, len(args))
|
||||||
|
argRefs := make([]ref, len(args))
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
argVals[i] = ValueOf(arg).ref
|
v := ValueOf(arg)
|
||||||
|
argVals[i] = v
|
||||||
|
argRefs[i] = v.ref
|
||||||
}
|
}
|
||||||
return argVals
|
return argVals, argRefs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Length returns the JavaScript property "length" of v.
|
// Length returns the JavaScript property "length" of v.
|
||||||
|
|
@ -314,7 +375,9 @@ func (v Value) Length() int {
|
||||||
if vType := v.Type(); !vType.isObject() {
|
if vType := v.Type(); !vType.isObject() {
|
||||||
panic(&ValueError{"Value.SetIndex", vType})
|
panic(&ValueError{"Value.SetIndex", vType})
|
||||||
}
|
}
|
||||||
return valueLength(v.ref)
|
r := valueLength(v.ref)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueLength(v ref) int
|
func valueLength(v ref) int
|
||||||
|
|
@ -323,7 +386,10 @@ func valueLength(v ref) int
|
||||||
// It panics if v has no method m.
|
// It panics if v has no method m.
|
||||||
// The arguments get mapped to JavaScript values according to the ValueOf function.
|
// The arguments get mapped to JavaScript values according to the ValueOf function.
|
||||||
func (v Value) Call(m string, args ...interface{}) Value {
|
func (v Value) Call(m string, args ...interface{}) Value {
|
||||||
res, ok := valueCall(v.ref, m, makeArgs(args))
|
argVals, argRefs := makeArgs(args)
|
||||||
|
res, ok := valueCall(v.ref, m, argRefs)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
runtime.KeepAlive(argVals)
|
||||||
if !ok {
|
if !ok {
|
||||||
if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case
|
if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case
|
||||||
panic(&ValueError{"Value.Call", vType})
|
panic(&ValueError{"Value.Call", vType})
|
||||||
|
|
@ -342,7 +408,10 @@ func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||||
// It panics if v is not a JavaScript function.
|
// It panics if v is not a JavaScript function.
|
||||||
// The arguments get mapped to JavaScript values according to the ValueOf function.
|
// The arguments get mapped to JavaScript values according to the ValueOf function.
|
||||||
func (v Value) Invoke(args ...interface{}) Value {
|
func (v Value) Invoke(args ...interface{}) Value {
|
||||||
res, ok := valueInvoke(v.ref, makeArgs(args))
|
argVals, argRefs := makeArgs(args)
|
||||||
|
res, ok := valueInvoke(v.ref, argRefs)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
runtime.KeepAlive(argVals)
|
||||||
if !ok {
|
if !ok {
|
||||||
if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
|
if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
|
||||||
panic(&ValueError{"Value.Invoke", vType})
|
panic(&ValueError{"Value.Invoke", vType})
|
||||||
|
|
@ -358,7 +427,10 @@ func valueInvoke(v ref, args []ref) (ref, bool)
|
||||||
// It panics if v is not a JavaScript function.
|
// It panics if v is not a JavaScript function.
|
||||||
// The arguments get mapped to JavaScript values according to the ValueOf function.
|
// The arguments get mapped to JavaScript values according to the ValueOf function.
|
||||||
func (v Value) New(args ...interface{}) Value {
|
func (v Value) New(args ...interface{}) Value {
|
||||||
res, ok := valueNew(v.ref, makeArgs(args))
|
argVals, argRefs := makeArgs(args)
|
||||||
|
res, ok := valueNew(v.ref, argRefs)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
runtime.KeepAlive(argVals)
|
||||||
if !ok {
|
if !ok {
|
||||||
if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
|
if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case
|
||||||
panic(&ValueError{"Value.Invoke", vType})
|
panic(&ValueError{"Value.Invoke", vType})
|
||||||
|
|
@ -373,7 +445,7 @@ func valueNew(v ref, args []ref) (ref, bool)
|
||||||
func (v Value) isNumber() bool {
|
func (v Value) isNumber() bool {
|
||||||
return v.ref == valueZero.ref ||
|
return v.ref == valueZero.ref ||
|
||||||
v.ref == valueNaN.ref ||
|
v.ref == valueNaN.ref ||
|
||||||
(v.ref != valueUndefined.ref && v.ref>>32&nanHead != nanHead)
|
(v.ref != valueUndefined.ref && (v.ref>>32)&nanHead != nanHead)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Value) float(method string) float64 {
|
func (v Value) float(method string) float64 {
|
||||||
|
|
@ -438,15 +510,15 @@ func (v Value) Truthy() bool {
|
||||||
func (v Value) String() string {
|
func (v Value) String() string {
|
||||||
switch v.Type() {
|
switch v.Type() {
|
||||||
case TypeString:
|
case TypeString:
|
||||||
return jsString(v.ref)
|
return jsString(v)
|
||||||
case TypeUndefined:
|
case TypeUndefined:
|
||||||
return "<undefined>"
|
return "<undefined>"
|
||||||
case TypeNull:
|
case TypeNull:
|
||||||
return "<null>"
|
return "<null>"
|
||||||
case TypeBoolean:
|
case TypeBoolean:
|
||||||
return "<boolean: " + jsString(v.ref) + ">"
|
return "<boolean: " + jsString(v) + ">"
|
||||||
case TypeNumber:
|
case TypeNumber:
|
||||||
return "<number: " + jsString(v.ref) + ">"
|
return "<number: " + jsString(v) + ">"
|
||||||
case TypeSymbol:
|
case TypeSymbol:
|
||||||
return "<symbol>"
|
return "<symbol>"
|
||||||
case TypeObject:
|
case TypeObject:
|
||||||
|
|
@ -458,10 +530,12 @@ func (v Value) String() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsString(v ref) string {
|
func jsString(v Value) string {
|
||||||
str, length := valuePrepareString(v)
|
str, length := valuePrepareString(v.ref)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
b := make([]byte, length)
|
b := make([]byte, length)
|
||||||
valueLoadString(str, b)
|
valueLoadString(str, b)
|
||||||
|
finalizeRef(str)
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -471,7 +545,10 @@ func valueLoadString(v ref, b []byte)
|
||||||
|
|
||||||
// InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
|
// InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator.
|
||||||
func (v Value) InstanceOf(t Value) bool {
|
func (v Value) InstanceOf(t Value) bool {
|
||||||
return valueInstanceOf(v.ref, t.ref)
|
r := valueInstanceOf(v.ref, t.ref)
|
||||||
|
runtime.KeepAlive(v)
|
||||||
|
runtime.KeepAlive(t)
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueInstanceOf(v ref, t ref) bool
|
func valueInstanceOf(v ref, t ref) bool
|
||||||
|
|
@ -493,6 +570,7 @@ func (e *ValueError) Error() string {
|
||||||
// CopyBytesToGo panics if src is not an Uint8Array.
|
// CopyBytesToGo panics if src is not an Uint8Array.
|
||||||
func CopyBytesToGo(dst []byte, src Value) int {
|
func CopyBytesToGo(dst []byte, src Value) int {
|
||||||
n, ok := copyBytesToGo(dst, src.ref)
|
n, ok := copyBytesToGo(dst, src.ref)
|
||||||
|
runtime.KeepAlive(src)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("syscall/js: CopyBytesToGo: expected src to be an Uint8Array")
|
panic("syscall/js: CopyBytesToGo: expected src to be an Uint8Array")
|
||||||
}
|
}
|
||||||
|
|
@ -506,6 +584,7 @@ func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||||
// CopyBytesToJS panics if dst is not an Uint8Array.
|
// CopyBytesToJS panics if dst is not an Uint8Array.
|
||||||
func CopyBytesToJS(dst Value, src []byte) int {
|
func CopyBytesToJS(dst Value, src []byte) int {
|
||||||
n, ok := copyBytesToJS(dst.ref, src)
|
n, ok := copyBytesToJS(dst.ref, src)
|
||||||
|
runtime.KeepAlive(dst)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("syscall/js: CopyBytesToJS: expected dst to be an Uint8Array")
|
panic("syscall/js: CopyBytesToJS: expected dst to be an Uint8Array")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·finalizeRef(SB), NOSPLIT, $0
|
||||||
|
CallImport
|
||||||
|
RET
|
||||||
|
|
||||||
TEXT ·stringVal(SB), NOSPLIT, $0
|
TEXT ·stringVal(SB), NOSPLIT, $0
|
||||||
CallImport
|
CallImport
|
||||||
RET
|
RET
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package js_test
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"runtime"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -53,7 +54,7 @@ func TestBool(t *testing.T) {
|
||||||
if got := dummys.Get("otherBool").Bool(); got != want {
|
if got := dummys.Get("otherBool").Bool(); got != want {
|
||||||
t.Errorf("got %#v, want %#v", got, want)
|
t.Errorf("got %#v, want %#v", got, want)
|
||||||
}
|
}
|
||||||
if dummys.Get("someBool") != dummys.Get("someBool") {
|
if !dummys.Get("someBool").Equal(dummys.Get("someBool")) {
|
||||||
t.Errorf("same value not equal")
|
t.Errorf("same value not equal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +69,7 @@ func TestString(t *testing.T) {
|
||||||
if got := dummys.Get("otherString").String(); got != want {
|
if got := dummys.Get("otherString").String(); got != want {
|
||||||
t.Errorf("got %#v, want %#v", got, want)
|
t.Errorf("got %#v, want %#v", got, want)
|
||||||
}
|
}
|
||||||
if dummys.Get("someString") != dummys.Get("someString") {
|
if !dummys.Get("someString").Equal(dummys.Get("someString")) {
|
||||||
t.Errorf("same value not equal")
|
t.Errorf("same value not equal")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +106,7 @@ func TestInt(t *testing.T) {
|
||||||
if got := dummys.Get("otherInt").Int(); got != want {
|
if got := dummys.Get("otherInt").Int(); got != want {
|
||||||
t.Errorf("got %#v, want %#v", got, want)
|
t.Errorf("got %#v, want %#v", got, want)
|
||||||
}
|
}
|
||||||
if dummys.Get("someInt") != dummys.Get("someInt") {
|
if !dummys.Get("someInt").Equal(dummys.Get("someInt")) {
|
||||||
t.Errorf("same value not equal")
|
t.Errorf("same value not equal")
|
||||||
}
|
}
|
||||||
if got := dummys.Get("zero").Int(); got != 0 {
|
if got := dummys.Get("zero").Int(); got != 0 {
|
||||||
|
|
@ -141,20 +142,20 @@ func TestFloat(t *testing.T) {
|
||||||
if got := dummys.Get("otherFloat").Float(); got != want {
|
if got := dummys.Get("otherFloat").Float(); got != want {
|
||||||
t.Errorf("got %#v, want %#v", got, want)
|
t.Errorf("got %#v, want %#v", got, want)
|
||||||
}
|
}
|
||||||
if dummys.Get("someFloat") != dummys.Get("someFloat") {
|
if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
|
||||||
t.Errorf("same value not equal")
|
t.Errorf("same value not equal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestObject(t *testing.T) {
|
func TestObject(t *testing.T) {
|
||||||
if dummys.Get("someArray") != dummys.Get("someArray") {
|
if !dummys.Get("someArray").Equal(dummys.Get("someArray")) {
|
||||||
t.Errorf("same value not equal")
|
t.Errorf("same value not equal")
|
||||||
}
|
}
|
||||||
|
|
||||||
// An object and its prototype should not be equal.
|
// An object and its prototype should not be equal.
|
||||||
proto := js.Global().Get("Object").Get("prototype")
|
proto := js.Global().Get("Object").Get("prototype")
|
||||||
o := js.Global().Call("eval", "new Object()")
|
o := js.Global().Call("eval", "new Object()")
|
||||||
if proto == o {
|
if proto.Equal(o) {
|
||||||
t.Errorf("object equals to its prototype")
|
t.Errorf("object equals to its prototype")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -167,26 +168,66 @@ func TestFrozenObject(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEqual(t *testing.T) {
|
||||||
|
if !dummys.Get("someFloat").Equal(dummys.Get("someFloat")) {
|
||||||
|
t.Errorf("same float is not equal")
|
||||||
|
}
|
||||||
|
if !dummys.Get("emptyObj").Equal(dummys.Get("emptyObj")) {
|
||||||
|
t.Errorf("same object is not equal")
|
||||||
|
}
|
||||||
|
if dummys.Get("someFloat").Equal(dummys.Get("someInt")) {
|
||||||
|
t.Errorf("different values are not unequal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNaN(t *testing.T) {
|
func TestNaN(t *testing.T) {
|
||||||
want := js.ValueOf(math.NaN())
|
if !dummys.Get("NaN").IsNaN() {
|
||||||
got := dummys.Get("NaN")
|
t.Errorf("JS NaN is not NaN")
|
||||||
if got != want {
|
}
|
||||||
t.Errorf("got %#v, want %#v", got, want)
|
if !js.ValueOf(math.NaN()).IsNaN() {
|
||||||
|
t.Errorf("Go NaN is not NaN")
|
||||||
|
}
|
||||||
|
if dummys.Get("NaN").Equal(dummys.Get("NaN")) {
|
||||||
|
t.Errorf("NaN is equal to NaN")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUndefined(t *testing.T) {
|
func TestUndefined(t *testing.T) {
|
||||||
dummys.Set("test", js.Undefined())
|
if !js.Undefined().IsUndefined() {
|
||||||
if dummys == js.Undefined() || dummys.Get("test") != js.Undefined() || dummys.Get("xyz") != js.Undefined() {
|
t.Errorf("undefined is not undefined")
|
||||||
t.Errorf("js.Undefined expected")
|
}
|
||||||
|
if !js.Undefined().Equal(js.Undefined()) {
|
||||||
|
t.Errorf("undefined is not equal to undefined")
|
||||||
|
}
|
||||||
|
if dummys.IsUndefined() {
|
||||||
|
t.Errorf("object is undefined")
|
||||||
|
}
|
||||||
|
if js.Undefined().IsNull() {
|
||||||
|
t.Errorf("undefined is null")
|
||||||
|
}
|
||||||
|
if dummys.Set("test", js.Undefined()); !dummys.Get("test").IsUndefined() {
|
||||||
|
t.Errorf("could not set undefined")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNull(t *testing.T) {
|
func TestNull(t *testing.T) {
|
||||||
dummys.Set("test1", nil)
|
if !js.Null().IsNull() {
|
||||||
dummys.Set("test2", js.Null())
|
t.Errorf("null is not null")
|
||||||
if dummys == js.Null() || dummys.Get("test1") != js.Null() || dummys.Get("test2") != js.Null() {
|
}
|
||||||
t.Errorf("js.Null expected")
|
if !js.Null().Equal(js.Null()) {
|
||||||
|
t.Errorf("null is not equal to null")
|
||||||
|
}
|
||||||
|
if dummys.IsNull() {
|
||||||
|
t.Errorf("object is null")
|
||||||
|
}
|
||||||
|
if js.Null().IsUndefined() {
|
||||||
|
t.Errorf("null is undefined")
|
||||||
|
}
|
||||||
|
if dummys.Set("test", js.Null()); !dummys.Get("test").IsNull() {
|
||||||
|
t.Errorf("could not set null")
|
||||||
|
}
|
||||||
|
if dummys.Set("test", nil); !dummys.Get("test").IsNull() {
|
||||||
|
t.Errorf("could not set nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -340,7 +381,7 @@ func TestValueOf(t *testing.T) {
|
||||||
|
|
||||||
func TestZeroValue(t *testing.T) {
|
func TestZeroValue(t *testing.T) {
|
||||||
var v js.Value
|
var v js.Value
|
||||||
if v != js.Undefined() {
|
if !v.IsUndefined() {
|
||||||
t.Error("zero js.Value is not js.Undefined()")
|
t.Error("zero js.Value is not js.Undefined()")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -497,12 +538,24 @@ func TestCopyBytesToJS(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGarbageCollection(t *testing.T) {
|
||||||
|
before := js.JSGo.Get("_values").Length()
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
_ = js.Global().Get("Object").New().Call("toString").String()
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
after := js.JSGo.Get("_values").Length()
|
||||||
|
if after-before > 500 {
|
||||||
|
t.Errorf("garbage collection ineffective")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BenchmarkDOM is a simple benchmark which emulates a webapp making DOM operations.
|
// BenchmarkDOM is a simple benchmark which emulates a webapp making DOM operations.
|
||||||
// It creates a div, and sets its id. Then searches by that id and sets some data.
|
// It creates a div, and sets its id. Then searches by that id and sets some data.
|
||||||
// Finally it removes that div.
|
// Finally it removes that div.
|
||||||
func BenchmarkDOM(b *testing.B) {
|
func BenchmarkDOM(b *testing.B) {
|
||||||
document := js.Global().Get("document")
|
document := js.Global().Get("document")
|
||||||
if document == js.Undefined() {
|
if document.IsUndefined() {
|
||||||
b.Skip("Not a browser environment. Skipping.")
|
b.Skip("Not a browser environment. Skipping.")
|
||||||
}
|
}
|
||||||
const data = "someString"
|
const data = "someString"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue