webview / webview

Tiny cross-platform webview library for C/C++. Uses WebKit (GTK/Cocoa) and Edge WebView2 (Windows).
MIT License
12.54k stars 942 forks source link

problem with go routine and webview.Eval() #248

Open williamgorden opened 5 years ago

williamgorden commented 5 years ago

In a handleRPC(webview.WebView, string) function in golang the following Problem occurs: I I want to run a very long process (2 minutes) and update my html along the way, sort of like a Progress indicator, but only with text.

I would actually be happy just to write "process running…" at the beginning and then "process finished!" at the end of the calculation process.

I am only getting the text into the html at the end...

In the atached source code, I modified the window example to do just this with 2 dummy processes.

If you run this, in the console you will see that everything is Happening as planned. However the Eval() call before the process doesn't make it to the html!

Only the Eval() call afterwards arrives.

Screenshot before and during process:

image

Screenshot after process:

window2

Screenshot Console Output:

console

Source Code (sorry About the formatting...):

package main

import ( "log" "strconv" "sync" "time"

"github.com/zserge/webview"

)

const ( windowWidth = 300 windowHeight = 200 )

var indexHTML = ` <!doctype html>

idle

`

var wg sync.WaitGroup

func handleRPC(w webview.WebView, data string) { if data == "startprocess" {

    log.Println("process started")
    // call Eval() to update the html in the window to show that the process is running
    // this is not showing up in the html!
    w.Eval(`document.getElementById('processstatus').innerHTML='process running ...';`)

    log.Println("run process ...")
    // the process in a separate go routine (I thought it might not slow down the update to the html!!)
    wg.Add(2)
    go func() {
        // process number 1, 4 intervals of 1 second = 4 seconds
        go process(1, 4, 1)
    }()
    go func() {
        // process number 2, 3 intervals of 2 second = 6 seconds
        go process(2, 3, 2)
    }()
    wg.Wait()

    log.Println("process finished!")
    // call Eval() to update the html in the window to show that the process is finished
    // this shows up in the html at the end
    w.Eval(`document.getElementById('processstatus').innerHTML='process finished!';`)

}

}

func process(nr int, count, seconds int) { defer wg.Done() for i := 0; i < count; i++ { time.Sleep(time.Duration(seconds) 1000 time.Millisecond) log.Println("process nr: " + strconv.FormatInt(int64(nr), 10) + ", " + strconv.FormatInt(int64((i+1)*seconds), 10) + " seconds ...") } }

func main() { url := "data:text/html," + indexHTML w := setup(url) defer w.Exit() w.Run() }

func setup(url string) webview.WebView { webView := webview.New(webview.Settings{ Width: windowWidth, Height: windowHeight, Title: "concurrence demo", Resizable: true, URL: url, ExternalInvokeCallback: handleRPC, }) webView.SetColor(255, 255, 255, 255) webView.SetFullscreen(false) return webView }

I am grateful for any help here. Thanks.

DanielSek commented 5 years ago

If handleRPC is running on GUI thread, web control has no chance to refresh itself. Offload lengthy calculations to other threads or periodically pump messages. (I assume you are working on Windows)

MeeGene commented 5 years ago

Use webView.Dispatch to ensure that the task is triggered on the main thread

vikulin commented 3 years ago

This is minimal app example where the issue reproduces. Nothing happens after SignIn button click for WIndows app, the same app works well in Linux Ubuntu. Dispatch has been added as recommended by devs. Cross build:

$ GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags "-H windowsgui"

main.go

package main

import "github.com/webview/webview"
import "os"
import "log"
import "io/ioutil"
import "fmt"

func main() {
        debug := true
        w := webview.New(debug)
        defer w.Destroy()
        w.SetTitle("Test 0.0.1")
        w.SetSize(250, 100, webview.HintNone)
        path, err := os.Getwd()
        if err != nil {
           log.Println(err)
        }
        log.Println(path)
        dat, err := ioutil.ReadFile(path+"/test.html")
        w.Navigate("data:text/html,"+string(dat))
        w.Bind("signIn", func(returned string) {
                log.Println("Sign In button clicked!")
                go signIn(w, returned)
        })

        w.Run()
}

func signIn(p webview.WebView, returned string) {
        js := fmt.Sprintf("'Sign In button event received from %s'", returned)

        p.Dispatch(func() {
                p.Eval("document.getElementById('label').innerHTML = "+js)
        })
}

test.html

<html>
        <button onClick="signIn('test value');">Sign in</button>
        <label id="label"/>
</html>

AR: in Linux Ubuntu 20: 2021-04-01_12h23_36 AR: in Windows 10: 2021-04-01_12h24_24

Most probably IE restricts JS being loaded from local file. @zserge please confirm.

vikulin commented 3 years ago

I managed to fix it: the issue was in Bind and Navigate order. Navigate must follow to Bind. This is not critical for Linux but for Windows it is! I think It can be closed. @williamgorden please confirm it.

williamgorden commented 3 years ago

Wow! Congratulations. I have not even thought about this in a long time. I am not able to confirm this right now. I am not using webview any more. However, I think that Windows users especially will benefit from your fix! Have a nice Easter!

vikulin commented 3 years ago

Wow! Congratulations. I have not even thought about this in a long time. I am not able to confirm this right now. I am not using webview any more. However, I think that Windows users especially will benefit from your fix! Have a nice Easter!

Thanks @williamgorden! I hope this will be reflected in README.