diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 340bdd7f0cdb..f4614fb19749 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -21576,7 +21576,7 @@ repositoryURL = "https://github.com/mozilla/rust-components-swift.git"; requirement = { kind = exactVersion; - version = 127.0.20240417050328; + version = 127.0.20240424134628; }; }; 435C85EE2788F4D00072B526 /* XCRemoteSwiftPackageReference "glean-swift" */ = { @@ -21584,7 +21584,7 @@ repositoryURL = "https://github.com/mozilla/glean-swift"; requirement = { kind = exactVersion; - version = 59.0.0; + version = 60.0.0; }; }; 4368F83B279669690013419B /* XCRemoteSwiftPackageReference "SnapKit" */ = { diff --git a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cfd6eed8c61a..7ed1afa517c5 100644 --- a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mozilla/glean-swift", "state" : { - "revision" : "fd5a73d20b519517a7840526266ef20901aedbcb", - "version" : "59.0.0" + "revision" : "227b9d80062eb6921963fac1899a25fff6babe02", + "version" : "60.0.0" } }, { @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mozilla/rust-components-swift.git", "state" : { - "revision" : "6fc91097ce72934b00fe1fa22de2f0020bfa896d", - "version" : "127.0.20240417050328" + "revision" : "86be6a173c32ee2403f3cd61bbdb8c39c6e0152b", + "version" : "127.0.20240424134628" } }, { diff --git a/firefox-ios/Client/Application/SceneDelegate.swift b/firefox-ios/Client/Application/SceneDelegate.swift index 8492d18c95bd..7750b7fc7a3f 100644 --- a/firefox-ios/Client/Application/SceneDelegate.swift +++ b/firefox-ios/Client/Application/SceneDelegate.swift @@ -70,7 +70,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard !AppConstants.isRunningUnitTest else { return } // Resume previously stopped downloads for, and on, THIS scene only. - downloadQueue.resumeAll() + if let uuid = sceneCoordinator?.windowUUID { + downloadQueue.resumeAll(for: uuid) + } } // MARK: - Transitioning to Background @@ -80,7 +82,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { /// Use this method to reduce the scene's memory usage, clear claims to resources & dependencies / services. /// UIKit takes a snapshot of the scene for the app switcher after this method returns. func sceneDidEnterBackground(_ scene: UIScene) { - downloadQueue.pauseAll() + if let uuid = sceneCoordinator?.windowUUID { + downloadQueue.pauseAll(for: uuid) + } } // MARK: - Opening URLs diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift index 0d06420c5242..4b79a158542c 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift @@ -56,10 +56,6 @@ class BrowserCoordinator: BaseCoordinator, self.glean = glean super.init(router: router) - // TODO [7856]: Additional telemetry updates forthcoming once iPad multi-window enabled. - // For now, we only have a single BVC and TabManager. Plug it into our TelemetryWrapper: - TelemetryWrapper.shared.defaultTabManager = tabManager - browserViewController.browserDelegate = self browserViewController.navigationHandler = self tabManager.addDelegate(self) diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+DownloadQueueDelegate.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+DownloadQueueDelegate.swift index e05b49b4d806..cf455e2b082e 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+DownloadQueueDelegate.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+DownloadQueueDelegate.swift @@ -8,6 +8,10 @@ import Shared extension BrowserViewController: DownloadQueueDelegate { func downloadQueue(_ downloadQueue: DownloadQueue, didStartDownload download: Download) { + // For now, each window handles its downloads independently; ignore any messages for other windows' downloads. + let uuid = windowUUID + guard download.originWindow == uuid else { return } + // If no other download toast is shown, create a new download toast and show it. guard let downloadToast = self.downloadToast else { let downloadToast = DownloadToast(download: download, @@ -19,7 +23,7 @@ extension BrowserViewController: DownloadQueueDelegate { // Handle download cancellation if buttonPressed, !downloadQueue.isEmpty { - downloadQueue.cancelAll() + downloadQueue.cancelAll(for: uuid) SimpleToast().showAlertWithText(.DownloadCancelledToastLabelText, bottomContainer: self.contentContainer, @@ -47,12 +51,14 @@ extension BrowserViewController: DownloadQueueDelegate { func downloadQueue(_ downloadQueue: DownloadQueue, didCompleteWithError error: Error?) { guard let downloadToast = self.downloadToast, - let download = downloadToast.downloads.first + let download = downloadToast.downloads.first, + download.originWindow == windowUUID else { return } DispatchQueue.main.async { downloadToast.dismiss(false) + // We only care about download errors specific to our window's downloads if error == nil { let viewModel = ButtonToastViewModel(labelText: download.filename, imageName: StandardImageIdentifiers.Large.checkmark, diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift index 9c24d5712419..f08e102d3534 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift @@ -743,7 +743,8 @@ extension BrowserViewController: WKNavigationDelegate { } // Open our helper and cancel this response from the webview. - if let downloadViewModel = downloadHelper.downloadViewModel(okAction: downloadAction) { + if let downloadViewModel = downloadHelper.downloadViewModel(windowUUID: windowUUID, + okAction: downloadAction) { presentSheetWith(viewModel: downloadViewModel, on: self, from: urlBar) } decisionHandler(.cancel) diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index ec4ccba0c3f2..cc2e30fba7a9 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -271,7 +271,7 @@ class BrowserViewController: UIViewController, screenshotHelper = ScreenshotHelper(controller: self) tabManager.addDelegate(self) tabManager.addNavigationDelegate(self) - downloadQueue.delegate = self + downloadQueue.addDelegate(self) let tabWindowUUID = tabManager.windowUUID AppEventQueue.wait(for: [.startupFlowComplete, .tabRestoration(tabWindowUUID)]) { [weak self] in // Ensure we call into didBecomeActive at least once during startup flow (if needed) @@ -501,7 +501,7 @@ class BrowserViewController: UIViewController, private func nightModeUpdates() { if NightModeHelper.isActivated(), !featureFlags.isFeatureEnabled(.nightMode, checking: .buildOnly) { - NightModeHelper.turnOff(tabManager: tabManager) + NightModeHelper.turnOff() themeManager.reloadTheme(for: windowUUID) } diff --git a/firefox-ios/Client/Frontend/Browser/DownloadQueue.swift b/firefox-ios/Client/Frontend/Browser/DownloadQueue.swift index a249242d4109..5feaa8e8a38c 100644 --- a/firefox-ios/Client/Frontend/Browser/DownloadQueue.swift +++ b/firefox-ios/Client/Frontend/Browser/DownloadQueue.swift @@ -4,6 +4,7 @@ import Foundation import WebKit +import Shared protocol DownloadDelegate: AnyObject { func download(_ download: Download, didCompleteWithError error: Error?) @@ -16,16 +17,18 @@ class Download: NSObject { fileprivate(set) var filename: String fileprivate(set) var mimeType: String + let originWindow: WindowUUID fileprivate(set) var isComplete = false fileprivate(set) var totalBytesExpected: Int64? fileprivate(set) var bytesDownloaded: Int64 - override init() { + init(originWindow: WindowUUID) { self.filename = "unknown" self.mimeType = "application/octet-stream" + self.originWindow = originWindow self.bytesDownloaded = 0 super.init() @@ -83,7 +86,10 @@ class HTTPDownload: Download { return string.components(separatedBy: allowed.inverted).joined() } - init?(cookieStore: WKHTTPCookieStore, preflightResponse: URLResponse, request: URLRequest) { + init?(originWindow: WindowUUID, + cookieStore: WKHTTPCookieStore, + preflightResponse: URLResponse, + request: URLRequest) { self.cookieStore = cookieStore self.preflightResponse = preflightResponse @@ -99,7 +105,7 @@ class HTTPDownload: Download { guard let scheme = self.request.url?.scheme else { return nil } guard scheme == "http" || scheme == "https" else { return nil } - super.init() + super.init(originWindow: originWindow) if let filename = preflightResponse.suggestedFilename { self.filename = HTTPDownload.stripUnicode(fromFilename: filename) @@ -182,10 +188,10 @@ extension HTTPDownload: URLSessionTaskDelegate, URLSessionDownloadDelegate { class BlobDownload: Download { private let data: Data - init(filename: String, mimeType: String, size: Int64, data: Data) { + init(originWindow: WindowUUID, filename: String, mimeType: String, size: Int64, data: Data) { self.data = data - super.init() + super.init(originWindow: originWindow) self.filename = filename self.mimeType = mimeType @@ -211,6 +217,7 @@ class BlobDownload: Download { } protocol DownloadQueueDelegate: AnyObject { + var windowUUID: WindowUUID { get } func downloadQueue(_ downloadQueue: DownloadQueue, didStartDownload download: Download) func downloadQueue( _ downloadQueue: DownloadQueue, @@ -221,94 +228,141 @@ protocol DownloadQueueDelegate: AnyObject { func downloadQueue(_ downloadQueue: DownloadQueue, didCompleteWithError error: Error?) } +class WeakDownloadQueueDelegate { + private(set) weak var delegate: DownloadQueueDelegate? + init(delegate: DownloadQueueDelegate? = nil) { self.delegate = delegate } +} + +struct DownloadProgress { + var bytesDownloaded: Int64 + var totalExpected: Int64 +} + class DownloadQueue { var downloads: [Download] - weak var delegate: DownloadQueueDelegate? + private var delegates = [WeakDownloadQueueDelegate]() var isEmpty: Bool { return downloads.isEmpty } - fileprivate var combinedBytesDownloaded: Int64 = 0 - fileprivate var combinedTotalBytesExpected: Int64? - fileprivate var lastDownloadError: Error? + fileprivate var downloadProgress: [WindowUUID: DownloadProgress] = [:] + fileprivate var downloadErrors: [WindowUUID: Error] = [:] init() { self.downloads = [] } + func addDelegate(_ delegate: DownloadQueueDelegate) { + self.cleanUpDelegates() + delegates.append(WeakDownloadQueueDelegate(delegate: delegate)) + } + + func removeDelegate(_ delegate: DownloadQueueDelegate) { + delegates.removeAll(where: { $0.delegate === delegate || $0.delegate == nil }) + } + func enqueue(_ download: Download) { // Clear the download stats if the queue was empty at the start. - if downloads.isEmpty { - combinedBytesDownloaded = 0 - combinedTotalBytesExpected = 0 - lastDownloadError = nil + let uuid = download.originWindow + if downloads(for: uuid).isEmpty { + downloadProgress[uuid] = nil + downloadErrors[uuid] = nil } downloads.append(download) download.delegate = self - if let totalBytesExpected = download.totalBytesExpected, combinedTotalBytesExpected != nil { - combinedTotalBytesExpected! += totalBytesExpected + if let totalBytesExpected = download.totalBytesExpected { + var progress = downloadProgress[uuid] ?? DownloadProgress(bytesDownloaded: 0, totalExpected: 0) + progress.totalExpected += totalBytesExpected + downloadProgress[uuid] = progress } else { - combinedTotalBytesExpected = nil + downloadProgress[uuid] = nil } download.resume() - delegate?.downloadQueue(self, didStartDownload: download) + delegates.forEach { $0.delegate?.downloadQueue(self, didStartDownload: download) } } - func cancelAll() { - for download in downloads where !download.isComplete { + func cancelAll(for window: WindowUUID) { + for download in downloads where !download.isComplete && download.originWindow == window { download.cancel() } } - func pauseAll() { - for download in downloads where !download.isComplete { + func pauseAll(for window: WindowUUID) { + for download in downloads where !download.isComplete && download.originWindow == window { download.pause() } } - func resumeAll() { - for download in downloads where !download.isComplete { + func resumeAll(for window: WindowUUID) { + for download in downloads where !download.isComplete && download.originWindow == window { download.resume() } } + + // MARK: - Utility + + private func cleanUpDelegates() { + delegates.removeAll(where: { $0.delegate == nil }) + } + + private func downloads(for window: WindowUUID) -> [Download] { + return downloads.filter({ $0.originWindow == window }) + } } extension DownloadQueue: DownloadDelegate { func download(_ download: Download, didCompleteWithError error: Error?) { guard let error = error, let index = downloads.firstIndex(of: download) else { return } - lastDownloadError = error + let uuid = download.originWindow + downloadErrors[uuid] = error downloads.remove(at: index) - if downloads.isEmpty { - delegate?.downloadQueue(self, didCompleteWithError: lastDownloadError) + // If all downloads for the completed download's window are completed, we notify of error + if downloads(for: uuid).isEmpty { + delegates.forEach { + guard $0.delegate?.windowUUID == uuid else { return } + $0.delegate?.downloadQueue(self, didCompleteWithError: error) + } } } func download(_ download: Download, didDownloadBytes bytesDownloaded: Int64) { - combinedBytesDownloaded += bytesDownloaded - delegate?.downloadQueue( - self, - didDownloadCombinedBytes: combinedBytesDownloaded, - combinedTotalBytesExpected: combinedTotalBytesExpected - ) + let uuid = download.originWindow + var progress = downloadProgress[uuid] ?? DownloadProgress(bytesDownloaded: 0, totalExpected: 0) + progress.bytesDownloaded += bytesDownloaded + downloadProgress[uuid] = progress + + delegates.forEach { + guard $0.delegate?.windowUUID == uuid else { return } + $0.delegate?.downloadQueue(self, + didDownloadCombinedBytes: progress.bytesDownloaded, + combinedTotalBytesExpected: progress.totalExpected) + } } func download(_ download: Download, didFinishDownloadingTo location: URL) { guard let index = downloads.firstIndex(of: download) else { return } downloads.remove(at: index) - delegate?.downloadQueue(self, download: download, didFinishDownloadingTo: location) + delegates.forEach { $0.delegate?.downloadQueue(self, download: download, didFinishDownloadingTo: location) } NotificationCenter.default.post(name: .FileDidDownload, object: location) - if downloads.isEmpty { - delegate?.downloadQueue(self, didCompleteWithError: lastDownloadError) + // If all downloads for the completed download's window are completed, we notify of completion + let uuid = download.originWindow + if downloads(for: uuid).isEmpty { + let error = downloadErrors[uuid] + delegates.forEach { + guard $0.delegate?.windowUUID == uuid else { return } + $0.delegate?.downloadQueue(self, didCompleteWithError: error) + } + downloadErrors[uuid] = nil } } } diff --git a/firefox-ios/Client/Frontend/Browser/MainMenuActionHelper.swift b/firefox-ios/Client/Frontend/Browser/MainMenuActionHelper.swift index cf2040439c3c..2753d9e933fb 100644 --- a/firefox-ios/Client/Frontend/Browser/MainMenuActionHelper.swift +++ b/firefox-ios/Client/Frontend/Browser/MainMenuActionHelper.swift @@ -467,7 +467,6 @@ class MainMenuActionHelper: PhotonActionSheetProtocol, } private func getNightModeAction() -> [PhotonRowActions] { - let uuid = windowUUID var items: [PhotonRowActions] = [] let nightModeEnabled = NightModeHelper.isActivated() @@ -477,7 +476,7 @@ class MainMenuActionHelper: PhotonActionSheetProtocol, iconString: StandardImageIdentifiers.Large.nightMode, isEnabled: nightModeEnabled ) { _ in - NightModeHelper.toggle(tabManager: self.tabManager) + NightModeHelper.toggle() if NightModeHelper.isActivated() { TelemetryWrapper.recordEvent(category: .action, method: .tap, object: .nightModeEnabled) @@ -485,7 +484,11 @@ class MainMenuActionHelper: PhotonActionSheetProtocol, TelemetryWrapper.recordEvent(category: .action, method: .tap, object: .nightModeDisabled) } - self.themeManager.reloadTheme(for: uuid) + let windowManager: WindowManager = AppContainer.shared.resolve() + let allWindowUUIDS = windowManager.allWindowUUIDs(includingReserved: false) + allWindowUUIDS.forEach { uuid in + self.themeManager.reloadTheme(for: uuid) + } }.items items.append(nightMode) diff --git a/firefox-ios/Client/Frontend/Browser/OpenInHelper/DownloadHelper.swift b/firefox-ios/Client/Frontend/Browser/OpenInHelper/DownloadHelper.swift index 20586473bdcb..afa7a5921278 100644 --- a/firefox-ios/Client/Frontend/Browser/OpenInHelper/DownloadHelper.swift +++ b/firefox-ios/Client/Frontend/Browser/OpenInHelper/DownloadHelper.swift @@ -96,7 +96,8 @@ class DownloadHelper: NSObject { self.preflightResponse = response } - func downloadViewModel(okAction: @escaping (HTTPDownload) -> Void) -> PhotonActionSheetViewModel? { + func downloadViewModel(windowUUID: WindowUUID, + okAction: @escaping (HTTPDownload) -> Void) -> PhotonActionSheetViewModel? { var requestUrl = request.url if let url = requestUrl, url.scheme == "blob" { requestUrl = url.removeBlobFromUrl() @@ -104,7 +105,8 @@ class DownloadHelper: NSObject { guard let host = requestUrl?.host else { return nil } - guard let download = HTTPDownload(cookieStore: cookieStore, + guard let download = HTTPDownload(originWindow: windowUUID, + cookieStore: cookieStore, preflightResponse: preflightResponse, request: request) else { return nil } diff --git a/firefox-ios/Client/Frontend/Reader/ReadabilityService.swift b/firefox-ios/Client/Frontend/Reader/ReadabilityService.swift index 15b1807d1feb..c6b94464b93f 100644 --- a/firefox-ios/Client/Frontend/Reader/ReadabilityService.swift +++ b/firefox-ios/Client/Frontend/Reader/ReadabilityService.swift @@ -19,7 +19,7 @@ class ReadabilityOperation: Operation { var url: URL var semaphore: DispatchSemaphore var result: ReadabilityOperationResult? - var tab: Tab! + var tab: Tab? var readerModeCache: ReaderModeCache private var logger: Logger @@ -44,22 +44,26 @@ class ReadabilityOperation: Operation { // Setup a tab, attach a Readability helper. Kick all this off on the main thread since UIKit // and WebKit are not safe from other threads. - let windowUUID = tab.windowUUID DispatchQueue.main.async(execute: { () -> Void in let configuration = WKWebViewConfiguration() // TODO: To resolve profile from DI container - self.tab = Tab(profile: self.profile, configuration: configuration, windowUUID: windowUUID) - self.tab.createWebview() - self.tab.navigationDelegate = self - let readerMode = ReaderMode(tab: self.tab) + // TODO: Revisit window UUID here for multi-window [FXIOS-9043] + let windowUUID = (AppContainer.shared.resolve() as WindowManager).activeWindow + + let tab = Tab(profile: self.profile, configuration: configuration, windowUUID: windowUUID) + self.tab = tab + tab.createWebview() + tab.navigationDelegate = self + + let readerMode = ReaderMode(tab: tab) readerMode.delegate = self - self.tab.addContentScript(readerMode, name: ReaderMode.name()) + tab.addContentScript(readerMode, name: ReaderMode.name()) // Load the page in the webview. This either fails with a navigation error, or we // get a readability callback. Or it takes too long, in which case the semaphore // times out. The script on the page will retry every 500ms for 10 seconds. - self.tab.loadRequest(URLRequest(url: self.url)) + tab.loadRequest(URLRequest(url: self.url)) }) let timeout = DispatchTime.now() + .seconds(10) if semaphore.wait(timeout: timeout) == .timedOut { diff --git a/firefox-ios/Client/Frontend/TabContentsScripts/DownloadContentScript.swift b/firefox-ios/Client/Frontend/TabContentsScripts/DownloadContentScript.swift index 77073a31cd4e..05ab1c79208c 100644 --- a/firefox-ios/Client/Frontend/TabContentsScripts/DownloadContentScript.swift +++ b/firefox-ios/Client/Frontend/TabContentsScripts/DownloadContentScript.swift @@ -64,6 +64,7 @@ class DownloadContentScript: TabContentScript { let data = Bytes.decodeBase64(base64String) else { return } + let windowUUID = tab?.windowUUID ?? (AppContainer.shared.resolve() as WindowManager).activeWindow defer { notificationCenter.post(name: .PendingBlobDownloadAddedToQueue, withObject: nil) DownloadContentScript.blobUrlForDownload = nil @@ -89,7 +90,7 @@ class DownloadContentScript: TabContentScript { } } - let download = BlobDownload(filename: filename, mimeType: mimeType, size: size, data: data) + let download = BlobDownload(originWindow: windowUUID, filename: filename, mimeType: mimeType, size: size, data: data) downloadQueue.enqueue(download) } } diff --git a/firefox-ios/Client/Frontend/TabContentsScripts/NightModeHelper.swift b/firefox-ios/Client/Frontend/TabContentsScripts/NightModeHelper.swift index 7005ec484a3d..fdc844d69b68 100644 --- a/firefox-ios/Client/Frontend/TabContentsScripts/NightModeHelper.swift +++ b/firefox-ios/Client/Frontend/TabContentsScripts/NightModeHelper.swift @@ -31,22 +31,23 @@ class NightModeHelper: TabContentScript, FeatureFlaggable { } static func toggle( - _ userDefaults: UserDefaultsInterface = UserDefaults.standard, - tabManager: TabManager + _ userDefaults: UserDefaultsInterface = UserDefaults.standard ) { let isActive = userDefaults.bool(forKey: NightModeKeys.Status) - setNightMode(userDefaults, tabManager: tabManager, enabled: !isActive) + setNightMode(userDefaults, enabled: !isActive) } static func setNightMode( _ userDefaults: UserDefaultsInterface = UserDefaults.standard, - tabManager: TabManager, enabled: Bool ) { userDefaults.set(enabled, forKey: NightModeKeys.Status) - for tab in tabManager.tabs { - tab.nightMode = enabled - tab.webView?.scrollView.indicatorStyle = enabled ? .white : .default + let windowManager: WindowManager = AppContainer.shared.resolve() + for tabManager in windowManager.allWindowTabManagers() { + for tab in tabManager.tabs { + tab.nightMode = enabled + tab.webView?.scrollView.indicatorStyle = enabled ? .white : .default + } } } @@ -58,12 +59,12 @@ class NightModeHelper: TabContentScript, FeatureFlaggable { // These functions are only here to help with the night mode experiment // and will be removed once a decision from that experiment is reached. // TODO: https://mozilla-hub.atlassian.net/browse/FXIOS-8475 + // Reminder: Any future refactors for 8475 need to work with multi-window. static func turnOff( - _ userDefaults: UserDefaultsInterface = UserDefaults.standard, - tabManager: TabManager + _ userDefaults: UserDefaultsInterface = UserDefaults.standard ) { guard isActivated() else { return } - setNightMode(userDefaults, tabManager: tabManager, enabled: false) + setNightMode(userDefaults, enabled: false) } static func cleanNightModeDefaults( diff --git a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift index 2b6d0235b6c4..5ce3bf22e219 100644 --- a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift +++ b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift @@ -45,9 +45,6 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable { static let shared = TelemetryWrapper() - // TODO [7856]: Temporary. Additional telemetry updates forthcoming once iPad multi-window enabled. - var defaultTabManager: TabManager? - let glean = Glean.shared // Boolean flag to temporarily remember if we crashed during the // last run of the app. We cannot simply use `Sentry.crashedLastLaunch` @@ -152,10 +149,9 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable { GleanMetrics.Search.defaultEngine.set(defaultEngine?.engineID ?? "custom") // Record the open tab count - // TODO [7856]: Additional telemetry updates forthcoming once iPad multi-window enabled. - if let count = defaultTabManager?.count { - GleanMetrics.Tabs.cumulativeCount.add(Int32(count)) - } + let windowManager: WindowManager = AppContainer.shared.resolve() + let tabCount = windowManager.allWindowTabManagers().map({ $0.count }).reduce(0, +) + GleanMetrics.Tabs.cumulativeCount.add(Int32(tabCount)) // Record other preference settings. // If the setting exists at the key location, use that value. Otherwise record the default diff --git a/firefox-ios/bin/sdk_generator.sh b/firefox-ios/bin/sdk_generator.sh index e1eaff06dfcf..15b2f194e0d4 100755 --- a/firefox-ios/bin/sdk_generator.sh +++ b/firefox-ios/bin/sdk_generator.sh @@ -25,7 +25,7 @@ set -e -GLEAN_PARSER_VERSION=13.0 +GLEAN_PARSER_VERSION=14.0 # CMDNAME is used in the usage text below. # shellcheck disable=SC2034 diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadHelperTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadHelperTests.swift index 88e2966b752c..2963aa9b4913 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadHelperTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadHelperTests.swift @@ -142,7 +142,7 @@ class DownloadHelperTests: XCTestCase { forceDownload: false ) - let downloadViewModel = subject?.downloadViewModel(okAction: { _ in }) + let downloadViewModel = subject?.downloadViewModel(windowUUID: .XCTestDefaultUUID, okAction: { _ in }) XCTAssertNil(downloadViewModel) } @@ -157,7 +157,7 @@ class DownloadHelperTests: XCTestCase { forceDownload: false ) - let downloadViewModel = subject?.downloadViewModel(okAction: { _ in }) + let downloadViewModel = subject?.downloadViewModel(windowUUID: .XCTestDefaultUUID, okAction: { _ in }) XCTAssertEqual(downloadViewModel!.title!, "some-image.jpg") } @@ -171,7 +171,7 @@ class DownloadHelperTests: XCTestCase { forceDownload: false ) - let downloadViewModel = subject?.downloadViewModel(okAction: { _ in }) + let downloadViewModel = subject?.downloadViewModel(windowUUID: .XCTestDefaultUUID, okAction: { _ in }) XCTAssertEqual(downloadViewModel!.closeButtonTitle, .CancelString) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadQueueTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadQueueTests.swift index 02475e3514cf..ed7b42c6dce3 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadQueueTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadQueueTests.swift @@ -16,7 +16,7 @@ class DownloadQueueTests: XCTestCase { override func setUp() { queue = DownloadQueue() - download = MockDownload() + download = MockDownload(originWindow: .XCTestDefaultUUID) super.setUp() } @@ -43,20 +43,20 @@ class DownloadQueueTests: XCTestCase { func testEnqueueDownloadShouldCallDownloadQueueDidStartDownload() { let mockQueueDelegate = MockDownloadQueueDelegate() - queue.delegate = mockQueueDelegate + queue.addDelegate(mockQueueDelegate) queue.enqueue(download) XCTAssertEqual(mockQueueDelegate.methodCalled, didStartDownload) } func testCancelAllDownload() { queue.downloads = [download] - queue.cancelAll() + queue.cancelAll(for: .XCTestDefaultUUID) XCTAssertTrue(download.downloadCanceled) } func testDidDownloadBytes() { let mockQueueDelegate = MockDownloadQueueDelegate() - queue.delegate = mockQueueDelegate + queue.addDelegate(mockQueueDelegate) queue.downloads = [download] queue.download(download, didDownloadBytes: 0) XCTAssertEqual(mockQueueDelegate.methodCalled, didDownloadCombinedBytes) @@ -64,7 +64,7 @@ class DownloadQueueTests: XCTestCase { func testDidFinishDownloadingToWithOneElementsInQueue() { let mockQueueDelegate = MockDownloadQueueDelegate() - queue.delegate = mockQueueDelegate + queue.addDelegate(mockQueueDelegate) queue.downloads = [download] queue.download(download, didFinishDownloadingTo: url) XCTAssertEqual(mockQueueDelegate.methodCalled, didCompleteWithError) @@ -72,22 +72,22 @@ class DownloadQueueTests: XCTestCase { func testDidFinishDownloadingToWithTwoElementsInQueue() { let mockQueueDelegate = MockDownloadQueueDelegate() - queue.delegate = mockQueueDelegate - queue.downloads = [download, MockDownload()] + queue.addDelegate(mockQueueDelegate) + queue.downloads = [download, MockDownload(originWindow: .XCTestDefaultUUID)] queue.download(download, didFinishDownloadingTo: url) XCTAssertEqual(mockQueueDelegate.methodCalled, didFinishDownloadingTo) } func testDidFinishDownloadingToWithNoElementsInQueue() { let mockQueueDelegate = MockDownloadQueueDelegate() - queue.delegate = mockQueueDelegate + queue.addDelegate(mockQueueDelegate) queue.download(download, didFinishDownloadingTo: url) XCTAssertEqual(mockQueueDelegate.methodCalled, "noneOfMethodWasCalled") } func testDidCompleteWithError() { let mockQueueDelegate = MockDownloadQueueDelegate() - queue.delegate = mockQueueDelegate + queue.addDelegate(mockQueueDelegate) queue.downloads = [download] queue.download(download, didCompleteWithError: DownloadTestError.noError("OK")) XCTAssertEqual(mockQueueDelegate.methodCalled, didCompleteWithError) @@ -95,7 +95,7 @@ class DownloadQueueTests: XCTestCase { func testDelegateMemoryLeak() { let mockQueueDelegate = MockDownloadQueueDelegate() - queue.delegate = mockQueueDelegate + queue.addDelegate(mockQueueDelegate) trackForMemoryLeaks(queue) queue = nil } @@ -121,6 +121,8 @@ class MockDownload: Download { } class MockDownloadQueueDelegate: DownloadQueueDelegate { + let windowUUID: WindowUUID = .XCTestDefaultUUID + var methodCalled: String = "noneOfMethodWasCalled" func downloadQueue(_ downloadQueue: DownloadQueue, didStartDownload download: Download) { diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadTests.swift index 24f464f92000..b0fed86d9e6e 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DownloadTests.swift @@ -10,7 +10,7 @@ class DownloadTests: XCTestCase { override func setUp() { super.setUp() - download = Download() + download = Download(originWindow: .XCTestDefaultUUID) } override func tearDown() { diff --git a/focus-ios/.swiftlint.yml b/focus-ios/.swiftlint.yml index 51f593a8d387..a5ad990a9dfd 100644 --- a/focus-ios/.swiftlint.yml +++ b/focus-ios/.swiftlint.yml @@ -6,7 +6,7 @@ only_rules: # Only enforce these rules, ignore all others # - closure_parameter_position # - colon # - comma - # - comment_spacing + - comment_spacing - compiler_protocol_init - computed_accessors_order # - control_statement @@ -14,7 +14,7 @@ only_rules: # Only enforce these rules, ignore all others # - dynamic_inline - empty_enum_arguments - empty_parameters - # - empty_parentheses_with_trailing_closure + - empty_parentheses_with_trailing_closure # - for_where - force_try - implicit_getter @@ -52,7 +52,7 @@ only_rules: # Only enforce these rules, ignore all others # - unused_optional_binding # - unused_setter_value # - vertical_parameter_alignment - # - vertical_whitespace + - vertical_whitespace # - void_function_in_ternary # - void_return # - file_header @@ -63,7 +63,7 @@ only_rules: # Only enforce these rules, ignore all others # - contains_over_filter_count # - contains_over_filter_is_empty # - contains_over_first_not_nil - # - contains_over_range_nil_comparison + - contains_over_range_nil_comparison # - empty_collection_literal # - empty_count - empty_string @@ -74,8 +74,8 @@ only_rules: # Only enforce these rules, ignore all others - duplicate_imports - duplicate_enum_cases # - last_where - # - modifier_order - # - multiline_arguments + - modifier_order + - multiline_arguments - opening_brace - overridden_super_call - vertical_parameter_alignment_on_call diff --git a/focus-ios/Blockzilla.xcodeproj/project.pbxproj b/focus-ios/Blockzilla.xcodeproj/project.pbxproj index 36ad39a0fa99..278deca593f7 100644 --- a/focus-ios/Blockzilla.xcodeproj/project.pbxproj +++ b/focus-ios/Blockzilla.xcodeproj/project.pbxproj @@ -7185,7 +7185,7 @@ repositoryURL = "https://github.com/mozilla/rust-components-swift"; requirement = { kind = exactVersion; - version = 127.0.20240417050328; + version = 127.0.20240424134628; }; }; 8A0E7F2C2BA0F0E0006BC6B6 /* XCRemoteSwiftPackageReference "Fuzi" */ = { @@ -7201,7 +7201,7 @@ repositoryURL = "https://github.com/mozilla/glean-swift"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 59.0.0; + minimumVersion = 60.0.0; }; }; F8324C2D264C807C007E4BFA /* XCRemoteSwiftPackageReference "SnapKit" */ = { diff --git a/focus-ios/Blockzilla.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/focus-ios/Blockzilla.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ef151688ba7b..1f7a47d87653 100644 --- a/focus-ios/Blockzilla.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/focus-ios/Blockzilla.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/mozilla/glean-swift", "state": { "branch": null, - "revision": "fd5a73d20b519517a7840526266ef20901aedbcb", - "version": "59.0.0" + "revision": "227b9d80062eb6921963fac1899a25fff6babe02", + "version": "60.0.0" } }, { @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/mozilla/rust-components-swift", "state": { "branch": null, - "revision": "6fc91097ce72934b00fe1fa22de2f0020bfa896d", - "version": "127.0.20240417050328" + "revision": "86be6a173c32ee2403f3cd61bbdb8c39c6e0152b", + "version": "127.0.20240424134628" } }, { diff --git a/focus-ios/Blockzilla/BrowserViewController.swift b/focus-ios/Blockzilla/BrowserViewController.swift index 847597ac5788..ac677bb96720 100644 --- a/focus-ios/Blockzilla/BrowserViewController.swift +++ b/focus-ios/Blockzilla/BrowserViewController.swift @@ -382,7 +382,9 @@ class BrowserViewController: UIViewController { anchoredBy: self.urlBar.textFieldAnchor, sourceRect: CGRect( x: self.urlBar.textFieldAnchor.bounds.minX, - y: self.urlBar.textFieldAnchor.bounds.maxY, width: 0, height: 0 + y: self.urlBar.textFieldAnchor.bounds.maxY, + width: 0, + height: 0 ), body: UIConstants.strings.tooltipBodyTextStartPrivateBrowsing, dismiss: { [unowned self] in self.onboardingEventsHandler.route = nil } @@ -803,22 +805,26 @@ class BrowserViewController: UIViewController { clearBrowser() - UIView.animate(withDuration: UIConstants.layout.deleteAnimationDuration, animations: { - screenshotView.snp.remakeConstraints { make in - make.centerX.equalTo(self.mainContainerView) - make.top.equalTo(self.mainContainerView.snp.bottom) - make.size.equalTo(self.mainContainerView).multipliedBy(0.9) - } + UIView.animate( + withDuration: UIConstants.layout.deleteAnimationDuration, + animations: { + screenshotView.snp.remakeConstraints { make in + make.centerX.equalTo(self.mainContainerView) + make.top.equalTo(self.mainContainerView.snp.bottom) + make.size.equalTo(self.mainContainerView).multipliedBy(0.9) + } screenshotView.alpha = 0 self.mainContainerView.layoutIfNeeded() - }, completion: { _ in - self.urlBar.activateTextField() - Toast(text: UIConstants.strings.eraseMessage).show() - screenshotView.removeFromSuperview() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - self.onboardingEventsHandler.send(.clearTapped) + }, + completion: { _ in + self.urlBar.activateTextField() + Toast(text: UIConstants.strings.eraseMessage).show() + screenshotView.removeFromSuperview() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + self.onboardingEventsHandler.send(.clearTapped) + } } - }) + ) userActivity = SiriShortcuts().getActivity(for: .eraseAndOpen) let interaction = INInteraction(intent: eraseIntent, response: nil) @@ -1714,7 +1720,6 @@ extension BrowserViewController: LegacyWebControllerDelegate { // from catching the global progress events. guard urlBar.inBrowsingMode else { return } - urlBarViewModel.loadingProgres = estimatedProgress } @@ -1853,32 +1858,44 @@ extension BrowserViewController: LegacyWebControllerDelegate { scrollBarState = .animating - UIView.animate(withDuration: UIConstants.layout.urlBarTransitionAnimationDuration, delay: 0, options: .allowUserInteraction, animations: { - self.urlBar.collapsedState = .extended - self.urlBarTopConstraint.update(offset: 0) - self.toolbarBottomConstraint.update(inset: 0) - scrollView.bounds.origin.y += self.scrollBarOffsetAlpha * UIConstants.layout.urlBarHeight - self.scrollBarOffsetAlpha = 0 - self.view.layoutIfNeeded() - }, completion: { _ in - self.scrollBarState = .expanded - }) + UIView.animate( + withDuration: UIConstants.layout.urlBarTransitionAnimationDuration, + delay: 0, + options: .allowUserInteraction, + animations: { + self.urlBar.collapsedState = .extended + self.urlBarTopConstraint.update(offset: 0) + self.toolbarBottomConstraint.update(inset: 0) + scrollView.bounds.origin.y += self.scrollBarOffsetAlpha * UIConstants.layout.urlBarHeight + self.scrollBarOffsetAlpha = 0 + self.view.layoutIfNeeded() + }, + completion: { _ in + self.scrollBarState = .expanded + } + ) } // FXIOS-8642 - #19165 ⁃ Integrate scroll controller delegate with Focus iOS private func hideToolbars() { let scrollView = webViewController.scrollView scrollBarState = .animating - UIView.animate(withDuration: UIConstants.layout.urlBarTransitionAnimationDuration, delay: 0, options: .allowUserInteraction, animations: { - self.urlBar.collapsedState = .collapsed - self.urlBarTopConstraint.update(offset: -UIConstants.layout.urlBarHeight + UIConstants.layout.collapsedUrlBarHeight) - self.toolbarBottomConstraint.update(offset: UIConstants.layout.browserToolbarHeight + self.view.safeAreaInsets.bottom) - scrollView.bounds.origin.y += (self.scrollBarOffsetAlpha - 1) * UIConstants.layout.urlBarHeight - self.scrollBarOffsetAlpha = 1 - self.view.layoutIfNeeded() - }, completion: { _ in - self.scrollBarState = .collapsed - }) + UIView.animate( + withDuration: UIConstants.layout.urlBarTransitionAnimationDuration, + delay: 0, + options: .allowUserInteraction, + animations: { + self.urlBar.collapsedState = .collapsed + self.urlBarTopConstraint.update(offset: -UIConstants.layout.urlBarHeight + UIConstants.layout.collapsedUrlBarHeight) + self.toolbarBottomConstraint.update(offset: UIConstants.layout.browserToolbarHeight + self.view.safeAreaInsets.bottom) + scrollView.bounds.origin.y += (self.scrollBarOffsetAlpha - 1) * UIConstants.layout.urlBarHeight + self.scrollBarOffsetAlpha = 1 + self.view.layoutIfNeeded() + }, + completion: { _ in + self.scrollBarState = .collapsed + } + ) } private func snapToolbars(scrollView: UIScrollView) { diff --git a/focus-ios/Blockzilla/Settings/Controller/AboutViewController.swift b/focus-ios/Blockzilla/Settings/Controller/AboutViewController.swift index 0929bbf788de..b71e5df82652 100644 --- a/focus-ios/Blockzilla/Settings/Controller/AboutViewController.swift +++ b/focus-ios/Blockzilla/Settings/Controller/AboutViewController.swift @@ -301,8 +301,16 @@ private class AboutHeaderView: UIView { Settings.set(true, forToggle: .displaySecretMenu) // Give the logo a little shake as a confirmation logo.transform = CGAffineTransform(translationX: 20, y: 0) - UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 0.1, initialSpringVelocity: 1, options: .curveEaseInOut, animations: { - self.logo.transform = CGAffineTransform.identity - }, completion: nil) + UIView.animate( + withDuration: 0.25, + delay: 0, + usingSpringWithDamping: 0.1, + initialSpringVelocity: 1, + options: .curveEaseInOut, + animations: { + self.logo.transform = CGAffineTransform.identity + }, + completion: nil + ) } } diff --git a/focus-ios/Blockzilla/Settings/Controller/SettingsContentViewController.swift b/focus-ios/Blockzilla/Settings/Controller/SettingsContentViewController.swift index 8142c3cbb45c..186b2d1ebae1 100644 --- a/focus-ios/Blockzilla/Settings/Controller/SettingsContentViewController.swift +++ b/focus-ios/Blockzilla/Settings/Controller/SettingsContentViewController.swift @@ -23,13 +23,16 @@ class SettingsContentViewController: UIViewController, WKNavigationDelegate { // Add a small delay to allow the stylesheets to load and avoid flicker. let delayTime = DispatchTime.now() + Double(Int64(200 * Double(NSEC_PER_MSEC))) / Double(NSEC_PER_SEC) DispatchQueue.main.asyncAfter(deadline: delayTime) { - UIView.transition(from: self.interstitialView, to: self.webView, - duration: 0.5, - options: .transitionCrossDissolve, - completion: { finished in - self.interstitialView.removeFromSuperview() - self.interstitialSpinnerView.stopAnimating() - }) + UIView.transition( + from: self.interstitialView, + to: self.webView, + duration: 0.5, + options: .transitionCrossDissolve, + completion: { finished in + self.interstitialView.removeFromSuperview() + self.interstitialSpinnerView.stopAnimating() + } + ) } } } @@ -42,13 +45,16 @@ class SettingsContentViewController: UIViewController, WKNavigationDelegate { // Add a small delay to allow the stylesheets to load and avoid flicker. let delayTime = DispatchTime.now() + Double(Int64(200 * Double(NSEC_PER_MSEC))) / Double(NSEC_PER_SEC) DispatchQueue.main.asyncAfter(deadline: delayTime) { - UIView.transition(from: self.interstitialSpinnerView, to: self.interstitialErrorView, - duration: 0.5, - options: .transitionCrossDissolve, - completion: { finished in - self.interstitialSpinnerView.removeFromSuperview() - self.interstitialSpinnerView.stopAnimating() - }) + UIView.transition( + from: self.interstitialSpinnerView, + to: self.interstitialErrorView, + duration: 0.5, + options: .transitionCrossDissolve, + completion: { finished in + self.interstitialSpinnerView.removeFromSuperview() + self.interstitialSpinnerView.stopAnimating() + } + ) } } } diff --git a/focus-ios/Blockzilla/UIComponents/URLBar/URLBar.swift b/focus-ios/Blockzilla/UIComponents/URLBar/URLBar.swift index 3599a2e66e0b..3f4aa3d18b6d 100644 --- a/focus-ios/Blockzilla/UIComponents/URLBar/URLBar.swift +++ b/focus-ios/Blockzilla/UIComponents/URLBar/URLBar.swift @@ -838,25 +838,29 @@ class URLBar: UIView { backgroundColor = .clear } - UIView.animate(withDuration: UIConstants.layout.urlBarTransitionAnimationDuration, animations: { - self.layoutIfNeeded() + UIView.animate( + withDuration: UIConstants.layout.urlBarTransitionAnimationDuration, + animations: { + self.layoutIfNeeded() - if self.inBrowsingMode && !self.isIPadRegularDimensions { - self.updateURLBorderConstraints() - } + if self.inBrowsingMode && !self.isIPadRegularDimensions { + self.updateURLBorderConstraints() + } - self.urlBarBackgroundView.snp.remakeConstraints { make in - make.edges.equalToSuperview().inset(showBackgroundView ? UIConstants.layout.urlBarBorderInset : 1) - } + self.urlBarBackgroundView.snp.remakeConstraints { make in + make.edges.equalToSuperview().inset(showBackgroundView ? UIConstants.layout.urlBarBorderInset : 1) + } - self.urlBarBorderView.backgroundColor = borderColor - }, completion: { finished in - if finished { - if let isEmpty = self.urlTextField.text?.isEmpty { - self.displayClearButton(shouldDisplay: !isEmpty) + self.urlBarBorderView.backgroundColor = borderColor + }, + completion: { finished in + if finished { + if let isEmpty = self.urlTextField.text?.isEmpty { + self.displayClearButton(shouldDisplay: !isEmpty) + } } } - }) + ) } func updateURLBorderConstraints() { diff --git a/focus-ios/Blockzilla/Utilities/AdsTelemetryHelper.swift b/focus-ios/Blockzilla/Utilities/AdsTelemetryHelper.swift index eb66001713bf..82c80ab86ca2 100644 --- a/focus-ios/Blockzilla/Utilities/AdsTelemetryHelper.swift +++ b/focus-ios/Blockzilla/Utilities/AdsTelemetryHelper.swift @@ -23,33 +23,55 @@ public struct SearchProviderModel { let extraAdServersRegexps: [String] public static let searchProviderList = [ - SearchProviderModel(name: BasicSearchProvider.google.rawValue, - regexp: #"^https:\/\/www\.google\.(?:.+)\/search"#, - queryParam: "q", codeParam: "client", - codePrefixes: ["firefox"], - followOnParams: ["oq", "ved", "ei"], - extraAdServersRegexps: [ #"^https?:\/\/www\.google(?:adservices)?\.com\/(?:pagead\/)?aclk"#, #"^(http|https):\/\/clickserve.dartsearch.net\/link\/"#]), - SearchProviderModel(name: BasicSearchProvider.duckduckgo.rawValue, - regexp: #"^https:\/\/duckduckgo\.com\/"#, - queryParam: "q", - codeParam: "t", - codePrefixes: ["f"], - followOnParams: [], - extraAdServersRegexps: [ #"^https:\/\/duckduckgo.com\/y\.js"#, #"^https:\/\/www\.amazon\.(?:[a-z.]{2,24}).*(?:tag=duckduckgo-)"#]), - SearchProviderModel(name: BasicSearchProvider.yahoo.rawValue, - regexp: #"^https:\/\/(?:.*)search\.yahoo\.com\/search"#, - queryParam: "p", - codeParam: "", - codePrefixes: [], - followOnParams: [], - extraAdServersRegexps: [#"^(http|https):\/\/clickserve.dartsearch.net\/link\/"#, #"^https:\/\/www\.bing\.com\/acli?c?k"#, #"^https:\/\/www\.bing\.com\/fd\/ls\/GLinkPingPost\.aspx.*acli?c?k"#]), - SearchProviderModel(name: BasicSearchProvider.bing.rawValue, - regexp: #"^https:\/\/www\.bing\.com\/search"#, - queryParam: "q", - codeParam: "pc", - codePrefixes: ["MOZ", "MZ"], - followOnParams: ["oq"], - extraAdServersRegexps: [#"^https:\/\/www\.bing\.com\/acli?c?k"#, #"^https:\/\/www\.bing\.com\/fd\/ls\/GLinkPingPost\.aspx.*acli?c?k"#]) + SearchProviderModel( + name: BasicSearchProvider.google.rawValue, + regexp: #"^https:\/\/www\.google\.(?:.+)\/search"#, + queryParam: "q", + codeParam: "client", + codePrefixes: ["firefox"], + followOnParams: ["oq", "ved", "ei"], + extraAdServersRegexps: [ + #"^https?:\/\/www\.google(?:adservices)?\.com\/(?:pagead\/)?aclk"#, + #"^(http|https):\/\/clickserve.dartsearch.net\/link\/"# + ] + ), + SearchProviderModel( + name: BasicSearchProvider.duckduckgo.rawValue, + regexp: #"^https:\/\/duckduckgo\.com\/"#, + queryParam: "q", + codeParam: "t", + codePrefixes: ["f"], + followOnParams: [], + extraAdServersRegexps: [ + #"^https:\/\/duckduckgo.com\/y\.js"#, + #"^https:\/\/www\.amazon\.(?:[a-z.]{2,24}).*(?:tag=duckduckgo-)"# + ] + ), + SearchProviderModel( + name: BasicSearchProvider.yahoo.rawValue, + regexp: #"^https:\/\/(?:.*)search\.yahoo\.com\/search"#, + queryParam: "p", + codeParam: "", + codePrefixes: [], + followOnParams: [], + extraAdServersRegexps: [ + #"^(http|https):\/\/clickserve.dartsearch.net\/link\/"#, + #"^https:\/\/www\.bing\.com\/acli?c?k"#, + #"^https:\/\/www\.bing\.com\/fd\/ls\/GLinkPingPost\.aspx.*acli?c?k"# + ] + ), + SearchProviderModel( + name: BasicSearchProvider.bing.rawValue, + regexp: #"^https:\/\/www\.bing\.com\/search"#, + queryParam: "q", + codeParam: "pc", + codePrefixes: ["MOZ", "MZ"], + followOnParams: ["oq"], + extraAdServersRegexps: [ + #"^https:\/\/www\.bing\.com\/acli?c?k"#, + #"^https:\/\/www\.bing\.com\/fd\/ls\/GLinkPingPost\.aspx.*acli?c?k"# + ] + ) ] } diff --git a/focus-ios/Blockzilla/Utilities/URIFixup.swift b/focus-ios/Blockzilla/Utilities/URIFixup.swift index b4b3be120e15..86ee6e1636c7 100644 --- a/focus-ios/Blockzilla/Utilities/URIFixup.swift +++ b/focus-ios/Blockzilla/Utilities/URIFixup.swift @@ -24,7 +24,7 @@ class URIFixup { trimmed.range(of: "\\b:[0-9]{1,5}", options: .regularExpression) == nil { // check for top-level domain if scheme is "http://" or "https://" if trimmed.hasPrefix("http://") || trimmed.hasPrefix("https://") { - if trimmed.range(of: ".") == nil { + if trimmed.contains(".") == false { return nil } } @@ -35,11 +35,11 @@ class URIFixup { // make sure there's at least one "." in the host. This means // we'll allow single-word searches (e.g., "foo") at the expense // of breaking single-word hosts without a scheme (e.g., "localhost"). - if trimmed.range(of: ".") == nil { + if trimmed.contains(".") == false { return nil } - if trimmed.range(of: " ") != nil { + if trimmed.contains(" ") == true { return nil } diff --git a/focus-ios/BlockzillaPackage/Sources/Onboarding/SwiftUI Onboarding/GetStartedOnboardingView.swift b/focus-ios/BlockzillaPackage/Sources/Onboarding/SwiftUI Onboarding/GetStartedOnboardingView.swift index 5199a22d17bc..eb9ae2175960 100644 --- a/focus-ios/BlockzillaPackage/Sources/Onboarding/SwiftUI Onboarding/GetStartedOnboardingView.swift +++ b/focus-ios/BlockzillaPackage/Sources/Onboarding/SwiftUI Onboarding/GetStartedOnboardingView.swift @@ -99,11 +99,16 @@ internal extension OnboardingViewModel { config: GetStartedOnboardingViewConfig( title: "Welcome to Firefox Focus", subtitle: "Fast. Private. No distractions.", - buttonTitle: "Get Started"), + buttonTitle: "Get Started" + ), defaultBrowserConfig: DefaultBrowserViewConfig( title: "Focus isn't like other browsers", firstSubtitle: "We clear your history when you close the app for extra privacy", secondSubtitle: "Make Focus your default to protect your data with every link you open.", topButtonTitle: "Set as Default Browser", - bottomButtonTitle: "Skip"), dismissAction: {}, telemetry: { _ in }) + bottomButtonTitle: "Skip" + ), + dismissAction: {}, + telemetry: { _ in } + ) } diff --git a/focus-ios/BlockzillaPackage/Sources/UIHelpers/UIViewExtensions.swift b/focus-ios/BlockzillaPackage/Sources/UIHelpers/UIViewExtensions.swift index e8e512ffaee0..478d566905d6 100644 --- a/focus-ios/BlockzillaPackage/Sources/UIHelpers/UIViewExtensions.swift +++ b/focus-ios/BlockzillaPackage/Sources/UIHelpers/UIViewExtensions.swift @@ -9,17 +9,23 @@ public extension UIView { func animateHidden(_ hidden: Bool, duration: TimeInterval, completion: (() -> Void)? = nil) { self.isHidden = false - UIView.transition(with: self, duration: duration, options: .beginFromCurrentState, animations: { - self.alpha = hidden ? 0 : 1 - }, completion: { finished in - // Only update the hidden state if the animation finished. - // Otherwise, a new animation may have started on top of this one, in which case - // that animation will set the final state. - if finished { - self.isHidden = hidden + UIView.transition( + with: self, + duration: duration, + options: .beginFromCurrentState, + animations: { + self.alpha = hidden ? 0 : 1 + }, + completion: { finished in + // Only update the hidden state if the animation finished. + // Otherwise, a new animation may have started on top of this one, in which case + // that animation will set the final state. + if finished { + self.isHidden = hidden + } + completion?() } - completion?() - }) + ) } func applyGradient(colors: [UIColor]) { diff --git a/focus-ios/OpenInFocus/ActionViewController.swift b/focus-ios/OpenInFocus/ActionViewController.swift index f8598203c450..9cfe224783f6 100644 --- a/focus-ios/OpenInFocus/ActionViewController.swift +++ b/focus-ios/OpenInFocus/ActionViewController.swift @@ -125,10 +125,16 @@ class ActionViewController: SLComposeServiceViewController { extension ActionViewController { func finish(afterDelay: TimeInterval = 0) { - UIView.animate(withDuration: 0.2, delay: afterDelay, options: [], animations: { - self.view.alpha = 0 - }, completion: { _ in - self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) - }) + UIView.animate( + withDuration: 0.2, + delay: afterDelay, + options: [], + animations: { + self.view.alpha = 0 + }, + completion: { _ in + self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } + ) } } diff --git a/focus-ios/bin/sdk_generator.sh b/focus-ios/bin/sdk_generator.sh index e1eaff06dfcf..15b2f194e0d4 100644 --- a/focus-ios/bin/sdk_generator.sh +++ b/focus-ios/bin/sdk_generator.sh @@ -25,7 +25,7 @@ set -e -GLEAN_PARSER_VERSION=13.0 +GLEAN_PARSER_VERSION=14.0 # CMDNAME is used in the usage text below. # shellcheck disable=SC2034 diff --git a/taskcluster/docker/alpine/Dockerfile b/taskcluster/docker/alpine/Dockerfile index eb77daa9291e..7be3d68960cb 100644 --- a/taskcluster/docker/alpine/Dockerfile +++ b/taskcluster/docker/alpine/Dockerfile @@ -14,7 +14,7 @@ RUN apk add --no-cache python3 py3-pip py3-setuptools && \ ln -sf python3 /usr/bin/python # Setup other dependencies -RUN apk add bash git +RUN apk add bash coreutils git # %include-run-task diff --git a/taskcluster/kinds/bitrise-performance/kind.yml b/taskcluster/kinds/bitrise-performance/kind.yml index a8e4eefcde3b..a92d21339c75 100644 --- a/taskcluster/kinds/bitrise-performance/kind.yml +++ b/taskcluster/kinds/bitrise-performance/kind.yml @@ -13,6 +13,11 @@ tasks: tests: description: Run Performance Tests on iOS Simulator in Bitrise run-on-tasks-for: [] + treeherder: + symbol: bitrise-perf + kind: test + platform: ios-simulator-iphone14-16-4/opt + tier: 1 worker-type: bitrise bitrise: artifact_prefix: public diff --git a/taskcluster/kinds/build/kind.yml b/taskcluster/kinds/build/kind.yml index abfdedeca1ce..b5fd335ae05b 100644 --- a/taskcluster/kinds/build/kind.yml +++ b/taskcluster/kinds/build/kind.yml @@ -15,6 +15,11 @@ tasks: attributes: chunk_locales: ["en-US"] run-on-tasks-for: [] + treeherder: + symbol: B(screenshots) + kind: build + tier: 1 + platform: ios/opt worker-type: bitrise bitrise: artifact_prefix: public diff --git a/taskcluster/kinds/firebase-performance/kind.yml b/taskcluster/kinds/firebase-performance/kind.yml index abca0ca6f3b7..7aa3c4654a9f 100644 --- a/taskcluster/kinds/firebase-performance/kind.yml +++ b/taskcluster/kinds/firebase-performance/kind.yml @@ -13,6 +13,11 @@ tasks: tests: description: Run Performance Tests on Physical Devices in Firebase run-on-tasks-for: [] + treeherder: + symbol: firebase-perf + kind: test + platform: ios-physical-iphone14pro-16-6/opt + tier: 1 worker-type: bitrise bitrise: artifact_prefix: public diff --git a/taskcluster/requirements.in b/taskcluster/requirements.in index 75d784bdb724..d9ac933eadb3 100644 --- a/taskcluster/requirements.in +++ b/taskcluster/requirements.in @@ -2,4 +2,4 @@ # https://taskcluster-taskgraph.readthedocs.io/en/latest/howto/bootstrap-taskgraph.html mozilla-taskgraph>=1.5.0 -taskcluster-taskgraph>=8.0.0 +taskcluster-taskgraph>=8.0.1 diff --git a/taskcluster/requirements.txt b/taskcluster/requirements.txt index b52e46af80fa..857390158c63 100644 --- a/taskcluster/requirements.txt +++ b/taskcluster/requirements.txt @@ -307,9 +307,9 @@ slugid==2.0.0 \ --hash=sha256:a950d98b72691178bdd4d6c52743c4a2aa039207cf7a97d71060a111ff9ba297 \ --hash=sha256:aec8b0e01c4ad32e38e12d609eab3ec912fd129aaf6b2ded0199b56a5f8fd67c # via taskcluster-taskgraph -taskcluster-taskgraph==8.0.0 \ - --hash=sha256:14ebbfc272781a95e6b19fde5e0e8075201edba0b340113253383c154c34809c \ - --hash=sha256:7105b1ed979a6a0fe94f3e304bae0e2d14f15ced6ea86bb31a547f3fd9e77d25 +taskcluster-taskgraph==8.0.1 \ + --hash=sha256:14500bc703f64eb002c0cd505caaf2d34ffc0ae66d109b108e738661da1ae09c \ + --hash=sha256:21387537bbebab2a7b1890d03e20e49379bdda65efd45ca7fb8d01f5c29e1797 # via # -r requirements.in # mozilla-taskgraph