github.com/MontFerret/ferret@v0.18.0/pkg/drivers/cdp/driver.go (about) 1 package cdp 2 3 import ( 4 "context" 5 "sync" 6 7 "github.com/mafredri/cdp" 8 "github.com/mafredri/cdp/devtool" 9 "github.com/mafredri/cdp/protocol/browser" 10 "github.com/mafredri/cdp/protocol/target" 11 "github.com/mafredri/cdp/rpcc" 12 "github.com/mafredri/cdp/session" 13 "github.com/pkg/errors" 14 15 "github.com/MontFerret/ferret/pkg/drivers" 16 "github.com/MontFerret/ferret/pkg/runtime/logging" 17 ) 18 19 const DriverName = "cdp" 20 const BlankPageURL = "about:blank" 21 22 var defaultViewport = &drivers.Viewport{ 23 Width: 1600, 24 Height: 900, 25 } 26 27 type Driver struct { 28 mu sync.Mutex 29 dev *devtool.DevTools 30 conn *rpcc.Conn 31 client *cdp.Client 32 session *session.Manager 33 contextID browser.ContextID 34 options *Options 35 } 36 37 func NewDriver(opts ...Option) *Driver { 38 drv := new(Driver) 39 drv.options = NewOptions(opts) 40 drv.dev = devtool.New(drv.options.Address) 41 42 return drv 43 } 44 45 func (drv *Driver) Name() string { 46 return drv.options.Name 47 } 48 49 func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTMLPage, error) { 50 logger := logging.FromContext(ctx) 51 52 conn, err := drv.createConnection(ctx, params.KeepCookies) 53 54 if err != nil { 55 logger.Error(). 56 Err(err). 57 Str("driver", drv.options.Name). 58 Msg("failed to create a new connection") 59 60 return nil, err 61 } 62 63 return LoadHTMLPage(ctx, conn, drv.setDefaultParams(params)) 64 } 65 66 func (drv *Driver) Parse(ctx context.Context, params drivers.ParseParams) (drivers.HTMLPage, error) { 67 logger := logging.FromContext(ctx) 68 69 conn, err := drv.createConnection(ctx, true) 70 71 if err != nil { 72 logger.Error(). 73 Err(err). 74 Str("driver", drv.options.Name). 75 Msg("failed to create a new connection") 76 77 return nil, err 78 } 79 80 return LoadHTMLPageWithContent(ctx, conn, drv.setDefaultParams(drivers.Params{ 81 URL: BlankPageURL, 82 UserAgent: "", 83 KeepCookies: params.KeepCookies, 84 Cookies: params.Cookies, 85 Headers: params.Headers, 86 Viewport: params.Viewport, 87 }), params.Content) 88 } 89 90 func (drv *Driver) Close() error { 91 drv.mu.Lock() 92 defer drv.mu.Unlock() 93 94 if drv.session != nil { 95 drv.session.Close() 96 97 return drv.conn.Close() 98 } 99 100 return nil 101 } 102 103 func (drv *Driver) createConnection(ctx context.Context, keepCookies bool) (*rpcc.Conn, error) { 104 err := drv.init(ctx) 105 106 if err != nil { 107 return nil, errors.Wrap(err, "initialize driver") 108 } 109 110 // Args for a new target belonging to the browser context 111 createTargetArgs := target.NewCreateTargetArgs(BlankPageURL) 112 113 if !drv.options.KeepCookies && !keepCookies { 114 // Set it to an incognito mode 115 createTargetArgs.SetBrowserContextID(drv.contextID) 116 } 117 118 // New target 119 createTarget, err := drv.client.Target.CreateTarget(ctx, createTargetArgs) 120 121 if err != nil { 122 return nil, errors.Wrap(err, "create a browser target") 123 } 124 125 // Connect to target using the existing websocket connection. 126 conn, err := drv.session.Dial(ctx, createTarget.TargetID) 127 128 if err != nil { 129 return nil, errors.Wrap(err, "establish a new connection") 130 } 131 132 return conn, nil 133 } 134 135 func (drv *Driver) setDefaultParams(params drivers.Params) drivers.Params { 136 if params.Viewport == nil { 137 params.Viewport = defaultViewport 138 } 139 140 return drivers.SetDefaultParams(drv.options.Options, params) 141 } 142 143 func (drv *Driver) init(ctx context.Context) error { 144 drv.mu.Lock() 145 defer drv.mu.Unlock() 146 147 if drv.session == nil { 148 ver, err := drv.dev.Version(ctx) 149 150 if err != nil { 151 return errors.Wrap(err, "failed to initialize driver") 152 } 153 154 dialOpts := make([]rpcc.DialOption, 0, 2) 155 156 if drv.options.Connection != nil { 157 if drv.options.Connection.BufferSize > 0 { 158 dialOpts = append(dialOpts, rpcc.WithWriteBufferSize(drv.options.Connection.BufferSize)) 159 } 160 161 if drv.options.Connection.Compression { 162 dialOpts = append(dialOpts, rpcc.WithCompression()) 163 } 164 } 165 166 bconn, err := rpcc.DialContext( 167 ctx, 168 ver.WebSocketDebuggerURL, 169 dialOpts..., 170 ) 171 172 if err != nil { 173 return errors.Wrap(err, "failed to initialize driver") 174 } 175 176 bc := cdp.NewClient(bconn) 177 178 sess, err := session.NewManager(bc) 179 180 if err != nil { 181 bconn.Close() 182 183 return errors.Wrap(err, "failed to initialize driver") 184 } 185 186 drv.conn = bconn 187 drv.client = bc 188 drv.session = sess 189 190 if drv.options.KeepCookies { 191 return nil 192 } 193 194 createCtx, err := bc.Target.CreateBrowserContext(ctx, &target.CreateBrowserContextArgs{}) 195 196 if err != nil { 197 bconn.Close() 198 sess.Close() 199 200 return err 201 } 202 203 drv.contextID = createCtx.BrowserContextID 204 } 205 206 return nil 207 }