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 (
    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  

View as plain text