From 6b208f3f4e1d3e268a161e00013e8f832fa3dd5f Mon Sep 17 00:00:00 2001 From: Anh Do Date: Thu, 5 Dec 2024 20:25:09 -0500 Subject: [PATCH 1/4] Handoff prototype --- DuckDuckGo/AppDelegate.swift | 13 +++++++++ DuckDuckGo/Info.plist | 1 + DuckDuckGo/MainViewController.swift | 16 ++++++++++- DuckDuckGo/TabViewController.swift | 42 ++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 0ec3c93bf1..7ccf5eed2f 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -721,6 +721,8 @@ import os.log } AppDependencyProvider.shared.persistentPixel.sendQueuedPixels { _ in } + + mainViewController?.currentTab?.becomeCurrentActivity() } private func stopAndRemoveVPNIfNotAuthenticated() async { @@ -921,6 +923,17 @@ import os.log return true } + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool { + guard userActivity.activityType == "com.duckduckgo.mobile.ios.web-browsing", + let currentTab = mainViewController?.currentTab else { + return false + } + + restorationHandler([currentTab]) + + return true + } + // MARK: private private func sendAppLaunchPostback() { diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 6d3941b0ca..3df47d72d9 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -192,6 +192,7 @@ NSUserActivityTypes ConfigurationIntent + com.duckduckgo.mobile.ios.web-browsing UIApplicationShortcutItems diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index e5b54dca00..57d08408b8 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1055,6 +1055,7 @@ class MainViewController: UIViewController { hideNotificationBarIfBrokenSitePromptShown() if tab.link == nil { attachHomeScreen() + invalidateCurrentActivity() } else { attachTab(tab: tab) refreshControls() @@ -1072,7 +1073,7 @@ class MainViewController: UIViewController { hideNotificationBarIfBrokenSitePromptShown() currentTab?.progressWorker.progressBar = nil currentTab?.chromeDelegate = nil - + addToContentContainer(controller: tab) viewCoordinator.logoContainer.isHidden = true @@ -1080,6 +1081,7 @@ class MainViewController: UIViewController { tab.progressWorker.progressBar = viewCoordinator.progress chromeManager.attach(to: tab.webView.scrollView) tab.chromeDelegate = self + tab.becomeCurrentActivity() } private func addToContentContainer(controller: UIViewController) { @@ -1464,6 +1466,8 @@ class MainViewController: UIViewController { tabsBarController?.refresh(tabsModel: tabManager.model) swipeTabsCoordinator?.refresh(tabsModel: tabManager.model, scrollToSelected: true) newTabPageViewController?.openedAsNewTab(allowingKeyboard: allowingKeyboard) + + invalidateCurrentActivity() } func updateFindInPage() { @@ -2962,3 +2966,13 @@ extension MainViewController: AIChatViewControllerDelegate { loadUrlInNewTab(url, inheritedAttribution: nil) } } + +// NSUserActivity-related +extension MainViewController { + private func invalidateCurrentActivity() { + userActivity?.invalidate() + userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) + userActivity?.webpageURL = nil + userActivity?.becomeCurrent() + } +} diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 24dddd1628..3442c7e580 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -211,6 +211,7 @@ class TabViewController: UIViewController { updateTabModel() delegate?.tabLoadingStateDidChange(tab: self) checkLoginDetectionAfterNavigation() + updateCurrentActivity(url: url) } } @@ -274,7 +275,7 @@ class TabViewController: UIViewController { manager.delegate = self return manager }() - + private static let debugEvents = EventMapping { event, _, _, onComplete in let domainEvent: Pixel.Event switch event { @@ -3182,3 +3183,42 @@ extension TabViewController: DuckPlayerTabNavigationHandling { } } + +// NSUserActivity-related +extension TabViewController { + func becomeCurrentActivity() { + if userActivity?.webpageURL == nil { + userActivity?.invalidate() + userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) + userActivity?.webpageURL = nil + } + + userActivity?.becomeCurrent() + } + + private func updateCurrentActivity(url: URL?) { + let newURL: URL? = { + guard let url, let scheme = url.scheme, ["http", "https"].contains(scheme) else { return nil } + return url.isDuckDuckGo ? url.removingInternalSearchParameters() : url + }() + guard newURL != userActivity?.webpageURL else { return } + + userActivity?.invalidate() + if newURL != nil { + userActivity = NSUserActivity(activityType: "com.duckduckgo.mobile.ios.web-browsing") + } else { + userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) + } + userActivity?.webpageURL = newURL + + userActivity?.becomeCurrent() + } + + override func restoreUserActivityState(_ activity: NSUserActivity) { + guard activity.activityType == "com.duckduckgo.mobile.ios.web-browsing", let url = activity.webpageURL else { + return + } + + openTab(for: url) + } +} From f95ab234c0a552fe9494a18f618740bbf79e5279 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Fri, 6 Dec 2024 01:25:38 -0500 Subject: [PATCH 2/4] Issue with empty browser --- DuckDuckGo/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 7ccf5eed2f..8194bf5b88 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -925,7 +925,7 @@ import os.log func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool { guard userActivity.activityType == "com.duckduckgo.mobile.ios.web-browsing", - let currentTab = mainViewController?.currentTab else { + let currentTab = mainViewController?.tabManager.current(createIfNeeded: true) else { return false } From 48ce950e3ecdb98b0ace5931fc8fa84584f286ed Mon Sep 17 00:00:00 2001 From: Anh Do Date: Fri, 6 Dec 2024 01:43:20 -0500 Subject: [PATCH 3/4] Better way to open URL --- DuckDuckGo/AppDelegate.swift | 4 ++-- DuckDuckGo/MainViewController.swift | 8 ++++++++ DuckDuckGo/TabViewController.swift | 8 -------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 8194bf5b88..da568f09af 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -925,11 +925,11 @@ import os.log func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool { guard userActivity.activityType == "com.duckduckgo.mobile.ios.web-browsing", - let currentTab = mainViewController?.tabManager.current(createIfNeeded: true) else { + let mainViewController else { return false } - restorationHandler([currentTab]) + restorationHandler([mainViewController]) return true } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 57d08408b8..acdc541d2a 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -2975,4 +2975,12 @@ extension MainViewController { userActivity?.webpageURL = nil userActivity?.becomeCurrent() } + + override func restoreUserActivityState(_ activity: NSUserActivity) { + guard activity.activityType == "com.duckduckgo.mobile.ios.web-browsing", let url = activity.webpageURL else { + return + } + + loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: nil) + } } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 3442c7e580..74b5997a10 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -3213,12 +3213,4 @@ extension TabViewController { userActivity?.becomeCurrent() } - - override func restoreUserActivityState(_ activity: NSUserActivity) { - guard activity.activityType == "com.duckduckgo.mobile.ios.web-browsing", let url = activity.webpageURL else { - return - } - - openTab(for: url) - } } From 373fae2971e54cd831eab445a97eb2e4f602e355 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Fri, 6 Dec 2024 10:09:33 -0500 Subject: [PATCH 4/4] Internal users only --- Core/FeatureFlag.swift | 3 +++ DuckDuckGo/MainViewController.swift | 8 +++++++- DuckDuckGo/TabViewController.swift | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index 8bf183dafd..272e4ea794 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -47,6 +47,7 @@ public enum FeatureFlag: String { case autcompleteTabs case textZoom case adAttributionReporting + case handoff /// https://app.asana.com/0/72649045549333/1208231259093710/f case networkProtectionUserTips @@ -127,6 +128,8 @@ extension FeatureFlag: FeatureFlagDescribing { return .remoteReleasable(.feature(.autofillSurveys)) case .autcompleteTabs: return .remoteReleasable(.feature(.autocompleteTabs)) + case .handoff: + return .internalOnly() case .networkProtectionUserTips: return .remoteReleasable(.subfeature(NetworkProtectionSubfeature.userTips)) case .textZoom: diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index acdc541d2a..094b4eb5c7 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -2970,6 +2970,8 @@ extension MainViewController: AIChatViewControllerDelegate { // NSUserActivity-related extension MainViewController { private func invalidateCurrentActivity() { + guard supportsHandoff() else { return } + userActivity?.invalidate() userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) userActivity?.webpageURL = nil @@ -2977,10 +2979,14 @@ extension MainViewController { } override func restoreUserActivityState(_ activity: NSUserActivity) { - guard activity.activityType == "com.duckduckgo.mobile.ios.web-browsing", let url = activity.webpageURL else { + guard supportsHandoff(), activity.activityType == "com.duckduckgo.mobile.ios.web-browsing", let url = activity.webpageURL else { return } loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: nil) } + + private func supportsHandoff() -> Bool { + featureFlagger.isFeatureOn(.handoff) + } } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 74b5997a10..8d59725d76 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -3187,6 +3187,8 @@ extension TabViewController: DuckPlayerTabNavigationHandling { // NSUserActivity-related extension TabViewController { func becomeCurrentActivity() { + guard supportsHandoff() else { return } + if userActivity?.webpageURL == nil { userActivity?.invalidate() userActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) @@ -3197,6 +3199,8 @@ extension TabViewController { } private func updateCurrentActivity(url: URL?) { + guard supportsHandoff() else { return } + let newURL: URL? = { guard let url, let scheme = url.scheme, ["http", "https"].contains(scheme) else { return nil } return url.isDuckDuckGo ? url.removingInternalSearchParameters() : url @@ -3213,4 +3217,8 @@ extension TabViewController { userActivity?.becomeCurrent() } + + private func supportsHandoff() -> Bool { + featureFlagger.isFeatureOn(.handoff) + } }