mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 20:03:01 +08:00
[v2] Add meta tag to control script injection behaviour
This commit is contained in:
parent
7a0cb428f2
commit
afb1d12c3b
@ -21,63 +21,63 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AssetServer struct {
|
type BrowserAssetServer struct {
|
||||||
indexFile []byte
|
|
||||||
runtimeJS []byte
|
runtimeJS []byte
|
||||||
assetdir string
|
assetdir string
|
||||||
appOptions *options.App
|
appOptions *options.App
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAssetServer(assetdir string, bindingsJSON string, appOptions *options.App) (*AssetServer, error) {
|
func NewBrowserAssetServer(assetdir string, bindingsJSON string, appOptions *options.App) (*BrowserAssetServer, error) {
|
||||||
result := &AssetServer{
|
result := &BrowserAssetServer{
|
||||||
assetdir: assetdir,
|
assetdir: assetdir,
|
||||||
appOptions: appOptions,
|
appOptions: appOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := result.init()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
|
buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n")
|
||||||
buffer.Write(runtime.RuntimeDesktopJS)
|
buffer.Write(runtime.RuntimeDesktopJS)
|
||||||
result.runtimeJS = buffer.Bytes()
|
result.runtimeJS = buffer.Bytes()
|
||||||
err = result.init()
|
return result, nil
|
||||||
return result, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AssetServer) loadFileFromDisk(filename string) ([]byte, error) {
|
func (a *BrowserAssetServer) loadFileFromDisk(filename string) ([]byte, error) {
|
||||||
return os.ReadFile(filepath.Join(a.assetdir, filename))
|
return os.ReadFile(filepath.Join(a.assetdir, filename))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AssetServer) init() error {
|
func (a *BrowserAssetServer) processIndexHTML() ([]byte, error) {
|
||||||
var err error
|
indexHTML, err := a.loadFileFromDisk("index.html")
|
||||||
a.indexFile, err = a.loadFileFromDisk("index.html")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.indexFile, err = injectHTML(string(a.indexFile), `<div id="wails-spinner"></div>`)
|
indexHTML, err = injectHTML(string(indexHTML), `<div id="wails-spinner"></div>`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.indexFile, err = injectHTML(string(a.indexFile), `<script src="/wails/ipc.js"></script>`)
|
wailsOptions, err := extractOptions(indexHTML)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
a.indexFile, err = injectHTML(string(a.indexFile), `<script src="/wails/runtime.js"></script>`)
|
if wailsOptions.disableRuntimeInjection == false {
|
||||||
if err != nil {
|
indexHTML, err = injectHTML(string(indexHTML), `<script src="/wails/runtime.js"></script>`)
|
||||||
return err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
if wailsOptions.disableBindingsInjection == false {
|
||||||
|
indexHTML, err = injectHTML(string(indexHTML), `<script src="/wails/ipc.js"></script>`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indexHTML, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AssetServer) Load(filename string) ([]byte, string, error) {
|
func (a *BrowserAssetServer) Load(filename string) ([]byte, string, error) {
|
||||||
var content []byte
|
var content []byte
|
||||||
var err error
|
var err error
|
||||||
switch filename {
|
switch filename {
|
||||||
case "/":
|
case "/":
|
||||||
content = a.indexFile
|
content, err = a.processIndexHTML()
|
||||||
case "/wails/runtime.js":
|
case "/wails/runtime.js":
|
||||||
content = a.runtimeJS
|
content = a.runtimeJS
|
||||||
case "/wails/ipc.js":
|
case "/wails/ipc.js":
|
@ -10,13 +10,13 @@ import (
|
|||||||
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DesktopAssetServer struct {
|
type DesktopAssetServer struct {
|
||||||
assets debme.Debme
|
assets debme.Debme
|
||||||
indexFile []byte
|
|
||||||
runtimeJS []byte
|
runtimeJS []byte
|
||||||
assetdir string
|
assetdir string
|
||||||
logger *logger.Logger
|
logger *logger.Logger
|
||||||
@ -106,27 +106,42 @@ func (a *DesktopAssetServer) init(assets embed.FS) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
indexHTML, err := a.assets.ReadFile("index.html")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.indexFile, err = injectHTML(string(indexHTML), `<script src="/wails/runtime.js"></script>`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.indexFile, err = injectHTML(string(a.indexFile), `<script src="/wails/ipc.js"></script>`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *DesktopAssetServer) processIndexHTML() ([]byte, error) {
|
||||||
|
indexHTML, err := a.ReadFile("index.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wailsOptions, err := extractOptions(indexHTML)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fmt.Printf("%+v\n", wailsOptions)
|
||||||
|
if wailsOptions.disableRuntimeInjection == false {
|
||||||
|
indexHTML, err = injectHTML(string(indexHTML), `<script src="/wails/runtime.js"></script>`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if wailsOptions.disableBindingsInjection == false {
|
||||||
|
indexHTML, err = injectHTML(string(indexHTML), `<script src="/wails/ipc.js"></script>`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexHTML, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *DesktopAssetServer) Load(filename string) ([]byte, string, error) {
|
func (a *DesktopAssetServer) Load(filename string) ([]byte, string, error) {
|
||||||
var content []byte
|
var content []byte
|
||||||
var err error
|
var err error
|
||||||
switch filename {
|
switch filename {
|
||||||
case "/":
|
case "/":
|
||||||
content = a.indexFile
|
content, err = a.processIndexHTML()
|
||||||
case "/wails/runtime.js":
|
case "/wails/runtime.js":
|
||||||
content = a.runtimeJS
|
content = a.runtimeJS
|
||||||
case "/wails/ipc.js":
|
case "/wails/ipc.js":
|
||||||
|
@ -3,19 +3,86 @@ package assetserver
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"golang.org/x/net/html"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type optionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
noAutoInject optionType = "noautoinject"
|
||||||
|
noAutoInjectRuntime optionType = "noautoinjectruntime"
|
||||||
|
noAutoInjectBindings optionType = "noautoinjectbindings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
disableRuntimeInjection bool
|
||||||
|
disableBindingsInjection bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOptions(optionString string) *Options {
|
||||||
|
var result = &Options{}
|
||||||
|
optionString = strings.ToLower(optionString)
|
||||||
|
options := strings.Split(optionString, ",")
|
||||||
|
for _, option := range options {
|
||||||
|
switch optionType(strings.TrimSpace(option)) {
|
||||||
|
case noAutoInject:
|
||||||
|
result.disableRuntimeInjection = true
|
||||||
|
result.disableBindingsInjection = true
|
||||||
|
case noAutoInjectBindings:
|
||||||
|
result.disableBindingsInjection = true
|
||||||
|
case noAutoInjectRuntime:
|
||||||
|
result.disableRuntimeInjection = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func injectHTML(input string, html string) ([]byte, error) {
|
func injectHTML(input string, html string) ([]byte, error) {
|
||||||
splits := strings.Split(input, "</body>")
|
splits := strings.Split(input, "</head>")
|
||||||
if len(splits) != 2 {
|
if len(splits) != 2 {
|
||||||
return nil, fmt.Errorf("unable to locate a </body> tag in your html")
|
return nil, fmt.Errorf("unable to locate a </head> tag in your html")
|
||||||
}
|
}
|
||||||
|
|
||||||
var result bytes.Buffer
|
var result bytes.Buffer
|
||||||
result.WriteString(splits[0])
|
result.WriteString(splits[0])
|
||||||
result.WriteString(html)
|
result.WriteString(html)
|
||||||
result.WriteString("</body>")
|
result.WriteString("</head>")
|
||||||
result.WriteString(splits[1])
|
result.WriteString(splits[1])
|
||||||
return result.Bytes(), nil
|
return result.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractOptions(htmldata []byte) (*Options, error) {
|
||||||
|
doc, err := html.Parse(bytes.NewReader(htmldata))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var extractor func(*html.Node) *Options
|
||||||
|
extractor = func(node *html.Node) *Options {
|
||||||
|
if node.Type == html.ElementNode && node.Data == "meta" {
|
||||||
|
isWailsOptionsTag := false
|
||||||
|
wailsOptions := ""
|
||||||
|
for _, attr := range node.Attr {
|
||||||
|
if isWailsOptionsTag && attr.Key == "content" {
|
||||||
|
wailsOptions = attr.Val
|
||||||
|
}
|
||||||
|
if attr.Val == "wails-options" {
|
||||||
|
isWailsOptionsTag = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newOptions(wailsOptions)
|
||||||
|
}
|
||||||
|
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||||
|
result := extractor(child)
|
||||||
|
if result != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := extractor(doc)
|
||||||
|
if result == nil {
|
||||||
|
result = &Options{}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
70
v2/internal/frontend/assetserver/common_test.go
Normal file
70
v2/internal/frontend/assetserver/common_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package assetserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const realHTML = `<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>test3</title>
|
||||||
|
<meta name="wails-options" content="noautoinject">
|
||||||
|
<link rel="stylesheet" href="/main.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-wails-drag>
|
||||||
|
<div class="logo"></div>
|
||||||
|
<div class="result" id="result">Please enter your name below <EFBFBD></div>
|
||||||
|
<div class="input-box" id="input" data-wails-no-drag>
|
||||||
|
<input class="input" id="name" type="text" autocomplete="off">
|
||||||
|
<button class="btn" onclick="greet()">Greet</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
func genMeta(content string) []byte {
|
||||||
|
return []byte("<html><head><meta name=\"wails-options\" content=\"" + content + "\"></head><body></body></html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func genOptions(runtime bool, bindings bool) *Options {
|
||||||
|
return &Options{
|
||||||
|
disableRuntimeInjection: runtime,
|
||||||
|
disableBindingsInjection: bindings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_extractOptions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
htmldata []byte
|
||||||
|
want *Options
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"empty", []byte(""), &Options{}, false},
|
||||||
|
{"bad data", []byte("<"), &Options{}, false},
|
||||||
|
{"bad options", genMeta("noauto"), genOptions(false, false), false},
|
||||||
|
{"realhtml", []byte(realHTML), genOptions(true, true), false},
|
||||||
|
{"noautoinject", genMeta("noautoinject"), genOptions(true, true), false},
|
||||||
|
{"noautoinjectbindings", genMeta("noautoinjectbindings"), genOptions(false, true), false},
|
||||||
|
{"noautoinjectruntime", genMeta("noautoinjectruntime"), genOptions(true, false), false},
|
||||||
|
{"spaces", genMeta(" noautoinjectruntime "), genOptions(true, false), false},
|
||||||
|
{"multiple", genMeta("noautoinjectruntime,noautoinjectbindings"), genOptions(true, true), false},
|
||||||
|
{"multiple spaces", genMeta(" noautoinjectruntime, noautoinjectbindings "), genOptions(true, true), false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := extractOptions(tt.htmldata)
|
||||||
|
if !tt.wantError && err != nil {
|
||||||
|
t.Errorf("did not want error but got it")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("extractOptions() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,7 @@ type DevWebServer struct {
|
|||||||
logger *logger.Logger
|
logger *logger.Logger
|
||||||
appBindings *binding.Bindings
|
appBindings *binding.Bindings
|
||||||
dispatcher frontend.Dispatcher
|
dispatcher frontend.Dispatcher
|
||||||
assetServer *assetserver.AssetServer
|
assetServer *assetserver.BrowserAssetServer
|
||||||
socketMutex sync.Mutex
|
socketMutex sync.Mutex
|
||||||
websocketClients map[*websocket.Conn]struct{}
|
websocketClients map[*websocket.Conn]struct{}
|
||||||
menuManager *menumanager.Manager
|
menuManager *menumanager.Manager
|
||||||
@ -103,7 +103,7 @@ func (d *DevWebServer) Run(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
d.assetServer, err = assetserver.NewAssetServer(assetdir, bindingsJSON, d.appoptions)
|
d.assetServer, err = assetserver.NewBrowserAssetServer(assetdir, bindingsJSON, d.appoptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
77
website/docs/guides/frontend.mdx
Normal file
77
website/docs/guides/frontend.mdx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
|
||||||
|
# Frontend
|
||||||
|
|
||||||
|
## Execution Order
|
||||||
|
|
||||||
|
When Wails serves your `index.html`, by default, it will inject 2 script entries into the `<body>` tag to load `/wails/bindings.js`
|
||||||
|
and `/wails/runtime.js`. These files install the bindings and runtime respectively.
|
||||||
|
|
||||||
|
The code below shows where these are injected by default:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>injection example</title>
|
||||||
|
<link rel="stylesheet" href="/main.css">
|
||||||
|
<!-- <script src="/wails/ipc.js"></script> -->
|
||||||
|
<!-- <script src="/wails/runtime.js"></script> -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-wails-drag>
|
||||||
|
<div class="logo"></div>
|
||||||
|
<div class="result" id="result">Please enter your name below 👇</div>
|
||||||
|
<div class="input-box" id="input" data-wails-no-drag>
|
||||||
|
<input class="input" id="name" type="text" autocomplete="off">
|
||||||
|
<button class="btn" onclick="greet()">Greet</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Overriding Default Execution Order
|
||||||
|
|
||||||
|
To provide more flexibility to developers, there is a meta tag that may be used to customise this behaviour:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<meta name="wails-options" content="[options]">
|
||||||
|
```
|
||||||
|
|
||||||
|
The options are as follows:
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
| -------------------- | ------------------------------------------------- |
|
||||||
|
| noautoinjectruntime | Disable the autoinjection of `/wails/runtime.js` |
|
||||||
|
| noautoinjectbindings | Disable the autoinjection of `/wails/bindings.js` |
|
||||||
|
| noautoinject | Disable all autoinjection of scripts |
|
||||||
|
|
||||||
|
Multiple options may be used provided they are comma seperated.
|
||||||
|
|
||||||
|
This code is perfectly valid and operates the same as the autoinjection version:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>injection example</title>
|
||||||
|
<meta name="wails-options" content="noautoinject">
|
||||||
|
<link rel="stylesheet" href="/main.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-wails-drag>
|
||||||
|
<div class="logo"></div>
|
||||||
|
<div class="result" id="result">Please enter your name below 👇</div>
|
||||||
|
<div class="input-box" id="input" data-wails-no-drag>
|
||||||
|
<input class="input" id="name" type="text" autocomplete="off">
|
||||||
|
<button class="btn" onclick="greet()">Greet</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/wails/ipc.js"></script>
|
||||||
|
<script src="/wails/runtime.js"></script>
|
||||||
|
<script src="/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user