mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-09 01:50:30 +00:00
feat(extension): expose host version and WebView page events
This commit is contained in:
1 parent
7c1447f659
commit
dfe8e9a3fd
27 files changed
+1230
-191
No files matched your search
@@ -0,0 +1,93 @@
|
||||
package webview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Event is a main-page notification. Data follows WebviewEventMap.
|
||||
type Event struct {
|
||||
Name string `json:"event"`
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
|
||||
type EventSource interface {
|
||||
On(event string, handler func(Event)) (unsubscribe func(), err error)
|
||||
}
|
||||
|
||||
func ValidEvent(event string) bool {
|
||||
switch event {
|
||||
case "url-changed", "load", "load-error", "closed":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// EventHub delivers notifications in registration order. Handlers must only
|
||||
// enqueue work, never block or enter the browser. Closed is terminal.
|
||||
type EventHub struct {
|
||||
mu sync.Mutex
|
||||
seq uint64
|
||||
listeners []eventListener
|
||||
closed bool
|
||||
}
|
||||
type eventListener struct {
|
||||
id uint64
|
||||
name string
|
||||
handler func(Event)
|
||||
}
|
||||
|
||||
func (h *EventHub) On(name string, handler func(Event)) (func(), error) {
|
||||
if !ValidEvent(name) || handler == nil {
|
||||
return nil, fmt.Errorf("invalid webview event or handler: %s", name)
|
||||
}
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.closed {
|
||||
return nil, fmt.Errorf("webview page is closed")
|
||||
}
|
||||
h.seq++
|
||||
id := h.seq
|
||||
h.listeners = append(h.listeners, eventListener{id, name, handler})
|
||||
return func() {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
for i, listener := range h.listeners {
|
||||
if listener.id == id {
|
||||
h.listeners = append(h.listeners[:i], h.listeners[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *EventHub) Emit(event Event) {
|
||||
h.mu.Lock()
|
||||
if h.closed {
|
||||
h.mu.Unlock()
|
||||
return
|
||||
}
|
||||
listeners := append([]eventListener(nil), h.listeners...)
|
||||
if event.Name == "closed" {
|
||||
h.closed = true
|
||||
h.listeners = nil
|
||||
}
|
||||
h.mu.Unlock()
|
||||
for _, listener := range listeners {
|
||||
if listener.name == event.Name {
|
||||
listener.handler(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PageHandle) On(name string, handler func(Event)) (func(), error) {
|
||||
page, err := p.page()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
source, ok := page.(EventSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("WebView provider does not support events")
|
||||
}
|
||||
return source.On(name, handler)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package webview
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEventHubLifecycle(t *testing.T) {
|
||||
var hub EventHub
|
||||
var got []string
|
||||
hub.Emit(Event{Name: "load"}) // Never replay pre-registration events.
|
||||
off, err := hub.On("load", func(Event) { got = append(got, "first") })
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _ = hub.On("load", func(Event) { got = append(got, "second") })
|
||||
_, _ = hub.On("closed", func(e Event) {
|
||||
got = append(got, e.Data["reason"].(string))
|
||||
off() // Unsubscription from a callback must not deadlock.
|
||||
})
|
||||
hub.Emit(Event{Name: "load"})
|
||||
off()
|
||||
off()
|
||||
hub.Emit(Event{Name: "load"})
|
||||
hub.Emit(Event{Name: "closed", Data: map[string]any{"reason": "api"}})
|
||||
hub.Emit(Event{Name: "load"})
|
||||
hub.Emit(Event{Name: "closed"})
|
||||
if !reflect.DeepEqual(got, []string{"first", "second", "second", "api"}) {
|
||||
t.Fatal(got)
|
||||
}
|
||||
if len(hub.listeners) != 0 {
|
||||
t.Fatal("closed page retained listeners")
|
||||
}
|
||||
if _, err := hub.On("load", func(Event) {}); err == nil {
|
||||
t.Fatal("registration on closed page succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventHubConcurrentUnsubscribe(t *testing.T) {
|
||||
var hub EventHub
|
||||
off, _ := hub.On("load", func(Event) {})
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 20; i++ {
|
||||
wg.Add(1)
|
||||
go func() { defer wg.Done(); off(); hub.Emit(Event{Name: "load"}) }()
|
||||
}
|
||||
wg.Wait()
|
||||
if _, err := hub.On("invalid", func(Event) {}); err == nil {
|
||||
t.Fatal("accepted invalid event")
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ const (
|
||||
MethodProfileRemove = "profile.remove"
|
||||
MethodIsAvailable = "webview.isAvailable"
|
||||
MethodPageOpen = "page.open"
|
||||
MethodPageEvents = "page.events"
|
||||
MethodPageAddInitScript = "page.addInitScript"
|
||||
MethodPageGoto = "page.goto"
|
||||
MethodPageExecute = "page.execute"
|
||||
|
||||
Reference in new issue
Block a user