store a Target pointer directly in Context

That way, we avoid the racy map access via Browser.executorForTarget. If
a context is attached to a target, the Target field must be non-nil.

The Browser.pages map is still racy, since multiple tabs can be created
concurrently; we'll fix this other data race in another commit.
This commit is contained in:
Daniel Martí 2019-04-01 14:31:11 +01:00
parent 117274bc5d
commit 1decbccd74
4 changed files with 13 additions and 14 deletions

View File

@ -9,6 +9,7 @@ package chromedp
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"log" "log"
"sync/atomic" "sync/atomic"
@ -102,16 +103,16 @@ func (b *Browser) send(method cdproto.MethodType, params easyjson.RawMessage) er
return b.conn.Write(msg) return b.conn.Write(msg)
} }
func (b *Browser) executorForTarget(ctx context.Context, sessionID target.SessionID) *Target { func (b *Browser) newExecutorForTarget(ctx context.Context, sessionID target.SessionID) *Target {
if sessionID == "" { if sessionID == "" {
panic("empty session ID") panic("empty session ID")
} }
if t, ok := b.pages[sessionID]; ok { if _, ok := b.pages[sessionID]; ok {
return t panic(fmt.Sprintf("executor for %q already exists", sessionID))
} }
t := &Target{ t := &Target{
browser: b, browser: b,
sessionID: sessionID, SessionID: sessionID,
eventQueue: make(chan *cdproto.Message, 1024), eventQueue: make(chan *cdproto.Message, 1024),
waitQueue: make(chan func(cur *cdp.Frame) bool, 1024), waitQueue: make(chan func(cur *cdp.Frame) bool, 1024),

View File

@ -19,7 +19,7 @@ type Context struct {
Browser *Browser Browser *Browser
SessionID target.SessionID Target *Target
} }
// NewContext creates a browser context using the parent context. // NewContext creates a browser context using the parent context.
@ -72,12 +72,12 @@ func Run(ctx context.Context, action Action) error {
} }
c.Browser = browser c.Browser = browser
} }
if c.SessionID == "" { if c.Target == nil {
if err := c.newSession(ctx); err != nil { if err := c.newSession(ctx); err != nil {
return err return err
} }
} }
return action.Do(ctx, c.Browser.executorForTarget(ctx, c.SessionID)) return action.Do(ctx, c.Target)
} }
func (c *Context) newSession(ctx context.Context) error { func (c *Context) newSession(ctx context.Context) error {
@ -93,7 +93,7 @@ func (c *Context) newSession(ctx context.Context) error {
return err return err
} }
target := c.Browser.executorForTarget(ctx, sessionID) c.Target = c.Browser.newExecutorForTarget(ctx, sessionID)
// enable domains // enable domains
for _, enable := range []Action{ for _, enable := range []Action{
@ -105,12 +105,10 @@ func (c *Context) newSession(ctx context.Context) error {
dom.Enable(), dom.Enable(),
css.Enable(), css.Enable(),
} { } {
if err := enable.Do(ctx, target); err != nil { if err := enable.Do(ctx, c.Target); err != nil {
return fmt.Errorf("unable to execute %T: %v", enable, err) return fmt.Errorf("unable to execute %T: %v", enable, err)
} }
} }
c.SessionID = sessionID
return nil return nil
} }

View File

@ -99,7 +99,7 @@ func ExampleManyTabs() {
c2 := chromedp.FromContext(ctx2) c2 := chromedp.FromContext(ctx2)
fmt.Printf("Same browser: %t\n", c1.Browser == c2.Browser) fmt.Printf("Same browser: %t\n", c1.Browser == c2.Browser)
fmt.Printf("Same tab: %t\n", c1.SessionID == c2.SessionID) fmt.Printf("Same tab: %t\n", c1.Target == c2.Target)
// wait for the resources to be cleaned up // wait for the resources to be cleaned up
cancel() cancel()

View File

@ -19,7 +19,7 @@ import (
// Target manages a Chrome DevTools Protocol target. // Target manages a Chrome DevTools Protocol target.
type Target struct { type Target struct {
browser *Browser browser *Browser
sessionID target.SessionID SessionID target.SessionID
waitQueue chan func(cur *cdp.Frame) bool waitQueue chan func(cur *cdp.Frame) bool
eventQueue chan *cdproto.Message eventQueue chan *cdproto.Message
@ -87,7 +87,7 @@ func (t *Target) Execute(ctx context.Context, method string, params json.Marshal
return err return err
} }
sendParams := target.SendMessageToTarget(string(msgJSON)). sendParams := target.SendMessageToTarget(string(msgJSON)).
WithSessionID(t.sessionID) WithSessionID(t.SessionID)
sendParamsJSON, _ := json.Marshal(sendParams) sendParamsJSON, _ := json.Marshal(sendParams)
// We want to grab the response from the inner message. // We want to grab the response from the inner message.