Source file src/syscall/js/func.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build js && wasm
     6  
     7  package js
     8  
     9  import "sync"
    10  
    11  var (
    12  	funcsMu    sync.Mutex
    13  	funcs             = make(map[uint32]func(Value, []Value) any)
    14  	nextFuncID uint32 = 1
    15  )
    16  
    17  // Func is a wrapped Go function to be called by JavaScript.
    18  type Func struct {
    19  	Value // the JavaScript function that invokes the Go function
    20  	id    uint32
    21  }
    22  
    23  // FuncOf returns a function to be used by JavaScript.
    24  //
    25  // The Go function fn is called with the value of JavaScript's "this" keyword and the
    26  // arguments of the invocation. The return value of the invocation is
    27  // the result of the Go function mapped back to JavaScript according to ValueOf.
    28  //
    29  // Invoking the wrapped Go function from JavaScript will
    30  // pause the event loop and spawn a new goroutine.
    31  // Other wrapped functions which are triggered during a call from Go to JavaScript
    32  // get executed on the same goroutine.
    33  //
    34  // As a consequence, if one wrapped function blocks, JavaScript's event loop
    35  // is blocked until that function returns. Hence, calling any async JavaScript
    36  // API, which requires the event loop, like fetch (http.Client), will cause an
    37  // immediate deadlock. Therefore a blocking function should explicitly start a
    38  // new goroutine.
    39  //
    40  // Func.Release must be called to free up resources when the function will not be invoked any more.
    41  func FuncOf(fn func(this Value, args []Value) any) Func {
    42  	funcsMu.Lock()
    43  	id := nextFuncID
    44  	nextFuncID++
    45  	funcs[id] = fn
    46  	funcsMu.Unlock()
    47  	return Func{
    48  		id:    id,
    49  		Value: jsGo.Call("_makeFuncWrapper", id),
    50  	}
    51  }
    52  
    53  // Release frees up resources allocated for the function.
    54  // The function must not be invoked after calling Release.
    55  // It is allowed to call Release while the function is still running.
    56  func (c Func) Release() {
    57  	funcsMu.Lock()
    58  	delete(funcs, c.id)
    59  	funcsMu.Unlock()
    60  }
    61  
    62  // setEventHandler is defined in the runtime package.
    63  func setEventHandler(fn func())
    64  
    65  func init() {
    66  	setEventHandler(handleEvent)
    67  }
    68  
    69  func handleEvent() {
    70  	cb := jsGo.Get("_pendingEvent")
    71  	if cb.IsNull() {
    72  		return
    73  	}
    74  	jsGo.Set("_pendingEvent", Null())
    75  
    76  	id := uint32(cb.Get("id").Int())
    77  	if id == 0 { // zero indicates deadlock
    78  		select {}
    79  	}
    80  	funcsMu.Lock()
    81  	f, ok := funcs[id]
    82  	funcsMu.Unlock()
    83  	if !ok {
    84  		Global().Get("console").Call("error", "call to released function")
    85  		return
    86  	}
    87  
    88  	this := cb.Get("this")
    89  	argsObj := cb.Get("args")
    90  	args := make([]Value, argsObj.Length())
    91  	for i := range args {
    92  		args[i] = argsObj.Index(i)
    93  	}
    94  	result := f(this, args)
    95  	cb.Set("result", result)
    96  }
    97  

View as plain text