fix(webview): ignore load notifications from browser error documents

This commit is contained in:
Levi committed 2026-09-18 00:07:55 +08:00
1 parent 1776b05782
commit 2fd8a96c5d
4 files changed
+57 -5

No files matched your search

@@ -0,0 +1,49 @@
//go:build cgo
package goprovider
import (
enginewebview "github.com/GopeedLab/gopeed/pkg/download/engine/webview"
webview "github.com/GopeedLab/webview_go"
"github.com/dop251/goja"
"testing"
"time"
)
func TestInternalErrorDocumentDoesNotReportPageEvents(t *testing.T) {
vm := goja.New()
if _, err := vm.RunString(`
var window = globalThis;
window.top = window;
var location = {protocol: "chrome-error:", href: "chrome-error://chromewebdata/"};
var notifications = [];
var listeners = [];
function notify(payload) { notifications.push(payload); }
window.addEventListener = (name) => listeners.push(name);
`); err != nil {
t.Fatal(err)
}
if _, err := vm.RunString(buildLoadNotifyScript("notify")); err != nil {
t.Fatal(err)
}
for _, name := range []string{"notifications", "listeners"} {
value, err := vm.RunString(name + ".length")
if err != nil || value.ToInteger() != 0 {
t.Fatalf("internal error page registered %s: %v (%v)", name, value, err)
}
}
// The provider must wait for the native failure, preserving the failed URL.
page := newPageWrapper(enginewebview.OpenOptions{})
got := make(chan string, 1)
_, err := page.On("load-error", func(e enginewebview.Event) { got <- e.Data["url"].(string) })
if err != nil {
t.Fatal(err)
}
page.handleNativeEvent(webview.Event{Name: "load-error", URL: "https://example.test/failure", Message: "TLS handshake failed"})
if err := page.waitForNavigation(time.Second, "load"); err == nil {
t.Fatal("native failure completed navigation successfully")
}
if url := <-got; url != "https://example.test/failure" {
t.Fatal(url)
}
}
+4 -5
View File
@@ -369,7 +369,7 @@ func (p *pageWrapper) Close() error {
}
func (p *pageWrapper) tracef(format string, args ...any) {
if !strings.Contains(p.opts.Title, "youtube-sabr") && p.opts.Title != "WebView event contract" {
if !strings.Contains(p.opts.Title, "youtube-sabr") {
return
}
fmt.Printf("goprovider "+format+"\n", args...)
@@ -387,14 +387,12 @@ func (p *pageWrapper) waitForNavigation(timeout time.Duration, waitUntil string)
case <-timer.C:
return fmt.Errorf("webview navigation timeout")
case <-p.sameDocument:
p.tracef("goto completed via same-document notification")
return nil
case <-p.domReady:
if waitUntil == "domcontentloaded" {
return nil
}
case <-p.loads:
p.tracef("goto completed via load notification")
return nil
}
}
@@ -479,7 +477,6 @@ func (p *pageWrapper) handleCallback(payload string) error {
}
return nil
}
p.tracef("page event: event=%s load=%v url=%s sameDocument=%v", msg.Event, msg.Load, msg.URL, msg.SameDocument)
if msg.Event == "domcontentloaded" {
select {
case p.domReady <- struct{}{}:
@@ -607,6 +604,9 @@ func buildLoadNotifyScript(callbackName string) string {
callbackJSON, _ := json.Marshal(callbackName)
return fmt.Sprintf(`(() => {
if (window !== window.top) return;
// WebView2 runs init scripts in its internal error document too. Its load
// must not complete goto() before the native NavigationCompleted failure.
if (location.protocol === "chrome-error:") return;
const send = (message) => {
const cb = globalThis[%s];
if (typeof cb === "function") cb(JSON.stringify(message));
@@ -650,7 +650,6 @@ func (p *pageWrapper) failPendingLocked() {
}
func (p *pageWrapper) handleNativeEvent(event webview.Event) {
p.tracef("native event: %+v", event)
switch event.Name {
case "load-error":
p.events.Emit(enginewebview.Event{Name: "load-error", Data: map[string]any{"url": event.URL, "message": event.Message}})
@@ -5,6 +5,8 @@ String buildWebViewEventScript(String channel) =>
'''
(() => {
if (window !== window.top) return;
// Browser error documents are reported by native load-error callbacks.
if (location.protocol === 'chrome-error:') return;
const pending = [];
const flush = () => {
const bridge = window.flutter_inappwebview;
@@ -4,6 +4,8 @@ import 'dart:convert';
String buildWebViewEventScript(String channel) => '''
(() => {
if (window !== window.top) return;
// Browser error documents are reported by native load-error callbacks.
if (location.protocol === 'chrome-error:') return;
const pending = [];
const flush = () => {
const bridge = window.flutter_inappwebview;