diff --git a/common/frame.go b/common/frame.go index c907be9aa..2bd95ca15 100644 --- a/common/frame.go +++ b/common/frame.go @@ -447,10 +447,31 @@ func (f *Frame) setContext(world executionWorld, execCtx frameExecutionContext) panic(err) } + // There is a race condition when it comes to attaching iframes and the + // execution context that apply to these frames. What usually occurs is: + // + // 1. An exec context for about:blank is first set; + // 2. A new set event is received for exec context for the url pointing + // to the actual destination for the iframe; + // 3. Finally the execution context for about:blank is destroyed, not + // for the second execution context. + // + // This is the order of events when iframes are in use on a site, and + // so it is safe to nil the original execution context and overwrite it + // with the second one. + // + // The exec context destroyed event will not remove the new exec context + // since the ids do not match. + // + // If we didn't overwrite the first execCtx with the new one, then + // waitForExecutionContext could end up waiting indefinitely since all + // execCtx were destroyed. if f.executionContexts[world] != nil { - f.log.Debugf("Frame:setContext", "fid:%s furl:%q ectxid:%d world:%s, world exists", + f.log.Debugf("Frame:setContext", "fid:%s furl:%q ectxid:%d world:%s, overriding existing world", f.ID(), f.URL(), execCtx.ID(), world) - return + + f.executionContexts[world] = nil + f.documentHandle = nil } f.executionContexts[world] = execCtx diff --git a/common/frame_session.go b/common/frame_session.go index 4ed5c4af5..5fa411e7c 100644 --- a/common/frame_session.go +++ b/common/frame_session.go @@ -663,18 +663,25 @@ func (fs *FrameSession) onExecutionContextCreated(event *cdpruntime.EventExecuti if err := json.Unmarshal(auxData, &i); err != nil { k6ext.Panic(fs.ctx, "unmarshaling executionContextCreated event JSON: %w", err) } - var world executionWorld + frame, ok := fs.manager.getFrameByID(i.FrameID) - if ok { - if i.IsDefault { - world = mainWorld - } else if event.Context.Name == utilityWorldName && !frame.hasContext(utilityWorld) { - // In case of multiple sessions to the same target, there's a race between - // connections so we might end up creating multiple isolated worlds. - // We can use either. - world = utilityWorld - } + if !ok { + fs.logger.Debugf("FrameSession:onExecutionContextCreated:return", + "sid:%v tid:%v ectxid:%d missing frame", + fs.session.ID(), fs.targetID, event.Context.ID) + return } + + var world executionWorld + if i.IsDefault { + world = mainWorld + } else if event.Context.Name == utilityWorldName && !frame.hasContext(utilityWorld) { + // In case of multiple sessions to the same target, there's a race between + // connections so we might end up creating multiple isolated worlds. + // We can use either. + world = utilityWorld + } + if i.Type == "isolated" { fs.isolatedWorlds[event.Context.Name] = true }