1package content
2
3import (
4 "strings"
5
6 "github.com/bnema/dumber/internal/application/dto"
7 "github.com/bnema/dumber/internal/application/port"
8)
9
10type browsingContextPolicy struct{}
11
12func (browsingContextPolicy) Decide(req dto.NewBrowsingContextRequest, namedContextExists bool) dto.HostDecision {
13 decision := dto.HostDecision{
14 RequestContextDisposition: req.RequestContextDisposition,
15 RequiresNativeOpener: req.RequiresNativeOpener,
16 }
17 if decision.RequestContextDisposition == "" {
18 decision.RequestContextDisposition = dto.RequestContextInheritParent
19 }
20
21 if strings.TrimSpace(req.TargetURI) == "" {
22 decision.Kind = dto.HostDecisionDeny
23 decision.Reason = "empty target URI"
24 return decision
25 }
26
27 if req.AuthIntent {
28 decision.Kind = dto.HostDecisionCreateNativeWin
29 decision.Reason = "auth intent requires native window"
30 return decision
31 }
32 if req.RequiresNativeOpener {
33 decision.Kind = dto.HostDecisionCreateNativeWin
34 decision.Reason = "native opener required"
35 return decision
36 }
37 if isAmbiguousNativeBrowsingContext(req) {
38 decision.Kind = dto.HostDecisionCreateNativeWin
39 decision.Reason = "ambiguous opener-coupled request prefers native window"
40 return decision
41 }
42
43 name := reusableBrowsingContextName(req.TargetFrameName)
44 if namedContextExists && name != "" {
45 decision.Kind = dto.HostDecisionReuseNamedPane
46 decision.ReuseContextName = name
47 decision.BrowsingContextName = name
48 decision.Reason = "reuse named browsing context in current window"
49 return decision
50 }
51
52 decision.Kind = dto.HostDecisionCreatePane
53 decision.BrowsingContextName = name
54 decision.Reason = "pane-hosted browsing context"
55 return decision
56}
57
58func buildPopupBrowsingContextRequest(req port.PopupRequest) dto.NewBrowsingContextRequest {
59 return dto.NewBrowsingContextRequest{
60 ParentWebViewID: uint64(req.ParentViewID),
61 SourceBrowserID: req.SourceBrowserID,
62 SourceFrameID: req.SourceFrameID,
63 SourceFrameURL: req.SourceFrameURL,
64 TargetURI: req.TargetURI,
65 TargetFrameName: req.FrameName,
66 TargetDisposition: inferPopupWindowDisposition(req),
67 IsUserGesture: req.IsUserGesture,
68 NoJavaScriptAccess: req.NoJavaScriptAccess,
69 WindowFeatures: req.WindowFeatures,
70 TriggerKind: inferPopupTriggerKind(req),
71 AuthIntent: IsOAuthURL(req.TargetURI),
72 RequiresNativeOpener: false,
73 RequestContextDisposition: dto.RequestContextInheritParent,
74 }
75}
76
77func buildLinkBrowsingContextRequest(parentWebViewID port.WebViewID, uri string) dto.NewBrowsingContextRequest {
78 return dto.NewBrowsingContextRequest{
79 ParentWebViewID: uint64(parentWebViewID),
80 TargetURI: uri,
81 TargetFrameName: "_blank",
82 TargetDisposition: dto.WindowDispositionNewTab,
83 IsUserGesture: true,
84 NoJavaScriptAccess: true,
85 TriggerKind: dto.TriggerLinkNewPage,
86 AuthIntent: IsOAuthURL(uri),
87 RequestContextDisposition: dto.RequestContextInheritParent,
88 }
89}
90
91func inferPopupWindowDisposition(req port.PopupRequest) dto.WindowDisposition {
92 if req.TargetDisposition != "" {
93 return req.TargetDisposition
94 }
95 if strings.EqualFold(strings.TrimSpace(req.FrameName), "_blank") {
96 return dto.WindowDispositionNewTab
97 }
98 return dto.WindowDispositionNewPopup
99}
100
101func inferPopupTriggerKind(req port.PopupRequest) dto.TriggerKind {
102 switch inferPopupWindowDisposition(req) {
103 case dto.WindowDispositionCurrentTab:
104 if reusableBrowsingContextName(req.FrameName) != "" {
105 return dto.TriggerNamedTargetNavigation
106 }
107 return dto.TriggerLinkNewPage
108 case dto.WindowDispositionNewTab:
109 if reusableBrowsingContextName(req.FrameName) != "" {
110 return dto.TriggerNamedTargetNavigation
111 }
112 return dto.TriggerLinkNewPage
113 case dto.WindowDispositionNewPopup, dto.WindowDispositionNewWindow:
114 if reusableBrowsingContextName(req.FrameName) != "" {
115 return dto.TriggerNamedTargetNavigation
116 }
117 return dto.TriggerScriptWindowOpen
118 default:
119 if reusableBrowsingContextName(req.FrameName) != "" {
120 return dto.TriggerNamedTargetNavigation
121 }
122 return dto.TriggerUnknown
123 }
124}
125
126func isAmbiguousNativeBrowsingContext(req dto.NewBrowsingContextRequest) bool {
127 return req.TriggerKind == dto.TriggerUnknown && !req.NoJavaScriptAccess
128}
129
130func reusableBrowsingContextName(frameName string) string {
131 name := strings.TrimSpace(frameName)
132 if name == "" || strings.EqualFold(name, "_blank") {
133 return ""
134 }
135 return name
136}