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 (
"context"
"encoding/json"
"fmt"
"log"
"sync/atomic"
@ -102,16 +103,16 @@ func (b *Browser) send(method cdproto.MethodType, params easyjson.RawMessage) er
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 == "" {
panic("empty session ID")
}
if t, ok := b.pages[sessionID]; ok {
return t
if _, ok := b.pages[sessionID]; ok {
panic(fmt.Sprintf("executor for %q already exists", sessionID))
}
t := &Target{
browser: b,
sessionID: sessionID,
SessionID: sessionID,
eventQueue: make(chan *cdproto.Message, 1024),
waitQueue: make(chan func(cur *cdp.Frame) bool, 1024),

View File

@ -19,7 +19,7 @@ type Context struct {
Browser *Browser
SessionID target.SessionID
Target *Target
}
// NewContext creates a browser context using the parent context.
@ -72,12 +72,12 @@ func Run(ctx context.Context, action Action) error {
}
c.Browser = browser
}
if c.SessionID == "" {
if c.Target == nil {
if err := c.newSession(ctx); err != nil {
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 {
@ -93,7 +93,7 @@ func (c *Context) newSession(ctx context.Context) error {
return err
}
target := c.Browser.executorForTarget(ctx, sessionID)
c.Target = c.Browser.newExecutorForTarget(ctx, sessionID)
// enable domains
for _, enable := range []Action{
@ -105,12 +105,10 @@ func (c *Context) newSession(ctx context.Context) error {
dom.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)
}
}
c.SessionID = sessionID
return nil
}

View File

@ -99,7 +99,7 @@ func ExampleManyTabs() {
c2 := chromedp.FromContext(ctx2)
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
cancel()

View File

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