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() bool)
    64  
    65  func init() {
    66  	setEventHandler(handleEvent)
    67  }
    68  
    69  // handleEvent retrieves the pending event (window._pendingEvent) and calls the js.Func on it.
    70  // It returns true if an event was handled.
    71  func handleEvent() bool {
    72  	// Retrieve the event from js
    73  	cb := jsGo.Get("_pendingEvent")
    74  	if cb.IsNull() {
    75  		return false
    76  	}
    77  	jsGo.Set("_pendingEvent", Null())
    78  
    79  	id := uint32(cb.Get("id").Int())
    80  	if id == 0 { // zero indicates deadlock
    81  		select {}
    82  	}
    83  
    84  	// Retrieve the associated js.Func
    85  	funcsMu.Lock()
    86  	f, ok := funcs[id]
    87  	funcsMu.Unlock()
    88  	if !ok {
    89  		Global().Get("console").Call("error", "call to released function")
    90  		return true
    91  	}
    92  
    93  	// Call the js.Func with arguments
    94  	this := cb.Get("this")
    95  	argsObj := cb.Get("args")
    96  	args := make([]Value, argsObj.Length())
    97  	for i := range args {
    98  		args[i] = argsObj.Index(i)
    99  	}
   100  	result := f(this, args)
   101  
   102  	// Return the result to js
   103  	cb.Set("result", result)
   104  	return true
   105  }
   106  

View as plain text