2018-09-27 04:03:06 +02:00
|
|
|
package dynamic
|
2018-09-18 22:42:38 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-12-01 02:30:55 +02:00
|
|
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
|
|
|
"sync"
|
|
|
|
|
2018-10-08 04:18:57 +02:00
|
|
|
"github.com/MontFerret/ferret/pkg/html/common"
|
|
|
|
"github.com/MontFerret/ferret/pkg/runtime/logging"
|
2018-09-18 22:42:38 +02:00
|
|
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
2018-09-24 03:11:13 +02:00
|
|
|
"github.com/mafredri/cdp"
|
2018-09-18 22:42:38 +02:00
|
|
|
"github.com/mafredri/cdp/devtool"
|
2018-10-08 03:23:36 +02:00
|
|
|
"github.com/mafredri/cdp/protocol/emulation"
|
|
|
|
"github.com/mafredri/cdp/protocol/page"
|
2018-09-24 03:11:13 +02:00
|
|
|
"github.com/mafredri/cdp/protocol/target"
|
2018-09-18 22:42:38 +02:00
|
|
|
"github.com/mafredri/cdp/rpcc"
|
2018-09-24 03:11:13 +02:00
|
|
|
"github.com/mafredri/cdp/session"
|
|
|
|
"github.com/pkg/errors"
|
2018-09-18 22:42:38 +02:00
|
|
|
)
|
|
|
|
|
2018-12-01 02:30:55 +02:00
|
|
|
type (
|
|
|
|
ctxKey struct{}
|
|
|
|
|
|
|
|
Driver struct {
|
|
|
|
sync.Mutex
|
|
|
|
dev *devtool.DevTools
|
|
|
|
conn *rpcc.Conn
|
|
|
|
client *cdp.Client
|
|
|
|
session *session.Manager
|
|
|
|
contextID target.BrowserContextID
|
|
|
|
options *Options
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func WithContext(ctx context.Context, drv *Driver) context.Context {
|
|
|
|
return context.WithValue(
|
|
|
|
ctx,
|
|
|
|
ctxKey{},
|
|
|
|
drv,
|
|
|
|
)
|
2018-09-24 03:11:13 +02:00
|
|
|
}
|
2018-09-18 22:42:38 +02:00
|
|
|
|
2018-12-01 02:30:55 +02:00
|
|
|
func FromContext(ctx context.Context) (*Driver, error) {
|
|
|
|
val := ctx.Value(ctxKey{})
|
|
|
|
|
|
|
|
drv, ok := val.(*Driver)
|
2018-10-08 03:23:36 +02:00
|
|
|
|
2018-12-01 02:30:55 +02:00
|
|
|
if !ok {
|
|
|
|
return nil, core.Error(core.ErrNotFound, "dynamic HTML Driver")
|
2018-10-08 03:23:36 +02:00
|
|
|
}
|
2018-09-18 22:42:38 +02:00
|
|
|
|
2018-12-01 02:30:55 +02:00
|
|
|
return drv, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewDriver(opts ...Option) *Driver {
|
|
|
|
drv := new(Driver)
|
|
|
|
drv.options = newOptions(opts)
|
|
|
|
drv.dev = devtool.New(drv.options.cdp)
|
|
|
|
|
2018-09-24 03:11:13 +02:00
|
|
|
return drv
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
2018-10-07 04:33:39 +02:00
|
|
|
func (drv *Driver) GetDocument(ctx context.Context, targetURL values.String) (values.HTMLNode, error) {
|
2018-10-08 04:18:57 +02:00
|
|
|
logger := logging.FromContext(ctx)
|
|
|
|
|
2018-09-24 03:11:13 +02:00
|
|
|
err := drv.init(ctx)
|
|
|
|
|
|
|
|
if err != nil {
|
2018-10-08 04:18:57 +02:00
|
|
|
logger.
|
|
|
|
Error().
|
|
|
|
Err(err).
|
|
|
|
Str("driver", "dynamic").
|
|
|
|
Msg("failed to initialize the driver")
|
|
|
|
|
2018-09-24 03:11:13 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-07 04:33:39 +02:00
|
|
|
url := targetURL.String()
|
|
|
|
|
2018-10-05 03:37:28 +02:00
|
|
|
if url == "" {
|
2018-10-06 02:42:23 +02:00
|
|
|
url = BlankPageURL
|
2018-10-05 03:37:28 +02:00
|
|
|
}
|
|
|
|
|
2018-09-24 03:11:13 +02:00
|
|
|
// Create a new target belonging to the browser context, similar
|
|
|
|
// to opening a new tab in an incognito window.
|
|
|
|
createTargetArgs := target.NewCreateTargetArgs(url).SetBrowserContextID(drv.contextID)
|
|
|
|
createTarget, err := drv.client.Target.CreateTarget(ctx, createTargetArgs)
|
2018-09-18 22:42:38 +02:00
|
|
|
|
|
|
|
if err != nil {
|
2018-10-08 04:18:57 +02:00
|
|
|
logger.
|
|
|
|
Error().
|
|
|
|
Err(err).
|
|
|
|
Str("driver", "dynamic").
|
|
|
|
Msg("failed to create a browser target")
|
|
|
|
|
2018-09-18 22:42:38 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-09-24 03:11:13 +02:00
|
|
|
// Connect to target using the existing websocket connection.
|
|
|
|
conn, err := drv.session.Dial(ctx, createTarget.TargetID)
|
2018-09-18 22:42:38 +02:00
|
|
|
|
|
|
|
if err != nil {
|
2018-10-08 04:18:57 +02:00
|
|
|
logger.
|
|
|
|
Error().
|
|
|
|
Err(err).
|
|
|
|
Str("driver", "dynamic").
|
|
|
|
Msg("failed to establish a connection")
|
|
|
|
|
2018-09-18 22:42:38 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-08 03:23:36 +02:00
|
|
|
client := cdp.NewClient(conn)
|
|
|
|
|
|
|
|
err = runBatch(
|
|
|
|
func() error {
|
|
|
|
return client.Page.Enable(ctx)
|
|
|
|
},
|
|
|
|
|
|
|
|
func() error {
|
|
|
|
return client.Page.SetLifecycleEventsEnabled(
|
|
|
|
ctx,
|
|
|
|
page.NewSetLifecycleEventsEnabledArgs(true),
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
func() error {
|
|
|
|
return client.DOM.Enable(ctx)
|
|
|
|
},
|
|
|
|
|
|
|
|
func() error {
|
|
|
|
return client.Runtime.Enable(ctx)
|
|
|
|
},
|
|
|
|
|
|
|
|
func() error {
|
2018-10-08 04:18:57 +02:00
|
|
|
ua := common.GetUserAgent(drv.options.userAgent)
|
|
|
|
|
|
|
|
logger.
|
|
|
|
Debug().
|
|
|
|
Str("user-agent", ua).
|
|
|
|
Msg("using User-Agent")
|
|
|
|
|
|
|
|
// do not use custom user agent
|
|
|
|
if ua == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-10-08 03:23:36 +02:00
|
|
|
return client.Emulation.SetUserAgentOverride(
|
|
|
|
ctx,
|
2018-10-08 04:18:57 +02:00
|
|
|
emulation.NewSetUserAgentOverrideArgs(ua),
|
2018-10-08 03:23:36 +02:00
|
|
|
)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
2018-11-22 18:44:05 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-08 03:23:36 +02:00
|
|
|
return LoadHTMLDocument(ctx, conn, client, url)
|
2018-09-18 22:42:38 +02:00
|
|
|
}
|
|
|
|
|
2018-09-27 04:03:06 +02:00
|
|
|
func (drv *Driver) Close() error {
|
2018-09-24 03:11:13 +02:00
|
|
|
drv.Lock()
|
|
|
|
defer drv.Unlock()
|
|
|
|
|
|
|
|
if drv.session != nil {
|
|
|
|
drv.session.Close()
|
|
|
|
|
|
|
|
return drv.conn.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-27 04:03:06 +02:00
|
|
|
func (drv *Driver) init(ctx context.Context) error {
|
2018-09-24 03:11:13 +02:00
|
|
|
drv.Lock()
|
|
|
|
defer drv.Unlock()
|
|
|
|
|
|
|
|
if drv.session == nil {
|
|
|
|
ver, err := drv.dev.Version(ctx)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to initialize driver")
|
|
|
|
}
|
|
|
|
|
|
|
|
bconn, err := rpcc.DialContext(ctx, ver.WebSocketDebuggerURL)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to initialize driver")
|
|
|
|
}
|
|
|
|
|
|
|
|
bc := cdp.NewClient(bconn)
|
|
|
|
|
|
|
|
sess, err := session.NewManager(bc)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
bconn.Close()
|
|
|
|
|
|
|
|
return errors.Wrap(err, "failed to initialize driver")
|
|
|
|
}
|
|
|
|
|
|
|
|
createCtx, err := bc.Target.CreateBrowserContext(ctx)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
bconn.Close()
|
|
|
|
sess.Close()
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
drv.conn = bconn
|
|
|
|
drv.client = bc
|
|
|
|
drv.session = sess
|
|
|
|
drv.contextID = createCtx.BrowserContextID
|
|
|
|
}
|
|
|
|
|
2018-09-18 22:42:38 +02:00
|
|
|
return nil
|
|
|
|
}
|