summaryrefslogtreecommitdiff
path: root/vendor/github.com/playwright-community/playwright-go/browser_context.go
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-05-14 13:18:54 -0600
committermo khan <mo@mokhan.ca>2025-05-14 13:18:54 -0600
commit4b2d609a0efcc1d9b2f1a08f954d067ad1d9cd1e (patch)
tree0afacf9217b0569130da6b97d4197331681bf119 /vendor/github.com/playwright-community/playwright-go/browser_context.go
parentab373d1fe698cd3f53258c09bc8515d88a6d0b9e (diff)
test: use playwright to test out an OIDC login
Diffstat (limited to 'vendor/github.com/playwright-community/playwright-go/browser_context.go')
-rw-r--r--vendor/github.com/playwright-community/playwright-go/browser_context.go914
1 files changed, 914 insertions, 0 deletions
diff --git a/vendor/github.com/playwright-community/playwright-go/browser_context.go b/vendor/github.com/playwright-community/playwright-go/browser_context.go
new file mode 100644
index 0000000..1d420d3
--- /dev/null
+++ b/vendor/github.com/playwright-community/playwright-go/browser_context.go
@@ -0,0 +1,914 @@
+package playwright
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "regexp"
+ "slices"
+ "strings"
+ "sync"
+
+ "github.com/playwright-community/playwright-go/internal/safe"
+)
+
+type browserContextImpl struct {
+ channelOwner
+ timeoutSettings *timeoutSettings
+ closeWasCalled bool
+ options *BrowserNewContextOptions
+ pages []Page
+ routes []*routeHandlerEntry
+ webSocketRoutes []*webSocketRouteHandler
+ ownedPage Page
+ browser *browserImpl
+ serviceWorkers []Worker
+ backgroundPages []Page
+ bindings *safe.SyncMap[string, BindingCallFunction]
+ tracing *tracingImpl
+ request *apiRequestContextImpl
+ harRecorders map[string]harRecordingMetadata
+ closed chan struct{}
+ closeReason *string
+ harRouters []*harRouter
+ clock Clock
+}
+
+func (b *browserContextImpl) Clock() Clock {
+ return b.clock
+}
+
+func (b *browserContextImpl) SetDefaultNavigationTimeout(timeout float64) {
+ b.setDefaultNavigationTimeoutImpl(&timeout)
+}
+
+func (b *browserContextImpl) setDefaultNavigationTimeoutImpl(timeout *float64) {
+ b.timeoutSettings.SetDefaultNavigationTimeout(timeout)
+ b.channel.SendNoReplyInternal("setDefaultNavigationTimeoutNoReply", map[string]interface{}{
+ "timeout": timeout,
+ })
+}
+
+func (b *browserContextImpl) SetDefaultTimeout(timeout float64) {
+ b.setDefaultTimeoutImpl(&timeout)
+}
+
+func (b *browserContextImpl) setDefaultTimeoutImpl(timeout *float64) {
+ b.timeoutSettings.SetDefaultTimeout(timeout)
+ b.channel.SendNoReplyInternal("setDefaultTimeoutNoReply", map[string]interface{}{
+ "timeout": timeout,
+ })
+}
+
+func (b *browserContextImpl) Pages() []Page {
+ b.Lock()
+ defer b.Unlock()
+ return b.pages
+}
+
+func (b *browserContextImpl) Browser() Browser {
+ return b.browser
+}
+
+func (b *browserContextImpl) Tracing() Tracing {
+ return b.tracing
+}
+
+func (b *browserContextImpl) NewCDPSession(page interface{}) (CDPSession, error) {
+ params := map[string]interface{}{}
+
+ if p, ok := page.(*pageImpl); ok {
+ params["page"] = p.channel
+ } else if f, ok := page.(*frameImpl); ok {
+ params["frame"] = f.channel
+ } else {
+ return nil, fmt.Errorf("not page or frame: %v", page)
+ }
+
+ channel, err := b.channel.Send("newCDPSession", params)
+ if err != nil {
+ return nil, err
+ }
+
+ cdpSession := fromChannel(channel).(*cdpSessionImpl)
+
+ return cdpSession, nil
+}
+
+func (b *browserContextImpl) NewPage() (Page, error) {
+ if b.ownedPage != nil {
+ return nil, errors.New("Please use browser.NewContext()")
+ }
+ channel, err := b.channel.Send("newPage")
+ if err != nil {
+ return nil, err
+ }
+ return fromChannel(channel).(*pageImpl), nil
+}
+
+func (b *browserContextImpl) Cookies(urls ...string) ([]Cookie, error) {
+ result, err := b.channel.Send("cookies", map[string]interface{}{
+ "urls": urls,
+ })
+ if err != nil {
+ return nil, err
+ }
+ cookies := make([]Cookie, len(result.([]interface{})))
+ for i, item := range result.([]interface{}) {
+ cookie := &Cookie{}
+ remapMapToStruct(item, cookie)
+ cookies[i] = *cookie
+ }
+ return cookies, nil
+}
+
+func (b *browserContextImpl) AddCookies(cookies []OptionalCookie) error {
+ _, err := b.channel.Send("addCookies", map[string]interface{}{
+ "cookies": cookies,
+ })
+ return err
+}
+
+func (b *browserContextImpl) ClearCookies(options ...BrowserContextClearCookiesOptions) error {
+ params := map[string]interface{}{}
+ if len(options) == 1 {
+ if options[0].Domain != nil {
+ switch t := options[0].Domain.(type) {
+ case string:
+ params["domain"] = t
+ case *string:
+ params["domain"] = t
+ case *regexp.Regexp:
+ pattern, flag := convertRegexp(t)
+ params["domainRegexSource"] = pattern
+ params["domainRegexFlags"] = flag
+ default:
+ return errors.New("invalid type for domain, expected string or *regexp.Regexp")
+ }
+ }
+ if options[0].Name != nil {
+ switch t := options[0].Name.(type) {
+ case string:
+ params["name"] = t
+ case *string:
+ params["name"] = t
+ case *regexp.Regexp:
+ pattern, flag := convertRegexp(t)
+ params["nameRegexSource"] = pattern
+ params["nameRegexFlags"] = flag
+ default:
+ return errors.New("invalid type for name, expected string or *regexp.Regexp")
+ }
+ }
+ if options[0].Path != nil {
+ switch t := options[0].Path.(type) {
+ case string:
+ params["path"] = t
+ case *string:
+ params["path"] = t
+ case *regexp.Regexp:
+ pattern, flag := convertRegexp(t)
+ params["pathRegexSource"] = pattern
+ params["pathRegexFlags"] = flag
+ default:
+ return errors.New("invalid type for path, expected string or *regexp.Regexp")
+ }
+ }
+ }
+ _, err := b.channel.Send("clearCookies", params)
+ return err
+}
+
+func (b *browserContextImpl) GrantPermissions(permissions []string, options ...BrowserContextGrantPermissionsOptions) error {
+ _, err := b.channel.Send("grantPermissions", map[string]interface{}{
+ "permissions": permissions,
+ }, options)
+ return err
+}
+
+func (b *browserContextImpl) ClearPermissions() error {
+ _, err := b.channel.Send("clearPermissions")
+ return err
+}
+
+func (b *browserContextImpl) SetGeolocation(geolocation *Geolocation) error {
+ _, err := b.channel.Send("setGeolocation", map[string]interface{}{
+ "geolocation": geolocation,
+ })
+ return err
+}
+
+func (b *browserContextImpl) ResetGeolocation() error {
+ _, err := b.channel.Send("setGeolocation", map[string]interface{}{})
+ return err
+}
+
+func (b *browserContextImpl) SetExtraHTTPHeaders(headers map[string]string) error {
+ _, err := b.channel.Send("setExtraHTTPHeaders", map[string]interface{}{
+ "headers": serializeMapToNameAndValue(headers),
+ })
+ return err
+}
+
+func (b *browserContextImpl) SetOffline(offline bool) error {
+ _, err := b.channel.Send("setOffline", map[string]interface{}{
+ "offline": offline,
+ })
+ return err
+}
+
+func (b *browserContextImpl) AddInitScript(script Script) error {
+ var source string
+ if script.Content != nil {
+ source = *script.Content
+ }
+ if script.Path != nil {
+ content, err := os.ReadFile(*script.Path)
+ if err != nil {
+ return err
+ }
+ source = string(content)
+ }
+ _, err := b.channel.Send("addInitScript", map[string]interface{}{
+ "source": source,
+ })
+ return err
+}
+
+func (b *browserContextImpl) ExposeBinding(name string, binding BindingCallFunction, handle ...bool) error {
+ needsHandle := false
+ if len(handle) == 1 {
+ needsHandle = handle[0]
+ }
+ for _, page := range b.Pages() {
+ if _, ok := page.(*pageImpl).bindings.Load(name); ok {
+ return fmt.Errorf("Function '%s' has been already registered in one of the pages", name)
+ }
+ }
+ if _, ok := b.bindings.Load(name); ok {
+ return fmt.Errorf("Function '%s' has been already registered", name)
+ }
+ _, err := b.channel.Send("exposeBinding", map[string]interface{}{
+ "name": name,
+ "needsHandle": needsHandle,
+ })
+ if err != nil {
+ return err
+ }
+ b.bindings.Store(name, binding)
+ return err
+}
+
+func (b *browserContextImpl) ExposeFunction(name string, binding ExposedFunction) error {
+ return b.ExposeBinding(name, func(source *BindingSource, args ...interface{}) interface{} {
+ return binding(args...)
+ })
+}
+
+func (b *browserContextImpl) Route(url interface{}, handler routeHandler, times ...int) error {
+ b.Lock()
+ defer b.Unlock()
+ b.routes = slices.Insert(b.routes, 0, newRouteHandlerEntry(newURLMatcher(url, b.options.BaseURL), handler, times...))
+ return b.updateInterceptionPatterns()
+}
+
+func (b *browserContextImpl) Unroute(url interface{}, handlers ...routeHandler) error {
+ removed, remaining, err := unroute(b.routes, url, handlers...)
+ if err != nil {
+ return err
+ }
+ return b.unrouteInternal(removed, remaining, UnrouteBehaviorDefault)
+}
+
+func (b *browserContextImpl) unrouteInternal(removed []*routeHandlerEntry, remaining []*routeHandlerEntry, behavior *UnrouteBehavior) error {
+ b.Lock()
+ defer b.Unlock()
+ b.routes = remaining
+ if err := b.updateInterceptionPatterns(); err != nil {
+ return err
+ }
+ if behavior == nil || behavior == UnrouteBehaviorDefault {
+ return nil
+ }
+ wg := &sync.WaitGroup{}
+ for _, entry := range removed {
+ wg.Add(1)
+ go func(entry *routeHandlerEntry) {
+ defer wg.Done()
+ entry.Stop(string(*behavior))
+ }(entry)
+ }
+ wg.Wait()
+ return nil
+}
+
+func (b *browserContextImpl) UnrouteAll(options ...BrowserContextUnrouteAllOptions) error {
+ var behavior *UnrouteBehavior
+ if len(options) == 1 {
+ behavior = options[0].Behavior
+ }
+ defer b.disposeHarRouters()
+ return b.unrouteInternal(b.routes, []*routeHandlerEntry{}, behavior)
+}
+
+func (b *browserContextImpl) disposeHarRouters() {
+ for _, router := range b.harRouters {
+ router.dispose()
+ }
+ b.harRouters = make([]*harRouter, 0)
+}
+
+func (b *browserContextImpl) Request() APIRequestContext {
+ return b.request
+}
+
+func (b *browserContextImpl) RouteFromHAR(har string, options ...BrowserContextRouteFromHAROptions) error {
+ opt := BrowserContextRouteFromHAROptions{}
+ if len(options) == 1 {
+ opt = options[0]
+ }
+ if opt.Update != nil && *opt.Update {
+ var updateContent *HarContentPolicy
+ switch opt.UpdateContent {
+ case RouteFromHarUpdateContentPolicyAttach:
+ updateContent = HarContentPolicyAttach
+ case RouteFromHarUpdateContentPolicyEmbed:
+ updateContent = HarContentPolicyEmbed
+ }
+ return b.recordIntoHar(har, browserContextRecordIntoHarOptions{
+ URL: opt.URL,
+ UpdateContent: updateContent,
+ UpdateMode: opt.UpdateMode,
+ })
+ }
+ notFound := opt.NotFound
+ if notFound == nil {
+ notFound = HarNotFoundAbort
+ }
+ router := newHarRouter(b.connection.localUtils, har, *notFound, opt.URL)
+ b.harRouters = append(b.harRouters, router)
+ return router.addContextRoute(b)
+}
+
+func (b *browserContextImpl) WaitForEvent(event string, options ...BrowserContextWaitForEventOptions) (interface{}, error) {
+ return b.waiterForEvent(event, options...).Wait()
+}
+
+func (b *browserContextImpl) waiterForEvent(event string, options ...BrowserContextWaitForEventOptions) *waiter {
+ timeout := b.timeoutSettings.Timeout()
+ var predicate interface{} = nil
+ if len(options) == 1 {
+ if options[0].Timeout != nil {
+ timeout = *options[0].Timeout
+ }
+ predicate = options[0].Predicate
+ }
+ waiter := newWaiter().WithTimeout(timeout)
+ waiter.RejectOnEvent(b, "close", ErrTargetClosed)
+ return waiter.WaitForEvent(b, event, predicate)
+}
+
+func (b *browserContextImpl) ExpectConsoleMessage(cb func() error, options ...BrowserContextExpectConsoleMessageOptions) (ConsoleMessage, error) {
+ var w *waiter
+ if len(options) == 1 {
+ w = b.waiterForEvent("console", BrowserContextWaitForEventOptions{
+ Predicate: options[0].Predicate,
+ Timeout: options[0].Timeout,
+ })
+ } else {
+ w = b.waiterForEvent("console")
+ }
+ ret, err := w.RunAndWait(cb)
+ if err != nil {
+ return nil, err
+ }
+ return ret.(ConsoleMessage), nil
+}
+
+func (b *browserContextImpl) ExpectEvent(event string, cb func() error, options ...BrowserContextExpectEventOptions) (interface{}, error) {
+ if len(options) == 1 {
+ return b.waiterForEvent(event, BrowserContextWaitForEventOptions(options[0])).RunAndWait(cb)
+ }
+ return b.waiterForEvent(event).RunAndWait(cb)
+}
+
+func (b *browserContextImpl) ExpectPage(cb func() error, options ...BrowserContextExpectPageOptions) (Page, error) {
+ var w *waiter
+ if len(options) == 1 {
+ w = b.waiterForEvent("page", BrowserContextWaitForEventOptions{
+ Predicate: options[0].Predicate,
+ Timeout: options[0].Timeout,
+ })
+ } else {
+ w = b.waiterForEvent("page")
+ }
+ ret, err := w.RunAndWait(cb)
+ if err != nil {
+ return nil, err
+ }
+ return ret.(Page), nil
+}
+
+func (b *browserContextImpl) Close(options ...BrowserContextCloseOptions) error {
+ if b.closeWasCalled {
+ return nil
+ }
+ if len(options) == 1 {
+ b.closeReason = options[0].Reason
+ }
+ b.closeWasCalled = true
+
+ _, err := b.channel.connection.WrapAPICall(func() (interface{}, error) {
+ return nil, b.request.Dispose(APIRequestContextDisposeOptions{
+ Reason: b.closeReason,
+ })
+ }, true)
+ if err != nil {
+ return err
+ }
+
+ innerClose := func() (interface{}, error) {
+ for harId, harMetaData := range b.harRecorders {
+ overrides := map[string]interface{}{}
+ if harId != "" {
+ overrides["harId"] = harId
+ }
+ response, err := b.channel.Send("harExport", overrides)
+ if err != nil {
+ return nil, err
+ }
+ artifact := fromChannel(response).(*artifactImpl)
+ // Server side will compress artifact if content is attach or if file is .zip.
+ needCompressed := strings.HasSuffix(strings.ToLower(harMetaData.Path), ".zip")
+ if !needCompressed && harMetaData.Content == HarContentPolicyAttach {
+ tmpPath := harMetaData.Path + ".tmp"
+ if err := artifact.SaveAs(tmpPath); err != nil {
+ return nil, err
+ }
+ err = b.connection.localUtils.HarUnzip(tmpPath, harMetaData.Path)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ if err := artifact.SaveAs(harMetaData.Path); err != nil {
+ return nil, err
+ }
+ }
+ if err := artifact.Delete(); err != nil {
+ return nil, err
+ }
+ }
+ return nil, nil
+ }
+
+ _, err = b.channel.connection.WrapAPICall(innerClose, true)
+ if err != nil {
+ return err
+ }
+
+ _, err = b.channel.Send("close", map[string]interface{}{
+ "reason": b.closeReason,
+ })
+ if err != nil {
+ return err
+ }
+ <-b.closed
+ return err
+}
+
+type browserContextRecordIntoHarOptions struct {
+ Page Page
+ URL interface{}
+ UpdateContent *HarContentPolicy
+ UpdateMode *HarMode
+}
+
+func (b *browserContextImpl) recordIntoHar(har string, options ...browserContextRecordIntoHarOptions) error {
+ overrides := map[string]interface{}{}
+ harOptions := recordHarInputOptions{
+ Path: har,
+ Content: HarContentPolicyAttach,
+ Mode: HarModeMinimal,
+ }
+ if len(options) == 1 {
+ if options[0].UpdateContent != nil {
+ harOptions.Content = options[0].UpdateContent
+ }
+ if options[0].UpdateMode != nil {
+ harOptions.Mode = options[0].UpdateMode
+ }
+ harOptions.URL = options[0].URL
+ overrides["options"] = prepareRecordHarOptions(harOptions)
+ if options[0].Page != nil {
+ overrides["page"] = options[0].Page.(*pageImpl).channel
+ }
+ }
+ harId, err := b.channel.Send("harStart", overrides)
+ if err != nil {
+ return err
+ }
+ b.harRecorders[harId.(string)] = harRecordingMetadata{
+ Path: har,
+ Content: harOptions.Content,
+ }
+ return nil
+}
+
+func (b *browserContextImpl) StorageState(paths ...string) (*StorageState, error) {
+ result, err := b.channel.SendReturnAsDict("storageState")
+ if err != nil {
+ return nil, err
+ }
+ if len(paths) == 1 {
+ file, err := os.Create(paths[0])
+ if err != nil {
+ return nil, err
+ }
+ if err := json.NewEncoder(file).Encode(result); err != nil {
+ return nil, err
+ }
+ if err := file.Close(); err != nil {
+ return nil, err
+ }
+ }
+ var storageState StorageState
+ remapMapToStruct(result, &storageState)
+ return &storageState, nil
+}
+
+func (b *browserContextImpl) onBinding(binding *bindingCallImpl) {
+ function, ok := b.bindings.Load(binding.initializer["name"].(string))
+ if !ok || function == nil {
+ return
+ }
+ go binding.Call(function)
+}
+
+func (b *browserContextImpl) onClose() {
+ if b.browser != nil {
+ contexts := make([]BrowserContext, 0)
+ b.browser.Lock()
+ for _, context := range b.browser.contexts {
+ if context != b {
+ contexts = append(contexts, context)
+ }
+ }
+ b.browser.contexts = contexts
+ b.browser.Unlock()
+ }
+ b.disposeHarRouters()
+ b.Emit("close", b)
+}
+
+func (b *browserContextImpl) onPage(page Page) {
+ b.Lock()
+ b.pages = append(b.pages, page)
+ b.Unlock()
+ b.Emit("page", page)
+ opener, _ := page.Opener()
+ if opener != nil && !opener.IsClosed() {
+ opener.Emit("popup", page)
+ }
+}
+
+func (b *browserContextImpl) onRoute(route *routeImpl) {
+ b.Lock()
+ route.context = b
+ page := route.Request().(*requestImpl).safePage()
+ routes := make([]*routeHandlerEntry, len(b.routes))
+ copy(routes, b.routes)
+ b.Unlock()
+
+ checkInterceptionIfNeeded := func() {
+ b.Lock()
+ defer b.Unlock()
+ if len(b.routes) == 0 {
+ _, err := b.connection.WrapAPICall(func() (interface{}, error) {
+ err := b.updateInterceptionPatterns()
+ return nil, err
+ }, true)
+ if err != nil {
+ logger.Error("could not update interception patterns", "error", err)
+ }
+ }
+ }
+
+ url := route.Request().URL()
+ for _, handlerEntry := range routes {
+ // If the page or the context was closed we stall all requests right away.
+ if (page != nil && page.closeWasCalled) || b.closeWasCalled {
+ return
+ }
+ if !handlerEntry.Matches(url) {
+ continue
+ }
+ if !slices.ContainsFunc(b.routes, func(entry *routeHandlerEntry) bool {
+ return entry == handlerEntry
+ }) {
+ continue
+ }
+ if handlerEntry.WillExceed() {
+ b.routes = slices.DeleteFunc(b.routes, func(rhe *routeHandlerEntry) bool {
+ return rhe == handlerEntry
+ })
+ }
+ handled := handlerEntry.Handle(route)
+ checkInterceptionIfNeeded()
+ yes := <-handled
+ if yes {
+ return
+ }
+ }
+ // If the page is closed or unrouteAll() was called without waiting and interception disabled,
+ // the method will throw an error - silence it.
+ _ = route.internalContinue(true)
+}
+
+func (b *browserContextImpl) updateInterceptionPatterns() error {
+ patterns := prepareInterceptionPatterns(b.routes)
+ _, err := b.channel.Send("setNetworkInterceptionPatterns", map[string]interface{}{
+ "patterns": patterns,
+ })
+ return err
+}
+
+func (b *browserContextImpl) pause() <-chan error {
+ ret := make(chan error, 1)
+ go func() {
+ _, err := b.channel.Send("pause")
+ ret <- err
+ }()
+ return ret
+}
+
+func (b *browserContextImpl) onBackgroundPage(ev map[string]interface{}) {
+ b.Lock()
+ p := fromChannel(ev["page"]).(*pageImpl)
+ p.browserContext = b
+ b.backgroundPages = append(b.backgroundPages, p)
+ b.Unlock()
+ b.Emit("backgroundpage", p)
+}
+
+func (b *browserContextImpl) onServiceWorker(worker *workerImpl) {
+ worker.context = b
+ b.serviceWorkers = append(b.serviceWorkers, worker)
+ b.Emit("serviceworker", worker)
+}
+
+func (b *browserContextImpl) setOptions(options *BrowserNewContextOptions, tracesDir *string) {
+ if options == nil {
+ options = &BrowserNewContextOptions{}
+ }
+ b.options = options
+ if b.options != nil && b.options.RecordHarPath != nil {
+ b.harRecorders[""] = harRecordingMetadata{
+ Path: *b.options.RecordHarPath,
+ Content: b.options.RecordHarContent,
+ }
+ }
+ if tracesDir != nil {
+ b.tracing.tracesDir = *tracesDir
+ }
+}
+
+func (b *browserContextImpl) BackgroundPages() []Page {
+ b.Lock()
+ defer b.Unlock()
+ return b.backgroundPages
+}
+
+func (b *browserContextImpl) ServiceWorkers() []Worker {
+ b.Lock()
+ defer b.Unlock()
+ return b.serviceWorkers
+}
+
+func (b *browserContextImpl) OnBackgroundPage(fn func(Page)) {
+ b.On("backgroundpage", fn)
+}
+
+func (b *browserContextImpl) OnClose(fn func(BrowserContext)) {
+ b.On("close", fn)
+}
+
+func (b *browserContextImpl) OnConsole(fn func(ConsoleMessage)) {
+ b.On("console", fn)
+}
+
+func (b *browserContextImpl) OnDialog(fn func(Dialog)) {
+ b.On("dialog", fn)
+}
+
+func (b *browserContextImpl) OnPage(fn func(Page)) {
+ b.On("page", fn)
+}
+
+func (b *browserContextImpl) OnRequest(fn func(Request)) {
+ b.On("request", fn)
+}
+
+func (b *browserContextImpl) OnRequestFailed(fn func(Request)) {
+ b.On("requestfailed", fn)
+}
+
+func (b *browserContextImpl) OnRequestFinished(fn func(Request)) {
+ b.On("requestfinished", fn)
+}
+
+func (b *browserContextImpl) OnResponse(fn func(Response)) {
+ b.On("response", fn)
+}
+
+func (b *browserContextImpl) OnWebError(fn func(WebError)) {
+ b.On("weberror", fn)
+}
+
+func (b *browserContextImpl) RouteWebSocket(url interface{}, handler func(WebSocketRoute)) error {
+ b.Lock()
+ defer b.Unlock()
+ b.webSocketRoutes = slices.Insert(b.webSocketRoutes, 0, newWebSocketRouteHandler(newURLMatcher(url, b.options.BaseURL), handler))
+
+ return b.updateWebSocketInterceptionPatterns()
+}
+
+func (b *browserContextImpl) onWebSocketRoute(wr WebSocketRoute) {
+ b.Lock()
+ index := slices.IndexFunc(b.webSocketRoutes, func(r *webSocketRouteHandler) bool {
+ return r.Matches(wr.URL())
+ })
+ if index == -1 {
+ b.Unlock()
+ _, err := wr.ConnectToServer()
+ if err != nil {
+ logger.Error("could not connect to WebSocket server", "error", err)
+ }
+ return
+ }
+ handler := b.webSocketRoutes[index]
+ b.Unlock()
+ handler.Handle(wr)
+}
+
+func (b *browserContextImpl) updateWebSocketInterceptionPatterns() error {
+ patterns := prepareWebSocketRouteHandlerInterceptionPatterns(b.webSocketRoutes)
+ _, err := b.channel.Send("setWebSocketInterceptionPatterns", map[string]interface{}{
+ "patterns": patterns,
+ })
+ return err
+}
+
+func (b *browserContextImpl) effectiveCloseReason() *string {
+ b.Lock()
+ defer b.Unlock()
+ if b.closeReason != nil {
+ return b.closeReason
+ }
+ if b.browser != nil {
+ return b.browser.closeReason
+ }
+ return nil
+}
+
+func newBrowserContext(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *browserContextImpl {
+ bt := &browserContextImpl{
+ timeoutSettings: newTimeoutSettings(nil),
+ pages: make([]Page, 0),
+ backgroundPages: make([]Page, 0),
+ routes: make([]*routeHandlerEntry, 0),
+ bindings: safe.NewSyncMap[string, BindingCallFunction](),
+ harRecorders: make(map[string]harRecordingMetadata),
+ closed: make(chan struct{}, 1),
+ harRouters: make([]*harRouter, 0),
+ }
+ bt.createChannelOwner(bt, parent, objectType, guid, initializer)
+ if parent.objectType == "Browser" {
+ bt.browser = fromChannel(parent.channel).(*browserImpl)
+ bt.browser.contexts = append(bt.browser.contexts, bt)
+ }
+ bt.tracing = fromChannel(initializer["tracing"]).(*tracingImpl)
+ bt.request = fromChannel(initializer["requestContext"]).(*apiRequestContextImpl)
+ bt.clock = newClock(bt)
+ bt.channel.On("bindingCall", func(params map[string]interface{}) {
+ bt.onBinding(fromChannel(params["binding"]).(*bindingCallImpl))
+ })
+
+ bt.channel.On("close", bt.onClose)
+ bt.channel.On("page", func(payload map[string]interface{}) {
+ bt.onPage(fromChannel(payload["page"]).(*pageImpl))
+ })
+ bt.channel.On("route", func(params map[string]interface{}) {
+ bt.channel.CreateTask(func() {
+ bt.onRoute(fromChannel(params["route"]).(*routeImpl))
+ })
+ })
+ bt.channel.On("webSocketRoute", func(params map[string]interface{}) {
+ bt.channel.CreateTask(func() {
+ bt.onWebSocketRoute(fromChannel(params["webSocketRoute"]).(*webSocketRouteImpl))
+ })
+ })
+ bt.channel.On("backgroundPage", bt.onBackgroundPage)
+ bt.channel.On("serviceWorker", func(params map[string]interface{}) {
+ bt.onServiceWorker(fromChannel(params["worker"]).(*workerImpl))
+ })
+ bt.channel.On("console", func(ev map[string]interface{}) {
+ message := newConsoleMessage(ev)
+ bt.Emit("console", message)
+ if message.page != nil {
+ message.page.Emit("console", message)
+ }
+ })
+ bt.channel.On("dialog", func(params map[string]interface{}) {
+ dialog := fromChannel(params["dialog"]).(*dialogImpl)
+ go func() {
+ hasListeners := bt.Emit("dialog", dialog)
+ page := dialog.page
+ if page != nil {
+ if page.Emit("dialog", dialog) {
+ hasListeners = true
+ }
+ }
+ if !hasListeners {
+ // Although we do similar handling on the server side, we still need this logic
+ // on the client side due to a possible race condition between two async calls:
+ // a) removing "dialog" listener subscription (client->server)
+ // b) actual "dialog" event (server->client)
+ if dialog.Type() == "beforeunload" {
+ _ = dialog.Accept()
+ } else {
+ _ = dialog.Dismiss()
+ }
+ }
+ }()
+ })
+ bt.channel.On(
+ "pageError", func(ev map[string]interface{}) {
+ pwErr := &Error{}
+ remapMapToStruct(ev["error"].(map[string]interface{})["error"], pwErr)
+ err := parseError(*pwErr)
+ page := fromNullableChannel(ev["page"])
+ if page != nil {
+ bt.Emit("weberror", newWebError(page.(*pageImpl), err))
+ page.(*pageImpl).Emit("pageerror", err)
+ } else {
+ bt.Emit("weberror", newWebError(nil, err))
+ }
+ },
+ )
+ bt.channel.On("request", func(ev map[string]interface{}) {
+ request := fromChannel(ev["request"]).(*requestImpl)
+ page := fromNullableChannel(ev["page"])
+ bt.Emit("request", request)
+ if page != nil {
+ page.(*pageImpl).Emit("request", request)
+ }
+ })
+ bt.channel.On("requestFailed", func(ev map[string]interface{}) {
+ request := fromChannel(ev["request"]).(*requestImpl)
+ failureText := ev["failureText"]
+ if failureText != nil {
+ request.failureText = failureText.(string)
+ }
+ page := fromNullableChannel(ev["page"])
+ request.setResponseEndTiming(ev["responseEndTiming"].(float64))
+ bt.Emit("requestfailed", request)
+ if page != nil {
+ page.(*pageImpl).Emit("requestfailed", request)
+ }
+ })
+
+ bt.channel.On("requestFinished", func(ev map[string]interface{}) {
+ request := fromChannel(ev["request"]).(*requestImpl)
+ response := fromNullableChannel(ev["response"])
+ page := fromNullableChannel(ev["page"])
+ request.setResponseEndTiming(ev["responseEndTiming"].(float64))
+ bt.Emit("requestfinished", request)
+ if page != nil {
+ page.(*pageImpl).Emit("requestfinished", request)
+ }
+ if response != nil {
+ close(response.(*responseImpl).finished)
+ }
+ })
+ bt.channel.On("response", func(ev map[string]interface{}) {
+ response := fromChannel(ev["response"]).(*responseImpl)
+ page := fromNullableChannel(ev["page"])
+ bt.Emit("response", response)
+ if page != nil {
+ page.(*pageImpl).Emit("response", response)
+ }
+ })
+ bt.Once("close", func() {
+ bt.closed <- struct{}{}
+ })
+ bt.setEventSubscriptionMapping(map[string]string{
+ "console": "console",
+ "dialog": "dialog",
+ "request": "request",
+ "response": "response",
+ "requestfinished": "requestFinished",
+ "responsefailed": "responseFailed",
+ })
+ return bt
+}