func.mx raw

   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 (
  10  	"internal/synctest"
  11  	"sync"
  12  )
  13  
  14  var (
  15  	funcsMu    sync.Mutex
  16  	funcs             = make(map[uint32]func(Value, []Value) any)
  17  	nextFuncID uint32 = 1
  18  )
  19  
  20  // Func is a wrapped Go function to be called by JavaScript.
  21  type Func struct {
  22  	Value  // the JavaScript function that invokes the Go function
  23  	bubble *synctest.Bubble
  24  	id     uint32
  25  }
  26  
  27  // FuncOf returns a function to be used by JavaScript.
  28  //
  29  // The Go function fn is called with the value of JavaScript's "this" keyword and the
  30  // arguments of the invocation. The return value of the invocation is
  31  // the result of the Go function mapped back to JavaScript according to ValueOf.
  32  //
  33  // Invoking the wrapped Go function from JavaScript will
  34  // pause the event loop and spawn a new goroutine.
  35  // Other wrapped functions which are triggered during a call from Go to JavaScript
  36  // get executed on the same goroutine.
  37  //
  38  // As a consequence, if one wrapped function blocks, JavaScript's event loop
  39  // is blocked until that function returns. Hence, calling any async JavaScript
  40  // API, which requires the event loop, like fetch (http.Client), will cause an
  41  // immediate deadlock. Therefore a blocking function should explicitly start a
  42  // new goroutine.
  43  //
  44  // Func.Release must be called to free up resources when the function will not be invoked any more.
  45  func FuncOf(fn func(this Value, args []Value) any) Func {
  46  	funcsMu.Lock()
  47  	id := nextFuncID
  48  	nextFuncID++
  49  	bubble := synctest.Acquire()
  50  	if bubble != nil {
  51  		origFn := fn
  52  		fn = func(this Value, args []Value) any {
  53  			var r any
  54  			bubble.Run(func() {
  55  				r = origFn(this, args)
  56  			})
  57  			return r
  58  		}
  59  	}
  60  	funcs[id] = fn
  61  	funcsMu.Unlock()
  62  	return Func{
  63  		id:     id,
  64  		bubble: bubble,
  65  		Value:  jsGo.Call("_makeFuncWrapper", id),
  66  	}
  67  }
  68  
  69  // Release frees up resources allocated for the function.
  70  // The function must not be invoked after calling Release.
  71  // It is allowed to call Release while the function is still running.
  72  func (c Func) Release() {
  73  	c.bubble.Release()
  74  	funcsMu.Lock()
  75  	delete(funcs, c.id)
  76  	funcsMu.Unlock()
  77  }
  78  
  79  // setEventHandler is defined in the runtime package.
  80  func setEventHandler(fn func() bool)
  81  
  82  func init() {
  83  	setEventHandler(handleEvent)
  84  }
  85  
  86  // handleEvent retrieves the pending event (window._pendingEvent) and calls the js.Func on it.
  87  // It returns true if an event was handled.
  88  func handleEvent() bool {
  89  	// Retrieve the event from js
  90  	cb := jsGo.Get("_pendingEvent")
  91  	if cb.IsNull() {
  92  		return false
  93  	}
  94  	jsGo.Set("_pendingEvent", Null())
  95  
  96  	id := uint32(cb.Get("id").Int())
  97  	if id == 0 { // zero indicates deadlock
  98  		select {}
  99  	}
 100  
 101  	// Retrieve the associated js.Func
 102  	funcsMu.Lock()
 103  	f, ok := funcs[id]
 104  	funcsMu.Unlock()
 105  	if !ok {
 106  		Global().Get("console").Call("error", "call to released function")
 107  		return true
 108  	}
 109  
 110  	// Call the js.Func with arguments
 111  	this := cb.Get("this")
 112  	argsObj := cb.Get("args")
 113  	args := make([]Value, argsObj.Length())
 114  	for i := range args {
 115  		args[i] = argsObj.Index(i)
 116  	}
 117  	result := f(this, args)
 118  
 119  	// Return the result to js
 120  	cb.Set("result", result)
 121  	return true
 122  }
 123