fix: add involves edge from task to agent:nomos at creation
Plus sync vendor directory for Docker build compatibility.
This commit is contained in:
161
vendor/github.com/wailsapp/wails/v3/internal/assetserver/asset_fileserver.go
generated
vendored
Normal file
161
vendor/github.com/wailsapp/wails/v3/internal/assetserver/asset_fileserver.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
iofs "io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
indexHTML = "index.html"
|
||||
)
|
||||
|
||||
type assetFileServer struct {
|
||||
fs iofs.FS
|
||||
err error
|
||||
}
|
||||
|
||||
func newAssetFileServerFS(vfs iofs.FS) http.Handler {
|
||||
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)
|
||||
}
|
||||
|
||||
err = errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
vfs, err = iofs.Sub(vfs, path.Clean(subDir))
|
||||
}
|
||||
|
||||
return &assetFileServer{fs: vfs, err: err}
|
||||
}
|
||||
|
||||
func (d *assetFileServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
url := req.URL.Path
|
||||
|
||||
err := d.err
|
||||
if err == nil {
|
||||
filename := path.Clean(strings.TrimPrefix(url, "/"))
|
||||
d.logInfo(ctx, "Handling request", "url", url, "file", filename)
|
||||
err = d.serveFSFile(rw, req, filename)
|
||||
if os.IsNotExist(err) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
d.logError(ctx, "Unable to handle request", "url", url, "err", err)
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// serveFile will try to load the file from the fs.FS and write it to the response
|
||||
func (d *assetFileServer) 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 {
|
||||
if s := path.Ext(filename); s == "" {
|
||||
filename = filename + ".html"
|
||||
file, err = d.fs.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
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 *assetFileServer) logInfo(ctx context.Context, message string, args ...interface{}) {
|
||||
logInfo(ctx, "[AssetFileServerFS] "+message, args...)
|
||||
}
|
||||
|
||||
func (d *assetFileServer) logError(ctx context.Context, message string, args ...interface{}) {
|
||||
logError(ctx, "[AssetFileServerFS] "+message, args...)
|
||||
}
|
||||
175
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver.go
generated
vendored
Normal file
175
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver.go
generated
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
webViewRequestHeaderWindowId = "x-wails-window-id"
|
||||
webViewRequestHeaderWindowName = "x-wails-window-name"
|
||||
HeaderAcceptLanguage = "accept-language"
|
||||
)
|
||||
|
||||
type RuntimeHandler interface {
|
||||
HandleRuntimeCall(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
Route string
|
||||
Handler http.Handler
|
||||
}
|
||||
|
||||
type AssetServer struct {
|
||||
options *Options
|
||||
handler http.Handler
|
||||
services []service
|
||||
|
||||
assetServerWebView
|
||||
}
|
||||
|
||||
func NewAssetServer(options *Options) (*AssetServer, error) {
|
||||
result := &AssetServer{
|
||||
options: options,
|
||||
}
|
||||
|
||||
userHandler := options.Handler
|
||||
if userHandler == nil {
|
||||
userHandler = http.NotFoundHandler()
|
||||
}
|
||||
|
||||
handler := http.Handler(
|
||||
http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
result.serveHTTP(w, r, userHandler)
|
||||
}))
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
result.handler = handler
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
start := time.Now()
|
||||
wrapped := newContentTypeSniffer(rw)
|
||||
defer func() {
|
||||
if _, err := wrapped.complete(); err != nil {
|
||||
a.options.Logger.Error("Error writing response data.", "uri", req.RequestURI, "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
req = req.WithContext(contextWithLogger(req.Context(), a.options.Logger))
|
||||
a.handler.ServeHTTP(wrapped, req)
|
||||
|
||||
a.options.Logger.Debug(
|
||||
"Asset Request:",
|
||||
"windowName", req.Header.Get(webViewRequestHeaderWindowName),
|
||||
"windowID", req.Header.Get(webViewRequestHeaderWindowId),
|
||||
"code", wrapped.status,
|
||||
"method", req.Method,
|
||||
"path", req.URL.EscapedPath(),
|
||||
"duration", time.Since(start),
|
||||
)
|
||||
}
|
||||
|
||||
func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userHandler http.Handler) {
|
||||
if isWebSocket(req) {
|
||||
// WebSockets are not supported by the AssetServer
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
header := rw.Header()
|
||||
// TODO: I don't think this is needed now?
|
||||
//if a.servingFromDisk {
|
||||
// header.Add(HeaderCacheControl, "no-cache")
|
||||
//}
|
||||
|
||||
reqPath := req.URL.Path
|
||||
switch reqPath {
|
||||
case "", "/", "/index.html":
|
||||
// Cache the accept-language header
|
||||
// before passing the request down the chain.
|
||||
acceptLanguage := req.Header.Get(HeaderAcceptLanguage)
|
||||
if acceptLanguage == "" {
|
||||
acceptLanguage = "en"
|
||||
}
|
||||
|
||||
wrapped := &fallbackResponseWriter{
|
||||
rw: rw,
|
||||
req: req,
|
||||
fallback: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
// Set content type for default index.html
|
||||
header.Set(HeaderContentType, "text/html; charset=utf-8")
|
||||
a.writeBlob(rw, indexHTML, defaultIndexHTML(acceptLanguage))
|
||||
}),
|
||||
}
|
||||
userHandler.ServeHTTP(wrapped, req)
|
||||
|
||||
default:
|
||||
// Check if the path matches a service route
|
||||
for _, svc := range a.services {
|
||||
if strings.HasPrefix(reqPath, svc.Route) {
|
||||
req.URL.Path = strings.TrimPrefix(reqPath, svc.Route)
|
||||
svc.Handler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Forward to the user-provided handler
|
||||
userHandler.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AssetServer) AttachServiceHandler(route string, handler http.Handler) {
|
||||
a.services = append(a.services, service{route, handler})
|
||||
}
|
||||
|
||||
func (a *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
|
||||
err := ServeFile(rw, filename, blob)
|
||||
if err != nil {
|
||||
a.serveError(rw, err, "Error writing file content.", "filename", filename)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) {
|
||||
args = append(args, "error", err)
|
||||
a.options.Logger.Error(msg, args...)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func GetStartURL(userURL string) (string, error) {
|
||||
devServerURL := GetDevServerURL()
|
||||
startURL := baseURL.String()
|
||||
if devServerURL != "" {
|
||||
// Parse the port
|
||||
parsedURL, err := url.Parse(devServerURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error parsing environment variable `FRONTEND_DEVSERVER_URL`: %w. Please check your `Taskfile.yml` file", err)
|
||||
}
|
||||
port := parsedURL.Port()
|
||||
if port != "" {
|
||||
baseURL.Host = net.JoinHostPort(baseURL.Hostname(), port)
|
||||
startURL = baseURL.String()
|
||||
}
|
||||
}
|
||||
|
||||
if userURL != "" {
|
||||
parsedURL, err := baseURL.Parse(userURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error parsing URL: %w", err)
|
||||
}
|
||||
|
||||
startURL = parsedURL.String()
|
||||
}
|
||||
|
||||
return startURL, nil
|
||||
}
|
||||
12
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_android.go
generated
vendored
Normal file
12
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_android.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build android
|
||||
|
||||
package assetserver
|
||||
|
||||
import "net/url"
|
||||
|
||||
// Android uses https://wails.localhost as the base URL
|
||||
// This matches the WebViewAssetLoader domain configuration
|
||||
var baseURL = url.URL{
|
||||
Scheme: "https",
|
||||
Host: "wails.localhost",
|
||||
}
|
||||
10
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_darwin.go
generated
vendored
Normal file
10
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build darwin && !ios
|
||||
|
||||
package assetserver
|
||||
|
||||
import "net/url"
|
||||
|
||||
var baseURL = url.URL{
|
||||
Scheme: "wails",
|
||||
Host: "localhost",
|
||||
}
|
||||
50
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_dev.go
generated
vendored
Normal file
50
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_dev.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
//go:build !production
|
||||
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io"
|
||||
iofs "io/fs"
|
||||
)
|
||||
|
||||
//go:embed defaults
|
||||
var defaultHTML embed.FS
|
||||
|
||||
func defaultIndexHTML(language string) []byte {
|
||||
result := []byte("index.html not found")
|
||||
// Create an fs.Sub in the defaults directory
|
||||
defaults, err := iofs.Sub(defaultHTML, "defaults")
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
// Get the 2 character language code
|
||||
lang := "en"
|
||||
if len(language) >= 2 {
|
||||
lang = language[:2]
|
||||
}
|
||||
// Now we can read the index.html file in the format
|
||||
// index.<lang>.html.
|
||||
|
||||
indexFile, err := defaults.Open("index." + lang + ".html")
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
indexBytes, err := io.ReadAll(indexFile)
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
return indexBytes
|
||||
}
|
||||
|
||||
func (a *AssetServer) LogDetails() {
|
||||
var info = []any{
|
||||
"middleware", a.options.Middleware != nil,
|
||||
"handler", a.options.Handler != nil,
|
||||
}
|
||||
if devServerURL := GetDevServerURL(); devServerURL != "" {
|
||||
info = append(info, "devServerURL", devServerURL)
|
||||
}
|
||||
a.options.Logger.Info("AssetServer Info:", info...)
|
||||
}
|
||||
10
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_ios.go
generated
vendored
Normal file
10
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_ios.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build ios
|
||||
|
||||
package assetserver
|
||||
|
||||
import "net/url"
|
||||
|
||||
var baseURL = url.URL{
|
||||
Scheme: "wails",
|
||||
Host: "localhost",
|
||||
}
|
||||
10
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_linux.go
generated
vendored
Normal file
10
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_linux.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:build linux && !android
|
||||
|
||||
package assetserver
|
||||
|
||||
import "net/url"
|
||||
|
||||
var baseURL = url.URL{
|
||||
Scheme: "wails",
|
||||
Host: "localhost",
|
||||
}
|
||||
9
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_production.go
generated
vendored
Normal file
9
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_production.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build production
|
||||
|
||||
package assetserver
|
||||
|
||||
func defaultIndexHTML(_ string) []byte {
|
||||
return []byte("index.html not found")
|
||||
}
|
||||
|
||||
func (a *AssetServer) LogDetails() {}
|
||||
198
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_webview.go
generated
vendored
Normal file
198
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_webview.go
generated
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/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 (a *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
a.dispatchInit.Do(func() {
|
||||
workers := a.dispatchWorkers
|
||||
if workers <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
workerC := make(chan webview.Request, workers*2)
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
for req := range workerC {
|
||||
a.processWebViewRequest(req)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
dispatchC := make(chan webview.Request)
|
||||
go queueingDispatcher(50, dispatchC, workerC)
|
||||
|
||||
a.dispatchReqC = dispatchC
|
||||
})
|
||||
|
||||
if a.dispatchReqC == nil {
|
||||
go a.processWebViewRequest(req)
|
||||
} else {
|
||||
a.dispatchReqC <- req
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AssetServer) processWebViewRequest(r webview.Request) {
|
||||
uri, _ := r.URL()
|
||||
a.processWebViewRequestInternal(r)
|
||||
if err := r.Close(); err != nil {
|
||||
a.options.Logger.Error("Unable to call close for request for uri.", "uri", uri)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (a *AssetServer) processWebViewRequestInternal(r webview.Request) {
|
||||
uri := "unknown"
|
||||
var err error
|
||||
|
||||
wrw := r.Response()
|
||||
defer func() {
|
||||
if err := wrw.Finish(); err != nil {
|
||||
a.options.Logger.Error("Error finishing request.", "uri", uri, "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
rw := newContentTypeSniffer(wrw) // Make sure we have a Content-Type sniffer
|
||||
defer func() {
|
||||
if _, err := rw.complete(); err != nil {
|
||||
a.options.Logger.Error("Error writing response data.", "uri", uri, "error", err)
|
||||
}
|
||||
}()
|
||||
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 {
|
||||
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("URL: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
method, err := r.Method()
|
||||
if err != nil {
|
||||
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
header, err := r.Header()
|
||||
if err != nil {
|
||||
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := r.Body()
|
||||
if err != nil {
|
||||
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if body == nil {
|
||||
body = http.NoBody
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, uri, body)
|
||||
if err != nil {
|
||||
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
// For server requests, the URL is parsed from the URI supplied on the Request-Line as stored in RequestURI. For
|
||||
// most requests, fields other than Path and RawQuery will be empty. (See RFC 7230, Section 5.3)
|
||||
req.URL.Scheme = ""
|
||||
req.URL.Host = ""
|
||||
req.URL.Fragment = ""
|
||||
req.URL.RawFragment = ""
|
||||
|
||||
if requestURL := req.URL; req.RequestURI == "" && requestURL != nil {
|
||||
req.RequestURI = requestURL.String()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// iOS uses "localhost" while other platforms might use different hosts
|
||||
// Skip host check for iOS requests from wails:// scheme
|
||||
if expectedHost := a.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host && !strings.HasPrefix(uri, "wails://") {
|
||||
a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host))
|
||||
return
|
||||
}
|
||||
|
||||
a.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (a *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)
|
||||
}
|
||||
|
||||
a.options.Logger.Error("Error processing request (HttpResponse=500)", "details", logInfo, "error", 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_windows.go
generated
vendored
Normal file
8
vendor/github.com/wailsapp/wails/v3/internal/assetserver/assetserver_windows.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package assetserver
|
||||
|
||||
import "net/url"
|
||||
|
||||
var baseURL = url.URL{
|
||||
Scheme: "http",
|
||||
Host: "wails.localhost",
|
||||
}
|
||||
102
vendor/github.com/wailsapp/wails/v3/internal/assetserver/build_dev.go
generated
vendored
Normal file
102
vendor/github.com/wailsapp/wails/v3/internal/assetserver/build_dev.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
//go:build !production
|
||||
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// retryTransport implements http.RoundTripper with retry logic for transient connection failures.
|
||||
// This is particularly useful when the Vite dev server temporarily rejects connections due to
|
||||
// high concurrency with many dynamic imports.
|
||||
type retryTransport struct {
|
||||
base http.RoundTripper
|
||||
maxRetries int
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
// RoundTrip executes a single HTTP transaction with retry logic.
|
||||
func (t *retryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
var resp *http.Response
|
||||
var err error
|
||||
for i := 0; i < t.maxRetries; i++ {
|
||||
resp, err = t.base.RoundTrip(req)
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
// Only retry on connection errors (e.g., connection refused)
|
||||
if isConnectionError(err) && i < t.maxRetries-1 {
|
||||
time.Sleep(t.delay)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// isConnectionError checks if the error is a connection-related error that may be transient.
|
||||
func isConnectionError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
errStr := strings.ToLower(err.Error())
|
||||
return strings.Contains(errStr, "connection refused") ||
|
||||
strings.Contains(errStr, "connection reset") ||
|
||||
strings.Contains(errStr, "broken pipe") ||
|
||||
strings.Contains(errStr, "connectex")
|
||||
}
|
||||
|
||||
func NewAssetFileServer(vfs fs.FS) http.Handler {
|
||||
devServerURL := GetDevServerURL()
|
||||
if devServerURL == "" {
|
||||
return newAssetFileServerFS(vfs)
|
||||
}
|
||||
|
||||
parsedURL, err := url.Parse(devServerURL)
|
||||
if err != nil {
|
||||
return http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, req *http.Request) {
|
||||
logError(req.Context(), "[ExternalAssetHandler] Invalid FRONTEND_DEVSERVER_URL. Should be valid URL", "error", err.Error())
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(parsedURL)
|
||||
proxy.Transport = &retryTransport{
|
||||
base: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// Force IPv4 for localhost connections to avoid IPv6 issues on Windows
|
||||
if parsedURL.Hostname() == "localhost" || parsedURL.Hostname() == "127.0.0.1" {
|
||||
return dialer.DialContext(ctx, "tcp4", addr)
|
||||
}
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
},
|
||||
},
|
||||
maxRetries: 50,
|
||||
delay: 50 * time.Millisecond,
|
||||
}
|
||||
proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) {
|
||||
logError(r.Context(), "[ExternalAssetHandler] Proxy error", "error", err.Error())
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
}
|
||||
|
||||
return proxy
|
||||
}
|
||||
|
||||
func GetDevServerURL() string {
|
||||
return os.Getenv("FRONTEND_DEVSERVER_URL")
|
||||
}
|
||||
16
vendor/github.com/wailsapp/wails/v3/internal/assetserver/build_production.go
generated
vendored
Normal file
16
vendor/github.com/wailsapp/wails/v3/internal/assetserver/build_production.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build production
|
||||
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewAssetFileServer(vfs fs.FS) http.Handler {
|
||||
return newAssetFileServerFS(vfs)
|
||||
}
|
||||
|
||||
func GetDevServerURL() string {
|
||||
return ""
|
||||
}
|
||||
33
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundled_assetserver.go
generated
vendored
Normal file
33
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundled_assetserver.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/internal/assetserver/bundledassets"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BundledAssetServer struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func NewBundledAssetFileServer(fs fs.FS) *BundledAssetServer {
|
||||
return &BundledAssetServer{
|
||||
handler: NewAssetFileServer(fs),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BundledAssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasPrefix(req.URL.Path, "/wails/") {
|
||||
// Strip the /wails prefix
|
||||
req.URL.Path = req.URL.Path[6:]
|
||||
switch req.URL.Path {
|
||||
case "/runtime.js":
|
||||
rw.Header().Set("Content-Type", "application/javascript")
|
||||
rw.Write([]byte(bundledassets.RuntimeJS))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
b.handler.ServeHTTP(rw, req)
|
||||
}
|
||||
3245
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundledassets/runtime.debug.js
generated
vendored
Normal file
3245
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundledassets/runtime.debug.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundledassets/runtime.js
generated
vendored
Normal file
1
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundledassets/runtime.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
8
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundledassets/runtime_dev.go
generated
vendored
Normal file
8
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundledassets/runtime_dev.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
//go:build !production
|
||||
|
||||
package bundledassets
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed runtime.debug.js
|
||||
var RuntimeJS []byte
|
||||
8
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundledassets/runtime_production.go
generated
vendored
Normal file
8
vendor/github.com/wailsapp/wails/v3/internal/assetserver/bundledassets/runtime_production.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
//go:build production
|
||||
|
||||
package bundledassets
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed runtime.js
|
||||
var RuntimeJS []byte
|
||||
66
vendor/github.com/wailsapp/wails/v3/internal/assetserver/common.go
generated
vendored
Normal file
66
vendor/github.com/wailsapp/wails/v3/internal/assetserver/common.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderHost = "Host"
|
||||
HeaderContentType = "Content-Type"
|
||||
HeaderContentLength = "Content-Length"
|
||||
HeaderUserAgent = "User-Agent"
|
||||
// TODO: Is this needed?
|
||||
HeaderCacheControl = "Cache-Control"
|
||||
HeaderUpgrade = "Upgrade"
|
||||
|
||||
WailsUserAgentValue = "wails.io"
|
||||
)
|
||||
|
||||
type assetServerLogger struct{}
|
||||
|
||||
var assetServerLoggerKey assetServerLogger
|
||||
|
||||
// ServeFile writes the provided blob to rw as an HTTP 200 response, ensuring appropriate
|
||||
// Content-Length and Content-Type headers are set.
|
||||
//
|
||||
// If the Content-Type header is not already present, ServeFile determines an appropriate
|
||||
// MIME type from the filename and blob and sets the Content-Type header. It then writes
|
||||
// the 200 status and the blob body to the response, returning any error encountered while
|
||||
// writing the body.
|
||||
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 := rw.Write(blob)
|
||||
return err
|
||||
}
|
||||
|
||||
func isWebSocket(req *http.Request) bool {
|
||||
upgrade := req.Header.Get(HeaderUpgrade)
|
||||
return strings.EqualFold(upgrade, "websocket")
|
||||
}
|
||||
|
||||
func contextWithLogger(ctx context.Context, logger *slog.Logger) context.Context {
|
||||
return context.WithValue(ctx, assetServerLoggerKey, logger)
|
||||
}
|
||||
|
||||
func logInfo(ctx context.Context, message string, args ...interface{}) {
|
||||
if logger, _ := ctx.Value(assetServerLoggerKey).(*slog.Logger); logger != nil {
|
||||
logger.Info(message, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func logError(ctx context.Context, message string, args ...interface{}) {
|
||||
if logger, _ := ctx.Value(assetServerLoggerKey).(*slog.Logger); logger != nil {
|
||||
logger.Error(message, args...)
|
||||
}
|
||||
}
|
||||
142
vendor/github.com/wailsapp/wails/v3/internal/assetserver/content_type_sniffer.go
generated
vendored
Normal file
142
vendor/github.com/wailsapp/wails/v3/internal/assetserver/content_type_sniffer.go
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// newContentTypeSniffer creates a contentTypeSniffer that wraps the provided http.ResponseWriter.
|
||||
// The returned sniffer does not allocate a close notification channel; it will be initialized lazily by CloseNotify.
|
||||
func newContentTypeSniffer(rw http.ResponseWriter) *contentTypeSniffer {
|
||||
return &contentTypeSniffer{
|
||||
rw: rw,
|
||||
}
|
||||
}
|
||||
|
||||
type contentTypeSniffer struct {
|
||||
rw http.ResponseWriter
|
||||
prefix []byte
|
||||
closeChannel chan bool // lazily allocated only if CloseNotify is called
|
||||
status int
|
||||
headerCommitted bool
|
||||
headerWritten bool
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped [http.ResponseWriter] for use with [http.ResponseController].
|
||||
func (rw *contentTypeSniffer) Unwrap() http.ResponseWriter {
|
||||
return rw.rw
|
||||
}
|
||||
|
||||
func (rw *contentTypeSniffer) Header() http.Header {
|
||||
return rw.rw.Header()
|
||||
}
|
||||
|
||||
func (rw *contentTypeSniffer) Write(chunk []byte) (int, error) {
|
||||
if !rw.headerCommitted {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
if rw.headerWritten {
|
||||
return rw.rw.Write(chunk)
|
||||
}
|
||||
|
||||
if len(chunk) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Cut away at most 512 bytes from chunk, and not less than 0.
|
||||
cut := max(min(len(chunk), 512-len(rw.prefix)), 0)
|
||||
if cut >= 512 {
|
||||
// Avoid copying data if a full prefix is available on first non-zero write.
|
||||
cut = len(chunk)
|
||||
rw.prefix = chunk
|
||||
chunk = nil
|
||||
} else if cut > 0 {
|
||||
// First write had less than 512 bytes -- copy data to the prefix buffer.
|
||||
if rw.prefix == nil {
|
||||
// Preallocate space for the prefix to be used for sniffing.
|
||||
rw.prefix = make([]byte, 0, 512)
|
||||
}
|
||||
rw.prefix = append(rw.prefix, chunk[:cut]...)
|
||||
chunk = chunk[cut:]
|
||||
}
|
||||
|
||||
if len(rw.prefix) < 512 {
|
||||
return cut, nil
|
||||
}
|
||||
|
||||
if _, err := rw.complete(); err != nil {
|
||||
return cut, err
|
||||
}
|
||||
|
||||
n, err := rw.rw.Write(chunk)
|
||||
return cut + n, err
|
||||
}
|
||||
|
||||
func (rw *contentTypeSniffer) WriteHeader(code int) {
|
||||
if rw.headerCommitted {
|
||||
return
|
||||
}
|
||||
|
||||
rw.status = code
|
||||
rw.headerCommitted = true
|
||||
|
||||
if _, hasType := rw.Header()[HeaderContentType]; hasType {
|
||||
rw.rw.WriteHeader(rw.status)
|
||||
rw.headerWritten = true
|
||||
}
|
||||
}
|
||||
|
||||
// sniff sniffs the content type from the stored prefix if necessary,
|
||||
// then writes the header.
|
||||
func (rw *contentTypeSniffer) sniff() {
|
||||
if rw.headerWritten || !rw.headerCommitted {
|
||||
return
|
||||
}
|
||||
|
||||
m := rw.Header()
|
||||
if _, hasType := m[HeaderContentType]; !hasType {
|
||||
m.Set(HeaderContentType, http.DetectContentType(rw.prefix))
|
||||
}
|
||||
|
||||
rw.rw.WriteHeader(rw.status)
|
||||
rw.headerWritten = true
|
||||
}
|
||||
|
||||
// complete sniffs the content type if necessary, writes the header
|
||||
// and sends the data prefix that has been stored for sniffing.
|
||||
//
|
||||
// Whoever creates a contentTypeSniffer instance
|
||||
// is responsible for calling complete after the nested handler has returned.
|
||||
func (rw *contentTypeSniffer) complete() (n int, err error) {
|
||||
rw.sniff()
|
||||
|
||||
if rw.headerWritten && len(rw.prefix) > 0 {
|
||||
n, err = rw.rw.Write(rw.prefix)
|
||||
rw.prefix = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CloseNotify implements the http.CloseNotifier interface.
|
||||
// The channel is lazily allocated to avoid allocation overhead for requests
|
||||
// that don't use this deprecated interface.
|
||||
func (rw *contentTypeSniffer) CloseNotify() <-chan bool {
|
||||
if rw.closeChannel == nil {
|
||||
rw.closeChannel = make(chan bool, 1)
|
||||
}
|
||||
return rw.closeChannel
|
||||
}
|
||||
|
||||
func (rw *contentTypeSniffer) closeClient() {
|
||||
if rw.closeChannel != nil {
|
||||
rw.closeChannel <- true
|
||||
}
|
||||
}
|
||||
|
||||
// Flush implements the http.Flusher interface.
|
||||
func (rw *contentTypeSniffer) Flush() {
|
||||
if f, ok := rw.rw.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
350
vendor/github.com/wailsapp/wails/v3/internal/assetserver/defaults/index.en.html
generated
vendored
Normal file
350
vendor/github.com/wailsapp/wails/v3/internal/assetserver/defaults/index.en.html
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
302
vendor/github.com/wailsapp/wails/v3/internal/assetserver/defaults/index.zh.html
generated
vendored
Normal file
302
vendor/github.com/wailsapp/wails/v3/internal/assetserver/defaults/index.zh.html
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
80
vendor/github.com/wailsapp/wails/v3/internal/assetserver/fallback_response_writer.go
generated
vendored
Normal file
80
vendor/github.com/wailsapp/wails/v3/internal/assetserver/fallback_response_writer.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// fallbackResponseWriter wraps a [http.ResponseWriter].
|
||||
// If the main handler returns status code 404,
|
||||
// its response is discarded
|
||||
// and the request is forwarded to the fallback handler.
|
||||
type fallbackResponseWriter struct {
|
||||
rw http.ResponseWriter
|
||||
req *http.Request
|
||||
fallback http.Handler
|
||||
|
||||
header http.Header
|
||||
headerWritten bool
|
||||
complete bool
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped [http.ResponseWriter] for use with [http.ResponseController].
|
||||
func (fw *fallbackResponseWriter) Unwrap() http.ResponseWriter {
|
||||
return fw.rw
|
||||
}
|
||||
|
||||
func (fw *fallbackResponseWriter) Header() http.Header {
|
||||
if fw.header == nil {
|
||||
// Preserve original header in case we get a 404 response.
|
||||
fw.header = fw.rw.Header().Clone()
|
||||
}
|
||||
return fw.header
|
||||
}
|
||||
|
||||
func (fw *fallbackResponseWriter) Write(chunk []byte) (int, error) {
|
||||
if fw.complete {
|
||||
// Fallback triggered, discard further writes.
|
||||
return len(chunk), nil
|
||||
}
|
||||
|
||||
if !fw.headerWritten {
|
||||
fw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
return fw.rw.Write(chunk)
|
||||
}
|
||||
|
||||
func (fw *fallbackResponseWriter) WriteHeader(statusCode int) {
|
||||
if fw.headerWritten {
|
||||
return
|
||||
}
|
||||
fw.headerWritten = true
|
||||
|
||||
if statusCode == http.StatusNotFound {
|
||||
// Protect fallback header from external modifications.
|
||||
if fw.header == nil {
|
||||
fw.header = fw.rw.Header().Clone()
|
||||
}
|
||||
|
||||
// Invoke fallback handler.
|
||||
fw.complete = true
|
||||
fw.fallback.ServeHTTP(fw.rw, fw.req)
|
||||
return
|
||||
}
|
||||
|
||||
if fw.header != nil {
|
||||
// Apply headers and forward original map to the main handler.
|
||||
maps.Copy(fw.rw.Header(), fw.header)
|
||||
fw.header = fw.rw.Header()
|
||||
}
|
||||
|
||||
fw.rw.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
// Flush implements the http.Flusher interface.
|
||||
func (rw *fallbackResponseWriter) Flush() {
|
||||
if f, ok := rw.rw.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
76
vendor/github.com/wailsapp/wails/v3/internal/assetserver/fs.go
generated
vendored
Normal file
76
vendor/github.com/wailsapp/wails/v3/internal/assetserver/fs.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"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(fileSystem embed.FS) (string, error) {
|
||||
stopErr := errors.New("files or multiple dirs found")
|
||||
|
||||
fPath := ""
|
||||
err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
fPath = path
|
||||
if entries, dErr := fs.ReadDir(fileSystem, path); dErr != nil {
|
||||
return dErr
|
||||
} else if len(entries) <= 1 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return stopErr
|
||||
})
|
||||
|
||||
if err != nil && !errors.Is(err, stopErr) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fPath, nil
|
||||
}
|
||||
|
||||
func findPathToFile(fileSystem fs.FS, file string) (string, error) {
|
||||
stat, _ := fs.Stat(fileSystem, file)
|
||||
if stat != nil {
|
||||
return ".", nil
|
||||
}
|
||||
var indexFiles []string
|
||||
err := fs.WalkDir(fileSystem, ".", 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)
|
||||
}
|
||||
20
vendor/github.com/wailsapp/wails/v3/internal/assetserver/middleware.go
generated
vendored
Normal file
20
vendor/github.com/wailsapp/wails/v3/internal/assetserver/middleware.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
116
vendor/github.com/wailsapp/wails/v3/internal/assetserver/mimecache.go
generated
vendored
Normal file
116
vendor/github.com/wailsapp/wails/v3/internal/assetserver/mimecache.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// mimeCache uses sync.Map for better concurrent read performance
|
||||
// since reads are far more common than writes
|
||||
mimeCache sync.Map
|
||||
|
||||
// mimeTypesByExt maps file extensions to MIME types for common web formats.
|
||||
// This approach is preferred over content-based detection because:
|
||||
// 1. Extension-based lookup is O(1) vs O(n) content scanning
|
||||
// 2. Web assets typically have correct extensions
|
||||
// 3. stdlib's http.DetectContentType handles remaining cases adequately
|
||||
// 4. Saves ~208KB binary size by not using github.com/wailsapp/mimetype
|
||||
mimeTypesByExt = map[string]string{
|
||||
// HTML
|
||||
".htm": "text/html; charset=utf-8",
|
||||
".html": "text/html; charset=utf-8",
|
||||
|
||||
// CSS/JS
|
||||
".css": "text/css; charset=utf-8",
|
||||
".js": "text/javascript; charset=utf-8",
|
||||
".mjs": "text/javascript; charset=utf-8",
|
||||
".ts": "application/x-typescript; charset=utf-8",
|
||||
".tsx": "application/x-typescript; charset=utf-8",
|
||||
".jsx": "text/javascript; charset=utf-8",
|
||||
|
||||
// Data formats
|
||||
".json": "application/json",
|
||||
".xml": "text/xml; charset=utf-8",
|
||||
".yaml": "text/yaml; charset=utf-8",
|
||||
".yml": "text/yaml; charset=utf-8",
|
||||
".toml": "text/toml; charset=utf-8",
|
||||
|
||||
// Images
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".avif": "image/avif",
|
||||
".svg": "image/svg+xml",
|
||||
".ico": "image/x-icon",
|
||||
".bmp": "image/bmp",
|
||||
".tiff": "image/tiff",
|
||||
".tif": "image/tiff",
|
||||
|
||||
// Fonts
|
||||
".woff": "font/woff",
|
||||
".woff2": "font/woff2",
|
||||
".ttf": "font/ttf",
|
||||
".otf": "font/otf",
|
||||
".eot": "application/vnd.ms-fontobject",
|
||||
|
||||
// Audio
|
||||
".mp3": "audio/mpeg",
|
||||
".wav": "audio/wav",
|
||||
".ogg": "audio/ogg",
|
||||
".m4a": "audio/mp4",
|
||||
".aac": "audio/aac",
|
||||
".flac": "audio/flac",
|
||||
".opus": "audio/opus",
|
||||
|
||||
// Video
|
||||
".mp4": "video/mp4",
|
||||
".webm": "video/webm",
|
||||
".ogv": "video/ogg",
|
||||
".mov": "video/quicktime",
|
||||
".avi": "video/x-msvideo",
|
||||
".mkv": "video/x-matroska",
|
||||
".m4v": "video/mp4",
|
||||
|
||||
// Documents
|
||||
".pdf": "application/pdf",
|
||||
".txt": "text/plain; charset=utf-8",
|
||||
".md": "text/markdown; charset=utf-8",
|
||||
|
||||
// Archives
|
||||
".zip": "application/zip",
|
||||
".gz": "application/gzip",
|
||||
".tar": "application/x-tar",
|
||||
|
||||
// WebAssembly
|
||||
".wasm": "application/wasm",
|
||||
|
||||
// Source maps
|
||||
".map": "application/json",
|
||||
}
|
||||
)
|
||||
|
||||
// "application/octet-stream".
|
||||
func GetMimetype(filename string, data []byte) string {
|
||||
// Fast path: check extension map first (no lock needed)
|
||||
if result := mimeTypesByExt[filepath.Ext(filename)]; result != "" {
|
||||
return result
|
||||
}
|
||||
|
||||
// Check cache (lock-free read)
|
||||
if cached, ok := mimeCache.Load(filename); ok {
|
||||
return cached.(string)
|
||||
}
|
||||
|
||||
// Slow path: use stdlib content-based detection and cache
|
||||
result := http.DetectContentType(data)
|
||||
if result == "" {
|
||||
result = "application/octet-stream"
|
||||
}
|
||||
|
||||
mimeCache.Store(filename, result)
|
||||
return result
|
||||
}
|
||||
38
vendor/github.com/wailsapp/wails/v3/internal/assetserver/options.go
generated
vendored
Normal file
38
vendor/github.com/wailsapp/wails/v3/internal/assetserver/options.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Options defines the configuration of the AssetServer.
|
||||
type Options struct {
|
||||
// Handler which serves all the content to the WebView.
|
||||
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.
|
||||
//
|
||||
// This middleware injects itself before any of Wails internal middlewares.
|
||||
//
|
||||
// If not defined, the default AssetServer request chain is executed.
|
||||
//
|
||||
// Multiple Middlewares can be chained together with:
|
||||
// ChainMiddleware(middleware ...Middleware) Middleware
|
||||
Middleware Middleware
|
||||
|
||||
// Logger is the logger used by the AssetServer. If not defined, no logging will be done.
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// Validate the options
|
||||
func (o Options) Validate() error {
|
||||
if o.Handler == nil && o.Middleware == nil {
|
||||
return errors.New("AssetServer options invalid: either Handler or Middleware must be set")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
101
vendor/github.com/wailsapp/wails/v3/internal/assetserver/ringqueue.go
generated
vendored
Normal file
101
vendor/github.com/wailsapp/wails/v3/internal/assetserver/ringqueue.go
generated
vendored
Normal 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
|
||||
}
|
||||
152
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/mainthread_linux.go
generated
vendored
Normal file
152
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/mainthread_linux.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
//go:build linux && cgo && !android
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: glib-2.0
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern void webviewMainThreadCallback(uintptr_t id);
|
||||
|
||||
// webview_dispatch_mu serializes the enabled-check plus scheduling in
|
||||
// webview_invoke_on_main_sync against the flag clear in
|
||||
// webview_disable_main_dispatch. Without it a worker could read
|
||||
// enabled == TRUE, then have the flag flipped before it scheduled its source,
|
||||
// and still queue work onto the now-dead main loop — blocking forever. A
|
||||
// statically allocated GMutex needs no g_mutex_init.
|
||||
static GMutex webview_dispatch_mu;
|
||||
|
||||
// webview_main_dispatch_enabled gates whether webview_invoke_on_main_sync may
|
||||
// schedule work onto the GTK main loop. It starts enabled and is cleared once
|
||||
// the loop has stopped (see webview_disable_main_dispatch). It is only ever read
|
||||
// or written while holding webview_dispatch_mu.
|
||||
static gboolean webview_main_dispatch_enabled = TRUE;
|
||||
|
||||
static void webview_disable_main_dispatch(void) {
|
||||
g_mutex_lock(&webview_dispatch_mu);
|
||||
webview_main_dispatch_enabled = FALSE;
|
||||
g_mutex_unlock(&webview_dispatch_mu);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uintptr_t id;
|
||||
GMutex mutex;
|
||||
GCond cond;
|
||||
gboolean done;
|
||||
} webviewMainSyncCall;
|
||||
|
||||
// webview_main_sync_trampoline runs on the GTK main thread (scheduled via
|
||||
// g_main_context_invoke). It invokes the Go callback, then signals the waiting
|
||||
// worker. g_cond_signal happens while the mutex is held and before the unlock,
|
||||
// so the waiter cannot re-acquire the mutex (and destroy the primitives) until
|
||||
// the signal has completed — making the stack-allocated GMutex/GCond safe.
|
||||
static gboolean webview_main_sync_trampoline(gpointer data) {
|
||||
webviewMainSyncCall *call = (webviewMainSyncCall *)data;
|
||||
webviewMainThreadCallback(call->id);
|
||||
g_mutex_lock(&call->mutex);
|
||||
call->done = TRUE;
|
||||
g_cond_signal(&call->cond);
|
||||
g_mutex_unlock(&call->mutex);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// webview_invoke_on_main_sync schedules the Go callback identified by id on the
|
||||
// default GTK main context and blocks the calling thread until it has finished.
|
||||
//
|
||||
// WebKit2GTK objects may only be touched on the thread running the GTK main loop
|
||||
// (g_application_run). Asset-server responses are produced on worker goroutines,
|
||||
// so the WebKit calls that complete a request must hop here first. The wait is
|
||||
// safe because webkit_uri_scheme_request_finish_with_response returns before the
|
||||
// response stream is drained (WebKit reads it asynchronously), so the main loop
|
||||
// never blocks waiting on the worker.
|
||||
//
|
||||
// If the caller is already the main thread, g_main_context_invoke runs the
|
||||
// trampoline inline, so the wait completes immediately without deadlocking.
|
||||
static void webview_invoke_on_main_sync(uintptr_t id) {
|
||||
webviewMainSyncCall call;
|
||||
call.id = id;
|
||||
call.done = FALSE;
|
||||
g_mutex_init(&call.mutex);
|
||||
g_cond_init(&call.cond);
|
||||
|
||||
// The enabled-check and the g_main_context_invoke that acts on it must be
|
||||
// atomic with respect to webview_disable_main_dispatch. Holding
|
||||
// webview_dispatch_mu across both means a worker either schedules onto a live
|
||||
// loop or sees the loop already stopped — it can never schedule onto a loop
|
||||
// that stops in between (which would block it here forever). The trampoline
|
||||
// only touches the per-call mutex/cond, never webview_dispatch_mu, so when
|
||||
// g_main_context_invoke runs it inline (main-thread caller) there is no
|
||||
// self-deadlock.
|
||||
g_mutex_lock(&webview_dispatch_mu);
|
||||
if (!webview_main_dispatch_enabled) {
|
||||
// The GTK main loop has stopped: a scheduled source would never run. The
|
||||
// loop is no longer iterating, so the cross-thread race that makes
|
||||
// main-thread confinement necessary is gone — running the callback inline
|
||||
// on the worker lets in-flight asset requests drain during shutdown
|
||||
// instead of wedging. See #5631 (review question 5).
|
||||
g_mutex_unlock(&webview_dispatch_mu);
|
||||
webviewMainThreadCallback(id);
|
||||
g_mutex_clear(&call.mutex);
|
||||
g_cond_clear(&call.cond);
|
||||
return;
|
||||
}
|
||||
g_main_context_invoke(NULL, webview_main_sync_trampoline, &call);
|
||||
g_mutex_unlock(&webview_dispatch_mu);
|
||||
|
||||
g_mutex_lock(&call.mutex);
|
||||
while (!call.done) {
|
||||
g_cond_wait(&call.cond, &call.mutex);
|
||||
}
|
||||
g_mutex_unlock(&call.mutex);
|
||||
|
||||
g_mutex_clear(&call.mutex);
|
||||
g_cond_clear(&call.cond);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
mainSyncMu sync.Mutex
|
||||
mainSyncNextID uintptr
|
||||
mainSyncCallbacks = map[uintptr]func(){}
|
||||
)
|
||||
|
||||
// invokeOnMainSync runs fn on the GTK main thread and blocks until it returns.
|
||||
// It is safe to call from any goroutine, including the main thread itself.
|
||||
func invokeOnMainSync(fn func()) {
|
||||
mainSyncMu.Lock()
|
||||
mainSyncNextID++
|
||||
id := mainSyncNextID
|
||||
mainSyncCallbacks[id] = fn
|
||||
mainSyncMu.Unlock()
|
||||
|
||||
C.webview_invoke_on_main_sync(C.uintptr_t(id))
|
||||
}
|
||||
|
||||
// DisableMainThreadDispatch marks the GTK main loop as stopped. After it is
|
||||
// called, invokeOnMainSync runs callbacks inline on the calling goroutine
|
||||
// instead of scheduling them onto the now-dead main loop, so asset-server
|
||||
// workers that complete a request during shutdown cannot block forever waiting
|
||||
// for a source that will never be serviced. The application layer calls this
|
||||
// once g_application_run has returned. See issue #5631.
|
||||
func DisableMainThreadDispatch() {
|
||||
C.webview_disable_main_dispatch()
|
||||
}
|
||||
|
||||
//export webviewMainThreadCallback
|
||||
func webviewMainThreadCallback(id C.uintptr_t) {
|
||||
mainSyncMu.Lock()
|
||||
fn := mainSyncCallbacks[uintptr(id)]
|
||||
delete(mainSyncCallbacks, uintptr(id))
|
||||
mainSyncMu.Unlock()
|
||||
|
||||
if fn != nil {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
90
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/mainthread_testsupport_linux.go
generated
vendored
Normal file
90
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/mainthread_testsupport_linux.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
//go:build linux && cgo && !android
|
||||
|
||||
package webview
|
||||
|
||||
// This file provides cgo helpers for mainthread_linux_test.go. Go forbids cgo
|
||||
// (an `import "C"` preamble) inside _test.go files, so the small GLib main-loop
|
||||
// scaffolding the test needs lives here instead. None of it is referenced by
|
||||
// production code paths.
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: glib-2.0
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
// All shared loop state is published and read under webview_test_mu so the
|
||||
// worker goroutines that call the helpers below never observe it across threads
|
||||
// without synchronization. webview_test_ready is signalled from inside the loop
|
||||
// (via an idle source) so waiters block on the cond instead of spinning on an
|
||||
// unsynchronized flag.
|
||||
static GMutex webview_test_mu;
|
||||
static GCond webview_test_cond;
|
||||
static GMainLoop *webview_test_loop; // guarded by webview_test_mu
|
||||
static GThread *webview_test_loop_thread; // guarded by webview_test_mu
|
||||
static gboolean webview_test_ready; // guarded by webview_test_mu
|
||||
|
||||
// webview_test_mark_ready runs on the loop thread once the loop starts
|
||||
// iterating, publishing that it is live.
|
||||
static gboolean webview_test_mark_ready(gpointer data) {
|
||||
g_mutex_lock(&webview_test_mu);
|
||||
webview_test_ready = TRUE;
|
||||
g_cond_signal(&webview_test_cond);
|
||||
g_mutex_unlock(&webview_test_mu);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// webview_test_run_loop records the running thread and drives the default GLib
|
||||
// main context, mimicking the GTK main loop that invokeOnMainSync dispatches to.
|
||||
static void webview_test_run_loop(void) {
|
||||
GMainLoop *loop = g_main_loop_new(NULL, FALSE);
|
||||
|
||||
g_mutex_lock(&webview_test_mu);
|
||||
webview_test_loop = loop;
|
||||
webview_test_loop_thread = g_thread_self();
|
||||
g_mutex_unlock(&webview_test_mu);
|
||||
|
||||
g_idle_add(webview_test_mark_ready, NULL);
|
||||
g_main_loop_run(loop);
|
||||
|
||||
g_mutex_lock(&webview_test_mu);
|
||||
webview_test_loop = NULL;
|
||||
webview_test_ready = FALSE;
|
||||
g_mutex_unlock(&webview_test_mu);
|
||||
|
||||
g_main_loop_unref(loop);
|
||||
}
|
||||
|
||||
static void webview_test_wait_running(void) {
|
||||
g_mutex_lock(&webview_test_mu);
|
||||
while (!webview_test_ready) {
|
||||
g_cond_wait(&webview_test_cond, &webview_test_mu);
|
||||
}
|
||||
g_mutex_unlock(&webview_test_mu);
|
||||
}
|
||||
|
||||
static void webview_test_quit_loop(void) {
|
||||
g_mutex_lock(&webview_test_mu);
|
||||
GMainLoop *loop = webview_test_loop;
|
||||
g_mutex_unlock(&webview_test_mu);
|
||||
if (loop != NULL) {
|
||||
g_main_loop_quit(loop);
|
||||
}
|
||||
}
|
||||
|
||||
// webview_test_on_loop_thread reports whether the caller is the thread that runs
|
||||
// the main loop.
|
||||
static int webview_test_on_loop_thread(void) {
|
||||
g_mutex_lock(&webview_test_mu);
|
||||
GThread *loopThread = webview_test_loop_thread;
|
||||
g_mutex_unlock(&webview_test_mu);
|
||||
return g_thread_self() == loopThread ? 1 : 0;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func testRunMainLoop() { C.webview_test_run_loop() }
|
||||
func testWaitLoopRunning() { C.webview_test_wait_running() }
|
||||
func testQuitMainLoop() { C.webview_test_quit_loop() }
|
||||
func testOnLoopThread() bool {
|
||||
return C.webview_test_on_loop_thread() != 0
|
||||
}
|
||||
17
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request.go
generated
vendored
Normal file
17
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request.go
generated
vendored
Normal 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
|
||||
}
|
||||
102
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_android.go
generated
vendored
Normal file
102
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_android.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
//go:build android
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Request interface for Android asset requests
|
||||
// On Android, requests are handled via JNI from Java's WebViewAssetLoader
|
||||
|
||||
// androidRequest implements the Request interface for Android
|
||||
type androidRequest struct {
|
||||
url string
|
||||
method string
|
||||
headers http.Header
|
||||
body io.ReadCloser
|
||||
rw *androidResponseWriter
|
||||
}
|
||||
|
||||
// NewRequestFromJNI creates a new request from JNI parameters
|
||||
func NewRequestFromJNI(url string, method string, headersJSON string) Request {
|
||||
return &androidRequest{
|
||||
url: url,
|
||||
method: method,
|
||||
headers: http.Header{},
|
||||
body: http.NoBody,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *androidRequest) URL() (string, error) {
|
||||
return r.url, nil
|
||||
}
|
||||
|
||||
func (r *androidRequest) Method() (string, error) {
|
||||
return r.method, nil
|
||||
}
|
||||
|
||||
func (r *androidRequest) Header() (http.Header, error) {
|
||||
return r.headers, nil
|
||||
}
|
||||
|
||||
func (r *androidRequest) Body() (io.ReadCloser, error) {
|
||||
return r.body, nil
|
||||
}
|
||||
|
||||
func (r *androidRequest) Response() ResponseWriter {
|
||||
if r.rw == nil {
|
||||
r.rw = &androidResponseWriter{}
|
||||
}
|
||||
return r.rw
|
||||
}
|
||||
|
||||
func (r *androidRequest) Close() error {
|
||||
if r.body != nil {
|
||||
return r.body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// androidResponseWriter implements ResponseWriter for Android
|
||||
type androidResponseWriter struct {
|
||||
statusCode int
|
||||
headers http.Header
|
||||
body bytes.Buffer
|
||||
finished bool
|
||||
}
|
||||
|
||||
func (w *androidResponseWriter) Header() http.Header {
|
||||
if w.headers == nil {
|
||||
w.headers = http.Header{}
|
||||
}
|
||||
return w.headers
|
||||
}
|
||||
|
||||
func (w *androidResponseWriter) Write(data []byte) (int, error) {
|
||||
return w.body.Write(data)
|
||||
}
|
||||
|
||||
func (w *androidResponseWriter) WriteHeader(statusCode int) {
|
||||
w.statusCode = statusCode
|
||||
}
|
||||
|
||||
func (w *androidResponseWriter) Finish() error {
|
||||
w.finished = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Code returns the HTTP status code of the response
|
||||
func (w *androidResponseWriter) Code() int {
|
||||
if w.statusCode == 0 {
|
||||
return 200
|
||||
}
|
||||
return w.statusCode
|
||||
}
|
||||
|
||||
// GetResponseData returns the response data for JNI
|
||||
func (w *androidResponseWriter) GetResponseData() []byte {
|
||||
return w.body.Bytes()
|
||||
}
|
||||
250
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_darwin.go
generated
vendored
Normal file
250
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
//go:build darwin && !ios
|
||||
|
||||
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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// 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, errors.New("body: stream error")
|
||||
case -2:
|
||||
return 0, errors.New("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
|
||||
}
|
||||
40
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_finalizer.go
generated
vendored
Normal file
40
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_finalizer.go
generated
vendored
Normal 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
|
||||
}
|
||||
248
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_ios.go
generated
vendored
Normal file
248
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_ios.go
generated
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
//go:build ios
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework Foundation -framework WebKit -framework CoreFoundation
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
#include <string.h>
|
||||
|
||||
static void URLSchemeTaskRetain(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (__bridge id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
CFRetain((CFTypeRef)urlSchemeTask);
|
||||
}
|
||||
|
||||
static void URLSchemeTaskRelease(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (__bridge id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
CFRelease((CFTypeRef)urlSchemeTask);
|
||||
}
|
||||
|
||||
static const char * URLSchemeTaskRequestURL(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (__bridge id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
return [urlSchemeTask.request.URL.absoluteString UTF8String];
|
||||
}
|
||||
}
|
||||
|
||||
static const char * URLSchemeTaskRequestMethod(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (__bridge id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
return [urlSchemeTask.request.HTTPMethod UTF8String];
|
||||
}
|
||||
}
|
||||
|
||||
static const char * URLSchemeTaskRequestHeadersJSON(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (__bridge 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];
|
||||
const char *headerJSON = [headerString UTF8String];
|
||||
return strdup(headerJSON);
|
||||
}
|
||||
}
|
||||
|
||||
static bool URLSchemeTaskRequestBodyBytes(void *wkUrlSchemeTask, const void **body, int *bodyLen) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (__bridge 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 = (__bridge id<WKURLSchemeTask>) wkUrlSchemeTask;
|
||||
@autoreleasepool {
|
||||
if (!urlSchemeTask.request.HTTPBodyStream) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[urlSchemeTask.request.HTTPBodyStream open];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void URLSchemeTaskRequestBodyStreamClose(void *wkUrlSchemeTask) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (__bridge 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 = (__bridge 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// 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, errors.New("body: stream error")
|
||||
case -2:
|
||||
return 0, errors.New("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
|
||||
}
|
||||
106
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_linux.go
generated
vendored
Normal file
106
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_linux.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
//go:build linux && cgo && !gtk3 && !android
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk4 webkitgtk-6.0 gio-unix-2.0
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit/webkit.h>
|
||||
|
||||
static gboolean unref_request_on_main(gpointer data) {
|
||||
if (data != NULL) {
|
||||
g_object_unref(data);
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// releaseRequestOnMainThread schedules the WebKitURISchemeRequest unref on the
|
||||
// GTK main context. Close() runs on the assetserver goroutine, and dropping
|
||||
// what may be the last reference finalizes a WebKit GObject — only safe on the
|
||||
// UI thread (see #5557).
|
||||
static void releaseRequestOnMainThread(WebKitURISchemeRequest *request) {
|
||||
if (request == NULL) {
|
||||
return;
|
||||
}
|
||||
g_main_context_invoke(NULL, unref_request_on_main, request);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
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) {
|
||||
// Reading the URI touches the WebKit-owned request on the GTK main loop;
|
||||
// this runs on a worker goroutine, so it must hop to the main thread.
|
||||
// See mainthread_linux.go and issue #5631.
|
||||
var uri string
|
||||
invokeOnMainSync(func() {
|
||||
uri = C.GoString(C.webkit_uri_scheme_request_get_uri(r.req))
|
||||
})
|
||||
return uri, 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.releaseRequestOnMainThread(r.req)
|
||||
return err
|
||||
}
|
||||
107
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_linux_gtk3.go
generated
vendored
Normal file
107
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_linux_gtk3.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
//go:build linux && cgo && gtk3 && !android
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 gio-unix-2.0
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
|
||||
static gboolean unref_request_on_main(gpointer data) {
|
||||
if (data != NULL) {
|
||||
g_object_unref(data);
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// releaseRequestOnMainThread schedules the WebKitURISchemeRequest unref on the
|
||||
// GTK main context. Close() runs on the assetserver goroutine, and dropping
|
||||
// what may be the last reference finalizes a WebKit GObject — only safe on the
|
||||
// UI thread (see #5557).
|
||||
static void releaseRequestOnMainThread(WebKitURISchemeRequest *request) {
|
||||
if (request == NULL) {
|
||||
return;
|
||||
}
|
||||
g_main_context_invoke(NULL, unref_request_on_main, request);
|
||||
}
|
||||
*/
|
||||
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) {
|
||||
// Reading the URI touches the WebKit-owned request on the GTK main loop;
|
||||
// this runs on a worker goroutine, so it must hop to the main thread.
|
||||
// See mainthread_linux.go and issue #5631.
|
||||
var uri string
|
||||
invokeOnMainSync(func() {
|
||||
uri = C.GoString(C.webkit_uri_scheme_request_get_uri(r.req))
|
||||
})
|
||||
return uri, 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.releaseRequestOnMainThread(r.req)
|
||||
return err
|
||||
}
|
||||
218
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_windows.go
generated
vendored
Normal file
218
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/request_windows.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
//go:build windows
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/webview2/pkg/edge"
|
||||
)
|
||||
|
||||
// NewRequest creates as new WebViewRequest for chromium. This Method must be called from the Main-Thread!
|
||||
func NewRequest(env *edge.ICoreWebView2Environment, args *edge.ICoreWebView2WebResourceRequestedEventArgs, invokeSync func(fn func())) (Request, error) {
|
||||
req, err := args.GetRequest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetRequest failed: %s", err)
|
||||
}
|
||||
defer req.Release()
|
||||
|
||||
r := &request{
|
||||
invokeSync: invokeSync,
|
||||
}
|
||||
|
||||
code := http.StatusInternalServerError
|
||||
r.response, err = env.CreateWebResourceResponse(nil, code, http.StatusText(code), "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreateWebResourceResponse failed: %s", err)
|
||||
}
|
||||
|
||||
if err := args.PutResponse(r.response); err != nil {
|
||||
r.finishResponse()
|
||||
return nil, fmt.Errorf("PutResponse failed: %s", err)
|
||||
}
|
||||
|
||||
r.deferral, err = args.GetDeferral()
|
||||
if err != nil {
|
||||
r.finishResponse()
|
||||
return nil, fmt.Errorf("GetDeferral failed: %s", err)
|
||||
}
|
||||
|
||||
r.url, r.urlErr = req.GetUri()
|
||||
r.method, r.methodErr = req.GetMethod()
|
||||
r.header, r.headerErr = getHeaders(req)
|
||||
|
||||
if content, err := req.GetContent(); err != nil {
|
||||
r.bodyErr = err
|
||||
} else if content != nil {
|
||||
// It is safe to access Content from another Thread: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/threading-model#thread-safety
|
||||
r.body = &iStreamReleaseCloser{stream: content}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
var _ Request = &request{}
|
||||
|
||||
type request struct {
|
||||
response *edge.ICoreWebView2WebResourceResponse
|
||||
deferral *edge.ICoreWebView2Deferral
|
||||
|
||||
url string
|
||||
urlErr error
|
||||
|
||||
method string
|
||||
methodErr error
|
||||
|
||||
header http.Header
|
||||
headerErr error
|
||||
|
||||
body io.ReadCloser
|
||||
bodyErr error
|
||||
rw *responseWriter
|
||||
|
||||
invokeSync func(fn func())
|
||||
}
|
||||
|
||||
func (r *request) URL() (string, error) {
|
||||
return r.url, r.urlErr
|
||||
}
|
||||
|
||||
func (r *request) Method() (string, error) {
|
||||
return r.method, r.methodErr
|
||||
}
|
||||
|
||||
func (r *request) Header() (http.Header, error) {
|
||||
return r.header, r.headerErr
|
||||
}
|
||||
|
||||
func (r *request) Body() (io.ReadCloser, error) {
|
||||
return r.body, r.bodyErr
|
||||
}
|
||||
|
||||
func (r *request) Response() ResponseWriter {
|
||||
if r.rw != nil {
|
||||
return r.rw
|
||||
}
|
||||
|
||||
r.rw = &responseWriter{req: r}
|
||||
return r.rw
|
||||
}
|
||||
|
||||
func (r *request) Close() error {
|
||||
var errs []error
|
||||
if r.body != nil {
|
||||
if err := r.body.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
r.body = nil
|
||||
}
|
||||
|
||||
if err := r.Response().Finish(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return combineErrs(errs)
|
||||
}
|
||||
|
||||
// finishResponse must be called on the main-thread
|
||||
func (r *request) finishResponse() error {
|
||||
var errs []error
|
||||
if r.response != nil {
|
||||
if err := r.response.Release(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
r.response = nil
|
||||
}
|
||||
if r.deferral != nil {
|
||||
if err := r.deferral.Complete(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := r.deferral.Release(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
r.deferral = nil
|
||||
}
|
||||
return combineErrs(errs)
|
||||
}
|
||||
|
||||
type iStreamReleaseCloser struct {
|
||||
stream *edge.IStream
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (i *iStreamReleaseCloser) Read(p []byte) (int, error) {
|
||||
if i.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
return i.stream.Read(p)
|
||||
}
|
||||
|
||||
func (i *iStreamReleaseCloser) Close() error {
|
||||
if i.closed {
|
||||
return nil
|
||||
}
|
||||
i.closed = true
|
||||
return i.stream.Release()
|
||||
}
|
||||
|
||||
func getHeaders(req *edge.ICoreWebView2WebResourceRequest) (http.Header, error) {
|
||||
header := http.Header{}
|
||||
headers, err := req.GetHeaders()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetHeaders Error: %s", err)
|
||||
}
|
||||
defer headers.Release()
|
||||
|
||||
headersIt, err := headers.GetIterator()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetIterator Error: %s", err)
|
||||
}
|
||||
defer headersIt.Release()
|
||||
|
||||
for {
|
||||
has, err := headersIt.HasCurrentHeader()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HasCurrentHeader Error: %s", err)
|
||||
}
|
||||
if !has {
|
||||
break
|
||||
}
|
||||
|
||||
name, value, err := headersIt.GetCurrentHeader()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetCurrentHeader Error: %s", err)
|
||||
}
|
||||
|
||||
header.Set(name, value)
|
||||
if _, err := headersIt.MoveNext(); err != nil {
|
||||
return nil, fmt.Errorf("MoveNext Error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
|
||||
// requests including IPC calls.
|
||||
// So prevent 304 status codes by removing the headers that are used in combinationwith caching.
|
||||
header.Del("If-Modified-Since")
|
||||
header.Del("If-None-Match")
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func combineErrs(errs []error) error {
|
||||
err := errors.Join(errs...)
|
||||
|
||||
if err != nil {
|
||||
// errors.Join wraps even a single error.
|
||||
// Check the filtered error list,
|
||||
// and if it has just one element return it directly.
|
||||
errs = err.(interface{ Unwrap() []error }).Unwrap()
|
||||
if len(errs) == 1 {
|
||||
return errs[0]
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
28
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter.go
generated
vendored
Normal file
28
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
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() error
|
||||
|
||||
// Code returns the HTTP status code of the response
|
||||
Code() int
|
||||
}
|
||||
156
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_darwin.go
generated
vendored
Normal file
156
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
//go:build darwin && !ios
|
||||
|
||||
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 (
|
||||
"net/http"
|
||||
"unsafe"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
|
||||
type responseWriter struct {
|
||||
r *request
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
code int
|
||||
|
||||
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 && len(buf) > 0 {
|
||||
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) {
|
||||
rw.code = code
|
||||
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() error {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return nil
|
||||
}
|
||||
rw.finished = true
|
||||
|
||||
C.URLSchemeTaskDidFinish(rw.r.task)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Code() int {
|
||||
return rw.code
|
||||
}
|
||||
161
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_ios.go
generated
vendored
Normal file
161
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_ios.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
//go:build ios
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c -fobjc-arc
|
||||
#cgo LDFLAGS: -framework Foundation -framework WebKit
|
||||
|
||||
#include <stdlib.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
typedef void (^schemeTaskCaller)(id<WKURLSchemeTask>);
|
||||
|
||||
static bool urlSchemeTaskCall(void *wkUrlSchemeTask, schemeTaskCaller fn) {
|
||||
id<WKURLSchemeTask> urlSchemeTask = (__bridge 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:nil headerFields:headerFields];
|
||||
|
||||
[urlSchemeTask didReceiveResponse:response];
|
||||
});
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"unsafe"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
|
||||
type responseWriter struct {
|
||||
r *request
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
code int
|
||||
|
||||
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
|
||||
// Guard on length, not just nil: a non-nil but empty slice (e.g. the body of
|
||||
// a bound method that returned "") would make &buf[0] panic with an
|
||||
// index-out-of-range, aborting the Go runtime. An empty body is valid — pass
|
||||
// a nil pointer with length 0, which yields an empty NSData on the C side.
|
||||
if len(buf) != 0 {
|
||||
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) {
|
||||
rw.code = code
|
||||
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() error {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return nil
|
||||
}
|
||||
rw.finished = true
|
||||
|
||||
C.URLSchemeTaskDidFinish(rw.r.task)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Code() int {
|
||||
return rw.code
|
||||
}
|
||||
146
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_linux.go
generated
vendored
Normal file
146
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_linux.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
//go:build linux && cgo && !gtk3 && !android
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk4 webkitgtk-6.0
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit/webkit.h>
|
||||
|
||||
// webview_asset_error_quark returns a stable GError domain for asset-server
|
||||
// failures. The string literal is static storage, so g_quark_from_static_string
|
||||
// interns it once and never leaks. Interning the per-request error message
|
||||
// instead (the previous behaviour) grew the global quark table unboundedly on
|
||||
// long-running apps, since GQuarks are never freed.
|
||||
static GQuark webview_asset_error_quark(void) {
|
||||
return g_quark_from_static_string("wails-webview-assetserver");
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type responseWriter struct {
|
||||
req *C.WebKitURISchemeRequest
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
finished bool
|
||||
code int
|
||||
|
||||
w io.WriteCloser
|
||||
wErr error
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Code() int {
|
||||
return rw.code
|
||||
}
|
||||
|
||||
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) {
|
||||
rw.code = code
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
rFD, w, err := pipe()
|
||||
if err != nil {
|
||||
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err))
|
||||
return
|
||||
}
|
||||
rw.w = w
|
||||
|
||||
// webkit_uri_scheme_request_finish wraps the read end of the pipe in a
|
||||
// GUnixInputStream and completes the request on the GTK main thread; the
|
||||
// stream is created and released there too. See webkit_linux.go and #5631.
|
||||
if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), rFD, contentLength); err != nil {
|
||||
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Finish() error {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return nil
|
||||
}
|
||||
rw.finished = true
|
||||
if rw.w != nil {
|
||||
rw.w.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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())
|
||||
defer C.free(unsafe.Pointer(msg))
|
||||
|
||||
// webkit_uri_scheme_request_finish_error touches the WebKit-owned request
|
||||
// on the GTK main loop; this runs on an asset-server worker goroutine, so
|
||||
// it must hop to the main thread. See mainthread_linux.go and issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
gerr := C.g_error_new_literal(C.webview_asset_error_quark(), C.int(code), msg)
|
||||
C.webkit_uri_scheme_request_finish_error(rw.req, gerr)
|
||||
C.g_error_free(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
|
||||
}
|
||||
149
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_linux_gtk3.go
generated
vendored
Normal file
149
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_linux_gtk3.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
//go:build linux && cgo && gtk3 && !android
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
|
||||
// webview_asset_error_quark returns a stable GError domain for asset-server
|
||||
// failures. The string literal is static storage, so g_quark_from_static_string
|
||||
// interns it once and never leaks. Interning the per-request error message
|
||||
// instead (the previous behaviour) grew the global quark table unboundedly on
|
||||
// long-running apps, since GQuarks are never freed.
|
||||
static GQuark webview_asset_error_quark(void) {
|
||||
return g_quark_from_static_string("wails-webview-assetserver");
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type responseWriter struct {
|
||||
req *C.WebKitURISchemeRequest
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
finished bool
|
||||
code int
|
||||
|
||||
w io.WriteCloser
|
||||
wErr error
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Code() int {
|
||||
return rw.code
|
||||
}
|
||||
|
||||
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) {
|
||||
rw.code = code
|
||||
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
|
||||
|
||||
// webkit_uri_scheme_request_finish wraps the read end of the pipe in a
|
||||
// GUnixInputStream and completes the request on the GTK main thread; the
|
||||
// stream is created and released there too. See webkit_linux_gtk3.go and #5631.
|
||||
if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), rFD, contentLength); err != nil {
|
||||
rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Finish() error {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return nil
|
||||
}
|
||||
rw.finished = true
|
||||
if rw.w != nil {
|
||||
rw.w.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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())
|
||||
defer C.free(unsafe.Pointer(msg))
|
||||
|
||||
// webkit_uri_scheme_request_finish_error touches the WebKit-owned request
|
||||
// on the GTK main loop; this runs on an asset-server worker goroutine, so
|
||||
// it must hop to the main thread. See mainthread_linux.go and issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
gerr := C.g_error_new_literal(C.webview_asset_error_quark(), C.int(code), msg)
|
||||
C.webkit_uri_scheme_request_finish_error(rw.req, gerr)
|
||||
C.g_error_free(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
|
||||
}
|
||||
109
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_windows.go
generated
vendored
Normal file
109
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/responsewriter_windows.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
//go:build windows
|
||||
|
||||
package webview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ http.ResponseWriter = &responseWriter{}
|
||||
|
||||
type responseWriter struct {
|
||||
req *request
|
||||
|
||||
header http.Header
|
||||
wroteHeader bool
|
||||
code int
|
||||
body *bytes.Buffer
|
||||
|
||||
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)
|
||||
|
||||
return rw.body.Write(buf)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
if rw.wroteHeader || rw.finished {
|
||||
return
|
||||
}
|
||||
rw.wroteHeader = true
|
||||
|
||||
if rw.body == nil {
|
||||
rw.body = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
rw.code = code
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Finish() error {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return nil
|
||||
}
|
||||
rw.finished = true
|
||||
|
||||
var errs []error
|
||||
|
||||
code := rw.code
|
||||
if code == http.StatusNotModified {
|
||||
// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
|
||||
// requests including IPC calls.
|
||||
errs = append(errs, errors.New("AssetServer returned 304 - StatusNotModified which are going to hang WebView2, changed code to 505 - StatusInternalServerError"))
|
||||
code = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
rw.req.invokeSync(func() {
|
||||
resp := rw.req.response
|
||||
|
||||
hdrs, err := resp.GetHeaders()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("Resp.GetHeaders failed: %s", err))
|
||||
} else {
|
||||
for k, v := range rw.header {
|
||||
if err := hdrs.AppendHeader(k, strings.Join(v, ",")); err != nil {
|
||||
errs = append(errs, fmt.Errorf("Resp.AppendHeader failed: %s", err))
|
||||
}
|
||||
}
|
||||
hdrs.Release()
|
||||
}
|
||||
|
||||
if err := resp.PutStatusCode(code); err != nil {
|
||||
errs = append(errs, fmt.Errorf("Resp.PutStatusCode failed: %s", err))
|
||||
}
|
||||
|
||||
if err := resp.PutByteContent(rw.body.Bytes()); err != nil {
|
||||
errs = append(errs, fmt.Errorf("Resp.PutByteContent failed: %s", err))
|
||||
}
|
||||
|
||||
if err := rw.req.finishResponse(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("Resp.finishResponse failed: %s", err))
|
||||
}
|
||||
})
|
||||
|
||||
return combineErrs(errs)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Code() int {
|
||||
return rw.code
|
||||
}
|
||||
181
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/webkit_linux.go
generated
vendored
Normal file
181
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/webkit_linux.go
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
//go:build linux && cgo && !gtk3 && !android
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk4 webkitgtk-6.0 libsoup-3.0 gio-unix-2.0
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit/webkit.h>
|
||||
#include <libsoup/soup.h>
|
||||
#include <gio/gunixinputstream.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const Webkit2MinMinorVersion = 0
|
||||
|
||||
func webkit_uri_scheme_request_get_http_method(req *C.WebKitURISchemeRequest) string {
|
||||
// Reading request metadata touches the WebKit-owned request object, which
|
||||
// belongs to the GTK main loop; this runs on a worker goroutine, so it must
|
||||
// hop to the main thread. See mainthread_linux.go and issue #5631.
|
||||
var method string
|
||||
invokeOnMainSync(func() {
|
||||
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 {
|
||||
h := http.Header{}
|
||||
// Reading and iterating the request's libsoup headers touches WebKit-owned
|
||||
// state on the GTK main loop; this runs on a worker goroutine, so it must hop
|
||||
// to the main thread. See mainthread_linux.go and issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
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
|
||||
|
||||
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, rFD int, streamLength int64) error {
|
||||
// Completing the request touches WebKit/libsoup objects owned by the GTK
|
||||
// main loop, but this runs on an asset-server worker goroutine. WebKit2GTK
|
||||
// is not thread-safe, so the whole sequence must hop to the main thread.
|
||||
//
|
||||
// The response input stream is created and unref'd inside the same hop: it is
|
||||
// ref-taken by webkit_uri_scheme_response_new on the main thread, so creating
|
||||
// and releasing our reference here too keeps every refcount operation on a
|
||||
// single thread. Previously the stream was built and unref'd on the worker
|
||||
// while WebKit took its ref on the main thread, splitting the stream's
|
||||
// refcount across threads. See mainthread_linux.go and issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1))
|
||||
defer C.g_object_unref(C.gpointer(stream))
|
||||
|
||||
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))
|
||||
|
||||
// Ownership of hdrs is transferred to the response by
|
||||
// webkit_uri_scheme_response_set_http_headers (transfer full), so we must
|
||||
// not unref it here — doing so frees the headers while WebKit/libsoup still
|
||||
// reference them, crashing in soup_message_headers_iter_next on render.
|
||||
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
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser {
|
||||
// Fetching the request body stream touches the WebKit-owned request on the
|
||||
// GTK main loop; this runs on a worker goroutine, so it must hop to the main
|
||||
// thread. See mainthread_linux.go and issue #5631.
|
||||
var stream *C.GInputStream
|
||||
invokeOnMainSync(func() {
|
||||
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
|
||||
}
|
||||
|
||||
func (r *webkitRequestBody) Read(p []byte) (int, error) {
|
||||
if r.closed {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
// io.Reader allows a zero-length read; taking &p[0] on an empty slice would
|
||||
// panic, so return early before touching the backing array.
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
content := unsafe.Pointer(&p[0])
|
||||
contentLen := len(p)
|
||||
|
||||
var n C.gsize
|
||||
var gErr *C.GError
|
||||
var res C.gboolean
|
||||
// Reading the WebKit-owned request body stream must happen on the GTK main
|
||||
// loop thread; this runs on a worker goroutine. See issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
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
|
||||
|
||||
var err error
|
||||
var gErr *C.GError
|
||||
// Closing and unref-ing the WebKit-owned request body stream finalizes a
|
||||
// GObject tied to the GTK main loop; this runs on a worker goroutine, so it
|
||||
// must hop to the main thread. See issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
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...)
|
||||
}
|
||||
185
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/webkit_linux_gtk3.go
generated
vendored
Normal file
185
vendor/github.com/wailsapp/wails/v3/internal/assetserver/webview/webkit_linux_gtk3.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
//go:build linux && cgo && gtk3 && !android
|
||||
|
||||
package webview
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.1 libsoup-3.0 gio-unix-2.0
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
#include "libsoup/soup.h"
|
||||
#include "gio/gunixinputstream.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const Webkit2MinMinorVersion = 40
|
||||
|
||||
func webkit_uri_scheme_request_get_http_method(req *C.WebKitURISchemeRequest) string {
|
||||
// Reading request metadata touches the WebKit-owned request object, which
|
||||
// belongs to the GTK main loop; this runs on a worker goroutine, so it must
|
||||
// hop to the main thread. See mainthread_linux.go and issue #5631.
|
||||
var method string
|
||||
invokeOnMainSync(func() {
|
||||
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 {
|
||||
h := http.Header{}
|
||||
// Reading and iterating the request's libsoup headers touches WebKit-owned
|
||||
// state on the GTK main loop; this runs on a worker goroutine, so it must hop
|
||||
// to the main thread. See mainthread_linux.go and issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
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
|
||||
|
||||
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, rFD int, streamLength int64) error {
|
||||
// Completing the request touches WebKit/libsoup objects owned by the GTK
|
||||
// main loop, but this runs on an asset-server worker goroutine. WebKit2GTK
|
||||
// is not thread-safe, so the whole sequence must hop to the main thread.
|
||||
//
|
||||
// The response input stream is created and unref'd inside the same hop: it is
|
||||
// ref-taken by webkit_uri_scheme_response_new on the main thread, so creating
|
||||
// and releasing our reference here too keeps every refcount operation on a
|
||||
// single thread. Previously the stream was built and unref'd on the worker
|
||||
// while WebKit took its ref on the main thread, splitting the stream's
|
||||
// refcount across threads. See mainthread_linux.go and issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
stream := C.g_unix_input_stream_new(C.int(rFD), C.gboolean(1))
|
||||
defer C.g_object_unref(C.gpointer(stream))
|
||||
|
||||
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))
|
||||
|
||||
// Ownership of hdrs is transferred to the response by
|
||||
// webkit_uri_scheme_response_set_http_headers (transfer full), so we must
|
||||
// not unref it here — doing so frees the headers while WebKit/libsoup still
|
||||
// reference them, crashing in soup_message_headers_iter_next on render.
|
||||
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
|
||||
}
|
||||
|
||||
func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser {
|
||||
// Fetching the request body stream touches the WebKit-owned request on the
|
||||
// GTK main loop; this runs on a worker goroutine, so it must hop to the main
|
||||
// thread. See mainthread_linux.go and issue #5631.
|
||||
var stream *C.GInputStream
|
||||
invokeOnMainSync(func() {
|
||||
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
|
||||
}
|
||||
|
||||
// io.Reader allows a zero-length read; taking &p[0] on an empty slice would
|
||||
// panic, so return early before touching the backing array.
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
content := unsafe.Pointer(&p[0])
|
||||
contentLen := len(p)
|
||||
|
||||
var n C.gsize
|
||||
var gErr *C.GError
|
||||
var res C.gboolean
|
||||
// Reading the WebKit-owned request body stream must happen on the GTK main
|
||||
// loop thread; this runs on a worker goroutine. See issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
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
|
||||
// Closing and unref-ing the WebKit-owned request body stream finalizes a
|
||||
// GObject tied to the GTK main loop; this runs on a worker goroutine, so it
|
||||
// must hop to the main thread. See issue #5631.
|
||||
invokeOnMainSync(func() {
|
||||
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...)
|
||||
}
|
||||
Reference in New Issue
Block a user