diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 8763d385b5b0..6b4e35a4435c 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -632,6 +632,7 @@ 61A164492CE7BE84001D6058 /* WallpaperStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A164452CE7BE1A001D6058 /* WallpaperStateTests.swift */; }; 61A1644A2CE7BE8A001D6058 /* WallpaperMiddlewareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A164472CE7BE3D001D6058 /* WallpaperMiddlewareTests.swift */; }; 61E637852D03615D00E95B63 /* LabelButtonHeaderViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E637842D03615D00E95B63 /* LabelButtonHeaderViewTests.swift */; }; + 61F7A4332D136C3A00F7317B /* RouteBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61F7A4322D136C3A00F7317B /* RouteBuilderTests.swift */; }; 630FE1352C7FB42500D9D6B2 /* NativeErrorPageViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 630FE1322C7FB42500D9D6B2 /* NativeErrorPageViewControllerTests.swift */; }; 631A369F2CC0A4FE0044DFEB /* NativeErrorPageMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631A369E2CC0A4FE0044DFEB /* NativeErrorPageMiddleware.swift */; }; 631A36A32CC0B2470044DFEB /* NativeErrorPageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631A36A22CC0B2470044DFEB /* NativeErrorPageHelper.swift */; }; @@ -6936,6 +6937,7 @@ 61B340508D4D86B6519A165C /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = "id.lproj/Default Browser.strings"; sourceTree = ""; }; 61DA4B5AB7DA505B9C992F95 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ClearPrivateDataConfirm.strings; sourceTree = ""; }; 61E637842D03615D00E95B63 /* LabelButtonHeaderViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelButtonHeaderViewTests.swift; sourceTree = ""; }; + 61F7A4322D136C3A00F7317B /* RouteBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteBuilderTests.swift; sourceTree = ""; }; 623648C2A7D09ECA31155208 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/ErrorPages.strings; sourceTree = ""; }; 625D4575B4794C49451D6990 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/HistoryPanel.strings; sourceTree = ""; }; 627F483FB45E5DA53E1D5363 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/ClearPrivateData.strings; sourceTree = ""; }; @@ -11682,6 +11684,7 @@ 21FA8FAC2AE8561C0013B815 /* TabTray */, C8E531C929E5F7D300E03FEF /* URLScannerTests.swift */, C81C66C329F00D1000F6422F /* UserActivityRouteTests.swift */, + 61F7A4322D136C3A00F7317B /* RouteBuilderTests.swift */, ); path = Coordinators; sourceTree = ""; @@ -17270,6 +17273,7 @@ C23889E32A50319A00429673 /* ShareSheetCoordinatorTests.swift in Sources */, E1312FD129D237EE008DDA85 /* NotificationSurfaceManagerTests.swift in Sources */, 21371FA228A6C4A200BC3F37 /* OnboardingTelemetryUtilityTests.swift in Sources */, + 61F7A4332D136C3A00F7317B /* RouteBuilderTests.swift in Sources */, 814B71FF2CBEDC3B001B134A /* MainMenuDetailsStateTests.swift in Sources */, 5AE371842A4DD6F50092A760 /* PasswordManagerListViewControllerSpy.swift in Sources */, 8A2825352760399B00395E66 /* KeyboardPressesHandlerTests.swift in Sources */, diff --git a/firefox-ios/Client/Coordinators/Router/RouteBuilder.swift b/firefox-ios/Client/Coordinators/Router/RouteBuilder.swift index a64838e5b944..e1ae1085ca19 100644 --- a/firefox-ios/Client/Coordinators/Router/RouteBuilder.swift +++ b/firefox-ios/Client/Coordinators/Router/RouteBuilder.swift @@ -6,7 +6,7 @@ import Foundation import CoreSpotlight import Shared -final class RouteBuilder { +final class RouteBuilder: FeatureFlaggable { private var isPrivate = false private var prefs: Prefs? @@ -158,7 +158,8 @@ final class RouteBuilder { // If the user activity has a webpageURL, it's a deep link or an old history item. // Use the URL to create a new search tab. - if let url = userActivity.webpageURL { + if let url = userActivity.webpageURL, + isBrowsingActivity(userActivity) { return .search(url: url, isPrivate: false) } @@ -206,6 +207,15 @@ final class RouteBuilder { } } + private func isBrowsingActivity(_ userActivity: NSUserActivity) -> Bool { + if featureFlags.isFeatureEnabled(.universalLinks, checking: .buildOnly) { + return userActivity.activityType == NSUserActivityTypeBrowsingWeb || + userActivity.activityType == browsingActivityType + } else { + return true + } + } + // MARK: - Telemetry private func recordTelemetry(input: DeeplinkInput.Host, isPrivate: Bool) { diff --git a/firefox-ios/Client/Entitlements/FirefoxApplication.entitlements b/firefox-ios/Client/Entitlements/FirefoxApplication.entitlements index cf1aa4e22c47..9489894774b5 100644 --- a/firefox-ios/Client/Entitlements/FirefoxApplication.entitlements +++ b/firefox-ios/Client/Entitlements/FirefoxApplication.entitlements @@ -8,6 +8,10 @@ Default + com.apple.developer.associated-domains + + applinks:blog.mozilla.org + com.apple.developer.authentication-services.autofill-credential-provider com.apple.developer.networking.multipath diff --git a/firefox-ios/Client/Entitlements/FirefoxBetaApplication.entitlements b/firefox-ios/Client/Entitlements/FirefoxBetaApplication.entitlements index 7a196f0514d3..5cbdae417177 100644 --- a/firefox-ios/Client/Entitlements/FirefoxBetaApplication.entitlements +++ b/firefox-ios/Client/Entitlements/FirefoxBetaApplication.entitlements @@ -4,6 +4,10 @@ aps-environment production + com.apple.developer.associated-domains + + applinks:blog.mozilla.org + com.apple.developer.authentication-services.autofill-credential-provider com.apple.developer.networking.multipath diff --git a/firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift b/firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift index 4f4e8a62b17e..9198500e4a64 100644 --- a/firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift +++ b/firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift @@ -45,6 +45,7 @@ enum NimbusFeatureFlagID: String, CaseIterable { case splashScreen case unifiedAds case unifiedSearch + case universalLinks case toolbarRefactor case toolbarOneTapNewTab case toolbarNavigationHint @@ -133,6 +134,7 @@ struct NimbusFlaggableFeature: HasNimbusSearchBar { .splashScreen, .unifiedAds, .unifiedSearch, + .universalLinks, .toolbarRefactor, .toolbarOneTapNewTab, .toolbarNavigationHint, diff --git a/firefox-ios/Client/Helpers/UserActivityHandler.swift b/firefox-ios/Client/Helpers/UserActivityHandler.swift index 96c25cf1bbcf..13c5b8c8fecf 100644 --- a/firefox-ios/Client/Helpers/UserActivityHandler.swift +++ b/firefox-ios/Client/Helpers/UserActivityHandler.swift @@ -11,7 +11,7 @@ import WebKit import SiteImageView import Common -private let browsingActivityType: String = "org.mozilla.ios.firefox.browsing" +let browsingActivityType: String = "org.mozilla.ios.firefox.browsing" private let searchableIndex = CSSearchableIndex.default() diff --git a/firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift b/firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift index 3e8da44582d3..a866172aac4c 100644 --- a/firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift +++ b/firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift @@ -112,6 +112,9 @@ final class NimbusFeatureFlagLayer { case .unifiedSearch: return checkUnifiedSearchFeature(from: nimbus) + case .universalLinks: + return checkUniversalLinkFeature(from: nimbus) + case .toolbarOneTapNewTab: return checkToolbarOneTapNewTabFeature(from: nimbus) @@ -225,6 +228,11 @@ final class NimbusFeatureFlagLayer { return config.unifiedSearch } + private func checkUniversalLinkFeature(from nimbus: FxNimbus) -> Bool { + let config = nimbus.features.universalLinks.value() + return config.enabled + } + private func checkToolbarOneTapNewTabFeature(from nimbus: FxNimbus) -> Bool { let config = nimbus.features.toolbarRefactorFeature.value() return config.oneTapNewTab diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/RouteBuilderTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/RouteBuilderTests.swift new file mode 100644 index 000000000000..08290c511f7d --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/RouteBuilderTests.swift @@ -0,0 +1,76 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/ + +import XCTest +@testable import Client + +class RouteBuilderTests: XCTestCase { + let testURL = URL(string: "https://example.com") + let handoffUserActivity = NSUserActivity(activityType: browsingActivityType) + let universalLinkUserActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) + let randomActivity = NSUserActivity(activityType: "random") + + override func setUp() { + super.setUp() + LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: MockProfile()) + handoffUserActivity.webpageURL = testURL + universalLinkUserActivity.webpageURL = testURL + randomActivity.webpageURL = testURL + } + func test_makeRoute_whenUniversalLinkIsDisabled_HandlesAnyACtivityType() { + setupNimbusUniversalLinksTesting(isEnabled: false) + let routeBuilder = createSubject() + + let route = routeBuilder.makeRoute( + userActivity: handoffUserActivity + ) + + let universalLinkRoute = routeBuilder.makeRoute( + userActivity: universalLinkUserActivity + ) + + let randomRoute = routeBuilder.makeRoute( + userActivity: randomActivity + ) + + XCTAssertEqual(route, .search(url: testURL, isPrivate: false)) + XCTAssertEqual(universalLinkRoute, .search(url: testURL, isPrivate: false)) + XCTAssertEqual(randomRoute, .search(url: testURL, isPrivate: false)) + } + + func test_makeRoute_whenUniversalLinkIsEnabled_handlesWebpageURLForActivityTypeBrowsingActivityAndBrowsingWeb() { + setupNimbusUniversalLinksTesting(isEnabled: true) + let routeBuilder = createSubject() + + let route = routeBuilder.makeRoute( + userActivity: handoffUserActivity + ) + + let universalLinkRoute = routeBuilder.makeRoute( + userActivity: universalLinkUserActivity + ) + + let randomRoute = routeBuilder.makeRoute( + userActivity: randomActivity + ) + + XCTAssertEqual(route, .search(url: testURL, isPrivate: false)) + XCTAssertEqual(universalLinkRoute, .search(url: testURL, isPrivate: false)) + XCTAssertNil(randomRoute) + } + + private func createSubject() -> RouteBuilder { + let subject = RouteBuilder() + trackForMemoryLeaks(subject) + return subject + } + + private func setupNimbusUniversalLinksTesting(isEnabled: Bool) { + FxNimbus.shared.features.universalLinks.with { _, _ in + return UniversalLinks( + enabled: isEnabled + ) + } + } +} diff --git a/firefox-ios/nimbus-features/universalLinks.yaml b/firefox-ios/nimbus-features/universalLinks.yaml new file mode 100644 index 000000000000..067e5e248a32 --- /dev/null +++ b/firefox-ios/nimbus-features/universalLinks.yaml @@ -0,0 +1,18 @@ +# The configuration for the universalLinks feature +features: + universal-links: + description: > + This feature is for managing the roll out of universal link support in the iOS App + variables: + enabled: + description: > + If true, enables the feature + type: Boolean + default: false + defaults: + - channel: beta + value: + enabled: false + - channel: developer + value: + enabled: true diff --git a/firefox-ios/nimbus.fml.yaml b/firefox-ios/nimbus.fml.yaml index 6158b4259ddd..8dbaa61ef64c 100644 --- a/firefox-ios/nimbus.fml.yaml +++ b/firefox-ios/nimbus.fml.yaml @@ -44,4 +44,5 @@ include: - nimbus-features/tosFeature.yaml - nimbus-features/trackingProtectionRefactor.yaml - nimbus-features/unifiedAds.yaml + - nimbus-features/universalLinks.yaml - nimbus-features/zoomFeature.yaml