diff --git a/cdp.go b/cdp.go index f00d1f1..fcfe728 100644 --- a/cdp.go +++ b/cdp.go @@ -5,7 +5,9 @@ import ( "encoding/json" "fmt" "io" + "iter" "net/http" + "slices" "sync" "sync/atomic" @@ -251,7 +253,7 @@ func (c *CDPClient) readLoop() { // dispatchEvent dispatches an event to registered handlers. func (c *CDPClient) dispatchEvent(method string, params map[string]any) { c.handMu.RLock() - handlers := c.handlers[method] + handlers := slices.Clone(c.handlers[method]) c.handMu.RUnlock() for _, handler := range handlers { @@ -365,6 +367,21 @@ func ListTargets(debugURL string) ([]targetInfo, error) { return targets, nil } +// ListTargetsAll returns an iterator over all available targets. +func ListTargetsAll(debugURL string) iter.Seq[targetInfo] { + return func(yield func(targetInfo) bool) { + targets, err := ListTargets(debugURL) + if err != nil { + return + } + for _, t := range targets { + if !yield(t) { + return + } + } + } +} + // GetVersion returns Chrome version information. func GetVersion(debugURL string) (map[string]string, error) { resp, err := http.Get(debugURL + "/json/version") diff --git a/console.go b/console.go index b686be8..a14a956 100644 --- a/console.go +++ b/console.go @@ -3,6 +3,8 @@ package webview import ( "context" "fmt" + "iter" + "slices" "strings" "sync" "time" @@ -75,60 +77,84 @@ func (cw *ConsoleWatcher) SetLimit(limit int) { // Messages returns all captured messages. func (cw *ConsoleWatcher) Messages() []ConsoleMessage { - cw.mu.RLock() - defer cw.mu.RUnlock() + return slices.Collect(cw.MessagesAll()) +} - result := make([]ConsoleMessage, len(cw.messages)) - copy(result, cw.messages) - return result +// MessagesAll returns an iterator over all captured messages. +func (cw *ConsoleWatcher) MessagesAll() iter.Seq[ConsoleMessage] { + return func(yield func(ConsoleMessage) bool) { + cw.mu.RLock() + defer cw.mu.RUnlock() + + for _, msg := range cw.messages { + if !yield(msg) { + return + } + } + } } // FilteredMessages returns messages matching the current filters. func (cw *ConsoleWatcher) FilteredMessages() []ConsoleMessage { - cw.mu.RLock() - defer cw.mu.RUnlock() + return slices.Collect(cw.FilteredMessagesAll()) +} - if len(cw.filters) == 0 { - result := make([]ConsoleMessage, len(cw.messages)) - copy(result, cw.messages) - return result - } +// FilteredMessagesAll returns an iterator over messages matching the current filters. +func (cw *ConsoleWatcher) FilteredMessagesAll() iter.Seq[ConsoleMessage] { + return func(yield func(ConsoleMessage) bool) { + cw.mu.RLock() + defer cw.mu.RUnlock() - result := make([]ConsoleMessage, 0) - for _, msg := range cw.messages { - if cw.matchesFilter(msg) { - result = append(result, msg) + for _, msg := range cw.messages { + if cw.matchesFilter(msg) { + if !yield(msg) { + return + } + } } } - return result } // Errors returns all error messages. func (cw *ConsoleWatcher) Errors() []ConsoleMessage { - cw.mu.RLock() - defer cw.mu.RUnlock() + return slices.Collect(cw.ErrorsAll()) +} - result := make([]ConsoleMessage, 0) - for _, msg := range cw.messages { - if msg.Type == "error" { - result = append(result, msg) +// ErrorsAll returns an iterator over all error messages. +func (cw *ConsoleWatcher) ErrorsAll() iter.Seq[ConsoleMessage] { + return func(yield func(ConsoleMessage) bool) { + cw.mu.RLock() + defer cw.mu.RUnlock() + + for _, msg := range cw.messages { + if msg.Type == "error" { + if !yield(msg) { + return + } + } } } - return result } // Warnings returns all warning messages. func (cw *ConsoleWatcher) Warnings() []ConsoleMessage { - cw.mu.RLock() - defer cw.mu.RUnlock() + return slices.Collect(cw.WarningsAll()) +} - result := make([]ConsoleMessage, 0) - for _, msg := range cw.messages { - if msg.Type == "warning" { - result = append(result, msg) +// WarningsAll returns an iterator over all warning messages. +func (cw *ConsoleWatcher) WarningsAll() iter.Seq[ConsoleMessage] { + return func(yield func(ConsoleMessage) bool) { + cw.mu.RLock() + defer cw.mu.RUnlock() + + for _, msg := range cw.messages { + if msg.Type == "warning" { + if !yield(msg) { + return + } + } } } - return result } // Clear clears all captured messages. @@ -271,8 +297,7 @@ func (cw *ConsoleWatcher) addMessage(msg ConsoleMessage) { cw.messages = append(cw.messages, msg) // Copy handlers to call outside lock - handlers := make([]ConsoleHandler, len(cw.handlers)) - copy(handlers, cw.handlers) + handlers := slices.Clone(cw.handlers) cw.mu.Unlock() // Call handlers @@ -315,7 +340,7 @@ func containsString(s, substr string) bool { // findString finds substr in s, returns -1 if not found. func findString(s, substr string) int { - for i := 0; i <= len(s)-len(substr); i++ { + for i := range len(s) - len(substr) + 1 { if s[i:i+len(substr)] == substr { return i } @@ -359,12 +384,21 @@ func NewExceptionWatcher(wv *Webview) *ExceptionWatcher { // Exceptions returns all captured exceptions. func (ew *ExceptionWatcher) Exceptions() []ExceptionInfo { - ew.mu.RLock() - defer ew.mu.RUnlock() + return slices.Collect(ew.ExceptionsAll()) +} - result := make([]ExceptionInfo, len(ew.exceptions)) - copy(result, ew.exceptions) - return result +// ExceptionsAll returns an iterator over all captured exceptions. +func (ew *ExceptionWatcher) ExceptionsAll() iter.Seq[ExceptionInfo] { + return func(yield func(ExceptionInfo) bool) { + ew.mu.RLock() + defer ew.mu.RUnlock() + + for _, exc := range ew.exceptions { + if !yield(exc) { + return + } + } + } } // Clear clears all captured exceptions. @@ -476,8 +510,7 @@ func (ew *ExceptionWatcher) handleException(params map[string]any) { ew.mu.Lock() ew.exceptions = append(ew.exceptions, info) - handlers := make([]func(ExceptionInfo), len(ew.handlers)) - copy(handlers, ew.handlers) + handlers := slices.Clone(ew.handlers) ew.mu.Unlock() // Call handlers diff --git a/webview.go b/webview.go index a0119be..ba9a354 100644 --- a/webview.go +++ b/webview.go @@ -25,6 +25,8 @@ import ( "context" "encoding/base64" "fmt" + "iter" + "slices" "strings" "sync" "time" @@ -192,14 +194,42 @@ func (wv *Webview) QuerySelectorAll(selector string) ([]*ElementInfo, error) { return wv.querySelectorAll(ctx, selector) } +// QuerySelectorAllAll returns an iterator over all elements matching the selector. +func (wv *Webview) QuerySelectorAllAll(selector string) iter.Seq[*ElementInfo] { + return func(yield func(*ElementInfo) bool) { + ctx, cancel := context.WithTimeout(wv.ctx, wv.timeout) + defer cancel() + + elements, err := wv.querySelectorAll(ctx, selector) + if err != nil { + return + } + + for _, elem := range elements { + if !yield(elem) { + return + } + } + } +} + // GetConsole returns captured console messages. func (wv *Webview) GetConsole() []ConsoleMessage { - wv.mu.RLock() - defer wv.mu.RUnlock() + return slices.Collect(wv.GetConsoleAll()) +} - result := make([]ConsoleMessage, len(wv.consoleLogs)) - copy(result, wv.consoleLogs) - return result +// GetConsoleAll returns an iterator over captured console messages. +func (wv *Webview) GetConsoleAll() iter.Seq[ConsoleMessage] { + return func(yield func(ConsoleMessage) bool) { + wv.mu.RLock() + defer wv.mu.RUnlock() + + for _, msg := range wv.consoleLogs { + if !yield(msg) { + return + } + } + } } // ClearConsole clears captured console messages.