Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions firefox-ios/Ecosia/UI/Account/EcosiaWebViewModal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ private struct WebViewRepresentable: UIViewRepresentable {

let webView = WKWebView(frame: .zero, configuration: configuration)
webView.navigationDelegate = context.coordinator
webView.uiDelegate = context.coordinator
webView.allowsBackForwardNavigationGestures = true

if let userAgent = userAgent {
Expand All @@ -153,22 +154,23 @@ private struct WebViewRepresentable: UIViewRepresentable {
}

func updateUIView(_ uiView: WKWebView, context: Context) {
// No updates needed
// No-op: WebView state is managed through bindings and coordinator
}

func makeCoordinator() -> Coordinator {
Coordinator(self, retryCount: retryCount)
Coordinator(parent: self)
}

class Coordinator: NSObject, WKNavigationDelegate {
class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
let parent: WebViewRepresentable
private let retryCount: Int
private var remainingRetries = 0
private var blankTargetURLs: Set<String> = []

init(_ parent: WebViewRepresentable, retryCount: Int) {
init(parent: WebViewRepresentable) {
self.parent = parent
self.retryCount = retryCount
remainingRetries = retryCount
self.retryCount = parent.retryCount
self.remainingRetries = parent.retryCount
}

func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
Expand All @@ -190,6 +192,24 @@ private struct WebViewRepresentable: UIViewRepresentable {
return
}

// If this is a back navigation to a page we loaded from a target="_blank" link,
// prevent it and go back further (to the page before the blank link was clicked)
if navigationAction.navigationType == .backForward,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WebView delegates carry quite some logic. Can you please add unit test coverage making sure the logic works? As well to the addition below? Thank you

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added UnitTest! The only way I could do it was to mock the original implementation back into our tests, which will give us a reminder to update the mocked logic in case we update the main logic (thought that's what PR checklist is for 👍)

blankTargetURLs.contains(url.absoluteString) {
EcosiaLogger.auth.debug("🔐 [WEBVIEW] Preventing navigation back to target='_blank' origin: \(url)")
decisionHandler(.cancel)

// Keep going back to escape the blank-target page
if webView.canGoBack {
webView.goBack()
} else {
// If we can't go back, reload the initial URL (root)
webView.load(URLRequest(url: parent.url))
blankTargetURLs.removeAll()
}
return
}

let interceptor = EcosiaURLInterceptor()
if interceptor.interceptedType(for: url) == .signIn,
let redirectURL = EcosiaAuthRedirector.redirectURLForSignIn(url, redirectURLString: parent.redirectURLString) {
Expand Down Expand Up @@ -218,6 +238,34 @@ private struct WebViewRepresentable: UIViewRepresentable {
parent.hasError = true
parent.errorMessage = error.localizedDescription
}

// MARK: - WKUIDelegate

func webView(
_ webView: WKWebView,
createWebViewWith configuration: WKWebViewConfiguration,
for navigationAction: WKNavigationAction,
windowFeatures: WKWindowFeatures
) -> WKWebView? {
// When targetFrame is nil, it means the link should open in a new tab/window
// (e.g., target="_blank" or window.open())
guard navigationAction.targetFrame == nil,
let url = navigationAction.request.url else {
return nil
}

// Record the current page before loading the blank target
if let currentURL = webView.url {
blankTargetURLs.insert(currentURL.absoluteString)
EcosiaLogger.auth.info("🔐 [WEBVIEW] Recorded blank-target origin: \(currentURL)")
}

EcosiaLogger.auth.info("🔐 [WEBVIEW] Loading target='_blank' URL in modal: \(url)")
webView.load(URLRequest(url: url))

// Return nil to prevent creating a new window
return nil
}
}
}

Expand Down
Loading