275 lines
7.0 KiB
Go
275 lines
7.0 KiB
Go
package playwright
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
type browserImpl struct {
|
|
channelOwner
|
|
isConnected bool
|
|
shouldCloseConnectionOnClose bool
|
|
contexts []BrowserContext
|
|
browserType BrowserType
|
|
chromiumTracingPath *string
|
|
closeReason *string
|
|
}
|
|
|
|
func (b *browserImpl) BrowserType() BrowserType {
|
|
return b.browserType
|
|
}
|
|
|
|
func (b *browserImpl) IsConnected() bool {
|
|
b.RLock()
|
|
defer b.RUnlock()
|
|
return b.isConnected
|
|
}
|
|
|
|
func (b *browserImpl) NewContext(options ...BrowserNewContextOptions) (BrowserContext, error) {
|
|
overrides := map[string]interface{}{}
|
|
option := BrowserNewContextOptions{}
|
|
if len(options) == 1 {
|
|
option = options[0]
|
|
}
|
|
if option.AcceptDownloads != nil {
|
|
if *option.AcceptDownloads {
|
|
overrides["acceptDownloads"] = "accept"
|
|
} else {
|
|
overrides["acceptDownloads"] = "deny"
|
|
}
|
|
options[0].AcceptDownloads = nil
|
|
}
|
|
if option.ExtraHttpHeaders != nil {
|
|
overrides["extraHTTPHeaders"] = serializeMapToNameAndValue(options[0].ExtraHttpHeaders)
|
|
options[0].ExtraHttpHeaders = nil
|
|
}
|
|
if option.ClientCertificates != nil {
|
|
certs, err := transformClientCertificate(option.ClientCertificates)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
overrides["clientCertificates"] = certs
|
|
options[0].ClientCertificates = nil
|
|
}
|
|
if option.StorageStatePath != nil {
|
|
var storageState *OptionalStorageState
|
|
storageString, err := os.ReadFile(*options[0].StorageStatePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not read storage state file: %w", err)
|
|
}
|
|
err = json.Unmarshal(storageString, &storageState)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse storage state file: %w", err)
|
|
}
|
|
options[0].StorageState = storageState
|
|
options[0].StorageStatePath = nil
|
|
}
|
|
if option.NoViewport != nil && *options[0].NoViewport {
|
|
overrides["noDefaultViewport"] = true
|
|
options[0].NoViewport = nil
|
|
}
|
|
if option.RecordHarPath != nil {
|
|
overrides["recordHar"] = prepareRecordHarOptions(recordHarInputOptions{
|
|
Path: *options[0].RecordHarPath,
|
|
URL: options[0].RecordHarURLFilter,
|
|
Mode: options[0].RecordHarMode,
|
|
Content: options[0].RecordHarContent,
|
|
OmitContent: options[0].RecordHarOmitContent,
|
|
})
|
|
options[0].RecordHarPath = nil
|
|
options[0].RecordHarURLFilter = nil
|
|
options[0].RecordHarMode = nil
|
|
options[0].RecordHarContent = nil
|
|
options[0].RecordHarOmitContent = nil
|
|
}
|
|
channel, err := b.channel.Send("newContext", options, overrides)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
context := fromChannel(channel).(*browserContextImpl)
|
|
context.browser = b
|
|
b.browserType.(*browserTypeImpl).didCreateContext(context, &option, nil)
|
|
return context, nil
|
|
}
|
|
|
|
func (b *browserImpl) NewPage(options ...BrowserNewPageOptions) (Page, error) {
|
|
opts := make([]BrowserNewContextOptions, 0)
|
|
if len(options) == 1 {
|
|
opts = append(opts, BrowserNewContextOptions(options[0]))
|
|
}
|
|
context, err := b.NewContext(opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
page, err := context.NewPage()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
page.(*pageImpl).ownedContext = context
|
|
context.(*browserContextImpl).ownedPage = page
|
|
return page, nil
|
|
}
|
|
|
|
func (b *browserImpl) NewBrowserCDPSession() (CDPSession, error) {
|
|
channel, err := b.channel.Send("newBrowserCDPSession")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cdpSession := fromChannel(channel).(*cdpSessionImpl)
|
|
|
|
return cdpSession, nil
|
|
}
|
|
|
|
func (b *browserImpl) Contexts() []BrowserContext {
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
return b.contexts
|
|
}
|
|
|
|
func (b *browserImpl) Close(options ...BrowserCloseOptions) (err error) {
|
|
if len(options) == 1 {
|
|
b.closeReason = options[0].Reason
|
|
}
|
|
|
|
if b.shouldCloseConnectionOnClose {
|
|
err = b.connection.Stop()
|
|
} else if b.closeReason != nil {
|
|
_, err = b.channel.Send("close", map[string]interface{}{
|
|
"reason": b.closeReason,
|
|
})
|
|
} else {
|
|
_, err = b.channel.Send("close")
|
|
}
|
|
if err != nil && !errors.Is(err, ErrTargetClosed) {
|
|
return fmt.Errorf("close browser failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *browserImpl) Version() string {
|
|
return b.initializer["version"].(string)
|
|
}
|
|
|
|
func (b *browserImpl) StartTracing(options ...BrowserStartTracingOptions) error {
|
|
overrides := map[string]interface{}{}
|
|
option := BrowserStartTracingOptions{}
|
|
if len(options) == 1 {
|
|
option = options[0]
|
|
}
|
|
if option.Page != nil {
|
|
overrides["page"] = option.Page.(*pageImpl).channel
|
|
option.Page = nil
|
|
}
|
|
if option.Path != nil {
|
|
b.chromiumTracingPath = option.Path
|
|
option.Path = nil
|
|
}
|
|
_, err := b.channel.Send("startTracing", option, overrides)
|
|
return err
|
|
}
|
|
|
|
func (b *browserImpl) StopTracing() ([]byte, error) {
|
|
channel, err := b.channel.Send("stopTracing")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
artifact := fromChannel(channel).(*artifactImpl)
|
|
binary, err := artifact.ReadIntoBuffer()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = artifact.Delete()
|
|
if err != nil {
|
|
return binary, err
|
|
}
|
|
if b.chromiumTracingPath != nil {
|
|
err := os.MkdirAll(filepath.Dir(*b.chromiumTracingPath), 0o777)
|
|
if err != nil {
|
|
return binary, err
|
|
}
|
|
err = os.WriteFile(*b.chromiumTracingPath, binary, 0o644)
|
|
if err != nil {
|
|
return binary, err
|
|
}
|
|
}
|
|
return binary, nil
|
|
}
|
|
|
|
func (b *browserImpl) onClose() {
|
|
b.Lock()
|
|
if b.isConnected {
|
|
b.isConnected = false
|
|
b.Unlock()
|
|
b.Emit("disconnected", b)
|
|
return
|
|
}
|
|
b.Unlock()
|
|
}
|
|
|
|
func (b *browserImpl) OnDisconnected(fn func(Browser)) {
|
|
b.On("disconnected", fn)
|
|
}
|
|
|
|
func newBrowser(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *browserImpl {
|
|
b := &browserImpl{
|
|
isConnected: true,
|
|
contexts: make([]BrowserContext, 0),
|
|
}
|
|
b.createChannelOwner(b, parent, objectType, guid, initializer)
|
|
// convert parent to *browserTypeImpl
|
|
b.browserType = newBrowserType(parent.parent, parent.objectType, parent.guid, parent.initializer)
|
|
b.channel.On("close", b.onClose)
|
|
return b
|
|
}
|
|
|
|
func transformClientCertificate(clientCertificates []ClientCertificate) ([]map[string]interface{}, error) {
|
|
results := make([]map[string]interface{}, 0)
|
|
|
|
for _, cert := range clientCertificates {
|
|
data := map[string]interface{}{
|
|
"origin": cert.Origin,
|
|
"passphrase": cert.Passphrase,
|
|
}
|
|
if len(cert.Cert) > 0 {
|
|
data["cert"] = base64.StdEncoding.EncodeToString(cert.Cert)
|
|
} else if cert.CertPath != nil {
|
|
content, err := os.ReadFile(*cert.CertPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data["cert"] = base64.StdEncoding.EncodeToString(content)
|
|
}
|
|
|
|
if len(cert.Key) > 0 {
|
|
data["key"] = base64.StdEncoding.EncodeToString(cert.Key)
|
|
} else if cert.KeyPath != nil {
|
|
content, err := os.ReadFile(*cert.KeyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data["key"] = base64.StdEncoding.EncodeToString(content)
|
|
}
|
|
|
|
if len(cert.Pfx) > 0 {
|
|
data["pfx"] = base64.StdEncoding.EncodeToString(cert.Pfx)
|
|
} else if cert.PfxPath != nil {
|
|
content, err := os.ReadFile(*cert.PfxPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data["pfx"] = base64.StdEncoding.EncodeToString(content)
|
|
}
|
|
|
|
results = append(results, data)
|
|
}
|
|
if len(results) == 0 {
|
|
return nil, nil
|
|
}
|
|
return results, nil
|
|
}
|