Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fix URLs not opening in active-tab
Browse files Browse the repository at this point in the history
Fix spamming error alerts

Block synthetic clicks. Also do not allow the error alert to show above the suppression alert in Z-Order.

Get rid of main-frame check. Universal links can come from any frame.
Use tab visibility check so that in-active or backgrounded tabs cannot show alerts or open universal links.
  • Loading branch information
Brandon-T committed Nov 30, 2023
1 parent 4635cec commit 45df6b6
Showing 1 changed file with 148 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -179,32 +179,28 @@ extension BrowserViewController: WKNavigationDelegate {
let tab = tab(for: webView)

if ["sms", "tel", "facetime", "facetime-audio"].contains(requestURL.scheme) {
// Do not allow opening external URLs from child tabs
handleExternalURL(requestURL, tab: tab, navigationAction: navigationAction)
return (.cancel, preferences)
let shouldOpen = await handleExternalURL(requestURL, tab: tab, navigationAction: navigationAction)
return (shouldOpen ? .allow : .cancel, preferences)
}

// Second special case are a set of URLs that look like regular http links, but should be handed over to iOS
// instead of being loaded in the webview.
// In addition we are exchaging actual scheme with "maps" scheme
// So the Apple maps URLs will open properly
if let mapsURL = isAppleMapsURL(requestURL), mapsURL.enabled {
// Do not allow opening external URLs from child tabs
handleExternalURL(mapsURL.url, tab: tab, navigationAction: navigationAction)
return (.cancel, preferences)
let shouldOpen = await handleExternalURL(mapsURL.url, tab: tab, navigationAction: navigationAction)
return (shouldOpen ? .allow : .cancel, preferences)
}

if isStoreURL(requestURL) {
// Do not allow opening external URLs from child tabs
handleExternalURL(requestURL, tab: tab, navigationAction: navigationAction)
return (.cancel, preferences)
let shouldOpen = await handleExternalURL(requestURL, tab: tab, navigationAction: navigationAction)
return (shouldOpen ? .allow : .cancel, preferences)
}

// Handles custom mailto URL schemes.
if requestURL.scheme == "mailto" {
// Do not allow opening external URLs from child tabs
handleExternalURL(requestURL, tab: tab, navigationAction: navigationAction)
return (.cancel, preferences)
let shouldOpen = await handleExternalURL(requestURL, tab: tab, navigationAction: navigationAction)
return (shouldOpen ? .allow : .cancel, preferences)
}

// handles IPFS URL schemes
Expand Down Expand Up @@ -406,15 +402,29 @@ extension BrowserViewController: WKNavigationDelegate {
// Our own 'brave' scheme does not require the switch-app prompt.
if requestURL.scheme?.contains("brave") == false {
// Do not allow opening external URLs from child tabs
handleExternalURL(requestURL, tab: tab, navigationAction: navigationAction) { didOpenURL in
// Do not show error message for JS navigated links or redirect
// as it's not the result of a user action.
if !didOpenURL, navigationAction.navigationType == .linkActivated {
let alert = UIAlertController(title: Strings.unableToOpenURLErrorTitle, message: Strings.unableToOpenURLError, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: Strings.OKString, style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
let shouldOpen = await handleExternalURL(requestURL, tab: tab, navigationAction: navigationAction)
let isSyntheticClick = navigationAction.responds(to: Selector(("_syntheticClickType"))) &&
navigationAction.value(forKey: "syntheticClickType") as? Int == 0

// Do not show error message for JS navigated links or redirect
// as it's not the result of a user action.
if !shouldOpen, navigationAction.navigationType == .linkActivated && !isSyntheticClick {
if self.presentedViewController == nil &&
self.presentingViewController == nil &&
tab?.isExternalAppAlertPresented == false &&
tab?.isExternalAppAlertSuppressed == false {

return await withCheckedContinuation { continuation in
let alert = UIAlertController(title: Strings.unableToOpenURLErrorTitle, message: Strings.unableToOpenURLError, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: Strings.OKString, style: .default, handler: nil))
self.present(alert, animated: true) {
continuation.resume(returning: (shouldOpen ? .allow : .cancel, preferences))
}
}
}
}

return (shouldOpen ? .allow : .cancel, preferences)
}

return (.cancel, preferences)
Expand Down Expand Up @@ -797,125 +807,139 @@ extension BrowserViewController {
private func handleExternalURL(
_ url: URL,
tab: Tab?,
navigationAction: WKNavigationAction,
openedURLCompletionHandler: ((Bool) -> Void)? = nil
) {
// Do not open external links for child tabs automatically
// The user must tap on the link to open it.
if tab?.parent != nil && navigationAction.navigationType != .linkActivated {
return
}

// Check if the current url of the caller has changed
if let domain = tab?.url?.baseDomain,
domain != tab?.externalAppURLDomain {
tab?.externalAppAlertCounter = 0
tab?.isExternalAppAlertSuppressed = false
}
tab?.externalAppURLDomain = tab?.url?.baseDomain

// Do not try to present over existing warning
if tab?.isExternalAppAlertPresented == true || tab?.isExternalAppAlertSuppressed == true {
return
}

// We do not schemes to be opened externally when called from subframes.
// And external dialog should not be shown for non-active tabs #6687 - #7835

// Check navigation action is from main frame as first step
let isMainFrame = navigationAction.targetFrame?.isMainFrame == true

// Check user trying to open on NTP like external link browsing
var isAboutHome = false
if let url = tab?.url {
isAboutHome = InternalURL(url)?.isAboutHomeURL == true
}

// Finally check non-active tab
let isNonActiveTab = isAboutHome ? false : tab?.url?.host != topToolbar.currentURL?.host

if !isMainFrame || isNonActiveTab {
return
}

var alertTitle = Strings.openExternalAppURLGenericTitle

if let displayHost = tab?.url?.withoutWWW.host {
alertTitle = String(format: Strings.openExternalAppURLTitle, displayHost)
}

// Handling condition when Tab is empty when handling an external URL we should remove the tab once the user decides
let removeTabIfEmpty = { [weak self] in
if let tab = tab, tab.url == nil {
self?.tabManager.removeTab(tab)
navigationAction: WKNavigationAction) async -> Bool {
// Do not open external links for child tabs automatically
// The user must tap on the link to open it.
if tab?.parent != nil && navigationAction.navigationType != .linkActivated {
return false
}
}

// Show the external sceheme invoke alert
func showExternalSchemeAlert(isSuppressActive: Bool) {
// Check if active controller is bvc otherwise do not show show external sceheme alerts
guard shouldShowExternalSchemeAlert() else { return }

view.endEditing(true)
tab?.isExternalAppAlertPresented = true

let popup = AlertPopupView(
imageView: nil,
title: alertTitle,
message: String(format: Strings.openExternalAppURLMessage, url.relativeString),
titleWeight: .semibold,
titleSize: 21
)
if isSuppressActive {
popup.addButton(title: Strings.suppressAlertsActionTitle, type: .destructive) { [weak tab] () -> PopupViewDismissType in
tab?.isExternalAppAlertSuppressed = true
return .flyDown
// Check if the current url of the caller has changed
if let domain = tab?.url?.baseDomain,
domain != tab?.externalAppURLDomain {
tab?.externalAppAlertCounter = 0
tab?.isExternalAppAlertSuppressed = false
}

tab?.externalAppURLDomain = tab?.url?.baseDomain

// Do not try to present over existing warning
if tab?.isExternalAppAlertPresented == true || tab?.isExternalAppAlertSuppressed == true {
return false
}

// External dialog should not be shown for non-active tabs #6687 - #7835
let isVisibleTab = tab?.isTabVisible() == true

// Check user trying to open on NTP like external link browsing
var isAboutHome = false
if let url = tab?.url {
isAboutHome = InternalURL(url)?.isAboutHomeURL == true
}

// Finally check non-active tab
let isNonActiveTab = isAboutHome ? false : tab?.url?.host != topToolbar.currentURL?.host

if !isVisibleTab || isNonActiveTab {
return false
}

var alertTitle = Strings.openExternalAppURLGenericTitle

if let displayHost = tab?.url?.withoutWWW.host {
alertTitle = String(format: Strings.openExternalAppURLTitle, displayHost)
}

// Handling condition when Tab is empty when handling an external URL we should remove the tab once the user decides
let removeTabIfEmpty = { [weak self] in
if let tab = tab, tab.url == nil {
self?.tabManager.removeTab(tab)
}
} else {
popup.addButton(title: Strings.openExternalAppURLDontAllow) { [weak tab] () -> PopupViewDismissType in
}

// Show the external sceheme invoke alert
@MainActor
func showExternalSchemeAlert(isSuppressActive: Bool, openedURLCompletionHandler: @escaping (Bool) -> Void) {
// Check if active controller is bvc otherwise do not show show external sceheme alerts
guard shouldShowExternalSchemeAlert() else {
openedURLCompletionHandler(false)
return
}

view.endEditing(true)
tab?.isExternalAppAlertPresented = true

let popup = AlertPopupView(
imageView: nil,
title: alertTitle,
message: String(format: Strings.openExternalAppURLMessage, url.relativeString),
titleWeight: .semibold,
titleSize: 21
)

if isSuppressActive {
popup.addButton(title: Strings.suppressAlertsActionTitle, type: .destructive) { [weak tab] () -> PopupViewDismissType in
openedURLCompletionHandler(false)
tab?.isExternalAppAlertSuppressed = true
return .flyDown
}
} else {
popup.addButton(title: Strings.openExternalAppURLDontAllow) { [weak tab] () -> PopupViewDismissType in
openedURLCompletionHandler(false)
removeTabIfEmpty()
tab?.isExternalAppAlertPresented = false
return .flyDown
}
}
popup.addButton(title: Strings.openExternalAppURLAllow, type: .primary) { [weak tab] () -> PopupViewDismissType in
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:]) { didOpen in
openedURLCompletionHandler(!didOpen)
}
} else {
openedURLCompletionHandler(true)
}
removeTabIfEmpty()
tab?.isExternalAppAlertPresented = false
return .flyDown
}
}
popup.addButton(title: Strings.openExternalAppURLAllow, type: .primary) { [weak tab] () -> PopupViewDismissType in
UIApplication.shared.open(url, options: [:], completionHandler: openedURLCompletionHandler)
removeTabIfEmpty()
tab?.isExternalAppAlertPresented = false
return .flyDown
}
popup.showWithType(showType: .flyUp)
}

func shouldShowExternalSchemeAlert() -> Bool {
guard let rootVC = currentScene?.browserViewController else {
return false
popup.showWithType(showType: .flyUp)
}

func topViewController(startingFrom viewController: UIViewController) -> UIViewController {
var top = viewController
if let navigationController = top as? UINavigationController,
let vc = navigationController.visibleViewController {
return topViewController(startingFrom: vc)
}
if let tabController = top as? UITabBarController,
let vc = tabController.selectedViewController {
return topViewController(startingFrom: vc)
func shouldShowExternalSchemeAlert() -> Bool {
guard let rootVC = currentScene?.browserViewController else {
return false
}
while let next = top.presentedViewController {
top = next

func topViewController(startingFrom viewController: UIViewController) -> UIViewController {
var top = viewController
if let navigationController = top as? UINavigationController,
let vc = navigationController.visibleViewController {
return topViewController(startingFrom: vc)
}
if let tabController = top as? UITabBarController,
let vc = tabController.selectedViewController {
return topViewController(startingFrom: vc)
}
while let next = top.presentedViewController {
top = next
}
return top
}
return top

let isTopController = self == topViewController(startingFrom: rootVC)
let isTopWindow = view.window?.isKeyWindow == true
return isTopController && isTopWindow
}

let isTopController = self == topViewController(startingFrom: rootVC)
let isTopWindow = view.window?.isKeyWindow == true
return isTopController && isTopWindow
tab?.externalAppAlertCounter += 1

return await withCheckedContinuation { continuation in
showExternalSchemeAlert(isSuppressActive: tab?.externalAppAlertCounter ?? 0 > 2) {
continuation.resume(with: .success($0))
}
}
}

tab?.externalAppAlertCounter += 1
showExternalSchemeAlert(isSuppressActive: tab?.externalAppAlertCounter ?? 0 > 2)
}
}

// MARK: WKUIDelegate
Expand Down

0 comments on commit 45df6b6

Please sign in to comment.