5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-08 12:00:43 +08:00

Move v2 assetserver to v3

This commit is contained in:
Lea Anthony 2023-08-12 14:08:22 +10:00
parent 13be4a333f
commit 15f602f867
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
84 changed files with 2968 additions and 113 deletions

View File

@ -1,6 +1,8 @@
module binding
go 1.20
go 1.21
toolchain go1.21.0
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
@ -17,7 +19,6 @@ require (
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/go-webview2 v1.0.2 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.5.1 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.9.0 // indirect
@ -25,6 +26,4 @@ require (
replace github.com/wailsapp/wails/v3 => ../..
replace github.com/wailsapp/wails/v2 => ../../../v2
replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01

View File

@ -1,6 +1,8 @@
module frameless
go 1.20
go 1.21
toolchain go1.21.0
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
@ -17,12 +19,9 @@ require (
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/go-webview2 v1.0.2 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.5.1 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.9.0 // indirect
)
replace github.com/wailsapp/wails/v3 => ../..
replace github.com/wailsapp/wails/v2 => ../../../v2

View File

@ -1,6 +1,8 @@
module plugin_demo
go 1.20
go 1.21
toolchain go1.21.0
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
@ -23,7 +25,6 @@ require (
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/go-webview2 v1.0.2 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.5.1 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.7.0 // indirect
@ -42,5 +43,3 @@ require (
)
replace github.com/wailsapp/wails/v3 => ../..
replace github.com/wailsapp/wails/v2 => ../../../v2

View File

@ -28,7 +28,8 @@ require (
github.com/samber/lo v1.37.0
github.com/tc-hib/winres v0.1.6
github.com/wailsapp/go-webview2 v1.0.2
github.com/wailsapp/wails/v2 v2.5.1
github.com/wailsapp/mimetype v1.4.1
golang.org/x/net v0.7.0
golang.org/x/sys v0.9.0
modernc.org/sqlite v1.21.0
)
@ -56,7 +57,6 @@ require (
github.com/joho/godotenv v1.5.1 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/lithammer/fuzzysearch v1.1.5 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mattn/go-zglob v0.0.4 // indirect
@ -70,14 +70,12 @@ require (
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sajari/fuzzy v1.0.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/image v0.5.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/term v0.9.0 // indirect
@ -99,6 +97,4 @@ require (
mvdan.cc/sh/v3 v3.7.0 // indirect
)
replace github.com/wailsapp/wails/v2 => ../v2
replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01

View File

@ -232,7 +232,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ=
github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=

View File

@ -32,8 +32,6 @@ func isLocalBuild() bool {
// RelativePath returns a qualified path created by joining the
// directory of the calling file and the given relative path.
//
// Example: RelativePath("..") in *this* file would give you '/path/to/wails2/v2/internal`
func RelativePath(relativepath string, optionalpaths ...string) string {
_, thisFile, _, _ := runtime.Caller(1)
localDir := filepath.Dir(thisFile)

View File

@ -8,6 +8,5 @@ require (
github.com/imdario/mergo v0.3.12 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
)

View File

@ -5,8 +5,6 @@ github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5Az
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 h1:Wn+nhnS+VytzE0PegUzSh4T3hXJCtggKGD/4U5H9+wQ=
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
github.com/wailsapp/wails/v3 v3.0.0-alpha.0 h1:T5gqG98Xr8LBf69oxlPkhpsFD59w2SnqUZk6XHj8Zoc=
github.com/wailsapp/wails/v3 v3.0.0-alpha.0/go.mod h1:OAfO5bP0TSUvCIHZYc6Dqfow/9RqxzHvYtmhWPpo1c0=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,7 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build
build:

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,7 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build
build:

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -39,8 +39,9 @@ tasks:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -8,10 +8,8 @@ require (
github.com/imdario/mergo v0.3.12 // indirect
github.com/leaanthony/slicer v1.5.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,7 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build
build:

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -38,9 +38,10 @@ tasks:
platforms:
- darwin
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
@ -51,6 +52,7 @@ tasks:
- windows
cmds:
- task: pre-build
- task: build-frontend
- go build -gcflags=all="-N -l" -o bin/testapp.exe
- task: post-build

View File

@ -11,11 +11,9 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
)
{{if gt (len .LocalModulePath) 0}}
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
{{end}}

View File

@ -16,9 +16,8 @@ import (
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/pkg/assetserver"
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
assetserveroptions "github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v3/pkg/assetserver"
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
wailsruntime "github.com/wailsapp/wails/v3/internal/runtime"
"github.com/wailsapp/wails/v3/pkg/events"
@ -72,10 +71,10 @@ func New(appOptions Options) *App {
result.Events = NewWailsEventProcessor(result.dispatchEventToWindows)
opts := assetserveroptions.Options{
opts := &assetserver.Options{
Assets: appOptions.Assets.FS,
Handler: appOptions.Assets.Handler,
Middleware: assetserveroptions.Middleware(appOptions.Assets.Middleware),
Middleware: assetserver.Middleware(appOptions.Assets.Middleware),
}
// TODO ServingFrom disk?

View File

@ -128,7 +128,7 @@ import "C"
import (
"unsafe"
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
"github.com/wailsapp/wails/v3/pkg/events"
)

View File

@ -7,7 +7,7 @@ import (
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
"github.com/wailsapp/wails/v3/pkg/events"
)

View File

@ -9,7 +9,7 @@ import (
"unsafe"
"github.com/ebitengine/purego"
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
"github.com/wailsapp/wails/v3/pkg/events"
)

View File

@ -1,6 +1,6 @@
package application
import "github.com/wailsapp/wails/v2/pkg/assetserver"
import "github.com/wailsapp/wails/v3/pkg/assetserver"
type Plugin interface {
Name() string

View File

@ -8,7 +8,6 @@ import (
"sync"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v3/pkg/events"
)
@ -29,7 +28,7 @@ type linuxWebviewWindow struct {
parent *WebviewWindow
menubar pointer
vbox pointer
menu *menu.Menu
menu *Menu
accels pointer
lastWidth int
lastHeight int

View File

@ -7,8 +7,8 @@ import (
"fmt"
"github.com/bep/debounce"
"github.com/wailsapp/go-webview2/webviewloader"
"github.com/wailsapp/wails/v2/pkg/assetserver"
"github.com/wailsapp/wails/v3/internal/capabilities"
"github.com/wailsapp/wails/v3/pkg/assetserver"
"io"
"net/http"
"net/http/httptest"

View File

@ -0,0 +1,202 @@
package assetserver
import (
"bytes"
"embed"
"errors"
"fmt"
"io"
iofs "io/fs"
"net/http"
"os"
"path"
"strings"
)
type Logger interface {
Debug(message string, args ...interface{})
Error(message string, args ...interface{})
}
//go:embed defaultindex.html
var defaultHTML []byte
const (
indexHTML = "index.html"
)
type assetHandler struct {
fs iofs.FS
handler http.Handler
logger Logger
retryMissingFiles bool
}
func NewAssetHandler(options *Options, log Logger) (http.Handler, error) {
vfs := options.Assets
if vfs != nil {
if _, err := vfs.Open("."); err != nil {
return nil, err
}
subDir, err := FindPathToFile(vfs, indexHTML)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
msg := "no `index.html` could be found in your Assets fs.FS"
if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs {
rootFolder, _ := FindEmbedRootPath(embedFs)
msg += fmt.Sprintf(", please make sure the embedded directory '%s' is correct and contains your assets", rootFolder)
}
return nil, fmt.Errorf(msg)
}
return nil, err
}
vfs, err = iofs.Sub(vfs, path.Clean(subDir))
if err != nil {
return nil, err
}
}
var result http.Handler = &assetHandler{
fs: vfs,
handler: options.Handler,
logger: log,
}
if middleware := options.Middleware; middleware != nil {
result = middleware(result)
}
return result, nil
}
func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
url := req.URL.Path
handler := d.handler
if strings.EqualFold(req.Method, http.MethodGet) {
filename := path.Clean(strings.TrimPrefix(url, "/"))
d.logDebug("Handling request '%s' (file='%s')", url, filename)
if err := d.serveFSFile(rw, req, filename); err != nil {
if os.IsNotExist(err) {
if handler != nil {
d.logDebug("File '%s' not found, serving '%s' by AssetHandler", filename, url)
handler.ServeHTTP(rw, req)
err = nil
} else {
rw.WriteHeader(http.StatusNotFound)
err = nil
}
}
if err != nil {
d.logError("Unable to handle request '%s': %s", url, err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
}
} else if handler != nil {
d.logDebug("No GET request, serving '%s' by AssetHandler", url)
handler.ServeHTTP(rw, req)
} else {
rw.WriteHeader(http.StatusMethodNotAllowed)
}
}
// serveFile will try to load the file from the fs.FS and write it to the response
func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error {
if d.fs == nil {
return os.ErrNotExist
}
file, err := d.fs.Open(filename)
if err != nil {
return err
}
defer file.Close()
statInfo, err := file.Stat()
if err != nil {
return err
}
url := req.URL.Path
isDirectoryPath := url == "" || url[len(url)-1] == '/'
if statInfo.IsDir() {
if !isDirectoryPath {
// If the URL doesn't end in a slash normally a http.redirect should be done, but that currently doesn't work on
// WebKit WebViews (macOS/Linux).
// So we handle this as a specific error
return fmt.Errorf("a directory has been requested without a trailing slash, please add a trailing slash to your request")
}
filename = path.Join(filename, indexHTML)
file, err = d.fs.Open(filename)
if err != nil {
return err
}
defer file.Close()
statInfo, err = file.Stat()
if err != nil {
return err
}
} else if isDirectoryPath {
return fmt.Errorf("a file has been requested with a trailing slash, please remove the trailing slash from your request")
}
var buf [512]byte
var n int
if _, haveType := rw.Header()[HeaderContentType]; !haveType {
// Detect MimeType by sniffing the first 512 bytes
n, err = file.Read(buf[:])
if err != nil && err != io.EOF {
return err
}
// Do the custom MimeType sniffing even though http.ServeContent would do it in case
// of an io.ReadSeeker. We would like to have a consistent behaviour in both cases.
if contentType := GetMimetype(filename, buf[:n]); contentType != "" {
rw.Header().Set(HeaderContentType, contentType)
}
}
if fileSeeker, _ := file.(io.ReadSeeker); fileSeeker != nil {
if _, err := fileSeeker.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("seeker can't seek")
}
http.ServeContent(rw, req, statInfo.Name(), statInfo.ModTime(), fileSeeker)
return nil
}
rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size()))
// Write the first 512 bytes used for MimeType sniffing
_, err = io.Copy(rw, bytes.NewReader(buf[:n]))
if err != nil {
return err
}
// Copy the remaining content of the file
_, err = io.Copy(rw, file)
return err
}
func (d *assetHandler) logDebug(message string, args ...interface{}) {
if d.logger != nil {
d.logger.Debug("[AssetHandler] "+message, args...)
}
}
func (d *assetHandler) logError(message string, args ...interface{}) {
if d.logger != nil {
d.logger.Error("[AssetHandler] "+message, args...)
}
}

View File

@ -0,0 +1,78 @@
//go:build dev
// +build dev
package assetserver
import (
"errors"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
)
func NewExternalAssetsHandler(logger Logger, options Options, url *url.URL) http.Handler {
baseHandler := options.Handler
errSkipProxy := fmt.Errorf("skip proxying")
proxy := httputil.NewSingleHostReverseProxy(url)
baseDirector := proxy.Director
proxy.Director = func(r *http.Request) {
baseDirector(r)
if logger != nil {
logger.Debug("[ExternalAssetHandler] Loading '%s'", r.URL)
}
}
proxy.ModifyResponse = func(res *http.Response) error {
if baseHandler == nil {
return nil
}
if res.StatusCode == http.StatusSwitchingProtocols {
return nil
}
if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusMethodNotAllowed {
return errSkipProxy
}
return nil
}
proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) {
if baseHandler != nil && errors.Is(err, errSkipProxy) {
if logger != nil {
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL)
}
baseHandler.ServeHTTP(rw, r)
} else {
if logger != nil {
logger.Error("[ExternalAssetHandler] Proxy error: %v", err)
}
rw.WriteHeader(http.StatusBadGateway)
}
}
var result http.Handler = http.HandlerFunc(
func(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodGet {
proxy.ServeHTTP(rw, req)
return
}
if baseHandler != nil {
baseHandler.ServeHTTP(rw, req)
return
}
rw.WriteHeader(http.StatusMethodNotAllowed)
})
if middleware := options.Middleware; middleware != nil {
result = middleware(result)
}
return result
}

View File

@ -0,0 +1,246 @@
package assetserver
import (
"bytes"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"strings"
"golang.org/x/net/html"
)
const (
runtimeJSPath = "/wails/runtime.js"
ipcJSPath = "/wails/ipc.js"
runtimePath = "/wails/runtime"
capabilitiesPath = "/wails/capabilities"
flagsPath = "/wails/flags"
)
type RuntimeAssets interface {
DesktopIPC() []byte
WebsocketIPC() []byte
RuntimeDesktopJS() []byte
}
type RuntimeHandler interface {
HandleRuntimeCall(w http.ResponseWriter, r *http.Request)
}
type AssetServer struct {
handler http.Handler
runtimeJS []byte
ipcJS func(*http.Request) []byte
logger Logger
runtime RuntimeAssets
servingFromDisk bool
appendSpinnerToBody bool
// Use http based runtime
runtimeHandler RuntimeHandler
// plugin scripts
pluginScripts map[string]string
// GetCapabilities returns the capabilities of the runtime
GetCapabilities func() []byte
// GetFlags returns the application flags
GetFlags func() []byte
assetServerWebView
}
func NewAssetServerMainPage(bindingsJSON string, options *Options, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
return NewAssetServer(bindingsJSON, options, servingFromDisk, logger, runtime)
}
func NewAssetServer(bindingsJSON string, options *Options, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
handler, err := NewAssetHandler(options, logger)
if err != nil {
return nil, err
}
return NewAssetServerWithHandler(handler, bindingsJSON, servingFromDisk, logger, runtime)
}
func NewAssetServerWithHandler(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
var buffer bytes.Buffer
if bindingsJSON != "" {
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
}
buffer.Write(runtime.RuntimeDesktopJS())
result := &AssetServer{
handler: handler,
runtimeJS: buffer.Bytes(),
// Check if we have been given a directory to serve assets from.
// If so, this means we are in dev mode and are serving assets off disk.
// We indicate this through the `servingFromDisk` flag to ensure requests
// aren't cached in dev mode.
servingFromDisk: servingFromDisk,
logger: logger,
runtime: runtime,
}
return result, nil
}
func (d *AssetServer) UseRuntimeHandler(handler RuntimeHandler) {
d.runtimeHandler = handler
}
func (d *AssetServer) AddPluginScript(pluginName string, script string) {
if d.pluginScripts == nil {
d.pluginScripts = make(map[string]string)
}
pluginName = strings.ReplaceAll(pluginName, "/", "_")
pluginName = html.EscapeString(pluginName)
pluginScriptName := fmt.Sprintf("/plugin_%s_%d.js", pluginName, rand.Intn(100000))
d.pluginScripts[pluginScriptName] = script
}
func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if isWebSocket(req) {
// WebSockets are not supported by the AssetServer
rw.WriteHeader(http.StatusNotImplemented)
return
}
header := rw.Header()
if d.servingFromDisk {
header.Add(HeaderCacheControl, "no-cache")
}
path := req.URL.Path
switch path {
case "", "/", "/index.html":
recorder := httptest.NewRecorder()
d.handler.ServeHTTP(recorder, req)
for k, v := range recorder.HeaderMap {
header[k] = v
}
switch recorder.Code {
case http.StatusOK:
content, err := d.processIndexHTML(recorder.Body.Bytes())
if err != nil {
d.serveError(rw, err, "Unable to processIndexHTML")
return
}
d.writeBlob(rw, indexHTML, content)
case http.StatusNotFound:
d.writeBlob(rw, indexHTML, defaultHTML)
default:
rw.WriteHeader(recorder.Code)
}
case runtimeJSPath:
d.writeBlob(rw, path, d.runtimeJS)
case capabilitiesPath:
var data = []byte("{}")
if d.GetCapabilities != nil {
data = d.GetCapabilities()
}
d.writeBlob(rw, path, data)
case flagsPath:
var data = []byte("{}")
if d.GetFlags != nil {
data = d.GetFlags()
}
d.writeBlob(rw, path, data)
case runtimePath:
if d.runtimeHandler != nil {
d.runtimeHandler.HandleRuntimeCall(rw, req)
} else {
d.handler.ServeHTTP(rw, req)
}
case ipcJSPath:
content := d.runtime.DesktopIPC()
if d.ipcJS != nil {
content = d.ipcJS(req)
}
d.writeBlob(rw, path, content)
default:
// Check if this is a plugin script
if script, ok := d.pluginScripts[path]; ok {
d.writeBlob(rw, path, []byte(script))
return
}
d.handler.ServeHTTP(rw, req)
}
}
func (d *AssetServer) processIndexHTML(indexHTML []byte) ([]byte, error) {
htmlNode, err := getHTMLNode(indexHTML)
if err != nil {
return nil, err
}
if d.appendSpinnerToBody {
err = appendSpinnerToBody(htmlNode)
if err != nil {
return nil, err
}
}
if err := insertScriptInHead(htmlNode, runtimeJSPath); err != nil {
return nil, err
}
if err := insertScriptInHead(htmlNode, ipcJSPath); err != nil {
return nil, err
}
// Inject plugins
for scriptName := range d.pluginScripts {
if err := insertScriptInHead(htmlNode, scriptName); err != nil {
return nil, err
}
}
var buffer bytes.Buffer
err = html.Render(&buffer, htmlNode)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func (d *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
err := serveFile(rw, filename, blob)
if err != nil {
d.serveError(rw, err, "Unable to write content %s", filename)
}
}
func (d *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) {
args = append(args, err)
d.logError(msg+": %s", args...)
rw.WriteHeader(http.StatusInternalServerError)
}
func (d *AssetServer) logDebug(message string, args ...interface{}) {
if d.logger != nil {
d.logger.Debug("[AssetServer] "+message, args...)
}
}
func (d *AssetServer) logError(message string, args ...interface{}) {
if d.logger != nil {
d.logger.Error("[AssetServer] "+message, args...)
}
}

View File

@ -0,0 +1,31 @@
//go:build dev
// +build dev
package assetserver
import (
"net/http"
"strings"
)
/*
The assetserver for the dev mode.
Depending on the UserAgent it injects a websocket based IPC script into `index.html` or the default desktop IPC. The
default desktop IPC is injected when the webview accesses the devserver.
*/
func NewDevAssetServer(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) {
result, err := NewAssetServerWithHandler(handler, bindingsJSON, servingFromDisk, logger, runtime)
if err != nil {
return nil, err
}
result.appendSpinnerToBody = true
result.ipcJS = func(req *http.Request) []byte {
if strings.Contains(req.UserAgent(), WailsUserAgentValue) {
return runtime.DesktopIPC()
}
return runtime.WebsocketIPC()
}
return result, nil
}

View File

@ -0,0 +1,78 @@
package assetserver
import (
"io"
"net/http"
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
)
// ProcessHTTPRequest processes the HTTP Request by faking a golang HTTP Server.
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
func (d *AssetServer) ProcessHTTPRequestLegacy(rw http.ResponseWriter, reqGetter func() (*http.Request, error)) {
d.processWebViewRequest(&legacyRequest{reqGetter: reqGetter, rw: rw})
}
type legacyRequest struct {
req *http.Request
rw http.ResponseWriter
reqGetter func() (*http.Request, error)
}
func (r *legacyRequest) URL() (string, error) {
req, err := r.request()
if err != nil {
return "", err
}
return req.URL.String(), nil
}
func (r *legacyRequest) Method() (string, error) {
req, err := r.request()
if err != nil {
return "", err
}
return req.Method, nil
}
func (r *legacyRequest) Header() (http.Header, error) {
req, err := r.request()
if err != nil {
return nil, err
}
return req.Header, nil
}
func (r *legacyRequest) Body() (io.ReadCloser, error) {
req, err := r.request()
if err != nil {
return nil, err
}
return req.Body, nil
}
func (r legacyRequest) Response() webview.ResponseWriter {
return &legacyRequestNoOpCloserResponseWriter{r.rw}
}
func (r legacyRequest) Close() error { return nil }
func (r *legacyRequest) request() (*http.Request, error) {
if r.req != nil {
return r.req, nil
}
req, err := r.reqGetter()
if err != nil {
return nil, err
}
r.req = req
return req, nil
}
type legacyRequestNoOpCloserResponseWriter struct {
http.ResponseWriter
}
func (*legacyRequestNoOpCloserResponseWriter) Finish() {}

View File

@ -0,0 +1,161 @@
package assetserver
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"github.com/wailsapp/wails/v3/pkg/assetserver/webview"
)
type assetServerWebView struct {
// ExpectedWebViewHost is checked against the Request Host of every WebViewRequest, other hosts won't be processed.
ExpectedWebViewHost string
dispatchInit sync.Once
dispatchReqC chan<- webview.Request
dispatchWorkers int
}
// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server.
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way.
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
d.dispatchInit.Do(func() {
workers := d.dispatchWorkers
if workers == 0 {
workers = 10
}
workerC := make(chan webview.Request, workers*2)
for i := 0; i < workers; i++ {
go func() {
for req := range workerC {
uri, _ := req.URL()
d.processWebViewRequest(req)
if err := req.Close(); err != nil {
d.logError("Unable to call close for request for uri '%s'", uri)
}
}
}()
}
dispatchC := make(chan webview.Request)
go queueingDispatcher(50, dispatchC, workerC)
d.dispatchReqC = dispatchC
})
d.dispatchReqC <- req
}
// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server.
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
func (d *AssetServer) processWebViewRequest(r webview.Request) {
wrw := r.Response()
defer wrw.Finish()
var rw http.ResponseWriter = &contentTypeSniffer{rw: wrw} // Make sure we have a Content-Type sniffer
defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status
uri, err := r.URL()
if err != nil {
d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
method, err := r.Method()
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err))
return
}
header, err := r.Header()
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err))
return
}
body, err := r.Body()
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err))
return
}
if body == nil {
body = http.NoBody
}
defer body.Close()
req, err := http.NewRequest(method, uri, body)
if err != nil {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err))
return
}
req.Header = header
if req.RemoteAddr == "" {
// 192.0.2.0/24 is "TEST-NET" in RFC 5737
req.RemoteAddr = "192.0.2.1:1234"
}
if req.RequestURI == "" && req.URL != nil {
req.RequestURI = req.URL.String()
}
if req.ContentLength == 0 {
req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64)
} else {
req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength))
}
if host := req.Header.Get(HeaderHost); host != "" {
req.Host = host
}
if expectedHost := d.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host {
d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host))
return
}
d.ServeHTTP(rw, req)
}
func (d *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) {
logInfo := uri
if uri, err := url.ParseRequestURI(uri); err == nil {
logInfo = strings.Replace(logInfo, fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), "", 1)
}
d.logError("Error processing request '%s': %s (HttpResponse=500)", logInfo, err)
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
func queueingDispatcher[T any](minQueueSize uint, inC <-chan T, outC chan<- T) {
q := newRingqueue[T](minQueueSize)
for {
in, ok := <-inC
if !ok {
return
}
q.Add(in)
for q.Len() != 0 {
out, _ := q.Peek()
select {
case outC <- out:
q.Remove()
case in, ok := <-inC:
if !ok {
return
}
q.Add(in)
}
}
}
}

View File

@ -0,0 +1,115 @@
package assetserver
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"strings"
"golang.org/x/net/html"
)
const (
HeaderHost = "Host"
HeaderContentType = "Content-Type"
HeaderContentLength = "Content-Length"
HeaderUserAgent = "User-Agent"
HeaderCacheControl = "Cache-Control"
HeaderUpgrade = "Upgrade"
WailsUserAgentValue = "wails.io"
)
func serveFile(rw http.ResponseWriter, filename string, blob []byte) error {
header := rw.Header()
header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob)))
if mimeType := header.Get(HeaderContentType); mimeType == "" {
mimeType = GetMimetype(filename, blob)
header.Set(HeaderContentType, mimeType)
}
rw.WriteHeader(http.StatusOK)
_, err := io.Copy(rw, bytes.NewReader(blob))
return err
}
func createScriptNode(scriptName string) *html.Node {
return &html.Node{
Type: html.ElementNode,
Data: "script",
Attr: []html.Attribute{
{
Key: "src",
Val: scriptName,
},
},
}
}
func createDivNode(id string) *html.Node {
return &html.Node{
Type: html.ElementNode,
Data: "div",
Attr: []html.Attribute{
{
Namespace: "",
Key: "id",
Val: id,
},
},
}
}
func insertScriptInHead(htmlNode *html.Node, scriptName string) error {
headNode := findFirstTag(htmlNode, "head")
if headNode == nil {
return errors.New("cannot find head in HTML")
}
scriptNode := createScriptNode(scriptName)
if headNode.FirstChild != nil {
headNode.InsertBefore(scriptNode, headNode.FirstChild)
} else {
headNode.AppendChild(scriptNode)
}
return nil
}
func appendSpinnerToBody(htmlNode *html.Node) error {
bodyNode := findFirstTag(htmlNode, "body")
if bodyNode == nil {
return errors.New("cannot find body in HTML")
}
scriptNode := createDivNode("wails-spinner")
bodyNode.AppendChild(scriptNode)
return nil
}
func getHTMLNode(htmldata []byte) (*html.Node, error) {
return html.Parse(bytes.NewReader(htmldata))
}
func findFirstTag(htmlnode *html.Node, tagName string) *html.Node {
var extractor func(*html.Node) *html.Node
var result *html.Node
extractor = func(node *html.Node) *html.Node {
if node.Type == html.ElementNode && node.Data == tagName {
return node
}
for child := node.FirstChild; child != nil; child = child.NextSibling {
result := extractor(child)
if result != nil {
return result
}
}
return nil
}
result = extractor(htmlnode)
return result
}
func isWebSocket(req *http.Request) bool {
upgrade := req.Header.Get(HeaderUpgrade)
return strings.EqualFold(upgrade, "websocket")
}

View File

@ -0,0 +1,42 @@
package assetserver
import (
"net/http"
)
type contentTypeSniffer struct {
rw http.ResponseWriter
wroteHeader bool
}
func (rw *contentTypeSniffer) Header() http.Header {
return rw.rw.Header()
}
func (rw *contentTypeSniffer) Write(buf []byte) (int, error) {
rw.writeHeader(buf)
return rw.rw.Write(buf)
}
func (rw *contentTypeSniffer) WriteHeader(code int) {
if rw.wroteHeader {
return
}
rw.rw.WriteHeader(code)
rw.wroteHeader = true
}
func (rw *contentTypeSniffer) writeHeader(b []byte) {
if rw.wroteHeader {
return
}
m := rw.rw.Header()
if _, hasType := m[HeaderContentType]; !hasType {
m.Set(HeaderContentType, http.DetectContentType(b))
}
rw.WriteHeader(http.StatusOK)
}

File diff suppressed because one or more lines are too long

75
v3/pkg/assetserver/fs.go Normal file
View File

@ -0,0 +1,75 @@
package assetserver
import (
"embed"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
// FindEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files.
func FindEmbedRootPath(fsys embed.FS) (string, error) {
stopErr := fmt.Errorf("files or multiple dirs found")
fPath := ""
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
fPath = path
if entries, dErr := fs.ReadDir(fsys, path); dErr != nil {
return dErr
} else if len(entries) <= 1 {
return nil
}
}
return stopErr
})
if err != nil && err != stopErr {
return "", err
}
return fPath, nil
}
func FindPathToFile(fsys fs.FS, file string) (string, error) {
stat, _ := fs.Stat(fsys, file)
if stat != nil {
return ".", nil
}
var indexFiles []string
err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, file) {
indexFiles = append(indexFiles, path)
}
return nil
})
if err != nil {
return "", err
}
if len(indexFiles) > 1 {
selected := indexFiles[0]
for _, f := range indexFiles {
if len(f) < len(selected) {
selected = f
}
}
path, _ := filepath.Split(selected)
return path, nil
}
if len(indexFiles) > 0 {
path, _ := filepath.Split(indexFiles[0])
return path, nil
}
return "", fmt.Errorf("%s: %w", file, os.ErrNotExist)
}

View File

@ -0,0 +1,20 @@
package assetserver
import (
"net/http"
)
// Middleware defines a HTTP middleware that can be applied to the AssetServer.
// The handler passed as next is the next handler in the chain. One can decide to call the next handler
// or implement a specialized handling.
type Middleware func(next http.Handler) http.Handler
// ChainMiddleware allows chaining multiple middlewares to one middleware.
func ChainMiddleware(middleware ...Middleware) Middleware {
return func(h http.Handler) http.Handler {
for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
}
return h
}
}

View File

@ -0,0 +1,67 @@
package assetserver
import (
"net/http"
"path/filepath"
"sync"
"github.com/wailsapp/mimetype"
)
var (
mimeCache = map[string]string{}
mimeMutex sync.Mutex
// The list of builtin mime-types by extension as defined by
// the golang standard lib package "mime"
// The standard lib also takes into account mime type definitions from
// etc files like '/etc/apache2/mime.types' but we want to have the
// same behavivour on all platforms and not depend on some external file.
mimeTypesByExt = map[string]string{
".avif": "image/avif",
".css": "text/css; charset=utf-8",
".gif": "image/gif",
".htm": "text/html; charset=utf-8",
".html": "text/html; charset=utf-8",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "text/javascript; charset=utf-8",
".json": "application/json",
".mjs": "text/javascript; charset=utf-8",
".pdf": "application/pdf",
".png": "image/png",
".svg": "image/svg+xml",
".wasm": "application/wasm",
".webp": "image/webp",
".xml": "text/xml; charset=utf-8",
}
)
func GetMimetype(filename string, data []byte) string {
mimeMutex.Lock()
defer mimeMutex.Unlock()
result := mimeTypesByExt[filepath.Ext(filename)]
if result != "" {
return result
}
result = mimeCache[filename]
if result != "" {
return result
}
detect := mimetype.Detect(data)
if detect == nil {
result = http.DetectContentType(data)
} else {
result = detect.String()
}
if result == "" {
result = "application/octet-stream"
}
mimeCache[filename] = result
return result
}

View File

@ -0,0 +1,46 @@
package assetserver
import (
"testing"
)
func TestGetMimetype(t *testing.T) {
type args struct {
filename string
data []byte
}
bomUTF8 := []byte{0xef, 0xbb, 0xbf}
var emptyMsg []byte
css := []byte("body{margin:0;padding:0;background-color:#d579b2}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;background-color:#ededed}#nav{padding:30px}#nav a{font-weight:700;color:#2c\n3e50}#nav a.router-link-exact-active{color:#42b983}.hello[data-v-4e26ad49]{margin:10px 0}")
html := []byte("<!DOCTYPE html><html><head>title</head><body></body></html>")
bomHtml := append(bomUTF8, html...)
svg := []byte("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"><path d=\"M15.707 14.293l-4.822-4.822a6.019 6.019 0 1 0-1.414 1.414l4.822 4.822a1 1 0 0 0 1.414-1.414zM6 10a4 4 0 1 1 4-4 4 4 0 0 1-4 4z\"></path></svg>")
svgWithComment := append([]byte("<!-- this is a comment -->"), svg...)
svgWithCommentAndControlChars := append([]byte(" \r\n "), svgWithComment...)
svgWithBomCommentAndControlChars := append(bomUTF8, append([]byte(" \r\n "), svgWithComment...)...)
tests := []struct {
name string
args args
want string
}{
{"nil data", args{"nil.svg", nil}, "image/svg+xml"},
{"empty data", args{"empty.html", emptyMsg}, "text/html; charset=utf-8"},
{"css", args{"test.css", css}, "text/css; charset=utf-8"},
{"js", args{"test.js", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"},
{"mjs", args{"test.mjs", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"},
{"html-utf8", args{"test_utf8.html", html}, "text/html; charset=utf-8"},
{"html-bom-utf8", args{"test_bom_utf8.html", bomHtml}, "text/html; charset=utf-8"},
{"svg", args{"test.svg", svg}, "image/svg+xml"},
{"svg-w-comment", args{"test_comment.svg", svgWithComment}, "image/svg+xml"},
{"svg-w-control-comment", args{"test_control_comment.svg", svgWithCommentAndControlChars}, "image/svg+xml"},
{"svg-w-bom-control-comment", args{"test_bom_control_comment.svg", svgWithBomCommentAndControlChars}, "image/svg+xml"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetMimetype(tt.args.filename, tt.args.data); got != tt.want {
t.Errorf("GetMimetype() = '%v', want '%v'", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,45 @@
package assetserver
import (
"fmt"
"io/fs"
"net/http"
)
// Options defines the configuration of the AssetServer.
type Options struct {
// Assets defines the static assets to be used. A GET request is first tried to be served from this Assets. If the Assets returns
// `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET
// request from it.
//
// If set to nil, all GET requests will be forwarded to Handler.
Assets fs.FS
// Handler will be called for every GET request that can't be served from Assets, due to `os.ErrNotExist`. Furthermore all
// non GET requests will always be served from this Handler.
//
// If not defined, the result is the following in cases where the Handler would have been called:
// GET request: `http.StatusNotFound`
// Other request: `http.StatusMethodNotAllowed`
Handler http.Handler
// Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default
// request handler dynamically, e.g. implement specialized Routing etc.
// The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default
// handler used by the AssetServer as an argument.
//
// If not defined, the default AssetServer request chain is executed.
//
// Multiple Middlewares can be chained together with:
// ChainMiddleware(middleware ...Middleware) Middleware
Middleware Middleware
}
// Validate the options
func (o Options) Validate() error {
if o.Assets == nil && o.Handler == nil && o.Middleware == nil {
return fmt.Errorf("AssetServer options invalid: either Assets, Handler or Middleware must be set")
}
return nil
}

View File

@ -0,0 +1,101 @@
// Code from https://github.com/erikdubbelboer/ringqueue
/*
The MIT License (MIT)
Copyright (c) 2015 Erik Dubbelboer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package assetserver
type ringqueue[T any] struct {
nodes []T
head int
tail int
cnt int
minSize int
}
func newRingqueue[T any](minSize uint) *ringqueue[T] {
if minSize < 2 {
minSize = 2
}
return &ringqueue[T]{
nodes: make([]T, minSize),
minSize: int(minSize),
}
}
func (q *ringqueue[T]) resize(n int) {
nodes := make([]T, n)
if q.head < q.tail {
copy(nodes, q.nodes[q.head:q.tail])
} else {
copy(nodes, q.nodes[q.head:])
copy(nodes[len(q.nodes)-q.head:], q.nodes[:q.tail])
}
q.tail = q.cnt % n
q.head = 0
q.nodes = nodes
}
func (q *ringqueue[T]) Add(i T) {
if q.cnt == len(q.nodes) {
// Also tested a grow rate of 1.5, see: http://stackoverflow.com/questions/2269063/buffer-growth-strategy
// In Go this resulted in a higher memory usage.
q.resize(q.cnt * 2)
}
q.nodes[q.tail] = i
q.tail = (q.tail + 1) % len(q.nodes)
q.cnt++
}
func (q *ringqueue[T]) Peek() (T, bool) {
if q.cnt == 0 {
var none T
return none, false
}
return q.nodes[q.head], true
}
func (q *ringqueue[T]) Remove() (T, bool) {
if q.cnt == 0 {
var none T
return none, false
}
i := q.nodes[q.head]
q.head = (q.head + 1) % len(q.nodes)
q.cnt--
if n := len(q.nodes) / 2; n > q.minSize && q.cnt <= n {
q.resize(n)
}
return i, true
}
func (q *ringqueue[T]) Cap() int {
return cap(q.nodes)
}
func (q *ringqueue[T]) Len() int {
return q.cnt
}

View File

@ -0,0 +1,8 @@
<html>
<head>
<link href="/main.css" rel="stylesheet">
</head>
<body data-wails-drag>
<div id="logo"></div>
</body>
</html>

39
v3/pkg/assetserver/testdata/main.css vendored Normal file

File diff suppressed because one or more lines are too long

20
v3/pkg/assetserver/testdata/main.js vendored Normal file
View File

@ -0,0 +1,20 @@
import {ready} from '@wails/runtime';
ready(() => {
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
// Setup the greet function
window.greet = function () {
// Get name
let name = nameElement.value;
// Call App.Greet(name)
window.backend.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};
});

View File

@ -0,0 +1,8 @@
<html>
<head>
<link href="/main.css" rel="stylesheet">
</head>
<body data-wails-drag>
<div id="logo"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
import {ready} from '@wails/runtime';
ready(() => {
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
// Setup the greet function
window.greet = function () {
// Get name
let name = nameElement.value;
// Call App.Greet(name)
window.backend.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};
});

View File

@ -0,0 +1,6 @@
package testdata
import "embed"
//go:embed index.html main.css main.js
var TopLevelFS embed.FS

View File

@ -0,0 +1,17 @@
package webview
import (
"io"
"net/http"
)
type Request interface {
URL() (string, error)
Method() (string, error)
Header() (http.Header, error)
Body() (io.ReadCloser, error)
Response() ResponseWriter
Close() error
}

View File

@ -0,0 +1,248 @@
//go:build darwin
package webview
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework WebKit
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#include <string.h>
static void URLSchemeTaskRetain(void *wkUrlSchemeTask) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
[urlSchemeTask retain];
}
static void URLSchemeTaskRelease(void *wkUrlSchemeTask) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
[urlSchemeTask release];
}
static const char * URLSchemeTaskRequestURL(void *wkUrlSchemeTask) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
@autoreleasepool {
return [urlSchemeTask.request.URL.absoluteString UTF8String];
}
}
static const char * URLSchemeTaskRequestMethod(void *wkUrlSchemeTask) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
@autoreleasepool {
return [urlSchemeTask.request.HTTPMethod UTF8String];
}
}
static const char * URLSchemeTaskRequestHeadersJSON(void *wkUrlSchemeTask) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
@autoreleasepool {
NSData *headerData = [NSJSONSerialization dataWithJSONObject: urlSchemeTask.request.allHTTPHeaderFields options:0 error: nil];
if (!headerData) {
return nil;
}
NSString* headerString = [[[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding] autorelease];
const char * headerJSON = [headerString UTF8String];
return strdup(headerJSON);
}
}
static bool URLSchemeTaskRequestBodyBytes(void *wkUrlSchemeTask, const void **body, int *bodyLen) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
@autoreleasepool {
if (!urlSchemeTask.request.HTTPBody) {
return false;
}
*body = urlSchemeTask.request.HTTPBody.bytes;
*bodyLen = urlSchemeTask.request.HTTPBody.length;
return true;
}
}
static bool URLSchemeTaskRequestBodyStreamOpen(void *wkUrlSchemeTask) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
@autoreleasepool {
if (!urlSchemeTask.request.HTTPBodyStream) {
return false;
}
[urlSchemeTask.request.HTTPBodyStream open];
return true;
}
}
static void URLSchemeTaskRequestBodyStreamClose(void *wkUrlSchemeTask) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
@autoreleasepool {
if (!urlSchemeTask.request.HTTPBodyStream) {
return;
}
[urlSchemeTask.request.HTTPBodyStream close];
}
}
static int URLSchemeTaskRequestBodyStreamRead(void *wkUrlSchemeTask, void *buf, int bufLen) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
@autoreleasepool {
NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream;
if (!stream) {
return -2;
}
NSStreamStatus status = stream.streamStatus;
if (status == NSStreamStatusAtEnd || !stream.hasBytesAvailable) {
return 0;
} else if (status != NSStreamStatusOpen) {
return -3;
}
return [stream read:buf maxLength:bufLen];
}
}
*/
import "C"
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"unsafe"
)
// NewRequest creates as new WebViewRequest based on a pointer to an `id<WKURLSchemeTask>`
func NewRequest(wkURLSchemeTask unsafe.Pointer) Request {
C.URLSchemeTaskRetain(wkURLSchemeTask)
return newRequestFinalizer(&request{task: wkURLSchemeTask})
}
var _ Request = &request{}
type request struct {
task unsafe.Pointer
header http.Header
body io.ReadCloser
rw *responseWriter
}
func (r *request) URL() (string, error) {
return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil
}
func (r *request) Method() (string, error) {
return C.GoString(C.URLSchemeTaskRequestMethod(r.task)), nil
}
func (r *request) Header() (http.Header, error) {
if r.header != nil {
return r.header, nil
}
header := http.Header{}
if cHeaders := C.URLSchemeTaskRequestHeadersJSON(r.task); cHeaders != nil {
if headers := C.GoString(cHeaders); headers != "" {
var h map[string]string
if err := json.Unmarshal([]byte(headers), &h); err != nil {
return nil, fmt.Errorf("unable to unmarshal request headers: %s", err)
}
for k, v := range h {
header.Add(k, v)
}
}
C.free(unsafe.Pointer(cHeaders))
}
r.header = header
return header, nil
}
func (r *request) Body() (io.ReadCloser, error) {
if r.body != nil {
return r.body, nil
}
var body unsafe.Pointer
var bodyLen C.int
if C.URLSchemeTaskRequestBodyBytes(r.task, &body, &bodyLen) {
if body != nil && bodyLen > 0 {
r.body = io.NopCloser(bytes.NewReader(C.GoBytes(body, bodyLen)))
} else {
r.body = http.NoBody
}
} else if C.URLSchemeTaskRequestBodyStreamOpen(r.task) {
r.body = &requestBodyStreamReader{task: r.task}
}
return r.body, nil
}
func (r *request) Response() ResponseWriter {
if r.rw != nil {
return r.rw
}
r.rw = &responseWriter{r: r}
return r.rw
}
func (r *request) Close() error {
var err error
if r.body != nil {
err = r.body.Close()
}
r.Response().Finish()
C.URLSchemeTaskRelease(r.task)
return err
}
var _ io.ReadCloser = &requestBodyStreamReader{}
type requestBodyStreamReader struct {
task unsafe.Pointer
closed bool
}
// Read implements io.Reader
func (r *requestBodyStreamReader) Read(p []byte) (n int, err error) {
var content unsafe.Pointer
var contentLen int
if p != nil {
content = unsafe.Pointer(&p[0])
contentLen = len(p)
}
res := C.URLSchemeTaskRequestBodyStreamRead(r.task, content, C.int(contentLen))
if res > 0 {
return int(res), nil
}
switch res {
case 0:
return 0, io.EOF
case -1:
return 0, fmt.Errorf("body: stream error")
case -2:
return 0, fmt.Errorf("body: no stream defined")
case -3:
return 0, io.ErrClosedPipe
default:
return 0, fmt.Errorf("body: unknown error %d", res)
}
}
func (r *requestBodyStreamReader) Close() error {
if r.closed {
return nil
}
r.closed = true
C.URLSchemeTaskRequestBodyStreamClose(r.task)
return nil
}

View File

@ -0,0 +1,40 @@
package webview
import (
"runtime"
"sync/atomic"
)
var _ Request = &requestFinalizer{}
type requestFinalizer struct {
Request
closed int32
}
// newRequestFinalizer returns a request with a runtime finalizer to make sure it will be closed from the finalizer
// if it has not been already closed.
// It also makes sure Close() of the wrapping request is only called once.
func newRequestFinalizer(r Request) Request {
rf := &requestFinalizer{Request: r}
// Make sure to async release since it might block the finalizer goroutine for a longer period
runtime.SetFinalizer(rf, func(obj *requestFinalizer) { rf.close(true) })
return rf
}
func (r *requestFinalizer) Close() error {
return r.close(false)
}
func (r *requestFinalizer) close(asyncRelease bool) error {
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
runtime.SetFinalizer(r, nil)
if asyncRelease {
go r.Request.Close()
return nil
} else {
return r.Request.Close()
}
}
return nil
}

View File

@ -0,0 +1,83 @@
//go:build linux
// +build linux
package webview
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
*/
import "C"
import (
"io"
"net/http"
"unsafe"
)
// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest`
func NewRequest(webKitURISchemeRequest unsafe.Pointer) Request {
webkitReq := (*C.WebKitURISchemeRequest)(webKitURISchemeRequest)
C.g_object_ref(C.gpointer(webkitReq))
req := &request{req: webkitReq}
return newRequestFinalizer(req)
}
var _ Request = &request{}
type request struct {
req *C.WebKitURISchemeRequest
header http.Header
body io.ReadCloser
rw *responseWriter
}
func (r *request) URL() (string, error) {
return C.GoString(C.webkit_uri_scheme_request_get_uri(r.req)), nil
}
func (r *request) Method() (string, error) {
return webkit_uri_scheme_request_get_http_method(r.req), nil
}
func (r *request) Header() (http.Header, error) {
if r.header != nil {
return r.header, nil
}
r.header = webkit_uri_scheme_request_get_http_headers(r.req)
return r.header, nil
}
func (r *request) Body() (io.ReadCloser, error) {
if r.body != nil {
return r.body, nil
}
r.body = webkit_uri_scheme_request_get_http_body(r.req)
return r.body, nil
}
func (r *request) Response() ResponseWriter {
if r.rw != nil {
return r.rw
}
r.rw = &responseWriter{req: r.req}
return r.rw
}
func (r *request) Close() error {
var err error
if r.body != nil {
err = r.body.Close()
}
r.Response().Finish()
C.g_object_unref(C.gpointer(r.req))
return err
}

View File

@ -0,0 +1,94 @@
//go:build linux && purego
// +build linux,purego
package webview
import (
"io"
"net/http"
"github.com/ebitengine/purego"
)
// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest`
//
// Please make sure to call Release() when finished using the request.
func NewRequest(webKitURISchemeRequest uintptr) Request {
webkitReq := webKitURISchemeRequest
req := &request{req: webkitReq}
req.AddRef()
return req
}
var _ Request = &request{}
type request struct {
req uintptr
header http.Header
body io.ReadCloser
rw *responseWriter
}
func (r *request) AddRef() error {
var objectRef func(uintptr)
purego.RegisterLibFunc(&objectRef, gtk, "g_object_ref")
objectRef(r.req)
return nil
}
func (r *request) Release() error {
var objectUnref func(uintptr)
purego.RegisterLibFunc(&objectUnref, gtk, "g_object_unref")
objectUnref(r.req)
return nil
}
func (r *request) URL() (string, error) {
var getUri func(uintptr) string
purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_uri")
return getUri(r.req), nil
}
func (r *request) Method() (string, error) {
return webkit_uri_scheme_request_get_http_method(r.req), nil
}
func (r *request) Header() (http.Header, error) {
if r.header != nil {
return r.header, nil
}
r.header = webkit_uri_scheme_request_get_http_headers(r.req)
return r.header, nil
}
func (r *request) Body() (io.ReadCloser, error) {
if r.body != nil {
return r.body, nil
}
// WebKit2GTK has currently no support for request bodies.
r.body = http.NoBody
return r.body, nil
}
func (r *request) Response() ResponseWriter {
if r.rw != nil {
return r.rw
}
r.rw = &responseWriter{req: r.req}
return r.rw
}
func (r *request) Close() error {
var err error
if r.body != nil {
err = r.body.Close()
}
r.Response().Finish()
r.Release()
return err
}

View File

@ -0,0 +1,25 @@
package webview
import (
"errors"
"net/http"
)
const (
HeaderContentLength = "Content-Length"
HeaderContentType = "Content-Type"
)
var (
errRequestStopped = errors.New("request has been stopped")
errResponseFinished = errors.New("response has been finished")
)
// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response for the WebView.
type ResponseWriter interface {
http.ResponseWriter
// Finish the response and flush all data. A Finish after the request has already been finished has no effect.
Finish()
}

View File

@ -0,0 +1,147 @@
//go:build darwin
package webview
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework WebKit
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
static bool urlSchemeTaskCall(void *wkUrlSchemeTask, schemeTaskCaller fn) {
id<WKURLSchemeTask> urlSchemeTask = (id<WKURLSchemeTask>) wkUrlSchemeTask;
if (urlSchemeTask == nil) {
return false;
}
@autoreleasepool {
@try {
fn(urlSchemeTask);
} @catch (NSException *exception) {
// This is very bad to detect a stopped schemeTask this should be implemented in a better way
// But it seems to be very tricky to not deadlock when keeping a lock curing executing fn()
// It seems like those call switch the thread back to the main thread and then deadlocks when they reentrant want
// to get the lock again to start another request or stop it.
if ([exception.reason isEqualToString: @"This task has already been stopped"]) {
return false;
}
@throw exception;
}
return true;
}
}
static bool URLSchemeTaskDidReceiveData(void *wkUrlSchemeTask, void* data, int datalength) {
return urlSchemeTaskCall(
wkUrlSchemeTask,
^(id<WKURLSchemeTask> urlSchemeTask) {
NSData *nsdata = [NSData dataWithBytes:data length:datalength];
[urlSchemeTask didReceiveData:nsdata];
});
}
static bool URLSchemeTaskDidFinish(void *wkUrlSchemeTask) {
return urlSchemeTaskCall(
wkUrlSchemeTask,
^(id<WKURLSchemeTask> urlSchemeTask) {
[urlSchemeTask didFinish];
});
}
static bool URLSchemeTaskDidReceiveResponse(void *wkUrlSchemeTask, int statusCode, void *headersString, int headersStringLength) {
return urlSchemeTaskCall(
wkUrlSchemeTask,
^(id<WKURLSchemeTask> urlSchemeTask) {
NSData *nsHeadersJSON = [NSData dataWithBytes:headersString length:headersStringLength];
NSDictionary *headerFields = [NSJSONSerialization JSONObjectWithData:nsHeadersJSON options: NSJSONReadingMutableContainers error: nil];
NSHTTPURLResponse *response = [[[NSHTTPURLResponse alloc] initWithURL:urlSchemeTask.request.URL statusCode:statusCode HTTPVersion:@"HTTP/1.1" headerFields:headerFields] autorelease];
[urlSchemeTask didReceiveResponse:response];
});
}
*/
import "C"
import (
"encoding/json"
"net/http"
"unsafe"
)
var _ ResponseWriter = &responseWriter{}
type responseWriter struct {
r *request
header http.Header
wroteHeader bool
finished bool
}
func (rw *responseWriter) Header() http.Header {
if rw.header == nil {
rw.header = http.Header{}
}
return rw.header
}
func (rw *responseWriter) Write(buf []byte) (int, error) {
if rw.finished {
return 0, errResponseFinished
}
rw.WriteHeader(http.StatusOK)
var content unsafe.Pointer
var contentLen int
if buf != nil {
content = unsafe.Pointer(&buf[0])
contentLen = len(buf)
}
if !C.URLSchemeTaskDidReceiveData(rw.r.task, content, C.int(contentLen)) {
return 0, errRequestStopped
}
return contentLen, nil
}
func (rw *responseWriter) WriteHeader(code int) {
if rw.wroteHeader || rw.finished {
return
}
rw.wroteHeader = true
header := map[string]string{}
for k := range rw.Header() {
header[k] = rw.Header().Get(k)
}
headerData, _ := json.Marshal(header)
var headers unsafe.Pointer
var headersLen int
if len(headerData) != 0 {
headers = unsafe.Pointer(&headerData[0])
headersLen = len(headerData)
}
C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen))
}
func (rw *responseWriter) Finish() {
if !rw.wroteHeader {
rw.WriteHeader(http.StatusNotImplemented)
}
if rw.finished {
return
}
rw.finished = true
C.URLSchemeTaskDidFinish(rw.r.task)
}

View File

@ -0,0 +1,129 @@
//go:build linux
// +build linux
package webview
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include "gio/gunixinputstream.h"
*/
import "C"
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"syscall"
"unsafe"
)
type responseWriter struct {
req *C.WebKitURISchemeRequest
header http.Header
wroteHeader bool
finished bool
w io.WriteCloser
wErr error
}
func (rw *responseWriter) Header() http.Header {
if rw.header == nil {
rw.header = http.Header{}
}
return rw.header
}
func (rw *responseWriter) Write(buf []byte) (int, error) {
if rw.finished {
return 0, errResponseFinished
}
rw.WriteHeader(http.StatusOK)
if rw.wErr != nil {
return 0, rw.wErr
}
return rw.w.Write(buf)
}
func (rw *responseWriter) WriteHeader(code int) {
if rw.wroteHeader || rw.finished {
return
}
rw.wroteHeader = true
contentLength := int64(-1)
if sLen := rw.Header().Get(HeaderContentLength); sLen != "" {
if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 {
contentLength = pLen
}
}
// We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the
// read FD is given to the InputStream and will be closed there.
// Furthermore we especially don't want to have the FD_CLOEXEC
rFD, w, err := pipe()
if err != nil {
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err))
return
}
rw.w = w
stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1))
defer C.g_object_unref(C.gpointer(stream))
if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil {
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
return
}
}
func (rw *responseWriter) Finish() {
if !rw.wroteHeader {
rw.WriteHeader(http.StatusNotImplemented)
}
if rw.finished {
return
}
rw.finished = true
if rw.w != nil {
rw.w.Close()
}
}
func (rw *responseWriter) finishWithError(code int, err error) {
if rw.w != nil {
rw.w.Close()
rw.w = &nopCloser{io.Discard}
}
rw.wErr = err
msg := C.CString(err.Error())
gerr := C.g_error_new_literal(C.g_quark_from_string(msg), C.int(code), msg)
C.webkit_uri_scheme_request_finish_error(rw.req, gerr)
C.g_error_free(gerr)
C.free(unsafe.Pointer(msg))
}
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error { return nil }
func pipe() (r int, w *os.File, err error) {
var p [2]int
e := syscall.Pipe2(p[0:], 0)
if e != nil {
return 0, nil, fmt.Errorf("pipe2: %s", e)
}
return p[0], os.NewFile(uintptr(p[1]), "|1"), nil
}

View File

@ -0,0 +1,174 @@
//go:build linux && purego
// +build linux,purego
package webview
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"syscall"
"github.com/ebitengine/purego"
)
const (
gtk3 = "libgtk-3.so"
gtk4 = "libgtk-4.so"
)
var (
gtk uintptr
webkit uintptr
version int
)
func init() {
var err error
// gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL)
// if err == nil {
// version = 4
// return
// }
// log.Println("Failed to open GTK4: Falling back to GTK3")
gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL)
if err != nil {
panic(err)
}
version = 3
var webkit4 string = "libwebkit2gtk-4.1.so"
webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL)
if err != nil {
panic(err)
}
}
type responseWriter struct {
req uintptr
header http.Header
wroteHeader bool
finished bool
w io.WriteCloser
wErr error
}
func (rw *responseWriter) Header() http.Header {
if rw.header == nil {
rw.header = http.Header{}
}
return rw.header
}
func (rw *responseWriter) Write(buf []byte) (int, error) {
if rw.finished {
return 0, errResponseFinished
}
rw.WriteHeader(http.StatusOK)
if rw.wErr != nil {
return 0, rw.wErr
}
return rw.w.Write(buf)
}
func (rw *responseWriter) WriteHeader(code int) {
// TODO? Is this ever called? I don't think so!
if rw.wroteHeader || rw.finished {
return
}
rw.wroteHeader = true
contentLength := int64(-1)
if sLen := rw.Header().Get(HeaderContentLength); sLen != "" {
if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 {
contentLength = pLen
}
}
// We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the
// read FD is given to the InputStream and will be closed there.
// Furthermore we especially don't want to have the FD_CLOEXEC
rFD, w, err := pipe()
if err != nil {
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err))
return
}
rw.w = w
var newStream func(int, bool) uintptr
purego.RegisterLibFunc(&newStream, gtk, "g_unix_input_stream_new")
var unRef func(uintptr)
purego.RegisterLibFunc(&unRef, gtk, "g_object_unref")
stream := newStream(rFD, true)
/* var reqFinish func(uintptr, uintptr, uintptr, uintptr, int64) int
purego.RegisterLibFunc(&reqFinish, webkit, "webkit_uri_scheme_request_finish")
header := rw.Header()
defer unRef(stream)
if err := reqFinish(rw.req, code, header, stream, contentLength); err != nil {
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
}
*/
if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil {
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
return
}
}
func (rw *responseWriter) Finish() {
if !rw.wroteHeader {
rw.WriteHeader(http.StatusNotImplemented)
}
if rw.finished {
return
}
rw.finished = true
if rw.w != nil {
rw.w.Close()
}
}
func (rw *responseWriter) finishWithError(code int, err error) {
if rw.w != nil {
rw.w.Close()
rw.w = &nopCloser{io.Discard}
}
rw.wErr = err
var newLiteral func(uint32, string, int, string) uintptr // is this correct?
purego.RegisterLibFunc(&newLiteral, gtk, "g_error_new_literal")
var newQuark func(string) uintptr
purego.RegisterLibFunc(&newQuark, gtk, "g_quark_from_string")
var freeError func(uintptr)
purego.RegisterLibFunc(&freeError, gtk, "g_error_free")
var finishError func(uintptr, uintptr)
purego.RegisterLibFunc(&finishError, webkit, "webkit_uri_scheme_request_finish_error")
msg := string(err.Error())
//gquark := newQuark(msg)
gerr := newLiteral(1, msg, code, msg)
finishError(rw.req, gerr)
freeError(gerr)
}
type nopCloser struct {
io.Writer
}
func (nopCloser) Close() error { return nil }
func pipe() (r int, w *os.File, err error) {
var p [2]int
e := syscall.Pipe2(p[0:], 0)
if e != nil {
return 0, nil, fmt.Errorf("pipe2: %s", e)
}
return p[0], os.NewFile(uintptr(p[1]), "|1"), nil
}

View File

@ -0,0 +1,69 @@
//go:build linux && (webkit2_36 || webkit2_40)
package webview
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 libsoup-2.4
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include "libsoup/soup.h"
*/
import "C"
import (
"net/http"
"strings"
"unsafe"
)
func webkit_uri_scheme_request_get_http_method(req *C.WebKitURISchemeRequest) string {
method := C.GoString(C.webkit_uri_scheme_request_get_http_method(req))
return strings.ToUpper(method)
}
func webkit_uri_scheme_request_get_http_headers(req *C.WebKitURISchemeRequest) http.Header {
hdrs := C.webkit_uri_scheme_request_get_http_headers(req)
var iter C.SoupMessageHeadersIter
C.soup_message_headers_iter_init(&iter, hdrs)
var name *C.char
var value *C.char
h := http.Header{}
for C.soup_message_headers_iter_next(&iter, &name, &value) != 0 {
h.Add(C.GoString(name), C.GoString(value))
}
return h
}
func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error {
resp := C.webkit_uri_scheme_response_new(stream, C.gint64(streamLength))
defer C.g_object_unref(C.gpointer(resp))
cReason := C.CString(http.StatusText(code))
C.webkit_uri_scheme_response_set_status(resp, C.guint(code), cReason)
C.free(unsafe.Pointer(cReason))
cMimeType := C.CString(header.Get(HeaderContentType))
C.webkit_uri_scheme_response_set_content_type(resp, cMimeType)
C.free(unsafe.Pointer(cMimeType))
hdrs := C.soup_message_headers_new(C.SOUP_MESSAGE_HEADERS_RESPONSE)
for name, values := range header {
cName := C.CString(name)
for _, value := range values {
cValue := C.CString(value)
C.soup_message_headers_append(hdrs, cName, cValue)
C.free(unsafe.Pointer(cValue))
}
C.free(unsafe.Pointer(cName))
}
C.webkit_uri_scheme_response_set_http_headers(resp, hdrs)
C.webkit_uri_scheme_request_finish_with_response(req, resp)
return nil
}

View File

@ -0,0 +1,94 @@
//go:build linux && (webkit2_36 || webkit2_40) && purego
package webview
import (
"net/http"
"strings"
"github.com/ebitengine/purego"
)
func webkit_uri_scheme_request_get_http_method(req uintptr) string {
var getMethod func(uintptr) string
purego.RegisterLibFunc(&getMethod, gtk, "webkit_uri_scheme_request_get_http_method")
return strings.ToUpper(getMethod(req))
}
func webkit_uri_scheme_request_get_http_headers(req uintptr) http.Header {
var getHeaders func(uintptr) uintptr
purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_http_headers")
hdrs := getHeaders(req)
var headersIterInit func(uintptr, uintptr) uintptr
purego.RegisterLibFunc(&headersIterInit, gtk, "soup_message_headers_iter_init")
// TODO: How do we get a struct?
/*
typedef struct {
SoupMessageHeaders *hdrs;
int index_common;
int index_uncommon;
} SoupMessageHeadersIterReal;
*/
iter := make([]byte, 12)
headersIterInit(&iter, hdrs)
var iterNext func(uintptr, *string, *string) int
purego.RegisterLibFunc(&iterNext, gtk, "soup_message_headers_iter_next")
var name string
var value string
h := http.Header{}
for iterNext(&iter, &name, &value) != 0 {
h.Add(name, value)
}
return h
}
func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error {
var newResponse func(uintptr, int64) string
purego.RegisterLibFunc(&newResponse, webkit, "webkit_uri_scheme_response_new")
var unRef func(uintptr)
purego.RegisterLibFunc(&unRef, gtk, "g_object_unref")
resp := newResponse(stream, streamLength)
defer unRef(resp)
var setStatus func(uintptr, int, string)
purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_status")
setStatus(resp, code, cReason)
var setContentType func(uintptr, string)
purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_content_type")
setContentType(resp, header.Get(HeaderContentType))
soup := gtk
var soupHeadersNew func(int) uintptr
purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_new")
var soupHeadersAppend func(uintptr, string, string)
purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_append")
hdrs := soupHeadersNew(SOUP_MESSAGE_HEADERS_RESPONSE)
for name, values := range header {
for _, value := range values {
soupHeadersAppend(hdrs, name, value)
}
}
var setHttpHeaders func(uintptr, uintptr)
purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_http_headers")
setHttpHeaders(resp, hdrs)
var finishWithResponse func(uintptr, uintptr)
purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_request_finish_with_response")
finishWithResponse(req, resp)
return nil
}

View File

@ -0,0 +1,21 @@
//go:build linux && webkit2_36
package webview
/*
#cgo linux pkg-config: webkit2gtk-4.0
#include "webkit2/webkit2.h"
*/
import "C"
import (
"io"
"net/http"
)
const Webkit2MinMinorVersion = 36
func webkit_uri_scheme_request_get_http_body(_ *C.WebKitURISchemeRequest) io.ReadCloser {
return http.NoBody
}

View File

@ -0,0 +1,83 @@
//go:build linux && webkit2_40
package webview
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 gio-unix-2.0
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
#include "gio/gunixinputstream.h"
*/
import "C"
import (
"fmt"
"io"
"net/http"
"unsafe"
)
func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser {
stream := C.webkit_uri_scheme_request_get_http_body(req)
if stream == nil {
return http.NoBody
}
return &webkitRequestBody{stream: stream}
}
type webkitRequestBody struct {
stream *C.GInputStream
closed bool
}
// Read implements io.Reader
func (r *webkitRequestBody) Read(p []byte) (int, error) {
if r.closed {
return 0, io.ErrClosedPipe
}
var content unsafe.Pointer
var contentLen int
if p != nil {
content = unsafe.Pointer(&p[0])
contentLen = len(p)
}
var n C.gsize
var gErr *C.GError
res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr)
if res == 0 {
return 0, formatGError("stream read failed", gErr)
} else if n == 0 {
return 0, io.EOF
}
return int(n), nil
}
func (r *webkitRequestBody) Close() error {
if r.closed {
return nil
}
r.closed = true
// https://docs.gtk.org/gio/method.InputStream.close.html
// Streams will be automatically closed when the last reference is dropped, but you might want to call this function
// to make sure resources are released as early as possible.
var err error
var gErr *C.GError
if C.g_input_stream_close(r.stream, nil, &gErr) == 0 {
err = formatGError("stream close failed", gErr)
}
C.g_object_unref(C.gpointer(r.stream))
r.stream = nil
return err
}
func formatGError(msg string, gErr *C.GError, args ...any) error {
if gErr != nil && gErr.message != nil {
msg += ": " + C.GoString(gErr.message)
C.g_error_free(gErr)
}
return fmt.Errorf(msg, args...)
}

View File

@ -0,0 +1,74 @@
//go:build linux && webkit2_40 && purego
package webview
import (
"fmt"
"io"
"net/http"
"unsafe"
)
func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser {
stream := C.webkit_uri_scheme_request_get_http_body(req)
if stream == nil {
return http.NoBody
}
return &webkitRequestBody{stream: stream}
}
type webkitRequestBody struct {
stream *C.GInputStream
closed bool
}
// Read implements io.Reader
func (r *webkitRequestBody) Read(p []byte) (int, error) {
if r.closed {
return 0, io.ErrClosedPipe
}
var content unsafe.Pointer
var contentLen int
if p != nil {
content = unsafe.Pointer(&p[0])
contentLen = len(p)
}
var n C.gsize
var gErr *C.GError
res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr)
if res == 0 {
return 0, formatGError("stream read failed", gErr)
} else if n == 0 {
return 0, io.EOF
}
return int(n), nil
}
func (r *webkitRequestBody) Close() error {
if r.closed {
return nil
}
r.closed = true
// https://docs.gtk.org/gio/method.InputStream.close.html
// Streams will be automatically closed when the last reference is dropped, but you might want to call this function
// to make sure resources are released as early as possible.
var err error
var gErr *C.GError
if C.g_input_stream_close(r.stream, nil, &gErr) == 0 {
err = formatGError("stream close failed", gErr)
}
C.g_object_unref(C.gpointer(r.stream))
r.stream = nil
return err
}
func formatGError(msg string, gErr *C.GError, args ...any) error {
if gErr != nil && gErr.message != nil {
msg += ": " + C.GoString(gErr.message)
C.g_error_free(gErr)
}
return fmt.Errorf(msg, args...)
}

View File

@ -0,0 +1,5 @@
//go:build linux && webkit2_40
package webview
const Webkit2MinMinorVersion = 40

View File

@ -0,0 +1,48 @@
//go:build linux && !(webkit2_36 || webkit2_40)
package webview
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
*/
import "C"
import (
"fmt"
"io"
"net/http"
"unsafe"
)
const Webkit2MinMinorVersion = 0
func webkit_uri_scheme_request_get_http_method(_ *C.WebKitURISchemeRequest) string {
return http.MethodGet
}
func webkit_uri_scheme_request_get_http_headers(_ *C.WebKitURISchemeRequest) http.Header {
// Fake some basic default headers that are needed if e.g. request are being proxied to the an external sever, like
// we do in the devserver.
h := http.Header{}
h.Add("Accept", "*/*")
h.Add("User-Agent", "wails.io/605.1.15")
return h
}
func webkit_uri_scheme_request_get_http_body(_ *C.WebKitURISchemeRequest) io.ReadCloser {
return http.NoBody
}
func webkit_uri_scheme_request_finish(req *C.WebKitURISchemeRequest, code int, header http.Header, stream *C.GInputStream, streamLength int64) error {
if code != http.StatusOK {
return fmt.Errorf("StatusCodes not supported: %d - %s", code, http.StatusText(code))
}
cMimeType := C.CString(header.Get(HeaderContentType))
C.webkit_uri_scheme_request_finish(req, stream, C.gint64(streamLength), cMimeType)
C.free(unsafe.Pointer(cMimeType))
return nil
}

View File

@ -0,0 +1,36 @@
//go:build linux && !(webkit2_36 || webkit2_40) && purego
package webview
import (
"fmt"
"io"
"net/http"
"github.com/ebitengine/purego"
)
const Webkit2MinMinorVersion = 0
func webkit_uri_scheme_request_get_http_method(_ uintptr) string {
return http.MethodGet
}
func webkit_uri_scheme_request_get_http_headers(_ uintptr) http.Header {
return http.Header{}
}
func webkit_uri_scheme_request_get_http_body(_ uintptr) io.ReadCloser {
return http.NoBody
}
func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error {
if code != http.StatusOK {
return fmt.Errorf("StatusCodes not supported: %d - %s", code, http.StatusText(code))
}
var requestFinish func(uintptr, uintptr, int64, string)
purego.RegisterLibFunc(&requestFinish, webkit, "webkit_uri_scheme_request_finish")
requestFinish(req, stream, streamLength, header.Get(HeaderContentType))
return nil
}