close a page when cancelling its context

For all contexts except the first browser context, as in that case the
allocator and browser handler already take care of shutting down the
process and goroutines, respectively.

Fixes #293.
This commit is contained in:
Daniel Martí 2019-04-07 14:17:15 +02:00
parent c313fa1c1d
commit c41ed01b6a
2 changed files with 62 additions and 6 deletions

View File

@ -3,6 +3,8 @@ package chromedp
import ( import (
"context" "context"
"fmt" "fmt"
"sync"
"time"
"github.com/chromedp/cdproto/css" "github.com/chromedp/cdproto/css"
"github.com/chromedp/cdproto/dom" "github.com/chromedp/cdproto/dom"
@ -15,11 +17,27 @@ import (
// Context is attached to any context.Context which is valid for use with Run. // Context is attached to any context.Context which is valid for use with Run.
type Context struct { type Context struct {
// Allocator is used to create new browsers. It is inherited from the
// parent context when using NewContext.
Allocator Allocator Allocator Allocator
// Browser is the browser being used in the context. It is inherited
// from the parent context when using NewContext.
Browser *Browser Browser *Browser
// Target is the target to run actions (commands) against. It is not
// inherited from the parent context, and typically each context will
// have its own unique Target pointing to a separate browser tab (page).
Target *Target Target *Target
// first records whether this context was the one that allocated
// Browser. This is important, because its cancellation will stop the
// entire browser handler, meaning that no further actions can be
// executed.
first bool
// wg allows waiting for a target to be closed on cancellation.
wg sync.WaitGroup
} }
// NewContext creates a browser context using the parent context. // NewContext creates a browser context using the parent context.
@ -46,7 +64,38 @@ func NewContext(parent context.Context, opts ...ContextOption) (context.Context,
} }
ctx = context.WithValue(ctx, contextKey{}, c) ctx = context.WithValue(ctx, contextKey{}, c)
return ctx, cancel go func() {
<-ctx.Done()
if c.first {
// This is the original browser tab, so the entire
// browser will already be cleaned up elsewhere.
c.wg.Done()
return
}
// Not the original browser tab; simply detach and close it.
// We need a new context, as ctx is cancelled; use a 1s timeout.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
if id := c.Target.SessionID; id != "" {
action := target.DetachFromTarget().WithSessionID(id)
if err := action.Do(ctx, c.Browser); err != nil {
c.Browser.errf("%s", err)
}
}
if id := c.Target.TargetID; id != "" {
action := target.CloseTarget(id)
if _, err := action.Do(ctx, c.Browser); err != nil {
c.Browser.errf("%s", err)
}
}
c.wg.Done()
}()
cancelWait := func() {
cancel()
c.wg.Wait()
}
return ctx, cancelWait
} }
type contextKey struct{} type contextKey struct{}
@ -65,8 +114,8 @@ func Run(ctx context.Context, actions ...Action) error {
if c == nil || c.Allocator == nil { if c == nil || c.Allocator == nil {
return ErrInvalidContext return ErrInvalidContext
} }
first := c.Browser == nil c.first = c.Browser == nil
if first { if c.first {
browser, err := c.Allocator.Allocate(ctx) browser, err := c.Allocator.Allocate(ctx)
if err != nil { if err != nil {
return err return err
@ -74,16 +123,16 @@ func Run(ctx context.Context, actions ...Action) error {
c.Browser = browser c.Browser = browser
} }
if c.Target == nil { if c.Target == nil {
if err := c.newSession(ctx, first); err != nil { if err := c.newSession(ctx); err != nil {
return err return err
} }
} }
return Tasks(actions).Do(ctx, c.Target) return Tasks(actions).Do(ctx, c.Target)
} }
func (c *Context) newSession(ctx context.Context, first bool) error { func (c *Context) newSession(ctx context.Context) error {
var targetID target.ID var targetID target.ID
if first { if c.first {
// If we just allocated this browser, and it has a single page // If we just allocated this browser, and it has a single page
// that's blank and not attached, use it. // that's blank and not attached, use it.
infos, err := target.GetTargets().Do(ctx, c.Browser) infos, err := target.GetTargets().Do(ctx, c.Browser)
@ -115,6 +164,8 @@ func (c *Context) newSession(ctx context.Context, first bool) error {
if err != nil { if err != nil {
return err return err
} }
c.wg.Add(1)
c.Target = c.Browser.newExecutorForTarget(ctx, targetID, sessionID) c.Target = c.Browser.newExecutorForTarget(ctx, targetID, sessionID)
// enable domains // enable domains

View File

@ -16,6 +16,7 @@ func TestTargets(t *testing.T) {
} }
wantTargets := func(ctx context.Context, want int) { wantTargets := func(ctx context.Context, want int) {
t.Helper()
infos, err := Targets(ctx) infos, err := Targets(ctx)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -36,4 +37,8 @@ func TestTargets(t *testing.T) {
// The first context should also see both targets. // The first context should also see both targets.
wantTargets(ctx1, 2) wantTargets(ctx1, 2)
// Cancelling the second context should close the second tab alone.
cancel2()
wantTargets(ctx1, 1)
} }