Skip to content

Commit 8b2bd7b

Browse files
authored
Refactor some code using the new stdlib and generics, and add several tests (#404)
* refactor: use stdlib atomic type and correct chan usages * refactor: call event listeners in order they added * refactor(test): add more tests and use generics in helpers * test: should handle nested frames
1 parent 968ab19 commit 8b2bd7b

18 files changed

+233
-144
lines changed

channel.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,5 @@ func newChannel(owner *channelOwner, object interface{}) *channel {
7070
owner: owner,
7171
object: object,
7272
}
73-
channel.initEventEmitter()
7473
return channel
7574
}

channel_owner.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ func (c *channelOwner) createChannelOwner(self interface{}, parent *channelOwner
9393
}
9494
c.channel = newChannel(c, self)
9595
c.eventToSubscriptionMapping = map[string]string{}
96-
c.initEventEmitter()
9796
}
9897

9998
type rootChannelOwner struct {

connection.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ type connection struct {
2828
transport transport
2929
apiZone sync.Map
3030
objects map[string]*channelOwner
31-
lastID int
32-
lastIDLock sync.Mutex
31+
lastID atomic.Uint32
3332
rootObject *rootChannelOwner
3433
callbacks sync.Map
3534
afterClose func()
@@ -97,7 +96,7 @@ func (c *connection) Dispatch(msg *message) {
9796
}
9897
method := msg.Method
9998
if msg.ID != 0 {
100-
cb, _ := c.callbacks.LoadAndDelete(msg.ID)
99+
cb, _ := c.callbacks.LoadAndDelete(uint32(msg.ID))
101100
if cb.(*protocolCallback).noReply {
102101
return
103102
}
@@ -226,10 +225,7 @@ func (c *connection) sendMessageToServer(object *channelOwner, method string, pa
226225
return nil, errors.New("The object has been collected to prevent unbounded heap growth.")
227226
}
228227

229-
c.lastIDLock.Lock()
230-
c.lastID++
231-
id := c.lastID
232-
c.lastIDLock.Unlock()
228+
id := c.lastID.Add(1)
233229
cb, _ := c.callbacks.LoadOrStore(id, newProtocolCallback(noReply, c.abort))
234230
var (
235231
metadata = make(map[string]interface{}, 0)
@@ -356,7 +352,7 @@ func fromNullableChannel(v interface{}) interface{} {
356352
}
357353

358354
type protocolCallback struct {
359-
Callback chan result
355+
callback chan result
360356
noReply bool
361357
abort <-chan struct{}
362358
}
@@ -367,8 +363,12 @@ func (pc *protocolCallback) SetResult(r result) {
367363
}
368364
select {
369365
case <-pc.abort:
366+
select {
367+
case pc.callback <- r:
368+
default:
369+
}
370370
return
371-
case pc.Callback <- r:
371+
case pc.callback <- r:
372372
}
373373
}
374374

@@ -377,10 +377,15 @@ func (pc *protocolCallback) GetResult() (interface{}, error) {
377377
return nil, nil
378378
}
379379
select {
380-
case result := <-pc.Callback:
380+
case result := <-pc.callback:
381381
return result.Data, result.Error
382382
case <-pc.abort:
383-
return nil, errors.New("Connection closed")
383+
select {
384+
case result := <-pc.callback:
385+
return result.Data, result.Error
386+
default:
387+
return nil, errors.New("Connection closed")
388+
}
384389
}
385390
}
386391

@@ -392,7 +397,7 @@ func newProtocolCallback(noReply bool, abort <-chan struct{}) *protocolCallback
392397
}
393398
}
394399
return &protocolCallback{
395-
Callback: make(chan result),
400+
callback: make(chan result, 1),
396401
abort: abort,
397402
}
398403
}

event_emitter.go

Lines changed: 78 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"math"
55
"reflect"
66
"sync"
7+
8+
"golang.org/x/exp/slices"
79
)
810

911
type EventEmitter interface {
@@ -15,44 +17,33 @@ type EventEmitter interface {
1517
}
1618

1719
type (
18-
eventRegister struct {
19-
once []interface{}
20-
on []interface{}
21-
}
2220
eventEmitter struct {
2321
eventsMutex sync.Mutex
2422
events map[string]*eventRegister
23+
hasInit bool
24+
}
25+
eventRegister struct {
26+
listeners []listener
27+
}
28+
listener struct {
29+
handler interface{}
30+
once bool
2531
}
2632
)
2733

28-
func (e *eventEmitter) Emit(name string, payload ...interface{}) (handled bool) {
34+
func (e *eventEmitter) Emit(name string, payload ...interface{}) (hasListener bool) {
2935
e.eventsMutex.Lock()
3036
defer e.eventsMutex.Unlock()
31-
if _, ok := e.events[name]; !ok {
32-
return
33-
}
34-
35-
if len(e.events[name].once) > 0 || len(e.events[name].on) > 0 {
36-
handled = true
37-
}
37+
e.init()
3838

39-
payloadV := make([]reflect.Value, 0)
40-
41-
for _, p := range payload {
42-
payloadV = append(payloadV, reflect.ValueOf(p))
43-
}
44-
45-
callHandlers := func(handlers []interface{}) {
46-
for _, handler := range handlers {
47-
handlerV := reflect.ValueOf(handler)
48-
handlerV.Call(payloadV[:int(math.Min(float64(handlerV.Type().NumIn()), float64(len(payloadV))))])
49-
}
39+
evt, ok := e.events[name]
40+
if !ok {
41+
return
5042
}
5143

52-
callHandlers(e.events[name].on)
53-
callHandlers(e.events[name].once)
44+
hasListener = evt.count() > 0
5445

55-
e.events[name].once = make([]interface{}, 0)
46+
evt.callHandlers(payload...)
5647
return
5748
}
5849

@@ -67,60 +58,88 @@ func (e *eventEmitter) On(name string, handler interface{}) {
6758
func (e *eventEmitter) RemoveListener(name string, handler interface{}) {
6859
e.eventsMutex.Lock()
6960
defer e.eventsMutex.Unlock()
61+
e.init()
62+
7063
if _, ok := e.events[name]; !ok {
7164
return
7265
}
73-
handlerPtr := reflect.ValueOf(handler).Pointer()
66+
e.events[name].removeHandler(handler)
67+
}
7468

75-
onHandlers := []interface{}{}
76-
for idx := range e.events[name].on {
77-
eventPtr := reflect.ValueOf(e.events[name].on[idx]).Pointer()
78-
if eventPtr != handlerPtr {
79-
onHandlers = append(onHandlers, e.events[name].on[idx])
80-
}
81-
}
82-
e.events[name].on = onHandlers
69+
// ListenerCount count the listeners by name, count all if name is empty
70+
func (e *eventEmitter) ListenerCount(name string) int {
71+
e.eventsMutex.Lock()
72+
defer e.eventsMutex.Unlock()
73+
e.init()
8374

84-
onceHandlers := []interface{}{}
85-
for idx := range e.events[name].once {
86-
eventPtr := reflect.ValueOf(e.events[name].once[idx]).Pointer()
87-
if eventPtr != handlerPtr {
88-
onceHandlers = append(onceHandlers, e.events[name].once[idx])
75+
if name != "" {
76+
evt, ok := e.events[name]
77+
if !ok {
78+
return 0
8979
}
80+
return evt.count()
9081
}
9182

92-
e.events[name].once = onceHandlers
93-
}
94-
95-
// ListenerCount count the listeners by name, count all if name is empty
96-
func (e *eventEmitter) ListenerCount(name string) int {
9783
count := 0
98-
e.eventsMutex.Lock()
9984
for key := range e.events {
100-
if name == "" || name == key {
101-
count += len(e.events[key].on) + len(e.events[key].once)
102-
}
85+
count += e.events[key].count()
10386
}
104-
e.eventsMutex.Unlock()
87+
10588
return count
10689
}
10790

10891
func (e *eventEmitter) addEvent(name string, handler interface{}, once bool) {
10992
e.eventsMutex.Lock()
93+
e.init()
94+
11095
if _, ok := e.events[name]; !ok {
11196
e.events[name] = &eventRegister{
112-
on: make([]interface{}, 0),
113-
once: make([]interface{}, 0),
97+
listeners: make([]listener, 0),
11498
}
11599
}
116-
if once {
117-
e.events[name].once = append(e.events[name].once, handler)
118-
} else {
119-
e.events[name].on = append(e.events[name].on, handler)
120-
}
100+
e.events[name].addHandler(handler, once)
121101
e.eventsMutex.Unlock()
122102
}
123103

124-
func (e *eventEmitter) initEventEmitter() {
125-
e.events = make(map[string]*eventRegister)
104+
func (e *eventEmitter) init() {
105+
if !e.hasInit {
106+
e.events = make(map[string]*eventRegister, 0)
107+
e.hasInit = true
108+
}
109+
}
110+
111+
func (e *eventRegister) addHandler(handler interface{}, once bool) {
112+
e.listeners = append(e.listeners, listener{handler: handler, once: once})
113+
}
114+
115+
func (e *eventRegister) count() int {
116+
return len(e.listeners)
117+
}
118+
119+
func (e *eventRegister) removeHandler(handler interface{}) {
120+
handlerPtr := reflect.ValueOf(handler).Pointer()
121+
122+
e.listeners = slices.DeleteFunc[[]listener](e.listeners, func(l listener) bool {
123+
return reflect.ValueOf(l.handler).Pointer() == handlerPtr
124+
})
125+
}
126+
127+
func (e *eventRegister) callHandlers(payloads ...interface{}) {
128+
payloadV := make([]reflect.Value, 0)
129+
130+
for _, p := range payloads {
131+
payloadV = append(payloadV, reflect.ValueOf(p))
132+
}
133+
134+
handle := func(l listener) {
135+
handlerV := reflect.ValueOf(l.handler)
136+
handlerV.Call(payloadV[:int(math.Min(float64(handlerV.Type().NumIn()), float64(len(payloadV))))])
137+
}
138+
139+
for _, l := range e.listeners {
140+
if l.once {
141+
defer e.removeHandler(l.handler)
142+
}
143+
handle(l)
144+
}
126145
}

event_emitter_test.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const (
1414

1515
func TestEventEmitterListenerCount(t *testing.T) {
1616
handler := &eventEmitter{}
17-
handler.initEventEmitter()
1817
wasCalled := make(chan interface{}, 1)
1918
myHandler := func(payload ...interface{}) {
2019
wasCalled <- payload[0]
@@ -32,7 +31,6 @@ func TestEventEmitterListenerCount(t *testing.T) {
3231

3332
func TestEventEmitterOn(t *testing.T) {
3433
handler := &eventEmitter{}
35-
handler.initEventEmitter()
3634
wasCalled := make(chan interface{}, 1)
3735
require.Nil(t, handler.events[testEventName])
3836
handler.On(testEventName, func(payload ...interface{}) {
@@ -48,7 +46,6 @@ func TestEventEmitterOn(t *testing.T) {
4846

4947
func TestEventEmitterOnce(t *testing.T) {
5048
handler := &eventEmitter{}
51-
handler.initEventEmitter()
5249
wasCalled := make(chan interface{}, 1)
5350
require.Nil(t, handler.events[testEventName])
5451
handler.Once(testEventName, func(payload ...interface{}) {
@@ -64,7 +61,6 @@ func TestEventEmitterOnce(t *testing.T) {
6461

6562
func TestEventEmitterRemove(t *testing.T) {
6663
handler := &eventEmitter{}
67-
handler.initEventEmitter()
6864
wasCalled := make(chan interface{}, 1)
6965
require.Nil(t, handler.events[testEventName])
7066
myHandler := func(payload ...interface{}) {
@@ -84,14 +80,12 @@ func TestEventEmitterRemove(t *testing.T) {
8480

8581
func TestEventEmitterRemoveEmpty(t *testing.T) {
8682
handler := &eventEmitter{}
87-
handler.initEventEmitter()
8883
handler.RemoveListener(testEventName, func(...interface{}) {})
8984
require.Equal(t, 0, handler.ListenerCount(testEventName))
9085
}
9186

9287
func TestEventEmitterRemoveKeepExisting(t *testing.T) {
9388
handler := &eventEmitter{}
94-
handler.initEventEmitter()
9589
handler.On(testEventName, func(...interface{}) {})
9690
handler.Once(testEventName, func(...interface{}) {})
9791
handler.RemoveListener("abc123", func(...interface{}) {})
@@ -101,7 +95,6 @@ func TestEventEmitterRemoveKeepExisting(t *testing.T) {
10195

10296
func TestEventEmitterOnLessArgsAcceptingReceiver(t *testing.T) {
10397
handler := &eventEmitter{}
104-
handler.initEventEmitter()
10598
wasCalled := make(chan bool, 1)
10699
require.Nil(t, handler.events[testEventName])
107100
handler.Once(testEventName, func(ev ...interface{}) {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/stretchr/testify v1.8.4
1313
github.com/tidwall/gjson v1.17.0
1414
go.uber.org/multierr v1.11.0
15+
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc
1516
)
1617

1718
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
4343
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
4444
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4545
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
46+
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
47+
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
4648
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4749
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
4850
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=

local_utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func (l *localUtilsImpl) TraceDiscarded(stacksId string) error {
138138
return err
139139
}
140140

141-
func (l *localUtilsImpl) AddStackToTracingNoReply(id int, stack []map[string]interface{}) {
141+
func (l *localUtilsImpl) AddStackToTracingNoReply(id uint32, stack []map[string]interface{}) {
142142
l.channel.SendNoReply("addStackToTracingNoReply", map[string]interface{}{
143143
"callData": map[string]interface{}{
144144
"id": id,

tests/browser_context_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,18 @@ func TestPageErrorEventShouldWork(t *testing.T) {
520520
require.Equal(t, page, weberror.Page())
521521
require.ErrorContains(t, weberror.Error(), "boom")
522522
}
523+
524+
func TestBrowserContextOnResponse(t *testing.T) {
525+
BeforeEach(t)
526+
defer AfterEach(t)
527+
responseChan := make(chan playwright.Response, 1)
528+
context.OnResponse(func(response playwright.Response) {
529+
responseChan <- response
530+
})
531+
_, err := page.Goto(fmt.Sprintf("%s/title.html", server.PREFIX))
532+
require.NoError(t, err)
533+
response := <-responseChan
534+
body, err := response.Body()
535+
require.NoError(t, err)
536+
require.Equal(t, "<title>Woof-Woof</title>\n", string(body))
537+
}

0 commit comments

Comments
 (0)