From b4d64678b584a1aeb45a59761ec4379a85a0e21f Mon Sep 17 00:00:00 2001 From: stffabi Date: Tue, 10 Jan 2023 20:30:32 +0100 Subject: [PATCH] Basic integration of the assetserver --- exp/examples/plain/main.go | 10 ++++++- exp/go.mod | 5 ++++ exp/go.sum | 14 ++++++++++ exp/internal/runtime/assets.go | 26 ++++++++++++++++++ exp/internal/runtime/assets_dev.go | 27 ++++++++++++++++++ exp/pkg/application/application.go | 29 ++++++++++++++++++++ exp/pkg/application/application_darwin.go | 9 ++++++ exp/pkg/application/webview_window.go | 23 ++++++++++++++++ exp/pkg/application/webview_window.h | 2 +- exp/pkg/application/webview_window.m | 16 +++++++++++ exp/pkg/application/webview_window_darwin.go | 6 ++++ 11 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 exp/internal/runtime/assets.go create mode 100644 exp/internal/runtime/assets_dev.go diff --git a/exp/examples/plain/main.go b/exp/examples/plain/main.go index 0f7171083..ea2728758 100644 --- a/exp/examples/plain/main.go +++ b/exp/examples/plain/main.go @@ -3,6 +3,7 @@ package main import ( _ "embed" "log" + "net/http" "github.com/wailsapp/wails/exp/pkg/options" @@ -20,13 +21,20 @@ func main() { // Create window app.NewWebviewWindowWithOptions(&options.WebviewWindow{ Title: "Plain Bundle", - HTML: `Plain Bundle

Plain Bundle

This is a plain bundle. It has no frontend code.

`, CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`, Mac: options.MacWindow{ InvisibleTitleBarHeight: 50, Backdrop: options.MacBackdropTranslucent, TitleBar: options.TitleBarHiddenInset, }, + + URL: "/", + Assets: options.Assets{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`Plain Bundle

Plain Bundle

This is a plain bundle. It has no frontend code but this was Served by the AssetServer's Handler

`)) + }), + }, }) err := app.Run() diff --git a/exp/go.mod b/exp/go.mod index 62732c088..41406f11b 100644 --- a/exp/go.mod +++ b/exp/go.mod @@ -22,7 +22,9 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/gookit/color v1.5.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/joho/godotenv v1.4.0 // indirect + github.com/leaanthony/slicer v1.5.0 // indirect github.com/lithammer/fuzzysearch v1.1.5 // indirect github.com/mattn/go-colorable v0.1.11 // indirect github.com/mattn/go-isatty v0.0.14 // indirect @@ -35,10 +37,13 @@ require ( github.com/radovskyb/watcher v1.0.7 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/sajari/fuzzy v1.0.0 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/term v0.3.0 // indirect diff --git a/exp/go.sum b/exp/go.sum index 2b249a9e5..5939a361b 100644 --- a/exp/go.sum +++ b/exp/go.sum @@ -28,6 +28,8 @@ github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQ github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jackmordaunt/icns/v2 v2.2.1 h1:MGklwYP2yohKn2Bw7XxlF69LZe98S1vUfl5OvAulPwg= github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= @@ -45,6 +47,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= github.com/leaanthony/clir v1.3.0 h1:L9nPDWrmc/qU9UWZZvRaFajWYuO0np9V5p+5gxyYno0= github.com/leaanthony/clir v1.3.0/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= +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= github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= @@ -105,8 +109,12 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tc-hib/winres v0.1.6 h1:qgsYHze+BxQPEYilxIz/KCQGaClvI2+yLBAZs+3+0B8= github.com/tc-hib/winres v0.1.6/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= +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/task/v3 v3.19.1 h1:syDKYaPBXgrXKKSJVEWcOEoSFtZzpvxqlHf90YRukRc= github.com/wailsapp/task/v3 v3.19.1/go.mod h1:y7rWakbLR5gFElGgo6rA2dyr6vU/zNIDVfn3S4Of6OI= +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/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -119,6 +127,9 @@ golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -128,6 +139,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -135,6 +147,7 @@ golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -155,6 +168,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/exp/internal/runtime/assets.go b/exp/internal/runtime/assets.go new file mode 100644 index 000000000..bf3caa37f --- /dev/null +++ b/exp/internal/runtime/assets.go @@ -0,0 +1,26 @@ +//go:build production + +package runtime + +var RuntimeAssetsBundle = &RuntimeAssets{ + desktopIPC: DesktopIPC, + runtimeDesktopJS: DesktopRuntime, +} + +type RuntimeAssets struct { + desktopIPC []byte + websocketIPC []byte + runtimeDesktopJS []byte +} + +func (r *RuntimeAssets) DesktopIPC() []byte { + return r.desktopIPC +} + +func (r *RuntimeAssets) WebsocketIPC() []byte { + return r.websocketIPC +} + +func (r *RuntimeAssets) RuntimeDesktopJS() []byte { + return r.runtimeDesktopJS +} diff --git a/exp/internal/runtime/assets_dev.go b/exp/internal/runtime/assets_dev.go new file mode 100644 index 000000000..1d9915d33 --- /dev/null +++ b/exp/internal/runtime/assets_dev.go @@ -0,0 +1,27 @@ +//go:build !production + +package runtime + +var RuntimeAssetsBundle = &RuntimeAssets{ + desktopIPC: DesktopIPC, + websocketIPC: WebsocketIPC, + runtimeDesktopJS: DesktopRuntime, +} + +type RuntimeAssets struct { + desktopIPC []byte + websocketIPC []byte + runtimeDesktopJS []byte +} + +func (r *RuntimeAssets) DesktopIPC() []byte { + return r.desktopIPC +} + +func (r *RuntimeAssets) WebsocketIPC() []byte { + return r.websocketIPC +} + +func (r *RuntimeAssets) RuntimeDesktopJS() []byte { + return r.runtimeDesktopJS +} diff --git a/exp/pkg/application/application.go b/exp/pkg/application/application.go index 28a102f44..69dacd91e 100644 --- a/exp/pkg/application/application.go +++ b/exp/pkg/application/application.go @@ -8,6 +8,8 @@ import ( "github.com/wailsapp/wails/exp/pkg/events" "github.com/wailsapp/wails/exp/pkg/options" + + "github.com/wailsapp/wails/v2/pkg/assetserver/webview" ) var globalApplication *App @@ -65,6 +67,13 @@ type windowMessage struct { var windowMessageBuffer = make(chan *windowMessage) +type webViewAssetRequest struct { + windowId uint + request webview.Request +} + +var webviewRequests = make(chan *webViewAssetRequest) + type App struct { options options.Application applicationEventListeners map[uint][]func() @@ -178,6 +187,13 @@ func (a *App) Run() error { a.handleWindowEvent(event) } }() + go func() { + for { + event := <-webviewRequests + a.handleWebViewRequest(event) + event.request.Release() + } + }() go func() { for { event := <-windowMessageBuffer @@ -236,6 +252,19 @@ func (a *App) handleWindowMessage(event *windowMessage) { window.handleMessage(event.message) } +func (a *App) handleWebViewRequest(event *webViewAssetRequest) { + // Get window from window map + a.windowsLock.Lock() + window, ok := a.windows[event.windowId] + a.windowsLock.Unlock() + if !ok { + log.Printf("WebviewWindow #%d not found", event.windowId) + return + } + // Get callback from window + window.handleWebViewRequest(event.request) +} + func (a *App) handleWindowEvent(event *WindowEvent) { // Get window from window map a.windowsLock.Lock() diff --git a/exp/pkg/application/application_darwin.go b/exp/pkg/application/application_darwin.go index 48fb30a0f..be665d362 100644 --- a/exp/pkg/application/application_darwin.go +++ b/exp/pkg/application/application_darwin.go @@ -119,6 +119,7 @@ import ( "unsafe" "github.com/wailsapp/wails/exp/pkg/events" + "github.com/wailsapp/wails/v2/pkg/assetserver/webview" ) type macosApp struct { @@ -203,6 +204,14 @@ func processMessage(windowID C.uint, message *C.char) { } } +//export processURLRequest +func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) { + webviewRequests <- &webViewAssetRequest{ + windowId: uint(windowID), + request: webview.NewRequest(wkUrlSchemeTask), + } +} + //export processMenuItemClick func processMenuItemClick(menuID C.uint) { menuItemClicked <- uint(menuID) diff --git a/exp/pkg/application/webview_window.go b/exp/pkg/application/webview_window.go index 6e6849830..33356f083 100644 --- a/exp/pkg/application/webview_window.go +++ b/exp/pkg/application/webview_window.go @@ -4,8 +4,12 @@ import ( "fmt" "sync" + "github.com/wailsapp/wails/exp/internal/runtime" "github.com/wailsapp/wails/exp/pkg/events" "github.com/wailsapp/wails/exp/pkg/options" + "github.com/wailsapp/wails/v2/pkg/assetserver" + "github.com/wailsapp/wails/v2/pkg/assetserver/webview" + assetserveroptions "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) type ( @@ -63,6 +67,8 @@ type WebviewWindow struct { implLock sync.RWMutex id uint + assets *assetserver.AssetServer + eventListeners map[uint][]func() eventListenersLock sync.RWMutex } @@ -84,10 +90,21 @@ func NewWindow(options *options.WebviewWindow) *WebviewWindow { if options.Height == 0 { options.Height = 600 } + + opts := assetserveroptions.Options{Assets: options.Assets.FS, Handler: options.Assets.Handler, Middleware: options.Assets.Middleware} + // TODO Bindings, Logger, ServingFrom disk? + srv, err := assetserver.NewAssetServer("", opts, false, nil, runtime.RuntimeAssetsBundle) + if err != nil { + // TODO handle errors + panic(err) + } + return &WebviewWindow{ id: getWindowID(), options: options, eventListeners: make(map[uint][]func()), + + assets: srv, } } @@ -326,6 +343,12 @@ func (w *WebviewWindow) handleMessage(message string) { } } +func (w *WebviewWindow) handleWebViewRequest(request webview.Request) { + url, _ := request.URL() + fmt.Printf("[window %d] Request %s\n", w.id, url) + w.assets.ServeWebViewRequest(request) +} + func (w *WebviewWindow) Center() { if w.impl == nil { return diff --git a/exp/pkg/application/webview_window.h b/exp/pkg/application/webview_window.h index b8962b056..72d56ee36 100644 --- a/exp/pkg/application/webview_window.h +++ b/exp/pkg/application/webview_window.h @@ -16,7 +16,7 @@ @end -@interface WebviewWindowDelegate : NSObject +@interface WebviewWindowDelegate : NSObject @property bool hideOnClose; @property (retain) WKWebView* webView; diff --git a/exp/pkg/application/webview_window.m b/exp/pkg/application/webview_window.m index ff5d22bc4..504871764 100644 --- a/exp/pkg/application/webview_window.m +++ b/exp/pkg/application/webview_window.m @@ -6,6 +6,7 @@ #import "../events/events.h" extern void processMessage(unsigned int, const char*); +extern void processURLRequest(unsigned int, void *); extern bool hasListeners(unsigned int); @implementation WebviewWindow @@ -82,6 +83,21 @@ extern bool hasListeners(unsigned int); - (void)handleLeftMouseUp:(NSWindow *)window { self.leftMouseEvent = nil; } + +- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask { + processURLRequest(self.windowId, urlSchemeTask); +} + +- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask { + NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream; + if (stream) { + NSStreamStatus status = stream.streamStatus; + if (status != NSStreamStatusClosed && status != NSStreamStatusNotOpen) { + [stream close]; + } + } +} + // GENERATED EVENTS START - (void)windowDidBecomeKey:(NSNotification *)notification { if( hasListeners(EventWindowDidBecomeKey) ) { diff --git a/exp/pkg/application/webview_window_darwin.go b/exp/pkg/application/webview_window_darwin.go index d66bc59ea..2f54dbbb9 100644 --- a/exp/pkg/application/webview_window_darwin.go +++ b/exp/pkg/application/webview_window_darwin.go @@ -48,6 +48,7 @@ void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWa WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init]; config.suppressesIncrementalRendering = true; config.applicationNameForUserAgent = @"wails.io"; + [config setURLSchemeHandler:delegate forURLScheme:@"wails"]; if (@available(macOS 10.15, *)) { config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled; } @@ -913,6 +914,11 @@ func (w *macosWebviewWindow) execJS(js string) { } func (w *macosWebviewWindow) setURL(url string) { + if url == "/" { + // TODO handle this in a central location and handle all urls without scheme and host. This might be platform + // dependant + url = "wails://wails/" + } C.navigationLoadURL(w.nsWindow, C.CString(url)) }