@@ -40,10 +40,14 @@ type HeadlessBrowser struct {
4040 logsMu sync.Mutex
4141 logs []ConsoleLogEntry
4242 maxLogs int // ring buffer capacity
43+
44+ readyCh chan struct {} // closed when client eval functions are ready
45+ readyErr error // non-nil if client failed to become ready
4346}
4447
4548// StartHeadlessBrowser launches a headless Chrome browser and navigates to the SilverBullet URL.
46- // It returns after the client's eval functions are ready, or returns an error on failure.
49+ // It returns as soon as the browser is navigating and collecting logs. The client may not be
50+ // fully ready yet; call WaitReady to block until eval functions are available.
4751func StartHeadlessBrowser (config * HeadlessConfig ) (* HeadlessBrowser , error ) {
4852 hb := & HeadlessBrowser {
4953 config : config ,
@@ -61,6 +65,16 @@ func StartHeadlessBrowser(config *HeadlessConfig) (*HeadlessBrowser, error) {
6165 return hb , nil
6266}
6367
68+ // WaitReady blocks until the client's eval functions are ready, or ctx is cancelled.
69+ func (hb * HeadlessBrowser ) WaitReady (ctx context.Context ) error {
70+ select {
71+ case <- hb .readyCh :
72+ return hb .readyErr
73+ case <- ctx .Done ():
74+ return ctx .Err ()
75+ }
76+ }
77+
6478func (hb * HeadlessBrowser ) launch () error {
6579 // Various options to reduce memory consumption, primarily
6680 opts := append (chromedp .DefaultExecAllocatorOptions [:],
@@ -134,17 +148,22 @@ func (hb *HeadlessBrowser) launch() error {
134148 return fmt .Errorf ("failed to navigate: %w" , err )
135149 }
136150
137- // Wait for the client eval functions to be ready
138- readyCtx , readyCancel := context .WithTimeout (ctx , 60 * time .Second )
139- defer readyCancel ()
140-
141- if err := waitForClientReady (readyCtx ); err != nil {
142- cancel ()
143- allocCancel ()
144- return fmt .Errorf ("client did not become ready: %w" , err )
145- }
151+ // Wait for client readiness in the background so logs are available immediately
152+ readyCh := make (chan struct {})
153+ hb .readyCh = readyCh
154+ hb .readyErr = nil
155+ go func () {
156+ defer close (readyCh )
157+ readyCtx , readyCancel := context .WithTimeout (ctx , 60 * time .Second )
158+ defer readyCancel ()
159+ if err := waitForClientReady (readyCtx ); err != nil {
160+ hb .readyErr = fmt .Errorf ("client did not become ready: %w" , err )
161+ log .Printf ("[Headless] %v" , hb .readyErr )
162+ } else {
163+ log .Println ("[Headless] Browser client connected successfully" )
164+ }
165+ }()
146166
147- log .Println ("[Headless] Browser client connected successfully" )
148167 return nil
149168}
150169
@@ -203,7 +222,13 @@ func (hb *HeadlessBrowser) monitor() {
203222 continue
204223 }
205224
206- // Success - reset backoff
225+ // Wait for client to become fully ready before declaring success
226+ if err := hb .WaitReady (hb .ctx ); err != nil {
227+ log .Printf ("[Headless] Restart client readiness failed: %v" , err )
228+ backoff = min (backoff * 2 , maxBackoff )
229+ continue
230+ }
231+
207232 log .Println ("[Headless] Restart successful" )
208233 backoff = 2 * time .Second
209234 }
0 commit comments