diff --git a/common/browser.go b/common/browser.go index df9b94a5d..d94d4287e 100644 --- a/common/browser.go +++ b/common/browser.go @@ -242,14 +242,16 @@ func (b *Browser) onAttachedToTarget(ev *target.EventAttachedToTarget) { b.sessionIDtoTargetID[ev.SessionID] = evti.TargetID b.sessionIDtoTargetIDMu.Unlock() case "page": - var opener *Page = nil + // Opener is nil for the initial page + var opener *Page b.pagesMu.RLock() if t, ok := b.pages[evti.OpenerID]; ok { opener = t } - b.logger.Debugf("Browser:onAttachedToTarget:page", "sid:%v tid:%v opener nil:%t", ev.SessionID, evti.TargetID, opener == nil) b.pagesMu.RUnlock() + b.logger.Debugf("Browser:onAttachedToTarget:page", "sid:%v tid:%v opener nil:%t", ev.SessionID, evti.TargetID, opener == nil) + p, err := NewPage(b.ctx, b.conn.getSession(ev.SessionID), browserCtx, evti.TargetID, opener, true, b.logger) if err != nil { isRunning := atomic.LoadInt64(&b.state) == BrowserStateOpen && b.IsConnected() //b.conn.isConnected() @@ -279,6 +281,8 @@ func (b *Browser) onAttachedToTarget(ev *target.EventAttachedToTarget) { } } +// onDetachedFromTarget event can be issued multiple times per target if multiple +// sessions have been attached to it. So we'll remove the page only once. func (b *Browser) onDetachedFromTarget(ev *target.EventDetachedFromTarget) { b.sessionIDtoTargetIDMu.RLock() targetID, ok := b.sessionIDtoTargetID[ev.SessionID] diff --git a/common/connection.go b/common/connection.go index dc89918c0..2d7cebd57 100644 --- a/common/connection.go +++ b/common/connection.go @@ -345,7 +345,7 @@ func (c *Connection) recvLoop() { } } -func (c *Connection) send(msg *cdproto.Message, recvCh chan *cdproto.Message, res easyjson.Unmarshaler) error { +func (c *Connection) send(ctx context.Context, msg *cdproto.Message, recvCh chan *cdproto.Message, res easyjson.Unmarshaler) error { select { case c.sendCh <- msg: case err := <-c.errorCh: @@ -358,9 +358,12 @@ func (c *Connection) send(msg *cdproto.Message, recvCh chan *cdproto.Message, re case <-c.done: c.logger.Debugf("Connection:send:<-c.done", "wsURL:%q sid:%v", c.wsURL, msg.SessionID) return nil + case <-ctx.Done(): + c.logger.Errorf("Connection:send:<-ctx.Done()", "wsURL:%q sid:%v err:%v", c.wsURL, msg.SessionID, c.ctx.Err()) + return ctx.Err() case <-c.ctx.Done(): c.logger.Errorf("Connection:send:<-c.ctx.Done()", "wsURL:%q sid:%v err:%v", c.wsURL, msg.SessionID, c.ctx.Err()) - return nil + return ctx.Err() } // Block waiting for response. @@ -395,13 +398,12 @@ func (c *Connection) send(msg *cdproto.Message, recvCh chan *cdproto.Message, re return &websocket.CloseError{Code: code} case <-c.done: c.logger.Debugf("Connection:send:<-c.done #2", "sid:%v tid:%v wsURL:%q", msg.SessionID, tid, c.wsURL) + case <-ctx.Done(): + c.logger.Debugf("Connection:send:<-ctx.Done()", "sid:%v tid:%v wsURL:%q err:%v", msg.SessionID, tid, c.wsURL, c.ctx.Err()) + return ctx.Err() case <-c.ctx.Done(): c.logger.Debugf("Connection:send:<-c.ctx.Done()", "sid:%v tid:%v wsURL:%q err:%v", msg.SessionID, tid, c.wsURL, c.ctx.Err()) - // TODO: this brings many bugs to the surface return c.ctx.Err() - // TODO: add a timeout? - // case <-timeout: - // return } return nil } @@ -443,15 +445,13 @@ func (c *Connection) sendLoop() { case code := <-c.closeCh: c.logger.Debugf("Connection:sendLoop:<-c.closeCh", "wsURL:%q code:%d", c.wsURL, code) _ = c.closeConnection(code) + return case <-c.done: c.logger.Debugf("Connection:sendLoop:<-c.done#2", "wsURL:%q", c.wsURL) + return case <-c.ctx.Done(): c.logger.Debugf("connection:sendLoop", "returning, ctx.Err: %q", c.ctx.Err()) return - // case <-time.After(time.Second * 10): - // c.logger.Errorf("connection:sendLoop", "returning, timeout") - // c.Close() - // return } } } @@ -513,5 +513,5 @@ func (c *Connection) Execute(ctx context.Context, method string, params easyjson Method: cdproto.MethodType(method), Params: buf, } - return c.send(msg, ch, res) + return c.send(c.ctx, msg, ch, res) } diff --git a/common/context.go b/common/context.go index 42c65fa45..3869a1a57 100644 --- a/common/context.go +++ b/common/context.go @@ -64,3 +64,14 @@ func GetProcessID(ctx context.Context) int { v, _ := ctx.Value(ctxKeyPid).(int) return v // it will return zero on error } + +// contextWithDoneChan returns a new context that is canceled when the done channel +// is closed. The context will leak if the done channel is never closed. +func contextWithDoneChan(ctx context.Context, done chan struct{}) context.Context { + ctx, cancel := context.WithCancel(ctx) + go func() { + <-done + cancel() + }() + return ctx +} diff --git a/common/frame.go b/common/frame.go index 07262049d..b1266d21f 100644 --- a/common/frame.go +++ b/common/frame.go @@ -542,7 +542,7 @@ func (f *Frame) waitForFunction( rt.ToValue(polling), }, args...)...) if err != nil { - return nil, err + return nil, fmt.Errorf("frame cannot wait for function: %w", err) } return result, nil } @@ -569,14 +569,14 @@ func (f *Frame) waitForSelector(selector string, opts *FrameWaitForSelectorOptio ec := f.executionContexts[mainWorld] if ec == nil { - return nil, fmt.Errorf("cannot find execution context: %q", mainWorld) + return nil, fmt.Errorf("wait for selector cannot find execution context: %q", mainWorld) } // an element should belong to the current execution context. // otherwise, we should adopt it to this execution context. if ec != handle.execCtx { defer handle.Dispose() if handle, err = ec.adoptElementHandle(handle); err != nil { - return nil, err + return nil, fmt.Errorf("wait for selector cannot adopt element handle: %w", err) } } @@ -1481,7 +1481,11 @@ func (f *Frame) evaluate( if ec == nil { return nil, fmt.Errorf("cannot find execution context: %q", world) } - return ec.evaluate(apiCtx, opts, pageFunc, args...) + eh, err := ec.evaluate(apiCtx, opts, pageFunc, args...) + if err != nil { + return nil, fmt.Errorf("frame cannot evaluate: %w", err) + } + return eh, nil } // frameExecutionContext represents a JS execution context that belongs to Frame. diff --git a/common/frame_session.go b/common/frame_session.go index addbcba3f..e4d1007d9 100644 --- a/common/frame_session.go +++ b/common/frame_session.go @@ -160,17 +160,17 @@ func NewFrameSession( return nil, err } - if err = fs.initDomains(); err != nil { + if err = fs.initOptions(); err != nil { logger.Debugf( - "NewFrameSession:initDomains", + "NewFrameSession:initOptions", "sid:%v tid:%v err:%v", session.id, targetID, err) return nil, err } - if err = fs.initOptions(); err != nil { + if err = fs.initDomains(); err != nil { logger.Debugf( - "NewFrameSession:initOptions", + "NewFrameSession:initDomains", "sid:%v tid:%v err:%v", session.id, targetID, err) @@ -208,7 +208,8 @@ func (fs *FrameSession) getNetworkManager() *NetworkManager { func (fs *FrameSession) initDomains() error { actions := []Action{ - dom.Enable(), // TODO: can we get rid of this by doing DOM related stuff in JS instead? + // TODO: can we get rid of the following by doing DOM related stuff in JS instead? + dom.Enable(), log.Enable(), runtime.Enable(), target.SetAutoAttach(true, true).WithFlatten(true), @@ -241,45 +242,51 @@ func (fs *FrameSession) initEvents() { for { select { + case <-fs.session.done: + fs.logger.Debugf("FrameSession:initEvents:go:session.done", + "sid:%v tid:%v", fs.session.id, fs.targetID) + return case <-fs.ctx.Done(): - fs.logger.Debugf("NewFrameSession:initEvents:go:ctx.Done", + fs.logger.Debugf("FrameSession:initEvents:go:ctx.Done", "sid:%v tid:%v", fs.session.id, fs.targetID) return case event := <-fs.eventCh: - if ev, ok := event.data.(*inspector.EventTargetCrashed); ok { + switch ev := event.data.(type) { + case *inspector.EventTargetCrashed: fs.onTargetCrashed(ev) - } else if ev, ok := event.data.(*log.EventEntryAdded); ok { + case *log.EventEntryAdded: fs.onLogEntryAdded(ev) - } else if ev, ok := event.data.(*cdppage.EventFrameAttached); ok { + case *cdppage.EventFrameAttached: fs.onFrameAttached(ev.FrameID, ev.ParentFrameID) - } else if ev, ok := event.data.(*cdppage.EventFrameDetached); ok { - fs.onFrameDetached(ev.FrameID) - } else if ev, ok := event.data.(*cdppage.EventFrameNavigated); ok { - fs.onFrameNavigated(ev.Frame, false) - } else if ev, ok := event.data.(*cdppage.EventFrameRequestedNavigation); ok { + case *cdppage.EventFrameDetached: + fs.onFrameDetached(ev.FrameID, ev.Reason) + case *cdppage.EventFrameNavigated: + const initial = false + fs.onFrameNavigated(ev.Frame, initial) + case *cdppage.EventFrameRequestedNavigation: fs.onFrameRequestedNavigation(ev) - } else if ev, ok := event.data.(*cdppage.EventFrameStartedLoading); ok { + case *cdppage.EventFrameStartedLoading: fs.onFrameStartedLoading(ev.FrameID) - } else if ev, ok := event.data.(*cdppage.EventFrameStoppedLoading); ok { + case *cdppage.EventFrameStoppedLoading: fs.onFrameStoppedLoading(ev.FrameID) - } else if ev, ok := event.data.(*cdppage.EventLifecycleEvent); ok { + case *cdppage.EventLifecycleEvent: fs.onPageLifecycle(ev) - } else if ev, ok := event.data.(*cdppage.EventNavigatedWithinDocument); ok { + case *cdppage.EventNavigatedWithinDocument: fs.onPageNavigatedWithinDocument(ev) - } else if ev, ok := event.data.(*runtime.EventConsoleAPICalled); ok { + case *runtime.EventConsoleAPICalled: fs.onConsoleAPICalled(ev) - } else if ev, ok := event.data.(*runtime.EventExceptionThrown); ok { + case *runtime.EventExceptionThrown: fs.onExceptionThrown(ev) - } else if ev, ok := event.data.(*runtime.EventExecutionContextCreated); ok { + case *runtime.EventExecutionContextCreated: fs.onExecutionContextCreated(ev) - } else if ev, ok := event.data.(*runtime.EventExecutionContextDestroyed); ok { + case *runtime.EventExecutionContextDestroyed: fs.onExecutionContextDestroyed(ev.ExecutionContextID) - } else if _, ok := event.data.(*runtime.EventExecutionContextsCleared); ok { + case *runtime.EventExecutionContextsCleared: fs.onExecutionContextsCleared() - } else if ev, ok := event.data.(*target.EventAttachedToTarget); ok { + case *target.EventAttachedToTarget: fs.onAttachedToTarget(ev) - } else if ev, ok := event.data.(*target.EventDetachedFromTarget); ok { + case *target.EventDetachedFromTarget: fs.onDetachedFromTarget(ev) } } @@ -355,13 +362,7 @@ func (fs *FrameSession) initIsolatedWorld(name string) error { action2 := cdppage.AddScriptToEvaluateOnNewDocument(`//# sourceURL=` + evaluationScriptURL). WithWorldName(name) if _, err := action2.Do(cdp.WithExecutor(fs.ctx, fs.session)); err != nil { - // TODO: find out whether select is necessary here - // select { - // case <-fs.ctx.Done(): - // return nil - // default: return fmt.Errorf("unable to add script to evaluate for isolated world: %w", err) - // } } return nil } @@ -471,7 +472,7 @@ func (fs *FrameSession) isMainFrame() bool { } func (fs *FrameSession) handleFrameTree(frameTree *cdppage.FrameTree) { - fs.logger.Debugf("NewFrameSession:handleFrameTree", + fs.logger.Debugf("FrameSession:handleFrameTree", "sid:%v tid:%v", fs.session.id, fs.targetID) if frameTree.Frame.ParentID != "" { @@ -487,7 +488,7 @@ func (fs *FrameSession) handleFrameTree(frameTree *cdppage.FrameTree) { } func (fs *FrameSession) navigateFrame(frame *Frame, url, referrer string) (string, error) { - fs.logger.Debugf("NewFrameSession:navigateFrame", + fs.logger.Debugf("FrameSession:navigateFrame", "sid:%v tid:%v url:%q referrer:%q", fs.session.id, fs.targetID, url, referrer) @@ -536,7 +537,7 @@ func (fs *FrameSession) onExceptionThrown(event *runtime.EventExceptionThrown) { } func (fs *FrameSession) onExecutionContextCreated(event *runtime.EventExecutionContextCreated) { - fs.logger.Debugf("NewFrameSession:onExecutionContextCreated", + fs.logger.Debugf("FrameSession:onExecutionContextCreated", "sid:%v tid:%v ectxid:%d", fs.session.id, fs.targetID, event.Context.ID) @@ -574,7 +575,7 @@ func (fs *FrameSession) onExecutionContextCreated(event *runtime.EventExecutionC } func (fs *FrameSession) onExecutionContextDestroyed(execCtxID runtime.ExecutionContextID) { - fs.logger.Debugf("NewFrameSession:onExecutionContextDestroyed", + fs.logger.Debugf("FrameSession:onExecutionContextDestroyed", "sid:%v tid:%v ectxid:%d", fs.session.id, fs.targetID, execCtxID) @@ -591,7 +592,7 @@ func (fs *FrameSession) onExecutionContextDestroyed(execCtxID runtime.ExecutionC } func (fs *FrameSession) onExecutionContextsCleared() { - fs.logger.Debugf("NewFrameSession:onExecutionContextsCleared", + fs.logger.Debugf("FrameSession:onExecutionContextsCleared", "sid:%v tid:%v", fs.session.id, fs.targetID) fs.contextIDToContextMu.Lock() @@ -608,7 +609,7 @@ func (fs *FrameSession) onExecutionContextsCleared() { } func (fs *FrameSession) onFrameAttached(frameID cdp.FrameID, parentFrameID cdp.FrameID) { - fs.logger.Debugf("NewFrameSession:onFrameAttached", + fs.logger.Debugf("FrameSession:onFrameAttached", "sid:%v tid:%v fid:%v pfid:%v", fs.session.id, fs.targetID, frameID, parentFrameID) @@ -616,16 +617,16 @@ func (fs *FrameSession) onFrameAttached(frameID cdp.FrameID, parentFrameID cdp.F fs.manager.frameAttached(frameID, parentFrameID) } -func (fs *FrameSession) onFrameDetached(frameID cdp.FrameID) { - fs.logger.Debugf("NewFrameSession:onFrameDetached", - "sid:%v tid:%v fid:%v", - fs.session.id, fs.targetID, frameID) +func (fs *FrameSession) onFrameDetached(frameID cdp.FrameID, reason cdppage.FrameDetachedReason) { + fs.logger.Debugf("FrameSession:onFrameDetached", + "sid:%v tid:%v fid:%v reason:%s", + fs.session.id, fs.targetID, frameID, reason) fs.manager.frameDetached(frameID) } func (fs *FrameSession) onFrameNavigated(frame *cdp.Frame, initial bool) { - fs.logger.Debugf("NewFrameSession:onFrameNavigated", + fs.logger.Debugf("FrameSession:onFrameNavigated", "sid:%v tid:%v fid:%v", fs.session.id, fs.targetID, frame.ID) @@ -636,7 +637,7 @@ func (fs *FrameSession) onFrameNavigated(frame *cdp.Frame, initial bool) { } func (fs *FrameSession) onFrameRequestedNavigation(event *cdppage.EventFrameRequestedNavigation) { - fs.logger.Debugf("NewFrameSession:onFrameRequestedNavigation", + fs.logger.Debugf("FrameSession:onFrameRequestedNavigation", "sid:%v tid:%v fid:%v url:%q", fs.session.id, fs.targetID, event.FrameID, event.URL) @@ -649,7 +650,7 @@ func (fs *FrameSession) onFrameRequestedNavigation(event *cdppage.EventFrameRequ } func (fs *FrameSession) onFrameStartedLoading(frameID cdp.FrameID) { - fs.logger.Debugf("NewFrameSession:onFrameStartedLoading", + fs.logger.Debugf("FrameSession:onFrameStartedLoading", "sid:%v tid:%v fid:%v", fs.session.id, fs.targetID, frameID) @@ -657,7 +658,7 @@ func (fs *FrameSession) onFrameStartedLoading(frameID cdp.FrameID) { } func (fs *FrameSession) onFrameStoppedLoading(frameID cdp.FrameID) { - fs.logger.Debugf("NewFrameSession:onFrameStoppedLoading", + fs.logger.Debugf("FrameSession:onFrameStoppedLoading", "sid:%v tid:%v fid:%v", fs.session.id, fs.targetID, frameID) @@ -687,7 +688,7 @@ func (fs *FrameSession) onLogEntryAdded(event *log.EventEntryAdded) { } func (fs *FrameSession) onPageLifecycle(event *cdppage.EventLifecycleEvent) { - fs.logger.Debugf("NewFrameSession:onPageLifecycle", + fs.logger.Debugf("FrameSession:onPageLifecycle", "sid:%v tid:%v fid:%v event:%q", fs.session.id, fs.targetID, event.FrameID, event.Name) @@ -768,7 +769,7 @@ func (fs *FrameSession) onPageLifecycle(event *cdppage.EventLifecycleEvent) { } func (fs *FrameSession) onPageNavigatedWithinDocument(event *cdppage.EventNavigatedWithinDocument) { - fs.logger.Debugf("NewFrameSession:onPageNavigatedWithinDocument", + fs.logger.Debugf("FrameSession:onPageNavigatedWithinDocument", "sid:%v tid:%v fid:%v", fs.session.id, fs.targetID, event.FrameID) @@ -776,98 +777,125 @@ func (fs *FrameSession) onPageNavigatedWithinDocument(event *cdppage.EventNaviga } func (fs *FrameSession) onAttachedToTarget(event *target.EventAttachedToTarget) { - fs.logger.Debugf("NewFrameSession:onAttachedToTarget", + var ( + ti = event.TargetInfo + sid = event.SessionID + err error + ) + + fs.logger.Debugf("FrameSession:onAttachedToTarget", "sid:%v tid:%v esid:%v etid:%v ebctxid:%v type:%q", fs.session.id, fs.targetID, event.SessionID, event.TargetInfo.TargetID, event.TargetInfo.BrowserContextID, event.TargetInfo.Type) session := fs.page.browserCtx.conn.getSession(event.SessionID) - targetID := event.TargetInfo.TargetID - - if event.TargetInfo.Type == "iframe" { - frame := fs.manager.getFrameByID(cdp.FrameID(targetID)) - if frame == nil { - return - } - - fs.manager.removeChildFramesRecursively(frame) - - // Successful case - frameSession, err := NewFrameSession(fs.ctx, session, fs.page, fs, targetID, fs.logger) - if err == nil { - fs.page.attachFrameSession(cdp.FrameID(targetID), frameSession) - return - } - // Erroneous cases - defer fs.logger.Debugf("FrameSession:onAttachedToTarget:NewFrameSession", - "sid:%v tid:%v esid:%v etid:%v ebctxid:%v type:%q err:%v", + if session == nil { + fs.logger.Debugf("FrameSession:onAttachedToTarget:NewFrameSession", + "sid:%v tid:%v esid:%v etid:%v ebctxid:%v type:%q err:nil session", fs.session.id, fs.targetID, event.SessionID, event.TargetInfo.TargetID, event.TargetInfo.BrowserContextID, - event.TargetInfo.Type, err) - // ignore errors if we're no longer connected to browser - // this happens when there is no browser but we still want to - // attach a frame to it. - if !fs.page.browserCtx.browser.IsConnected() { - return - } + event.TargetInfo.Type) + return + } + + switch ti.Type { + case "iframe": + err = fs.attachIFrameToTarget(ti, sid) + case "worker": + err = fs.attachWorkerToTarget(ti, sid) + default: + // Just unblock (debugger continue) these targets and detach from them. + s := fs.page.browserCtx.conn.getSession(sid) + _ = s.ExecuteWithoutExpectationOnReply(fs.ctx, runtime.CommandRunIfWaitingForDebugger, nil, nil) + _ = s.ExecuteWithoutExpectationOnReply(fs.ctx, target.CommandDetachFromTarget, + &target.DetachFromTargetParams{SessionID: s.id}, nil) + } + if err == nil { + return + } + // Handle or ignore errors. + var reason string + defer func() { + fs.logger.Debugf("FrameSession:onAttachedToTarget:return", + "sid:%v tid:%v esid:%v etid:%v ebctxid:%v type:%q reason:%s", + fs.session.id, fs.targetID, sid, + ti.TargetID, ti.BrowserContextID, + ti.Type, reason) + }() + // Ignore errors if we're no longer connected to browser. + // This happens when there is no browser but we still want to + // attach a frame/worker to it. + if !fs.page.browserCtx.browser.IsConnected() { + reason = "browser disconnected" + return // ignore + } + // Final chance: + // Ignore the error if the context was canceled, otherwise, + // throw a k6 error. + select { + case <-fs.ctx.Done(): + reason = "frame session context canceled" + return + case <-session.done: + reason = "session closed" + return + default: // Ignore context canceled error to gracefuly handle shutting down // of the extension. This may happen because of generated events // while a frame session is being created. if errors.Is(err, context.Canceled) { - return - } - // Final chance: - // Throw a k6 error if the error is something different than a context - // cancelation. - select { - case <-fs.ctx.Done(): - // ignore - return - default: - k6Throw(fs.ctx, "cannot create frame session (iframe): %w", err) + reason = "context canceled" + return // ignore } + reason = "fatal" + k6Throw(fs.ctx, "cannot attach %v: %w", ti.Type, err) } +} - if event.TargetInfo.Type != "worker" { - fs.logger.Debugf("FrameSession:onAttachedToTarget returns: target is not a worker", - "sid:%v tid:%v esid:%v etid:%v ebctxid:%v type:%q", - fs.session.id, fs.targetID, event.SessionID, - event.TargetInfo.TargetID, event.TargetInfo.BrowserContextID, - event.TargetInfo.Type) +// attachIFrameToTarget attaches an IFrame target to a given session. +func (fs *FrameSession) attachIFrameToTarget(ti *target.Info, sid target.SessionID) error { + fr := fs.manager.getFrameByID(cdp.FrameID(ti.TargetID)) + if fr == nil { + // IFrame should be attached to fs.page with EventFrameAttached + // event before. + fs.logger.Debugf("FrameSession:attachIFrameToTarget:return", + "sid:%v tid:%v esid:%v etid:%v ebctxid:%v type:%q, nil frame", + fs.session.id, fs.targetID, + sid, ti.TargetID, ti.BrowserContextID, ti.Type) + return nil + } + // Remove all children of the previously attached frame. + fs.manager.removeChildFramesRecursively(fr) - // Just unblock (debugger continue) these targets and detach from them. - session.ExecuteWithoutExpectationOnReply(fs.ctx, runtime.CommandRunIfWaitingForDebugger, nil, nil) - session.ExecuteWithoutExpectationOnReply(fs.ctx, target.CommandDetachFromTarget, &target.DetachFromTargetParams{SessionID: session.id}, nil) - return + nfs, err := NewFrameSession( + fs.ctx, + fs.page.browserCtx.conn.getSession(sid), + fs.page, fs, ti.TargetID, + fs.logger) + if err != nil { + return fmt.Errorf("cannot attach iframe target (%v) to session (%v): %w", + ti.TargetID, sid, err) } + fs.page.attachFrameSession(cdp.FrameID(ti.TargetID), nfs) + + return nil +} - // Handle new worker - w, err := NewWorker(fs.ctx, session, targetID, event.TargetInfo.URL) +// attachWorkerToTarget attaches a Worker target to a given session. +func (fs *FrameSession) attachWorkerToTarget(ti *target.Info, sid target.SessionID) error { + w, err := NewWorker(fs.ctx, fs.page.browserCtx.conn.getSession(sid), ti.TargetID, ti.URL) if err != nil { - // ignore errors if we're no longer connected to browser - // this happens when there is no browser but we still want to - // attach a worker to it. - if !fs.page.browserCtx.browser.IsConnected() { - return - } - select { - case <-fs.ctx.Done(): - fs.logger.Debugf("FrameSession:onAttachedToTarget:NewWorker:<-ctx.Done", - "sid:%v tid:%v esid:%v etid:%v ebctxid:%v type:%q", - fs.session.id, fs.targetID, event.SessionID, - event.TargetInfo.TargetID, event.TargetInfo.BrowserContextID, - event.TargetInfo.Type) - return - default: - k6Throw(fs.ctx, "cannot create new worker: %w", err) - } + return fmt.Errorf("cannot attach worker target (%v) to session (%v): %w", + ti.TargetID, sid, err) } - fs.page.workers[session.id] = w + fs.page.workers[sid] = w + + return nil } func (fs *FrameSession) onDetachedFromTarget(event *target.EventDetachedFromTarget) { - fs.logger.Debugf("NewFrameSession:onDetachedFromTarget", + fs.logger.Debugf("FrameSession:onDetachedFromTarget", "sid:%v tid:%v esid:%v", fs.session.id, fs.targetID, event.SessionID) @@ -875,7 +903,7 @@ func (fs *FrameSession) onDetachedFromTarget(event *target.EventDetachedFromTarg } func (fs *FrameSession) onTargetCrashed(event *inspector.EventTargetCrashed) { - fs.logger.Debugf("NewFrameSession:onTargetCrashed", "sid:%v tid:%v", fs.session.id, fs.targetID) + fs.logger.Debugf("FrameSession:onTargetCrashed", "sid:%v tid:%v", fs.session.id, fs.targetID) fs.session.markAsCrashed() fs.page.didCrash() diff --git a/common/page.go b/common/page.go index f9ab7702f..7b5a94350 100644 --- a/common/page.go +++ b/common/page.go @@ -137,24 +137,12 @@ func NewPage( p.Mouse = NewMouse(ctx, session, p.frameManager.MainFrame(), browserCtx.timeoutSettings, p.Keyboard) p.Touchscreen = NewTouchscreen(ctx, session, p.Keyboard) - if err := p.initEvents(); err != nil { - p.logger.Debugf("Page:NewPage:initEvents:return", "sid:%v tid:%v err:%v", - p.sessionID(), targetID, err) - - return nil, err - } - - return &p, nil -} - -func (p *Page) initEvents() error { - p.logger.Debugf("Page:initEvents", "sid:%v", p.sessionID()) - action := target.SetAutoAttach(true, true).WithFlatten(true) if err := action.Do(cdp.WithExecutor(p.ctx, p.session)); err != nil { - return fmt.Errorf("unable to execute %T: %w", action, err) + return nil, fmt.Errorf("cannot execute %T: %w", action, err) } - return nil + + return &p, nil } func (p *Page) closeWorker(sessionID target.SessionID) { diff --git a/common/session.go b/common/session.go index b91fbdad4..72304b02d 100644 --- a/common/session.go +++ b/common/session.go @@ -40,7 +40,6 @@ var _ cdp.Executor = &Session{} type Session struct { BaseEventEmitter - ctx context.Context conn *Connection id target.SessionID targetID target.ID @@ -57,7 +56,6 @@ type Session struct { func NewSession(ctx context.Context, conn *Connection, id target.SessionID, tid target.ID, logger *Logger) *Session { s := Session{ BaseEventEmitter: NewBaseEventEmitter(ctx), - ctx: ctx, conn: conn, id: id, targetID: tid, @@ -164,7 +162,8 @@ func (s *Session) Execute(ctx context.Context, method string, params easyjson.Ma s.onAll(evCancelCtx, chEvHandler) defer evCancelFn() // Remove event handler - // Send the message + s.logger.Debugf("Session:Execute:s.conn.send", "sid:%v tid:%v method:%q", s.id, s.targetID, method) + var buf []byte if params != nil { var err error @@ -179,8 +178,7 @@ func (s *Session) Execute(ctx context.Context, method string, params easyjson.Ma Method: cdproto.MethodType(method), Params: buf, } - s.logger.Debugf("Session:Execute:s.conn.send", "sid:%v tid:%v method:%q", s.id, s.targetID, method) - return s.conn.send(msg, ch, res) + return s.conn.send(contextWithDoneChan(ctx, s.done), msg, ch, res) } func (s *Session) ExecuteWithoutExpectationOnReply(ctx context.Context, method string, params easyjson.Marshaler, res easyjson.Unmarshaler) error { @@ -194,9 +192,8 @@ func (s *Session) ExecuteWithoutExpectationOnReply(ctx context.Context, method s return ErrTargetCrashed } - id := atomic.AddInt64(&s.msgID, 1) + s.logger.Debugf("Session:Execute:s.conn.send", "sid:%v tid:%v method:%q", s.id, s.targetID, method) - // Send the message var buf []byte if params != nil { var err error @@ -207,7 +204,7 @@ func (s *Session) ExecuteWithoutExpectationOnReply(ctx context.Context, method s } } msg := &cdproto.Message{ - ID: id, + ID: atomic.AddInt64(&s.msgID, 1), // We use different sessions to send messages to "targets" // (browser, page, frame etc.) in CDP. // @@ -226,5 +223,5 @@ func (s *Session) ExecuteWithoutExpectationOnReply(ctx context.Context, method s Method: cdproto.MethodType(method), Params: buf, } - return s.conn.send(msg, nil, res) + return s.conn.send(contextWithDoneChan(ctx, s.done), msg, nil, res) }