Skip to content

Commit bfe9a9d

Browse files
committed
[runtime] Add cancelation of bound method calls and context passing
1 parent 7e68775 commit bfe9a9d

File tree

25 files changed

+319
-124
lines changed

25 files changed

+319
-124
lines changed

v3/examples/binding/bindings_main.js v3/examples/binding/assets/bindings_main.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ window.go.main = {
1313
* @param name {string}
1414
* @returns {Promise<string>}
1515
**/
16-
Greet: function(name) { return wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); },
16+
Greet: function(name) { return wails.Call.ByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); },
1717

1818
/**
1919
* GreetService.GreetPerson
2020
* GreetPerson greets a person
2121
* @param person {main.Person}
2222
* @returns {Promise<string>}
2323
**/
24-
GreetPerson: function(person) { return wails.CallByID(4021313248, ...Array.prototype.slice.call(arguments, 0)); },
24+
GreetPerson: function(person) { return wails.Call.ByID(4021313248, ...Array.prototype.slice.call(arguments, 0)); },
2525
},
2626
};

v3/examples/binding/assets/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
<input autofocus autocomplete="off" class="input" id="name" type="text"/>
8181
<button class="btn" onclick="greet()">Greet</button>
8282
</div>
83+
<script src='runtime.js'></script>
8384
<script src='bindings_main.js'></script>
8485
<script>
8586
let resultText = "";

v3/examples/binding/assets/runtime.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/examples/binding/go.mod

+19-18
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,44 @@ toolchain go1.21.4
77
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
88

99
require (
10-
github.com/Microsoft/go-winio v0.4.16 // indirect
10+
dario.cat/mergo v1.0.0 // indirect
11+
github.com/Microsoft/go-winio v0.6.1 // indirect
12+
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
1113
github.com/bep/debounce v1.2.1 // indirect
14+
github.com/cloudflare/circl v1.3.3 // indirect
15+
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
1216
github.com/ebitengine/purego v0.4.0-alpha.4 // indirect
13-
github.com/emirpasic/gods v1.12.0 // indirect
14-
github.com/go-git/gcfg v1.5.0 // indirect
15-
github.com/go-git/go-billy/v5 v5.2.0 // indirect
16-
github.com/go-git/go-git/v5 v5.3.0 // indirect
17+
github.com/emirpasic/gods v1.18.1 // indirect
18+
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
19+
github.com/go-git/go-billy/v5 v5.5.0 // indirect
20+
github.com/go-git/go-git/v5 v5.11.0 // indirect
1721
github.com/go-ole/go-ole v1.2.6 // indirect
1822
github.com/godbus/dbus/v5 v5.1.0 // indirect
23+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
1924
github.com/google/uuid v1.3.0 // indirect
20-
github.com/imdario/mergo v0.3.12 // indirect
2125
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
2226
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
23-
github.com/json-iterator/go v1.1.12 // indirect
24-
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
25-
github.com/kr/pretty v0.3.1 // indirect
27+
github.com/kevinburke/ssh_config v1.2.0 // indirect
2628
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
2729
github.com/leaanthony/u v1.1.0 // indirect
2830
github.com/lmittmann/tint v1.0.3 // indirect
2931
github.com/mattn/go-colorable v0.1.13 // indirect
3032
github.com/mattn/go-isatty v0.0.19 // indirect
31-
github.com/mitchellh/go-homedir v1.1.0 // indirect
32-
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
33-
github.com/modern-go/reflect2 v1.0.2 // indirect
33+
github.com/pjbgf/sha1cd v0.3.0 // indirect
3434
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
3535
github.com/rivo/uniseg v0.4.4 // indirect
36-
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 // indirect
3736
github.com/samber/lo v1.38.1 // indirect
3837
github.com/sergi/go-diff v1.2.0 // indirect
39-
github.com/stretchr/testify v1.8.4 // indirect
38+
github.com/skeema/knownhosts v1.2.1 // indirect
4039
github.com/wailsapp/go-webview2 v1.0.9 // indirect
4140
github.com/wailsapp/mimetype v1.4.1 // indirect
42-
github.com/xanzy/ssh-agent v0.3.0 // indirect
43-
golang.org/x/crypto v0.9.0 // indirect
41+
github.com/xanzy/ssh-agent v0.3.3 // indirect
42+
golang.org/x/crypto v0.16.0 // indirect
4443
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
45-
golang.org/x/net v0.10.0 // indirect
46-
golang.org/x/sys v0.14.0 // indirect
44+
golang.org/x/mod v0.12.0 // indirect
45+
golang.org/x/net v0.19.0 // indirect
46+
golang.org/x/sys v0.15.0 // indirect
47+
golang.org/x/tools v0.13.0 // indirect
4748
gopkg.in/warnings.v0 v0.1.2 // indirect
4849
)
4950

v3/examples/binding/go.sum

+101-76
Large diffs are not rendered by default.

v3/examples/frameless/assets/runtime.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/examples/window-api/assets/runtime.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/examples/wml/assets/runtime.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/internal/assetserver/assetserver_webview.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package assetserver
22

33
import (
4+
"context"
45
"fmt"
56
"net/http"
67
"net/url"
@@ -106,7 +107,10 @@ func (a *AssetServer) processWebViewRequestInternal(r webview.Request) {
106107
}
107108
defer body.Close()
108109

109-
req, err := http.NewRequest(method, uri, body)
110+
ctx, cancel := context.WithCancel(context.Background())
111+
defer cancel()
112+
113+
req, err := http.NewRequestWithContext(ctx, method, uri, body)
110114
if err != nil {
111115
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err))
112116
return

v3/internal/commands/build_assets/runtime/runtime.debug.js

+24-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/internal/commands/build_assets/runtime/runtime.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/internal/parser/bindings.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func (p *Project) GenerateBinding(thisStructName string, method *BoundMethod, us
155155
}
156156
result = strings.ReplaceAll(result, "Comments", comments)
157157
var params string
158-
for _, input := range method.Inputs {
158+
for _, input := range method.JSInputs() {
159159
input.project = p
160160
inputName := sanitiseJSVarName(input.Name)
161161
pkgName := getPackageName(input)
@@ -181,7 +181,7 @@ func (p *Project) GenerateBinding(thisStructName string, method *BoundMethod, us
181181
//}
182182
result = strings.ReplaceAll(result, "* @param names {string}", params)
183183
var inputs string
184-
for _, input := range method.Inputs {
184+
for _, input := range method.JSInputs() {
185185
pkgName := getPackageName(input)
186186
if pkgName != "" {
187187
models = append(models, pkgName)
@@ -256,7 +256,7 @@ func (p *Project) GenerateBindingTypescript(thisStructName string, method *Bound
256256
}
257257
result = strings.ReplaceAll(result, "Comments", comments)
258258
var params string
259-
for _, input := range method.Inputs {
259+
for _, input := range method.JSInputs() {
260260
input.project = p
261261
inputName := sanitiseJSVarName(input.Name)
262262
pkgName := getPackageName(input)
@@ -279,7 +279,7 @@ func (p *Project) GenerateBindingTypescript(thisStructName string, method *Bound
279279
// params = "\n" + params
280280
//}
281281
var inputs string
282-
for _, input := range method.Inputs {
282+
for _, input := range method.JSInputs() {
283283
pkgName := getPackageName(input)
284284
if pkgName != "" {
285285
models = append(models, pkgName)
@@ -341,6 +341,10 @@ func getPackageName(input *Parameter) string {
341341
return result
342342
}
343343

344+
func isContext(input *Parameter) bool {
345+
return input.Type.Package == "context" && input.Type.Name == "Context"
346+
}
347+
344348
func (p *Project) GenerateBindings(bindings map[string]map[string][]*BoundMethod, useIDs bool, useTypescript bool) map[string]map[string]string {
345349

346350
var result = make(map[string]map[string]string)

v3/internal/parser/parser.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package parser
33
import (
44
"errors"
55
"fmt"
6-
"github.com/wailsapp/wails/v3/internal/flags"
76
"go/ast"
87
"go/build"
98
"go/parser"
@@ -16,6 +15,8 @@ import (
1615
"strings"
1716
"time"
1817

18+
"github.com/wailsapp/wails/v3/internal/flags"
19+
1920
"github.com/samber/lo"
2021
"github.com/wailsapp/wails/v3/internal/hash"
2122
)
@@ -138,6 +139,16 @@ type BoundMethod struct {
138139
Alias *uint32
139140
}
140141

142+
func (m BoundMethod) JSInputs() []*Parameter {
143+
if len(m.Inputs) > 0 {
144+
if firstArg := m.Inputs[0]; isContext(firstArg) {
145+
return m.Inputs[1:]
146+
}
147+
}
148+
149+
return m.Inputs
150+
}
151+
141152
func (m BoundMethod) IDAsString() string {
142153
return strconv.Itoa(int(m.ID))
143154
}

v3/internal/parser/testdata/function_single/frontend/bindings/main/GreetService.js

+10
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,13 @@ import {Call} from '@wailsio/runtime';
1313
export async function Greet(name) {
1414
return Call.ByID(1411160069, ...Array.prototype.slice.call(arguments, 0));
1515
}
16+
17+
/**
18+
* Greet someone
19+
* @function GreetWithContext
20+
* @param name {string}
21+
* @returns {Promise<string>}
22+
**/
23+
export async function GreetWithContext(name) {
24+
return Call.ByID(1310150960, ...Array.prototype.slice.call(arguments, 0));
25+
}

v3/internal/parser/testdata/function_single/frontend/bindings/main/GreetService.name.js

+10
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,13 @@ import {Call} from '@wailsio/runtime';
1313
export async function Greet(name) {
1414
return Call.ByName("main.GreetService.Greet", ...Array.prototype.slice.call(arguments, 0));
1515
}
16+
17+
/**
18+
* Greet someone
19+
* @function GreetWithContext
20+
* @param name {string}
21+
* @returns {Promise<string>}
22+
**/
23+
export async function GreetWithContext(name) {
24+
return Call.ByName("main.GreetService.GreetWithContext", ...Array.prototype.slice.call(arguments, 0));
25+
}

v3/internal/parser/testdata/function_single/frontend/bindings/main/GreetService.name.ts

+5
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ export async function Greet(name: string) : Promise<string> {
77
return Call.ByName("main.GreetService.Greet", name);
88
}
99

10+
// Greet someone
11+
export async function GreetWithContext(name: string) : Promise<string> {
12+
return Call.ByName("main.GreetService.GreetWithContext", name);
13+
}
14+

v3/internal/parser/testdata/function_single/frontend/bindings/main/GreetService.ts

+5
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ export async function Greet(name: string) : Promise<string> {
77
return Call.ByID(1411160069, name);
88
}
99

10+
// Greet someone
11+
export async function GreetWithContext(name: string) : Promise<string> {
12+
return Call.ByID(1310150960, name);
13+
}
14+

v3/internal/parser/testdata/function_single/main.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package main
22

33
import (
4+
"context"
45
_ "embed"
5-
"github.com/wailsapp/wails/v3/pkg/application"
66
"log"
7+
8+
"github.com/wailsapp/wails/v3/pkg/application"
79
)
810

911
// GreetService is great
@@ -17,6 +19,11 @@ func (*GreetService) Greet(name string) string {
1719
return "Hello " + name
1820
}
1921

22+
// Greet someone
23+
func (*GreetService) GreetWithContext(ctx context.Context, name string) string {
24+
return "Hello " + name
25+
}
26+
2027
func NewGreetService() *GreetService {
2128
return &GreetService{}
2229
}

v3/internal/runtime/Taskfile.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ tasks:
3535
- build:production
3636

3737
cmds:
38+
- cmd: wails3 tool cp ../commands/build_assets/runtime/runtime.js ../../examples/binding/assets/runtime.js
3839
- cmd: wails3 tool cp ../commands/build_assets/runtime/runtime.js ../../examples/frameless/assets/runtime.js
3940
- cmd: wails3 tool cp ../commands/build_assets/runtime/runtime.js ../../examples/window-api/assets/runtime.js
4041
- cmd: wails3 tool cp ../commands/build_assets/runtime/runtime.js ../../examples/wml/assets/runtime.js

v3/internal/runtime/desktop/@wailsio/runtime/src/calls.js

+26-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ window._wails.callErrorHandler = errorHandler;
2020

2121
const CallBinding = 0;
2222
const call = newRuntimeCallerWithID(objectNames.Call, '');
23+
const cancelCall = newRuntimeCallerWithID(objectNames.CancelCall, '');
2324
let callResponses = new Map();
2425

2526
/**
@@ -84,18 +85,36 @@ function getAndDeleteResponse(id) {
8485
*
8586
* @param {string|number} type - The type of call to execute.
8687
* @param {Object} [options={}] - Additional options for the call.
87-
* @return {Promise} - A promise that will be resolved or rejected based on the result of the call.
88+
* @return {Promise} - A promise that will be resolved or rejected based on the result of the call. It also has a cancel method to cancel a long running request.
8889
*/
8990
function callBinding(type, options = {}) {
90-
return new Promise((resolve, reject) => {
91-
const id = generateID();
91+
const id = generateID();
92+
const doCancel = () => { cancelCall(type, {"call-id": id}) };
93+
var queuedCancel = false, callRunning = false;
94+
var p = new Promise((resolve, reject) => {
9295
options["call-id"] = id;
9396
callResponses.set(id, { resolve, reject });
94-
call(type, options).catch((error) => {
95-
reject(error);
96-
callResponses.delete(id);
97-
});
97+
call(type, options).
98+
then((_) => {
99+
callRunning = true;
100+
if (queuedCancel) {
101+
doCancel();
102+
}
103+
}).
104+
catch((error) => {
105+
reject(error);
106+
callResponses.delete(id);
107+
});
98108
});
109+
p.cancel = () => {
110+
if (callRunning) {
111+
doCancel();
112+
} else {
113+
queuedCancel = true;
114+
}
115+
};
116+
117+
return p;
99118
}
100119

101120
/**

v3/internal/runtime/desktop/@wailsio/runtime/src/runtime.js

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const objectNames = {
2525
Screens: 7,
2626
System: 8,
2727
Browser: 9,
28+
CancelCall: 10,
2829
}
2930
export let clientId = nanoid();
3031

v3/pkg/application/assets/alpha/runtime.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v3/pkg/application/bindings.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package application
22

33
import (
4+
"context"
45
"fmt"
56
"reflect"
67
"runtime"
@@ -72,6 +73,8 @@ type BoundMethod struct {
7273
PackageName string
7374
StructName string
7475
PackagePath string
76+
77+
needsContext bool
7578
}
7679

7780
type Bindings struct {
@@ -232,6 +235,8 @@ func (b *Bindings) getMethods(value interface{}, isPlugin bool) ([]*BoundMethod,
232235
structTypeString := structType.String()
233236
baseName := structTypeString[1:]
234237

238+
ctxType := reflect.TypeOf((*context.Context)(nil)).Elem()
239+
235240
// Process Methods
236241
for i := 0; i < structType.NumMethod(); i++ {
237242
methodDef := structType.Method(i)
@@ -273,6 +278,9 @@ func (b *Bindings) getMethods(value interface{}, isPlugin bool) ([]*BoundMethod,
273278
var inputs []*Parameter
274279
for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ {
275280
input := methodType.In(inputIndex)
281+
if inputIndex == 0 && input.AssignableTo(ctxType) {
282+
boundMethod.needsContext = true
283+
}
276284
thisParam := newParameter("", input)
277285
inputs = append(inputs, thisParam)
278286
}
@@ -296,7 +304,7 @@ func (b *Bindings) getMethods(value interface{}, isPlugin bool) ([]*BoundMethod,
296304
}
297305

298306
// Call will attempt to call this bound method with the given args
299-
func (b *BoundMethod) Call(args []interface{}) (returnValue interface{}, err error) {
307+
func (b *BoundMethod) Call(ctx context.Context, args []interface{}) (returnValue interface{}, err error) {
300308

301309
// Use a defer statement to capture panics
302310
defer func() {
@@ -317,6 +325,10 @@ func (b *BoundMethod) Call(args []interface{}) (returnValue interface{}, err err
317325
}
318326
}()
319327

328+
if b.needsContext {
329+
args = append([]any{ctx}, args...)
330+
}
331+
320332
// Check inputs
321333
expectedInputLength := len(b.Inputs)
322334
actualInputLength := len(args)

0 commit comments

Comments
 (0)