diff --git a/BrowserKit/Sources/ComponentLibrary/Buttons/LinkButton.swift b/BrowserKit/Sources/ComponentLibrary/Buttons/LinkButton.swift index b36616f8432b..8b2b8b3fef23 100644 --- a/BrowserKit/Sources/ComponentLibrary/Buttons/LinkButton.swift +++ b/BrowserKit/Sources/ComponentLibrary/Buttons/LinkButton.swift @@ -26,6 +26,7 @@ open class LinkButton: UIButton, ThemeApplicable { updatedConfiguration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in var outgoing = incoming outgoing.font = viewModel.font + outgoing.underlineStyle = .single return outgoing } updatedConfiguration.contentInsets = viewModel.contentInsets diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 785ab6ddb1f2..2cf9262532a8 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -21593,7 +21593,7 @@ repositoryURL = "https://github.com/mozilla/rust-components-swift.git"; requirement = { kind = exactVersion; - version = 127.0.20240427050414; + version = 127.0.20240430050317; }; }; 435C85EE2788F4D00072B526 /* XCRemoteSwiftPackageReference "glean-swift" */ = { 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 edbe1ba87bde..ad2f7295ca90 100644 --- a/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/mozilla/rust-components-swift.git", "state" : { - "revision" : "67ea48dede8487276ccd6531f9da29ca793c11cc", - "version" : "127.0.20240427050414" + "revision" : "fb6671d24023c79a1dc2afd3aac7c850167c5c63", + "version" : "127.0.20240430050317" } }, { diff --git a/firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs index 1a5b3014c9ea..aa4f79552188 100644 --- a/firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs @@ -413,6 +413,7 @@ export class FormAutofillSection { profile[`${fieldDetail.fieldName}-formatted`] || profile[fieldDetail.fieldName] || ""; + if (HTMLSelectElement.isInstance(element)) { // Unlike text input, select element is always previewed even if // the option is already selected. diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+TabToolbarDelegate.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+TabToolbarDelegate.swift index 2c31b030ac0f..d1f705ddb34d 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+TabToolbarDelegate.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+TabToolbarDelegate.swift @@ -348,8 +348,11 @@ extension BrowserViewController: ToolBarActionMenuDelegate { textAlignment: .left) let toast = ButtonToast(viewModel: viewModel, theme: currentTheme()) { [weak self] isButtonTapped in - guard let self, let closedTab = tabManager.backupCloseTab else { return } - isButtonTapped ? self.tabManager.undoCloseTab(tab: closedTab.tab, position: closedTab.restorePosition) : nil + guard let self, + tabManager.backupCloseTab != nil, + isButtonTapped + else { return } + self.tabManager.undoCloseTab() } show(toast: toast) default: 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 f08e102d3534..fdb1c3f98d2b 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift @@ -912,8 +912,6 @@ extension BrowserViewController: WKNavigationDelegate { tab.contentBlocker?.notifyContentBlockingChanged() self.scrollController.resetZoomState() - scrollController.shouldScrollToTop = true - if tabManager.selectedTab === tab { updateUIForReaderHomeStateForTab(tab, focusUrlBar: true) updateFakespot(tab: tab, isReload: true) diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift index 4a832a78728f..82289a7094b8 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift @@ -81,7 +81,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { return BrowserViewControllerState( searchScreenState: state.searchScreenState, showDataClearanceFlow: state.showDataClearanceFlow, - fakespotState: FakespotState.reducer(state.fakespotState, action), + fakespotState: state.fakespotState, showOverlay: state.showOverlay, windowUUID: state.windowUUID, reloadWebView: false, @@ -111,7 +111,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { return BrowserViewControllerState( searchScreenState: SearchScreenState(inPrivateMode: privacyState), showDataClearanceFlow: privacyState, - fakespotState: FakespotState.reducer(state.fakespotState, action), + fakespotState: state.fakespotState, windowUUID: state.windowUUID, reloadWebView: true, browserViewType: browserViewType) @@ -128,7 +128,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { return BrowserViewControllerState( searchScreenState: state.searchScreenState, showDataClearanceFlow: state.showDataClearanceFlow, - fakespotState: FakespotState.reducer(state.fakespotState, action), + fakespotState: state.fakespotState, toast: toastType, windowUUID: state.windowUUID, browserViewType: state.browserViewType) @@ -137,7 +137,7 @@ struct BrowserViewControllerState: ScreenState, Equatable { return BrowserViewControllerState( searchScreenState: state.searchScreenState, showDataClearanceFlow: state.showDataClearanceFlow, - fakespotState: FakespotState.reducer(state.fakespotState, action), + fakespotState: state.fakespotState, showOverlay: showOverlay, windowUUID: state.windowUUID, browserViewType: state.browserViewType) diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index b1db0409be5f..1916ca2a7cce 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -510,7 +510,7 @@ class BrowserViewController: UIViewController, private func dismissModalsIfStartAtHome() { guard tabManager.startAtHomeCheck() else { return } - let action = FakespotAction(isExpanded: false, + let action = FakespotAction(isOpen: false, windowUUID: windowUUID, actionType: FakespotActionType.setAppearanceTo) store.dispatch(action) @@ -1807,6 +1807,11 @@ class BrowserViewController: UIViewController, self.screenshotHelper.takeScreenshot(tab) + // when navigate in tab, if the tab mime type is pdf, we should scroll to top + if tab.mimeType == MIMEType.PDF { + tab.shouldScrollToTop = true + } + if let url = webView.url { if (!InternalURL.isValid(url: url) || url.isReaderModeURL) && !url.isFileURL { postLocationChangeNotificationForTab(tab, navigation: navigation) @@ -1854,7 +1859,7 @@ class BrowserViewController: UIViewController, let url = webView.url else { // We're on homepage or a blank tab - let action = FakespotAction(isExpanded: false, + let action = FakespotAction(isOpen: false, windowUUID: windowUUID, actionType: FakespotActionType.setAppearanceTo) store.dispatch(action) @@ -1870,7 +1875,7 @@ class BrowserViewController: UIViewController, let product = ShoppingProduct(url: url, client: FakespotClient(environment: environment)) guard product.product != nil, !tab.isPrivate else { - let action = FakespotAction(isExpanded: false, + let action = FakespotAction(isOpen: false, windowUUID: windowUUID, actionType: FakespotActionType.setAppearanceTo) store.dispatch(action) @@ -1905,7 +1910,7 @@ class BrowserViewController: UIViewController, fakespotState.sidebarOpenForiPadLandscape, UIDevice.current.userInterfaceIdiom == .pad { // Sidebar should be displayed, display Fakespot - let action = FakespotAction(isExpanded: true, + let action = FakespotAction(isOpen: true, windowUUID: windowUUID, actionType: FakespotActionType.setAppearanceTo) store.dispatch(action) diff --git a/firefox-ios/Client/Frontend/Browser/TabDisplayManager.swift b/firefox-ios/Client/Frontend/Browser/TabDisplayManager.swift index e6d7d94b8069..8d85e189d791 100644 --- a/firefox-ios/Client/Frontend/Browser/TabDisplayManager.swift +++ b/firefox-ios/Client/Frontend/Browser/TabDisplayManager.swift @@ -426,7 +426,7 @@ class LegacyTabDisplayManager: NSObject, FeatureFlaggable { } func undoCloseTab(tab: Tab, index: Int?) { - tabManager.undoCloseTab(tab: tab, position: index) + tabManager.undoCloseTab() _ = profile.recentlyClosedTabs.popFirstTab() refreshStore { [weak self] in @@ -549,7 +549,10 @@ extension LegacyTabDisplayManager: UICollectionViewDataSource { // MARK: - InactiveTabsDelegate extension LegacyTabDisplayManager: LegacyInactiveTabsDelegate { func closeInactiveTab(_ tab: Tab, index: Int) { - tabManager.backupCloseTab = BackupCloseTab(tab: tab, restorePosition: index) + tabManager.backupCloseTab = BackupCloseTab( + tab: tab, + restorePosition: index, + isSelected: false) removeSingleInactiveTab(tab) cfrDelegate?.presentUndoSingleToast { [weak self] undoButtonPressed in @@ -622,7 +625,7 @@ extension LegacyTabDisplayManager: LegacyInactiveTabsDelegate { } private func undoDeleteInactiveTab(_ tab: Tab, at index: Int) { - tabManager.undoCloseTab(tab: tab, position: index) + tabManager.undoCloseTab() inactiveViewModel?.inactiveTabs.insert(tab, at: index) if inactiveViewModel?.inactiveTabs.count == 1 { diff --git a/firefox-ios/Client/Frontend/Browser/TabScrollController.swift b/firefox-ios/Client/Frontend/Browser/TabScrollController.swift index 3af03b63a2cc..2633e8107e26 100644 --- a/firefox-ios/Client/Frontend/Browser/TabScrollController.swift +++ b/firefox-ios/Client/Frontend/Browser/TabScrollController.swift @@ -60,12 +60,6 @@ class TabScrollingController: NSObject, FeatureFlaggable, SearchBarLocationProvi return isBottomSearchBar ? bottomShowing : headerTopOffset == 0 } - private var shouldSetInitialScrollToTop: Bool { - return tab?.mimeType == MIMEType.PDF && shouldScrollToTop - } - - var shouldScrollToTop = false - private var isZoomedOut = false private var lastZoomedScale: CGFloat = 0 private var isUserZoom = false @@ -160,6 +154,8 @@ class TabScrollingController: NSObject, FeatureFlaggable, SearchBarLocationProvi guard !tabIsLoading() else { return } + tab?.shouldScrollToTop = false + if let containerView = scrollView?.superview { let translation = gesture.translation(in: containerView) let delta = lastContentOffset - translation.y @@ -458,7 +454,7 @@ extension TabScrollingController: UIScrollViewDelegate { func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { guard !tabIsLoading(), !isBouncingAtBottom(), isAbleToScroll else { return } - shouldScrollToTop = false + tab?.shouldScrollToTop = false if decelerate || (toolbarState == .animating && !decelerate) { if scrollDirection == .up { @@ -473,7 +469,7 @@ extension TabScrollingController: UIScrollViewDelegate { // before the WKWebView's contentOffset is reset as a result of the contentView's frame becoming smaller func scrollViewDidScroll(_ scrollView: UIScrollView) { // for PDFs, we should set the initial offset to 0 (ZERO) - if shouldSetInitialScrollToTop { + if let tab, tab.shouldScrollToTop { setOffset(y: 0, for: scrollView) } diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Legacy/LegacyGridTabViewController.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Legacy/LegacyGridTabViewController.swift index 705e81d5d804..d77f1ccd1ec6 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Legacy/LegacyGridTabViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Legacy/LegacyGridTabViewController.swift @@ -398,8 +398,10 @@ class LegacyGridTabViewController: UIViewController, /// Handles close tab by clicking on close button or swipe gesture func closeTabAction(tab: Tab, cell: LegacyTabCell) { - tabManager.backupCloseTab = BackupCloseTab(tab: tab, - restorePosition: tabManager.tabs.firstIndex(of: tab)) + tabManager.backupCloseTab = BackupCloseTab( + tab: tab, + restorePosition: tabManager.tabs.firstIndex(of: tab), + isSelected: tabManager.selectedTab?.tabUUID == tab.tabUUID) tabDisplayManager.tabDisplayCompletionDelegate = self tabDisplayManager.performCloseAction(for: tab) diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift index 705ef7b09707..15f7798ce195 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift @@ -208,7 +208,7 @@ class TabManagerMiddleware { let tabManagerTabs = isPrivateMode ? tabManager.privateTabs : tabManager.normalActiveTabs tabManagerTabs.forEach { tab in let tabModel = TabModel(tabUUID: tab.tabUUID, - isSelected: tab == selectedTab, + isSelected: tab.tabUUID == selectedTab?.tabUUID, isPrivate: tab.isPrivate, isFxHomeTab: tab.isFxHomeTab, tabTitle: tab.displayTitle, @@ -358,10 +358,10 @@ class TabManagerMiddleware { private func undoCloseTab(state: AppState, uuid: WindowUUID) { let tabManager = tabManager(for: uuid) guard let tabsState = state.screenState(TabsPanelState.self, for: .tabsPanel, window: uuid), - let backupTab = tabManager.backupCloseTab + tabManager.backupCloseTab != nil else { return } - tabManager.undoCloseTab(tab: backupTab.tab, position: backupTab.restorePosition) + tabManager.undoCloseTab() let model = getTabsDisplayModel(for: tabsState.isPrivateMode, shouldScrollToTab: false, uuid: uuid) let action = TabPanelMiddlewareAction(tabDisplayModel: model, @@ -456,7 +456,10 @@ class TabManagerMiddleware { Task { if let tabToClose = tabManager.getTabForUUID(uuid: tabUUID) { let index = tabsState.inactiveTabs.firstIndex { $0.tabUUID == tabUUID } - tabManager.backupCloseTab = BackupCloseTab(tab: tabToClose, restorePosition: index) + tabManager.backupCloseTab = BackupCloseTab( + tab: tabToClose, + restorePosition: index, + isSelected: false) } await tabManager.removeTab(tabUUID) @@ -475,9 +478,9 @@ class TabManagerMiddleware { private func undoCloseInactiveTab(uuid: WindowUUID) { let windowTabManager = self.tabManager(for: uuid) - guard let backupTab = windowTabManager.backupCloseTab else { return } + guard windowTabManager.backupCloseTab != nil else { return } - windowTabManager.undoCloseTab(tab: backupTab.tab, position: backupTab.restorePosition) + windowTabManager.undoCloseTab() let inactiveTabs = self.refreshInactiveTabs(uuid: uuid) let refreshAction = TabPanelMiddlewareAction(inactiveTabModels: inactiveTabs, windowUUID: uuid, diff --git a/firefox-ios/Client/Frontend/Fakespot/FakespotAction.swift b/firefox-ios/Client/Frontend/Fakespot/FakespotAction.swift index 0697c419fd3c..6dc84a25bcde 100644 --- a/firefox-ios/Client/Frontend/Fakespot/FakespotAction.swift +++ b/firefox-ios/Client/Frontend/Fakespot/FakespotAction.swift @@ -6,15 +6,18 @@ import Common import Redux class FakespotAction: Action { + let isOpen: Bool? let isExpanded: Bool? let tabUUID: TabUUID? let productId: String? - init(isExpanded: Bool? = nil, + init(isOpen: Bool? = nil, + isExpanded: Bool? = nil, tabUUID: TabUUID? = nil, productId: String? = nil, windowUUID: UUID, actionType: ActionType) { + self.isOpen = isOpen self.isExpanded = isExpanded self.tabUUID = tabUUID self.productId = productId diff --git a/firefox-ios/Client/Frontend/Fakespot/FakespotState.swift b/firefox-ios/Client/Frontend/Fakespot/FakespotState.swift index a1c300258df4..ad2a4588a1fb 100644 --- a/firefox-ios/Client/Frontend/Fakespot/FakespotState.swift +++ b/firefox-ios/Client/Frontend/Fakespot/FakespotState.swift @@ -84,13 +84,13 @@ struct FakespotState: ScreenState, Equatable { return state case FakespotActionType.reviewQualityDidChange: - let isExpanded = action.isExpanded ?? state.isSettingsExpanded + let isExpanded = action.isExpanded ?? state.isReviewQualityExpanded var state = state state.expandState[state.currentTabUUID, default: ExpandState()].isReviewQualityExpanded = isExpanded return state case FakespotActionType.highlightsDidChange: - let isExpanded = action.isExpanded ?? state.isSettingsExpanded + let isExpanded = action.isExpanded ?? state.isHighlightsSectionExpanded var state = state state.expandState[state.currentTabUUID, default: ExpandState()].isHighlightsSectionExpanded = isExpanded return state @@ -138,16 +138,14 @@ struct FakespotState: ScreenState, Equatable { return state case FakespotActionType.setAppearanceTo: - let isEnabled = action.isExpanded ?? state.isSettingsExpanded + let isEnabled = action.isOpen ?? state.isOpen var state = state state.isOpen = isEnabled state.sendSurfaceDisplayedTelemetryEvent = !isEnabled return state case FakespotActionType.surfaceDisplayedEventSend: - let isEnabled = action.isExpanded ?? state.isSettingsExpanded var state = state - state.isOpen = isEnabled state.sendSurfaceDisplayedTelemetryEvent = false return state diff --git a/firefox-ios/Client/Frontend/Fakespot/FakespotViewController.swift b/firefox-ios/Client/Frontend/Fakespot/FakespotViewController.swift index fd3c235ac0b1..a3e8bd756907 100644 --- a/firefox-ios/Client/Frontend/Fakespot/FakespotViewController.swift +++ b/firefox-ios/Client/Frontend/Fakespot/FakespotViewController.swift @@ -423,7 +423,7 @@ class FakespotViewController: UIViewController, if dismissPermanently { self?.triggerDismiss() } else { - let appearanceAction = FakespotAction(isExpanded: false, + let appearanceAction = FakespotAction(isOpen: false, windowUUID: windowUUID, actionType: FakespotActionType.setAppearanceTo) store.dispatch(appearanceAction) @@ -468,7 +468,7 @@ class FakespotViewController: UIViewController, let reviewQualityCardView: FakespotReviewQualityCardView = .build() viewModel.reviewQualityCardViewModel.expandState = fakespotState.isReviewQualityExpanded ? .expanded : .collapsed viewModel.reviewQualityCardViewModel.dismissViewController = { - let action = FakespotAction(isExpanded: false, + let action = FakespotAction(isOpen: false, windowUUID: windowUUID, actionType: FakespotActionType.setAppearanceTo) store.dispatch(action) @@ -527,7 +527,7 @@ class FakespotViewController: UIViewController, self?.viewModel.addTab(url: adData.url) self?.viewModel.recordSurfaceAdsClickedTelemetry() self?.viewModel.reportAdEvent(eventName: .trustedDealsLinkClicked, aidvs: [adData.aid]) - let action = FakespotAction(isExpanded: false, + let action = FakespotAction(isOpen: false, windowUUID: windowUUID, actionType: FakespotActionType.setAppearanceTo) store.dispatch(action) diff --git a/firefox-ios/Client/TabManagement/Legacy/LegacyTabManager.swift b/firefox-ios/Client/TabManagement/Legacy/LegacyTabManager.swift index d262e439597b..5ebf71e9f9ee 100644 --- a/firefox-ios/Client/TabManagement/Legacy/LegacyTabManager.swift +++ b/firefox-ios/Client/TabManagement/Legacy/LegacyTabManager.swift @@ -60,6 +60,7 @@ enum SwitchPrivacyModeResult { struct BackupCloseTab { var tab: Tab var restorePosition: Int? + var isSelected: Bool } // TabManager must extend NSObjectProtocol in order to implement WKNavigationDelegate @@ -567,11 +568,17 @@ class LegacyTabManager: NSObject, FeatureFlaggable, TabManager, TabEventHandler let tab = tabs[index] let viableTabsIndex = deletedIndexForViableTabs(tab) if TabTrayFlagManager.isRefactorEnabled { - backupCloseTab = BackupCloseTab(tab: tab, restorePosition: viableTabsIndex) + backupCloseTab = BackupCloseTab( + tab: tab, + restorePosition: viableTabsIndex, + isSelected: selectedTab?.tabUUID == tab.tabUUID) } self.removeTab(tab, flushToDisk: true) self.updateIndexAfterRemovalOf(tab, deletedIndex: index, viableTabsIndex: viableTabsIndex) + // TODO: FXIOS-9084 This is not ideal, follow up in this ticket to make tab selection reasonably synchronous + try? await Task.sleep(nanoseconds: NSEC_PER_SEC/10) + TelemetryWrapper.recordEvent( category: .action, method: .close, @@ -593,7 +600,9 @@ class LegacyTabManager: NSObject, FeatureFlaggable, TabManager, TabEventHandler return } - backupCloseTab = BackupCloseTab(tab: tab, restorePosition: removalIndex) + backupCloseTab = BackupCloseTab(tab: tab, + restorePosition: removalIndex, + isSelected: selectedTab?.tabUUID == tab.tabUUID) let prevCount = count tabs.remove(at: removalIndex) assert(count == prevCount - 1, "Make sure the tab count was actually removed") @@ -632,7 +641,8 @@ class LegacyTabManager: NSObject, FeatureFlaggable, TabManager, TabEventHandler let currentModeTabs = tabs.filter { $0.isPrivate == isPrivateMode } if let tab = selectedTab, tab.isPrivate == isPrivateMode { backupCloseTab = BackupCloseTab(tab: tab, - restorePosition: tabs.firstIndex(of: tab)) + restorePosition: tabs.firstIndex(of: tab), + isSelected: selectedTab?.tabUUID == tab.tabUUID) } backupCloseTabs = tabs for tab in currentModeTabs { @@ -839,24 +849,21 @@ class LegacyTabManager: NSObject, FeatureFlaggable, TabManager, TabEventHandler storeChanges() } - func undoCloseTab(tab: Tab, position: Int?) { - if let index = position { - tabs.insert(tab, at: index) + func undoCloseTab() { + guard let backupCloseTab = self.backupCloseTab else { return } + + if let index = backupCloseTab.restorePosition { + tabs.insert(backupCloseTab.tab, at: index) } else { - tabs.append(tab) + tabs.append(backupCloseTab.tab) + } + + if backupCloseTab.isSelected { + self.selectTab(backupCloseTab.tab) } delegates.forEach { $0.get()?.tabManagerUpdateCount() } storeChanges() - - // Select previous selected tab - let tabUUID = selectedTab?.tabUUID - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { - if let tabUUID = tabUUID, - let tabToSelect = self.tabs.first(where: { $0.tabUUID == tabUUID }) { - self.selectTab(tabToSelect) - } - } } // Select the most recently visited tab, IFF it is also the parent tab of the closed tab. diff --git a/firefox-ios/Client/TabManagement/Tab.swift b/firefox-ios/Client/TabManagement/Tab.swift index f9816364e2bc..b5a07d074f10 100644 --- a/firefox-ios/Client/TabManagement/Tab.swift +++ b/firefox-ios/Client/TabManagement/Tab.swift @@ -126,6 +126,8 @@ class Tab: NSObject, ThemeApplicable { var startingSearchUrlWithAds: URL? var adsProviderName: String = "" var hasHomeScreenshot = false + var shouldScrollToTop = false + private var logger: Logger // To check if current URL is the starting page i.e. either blank page or internal page like topsites diff --git a/firefox-ios/Client/TabManagement/TabManager.swift b/firefox-ios/Client/TabManagement/TabManager.swift index 5a723694ed9d..8546e0657102 100644 --- a/firefox-ios/Client/TabManagement/TabManager.swift +++ b/firefox-ios/Client/TabManagement/TabManager.swift @@ -39,7 +39,7 @@ protocol TabManager: AnyObject { func addTabsForURLs(_ urls: [URL], zombie: Bool, shouldSelectTab: Bool) func removeTab(_ tab: Tab, completion: (() -> Void)?) func removeTabs(_ tabs: [Tab]) - func undoCloseTab(tab: Tab, position: Int?) + func undoCloseTab() func getMostRecentHomepageTab() -> Tab? func getTabFor(_ url: URL) -> Tab? func clearAllTabsHistory() diff --git a/firefox-ios/Providers/Profile.swift b/firefox-ios/Providers/Profile.swift index d05cd467be34..1f2819d9db08 100644 --- a/firefox-ios/Providers/Profile.swift +++ b/firefox-ios/Providers/Profile.swift @@ -306,24 +306,9 @@ open class BrowserProfile: Profile { object: nil ) - if AppInfo.isChinaEdition { - // Set the default homepage. - prefs.setString(PrefsDefaults.ChineseHomePageURL, forKey: PrefsKeys.KeyDefaultHomePageURL) - - if prefs.stringForKey(PrefsKeys.KeyNewTab) == nil { - prefs.setString(PrefsDefaults.ChineseHomePageURL, forKey: PrefsKeys.NewTabCustomUrlPrefKey) - prefs.setString(PrefsDefaults.ChineseNewTabDefault, forKey: PrefsKeys.KeyNewTab) - } - - if prefs.stringForKey(PrefsKeys.HomePageTab) == nil { - prefs.setString(PrefsDefaults.ChineseHomePageURL, forKey: PrefsKeys.HomeButtonHomePageURL) - prefs.setString(PrefsDefaults.ChineseNewTabDefault, forKey: PrefsKeys.HomePageTab) - } - } else { - // Remove the default homepage. This does not change the user's preference, - // just the behaviour when there is no homepage. - prefs.removeObjectForKey(PrefsKeys.KeyDefaultHomePageURL) - } + // Remove the default homepage. This does not change the user's preference, + // just the behaviour when there is no homepage. + prefs.removeObjectForKey(PrefsKeys.KeyDefaultHomePageURL) // Create the "Downloads" folder in the documents directory. if let downloadsPath = try? FileManager.default.url( diff --git a/firefox-ios/Shared/Prefs.swift b/firefox-ios/Shared/Prefs.swift index 68dc99818174..228668edaa95 100644 --- a/firefox-ios/Shared/Prefs.swift +++ b/firefox-ios/Shared/Prefs.swift @@ -176,11 +176,6 @@ public struct PrefsKeys { public static let splashScreenShownKey = "splashScreenShownKey" } -public struct PrefsDefaults { - public static let ChineseHomePageURL = "https://mobile.firefoxchina.cn/?ios" - public static let ChineseNewTabDefault = "HomePage" -} - public protocol Prefs { func getBranchPrefix() -> String func branch(_ branch: String) -> Prefs diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift index 24da7879c182..a453aac6d8db 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift @@ -87,7 +87,7 @@ class MockTabManager: TabManager { func undoCloseAllTabs() {} - func undoCloseTab(tab: Client.Tab, position: Int?) {} + func undoCloseTab() {} func getTabFor(_ url: URL) -> Tab? { return nil diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabTray/Legacy/LegacyTabTrayViewControllerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabTray/Legacy/LegacyTabTrayViewControllerTests.swift index 681199cbedef..7d239dbdd70c 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabTray/Legacy/LegacyTabTrayViewControllerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabTray/Legacy/LegacyTabTrayViewControllerTests.swift @@ -49,28 +49,27 @@ final class LegacyTabTrayViewControllerTests: XCTestCase { } func testCountUpdatesAfterTabRemoval() throws { - throw XCTSkip("Skipping since fails in Bitrise fix in FXIOS-7487") -// let tabToRemove = manager.addTab() -// manager.addTab() -// -// XCTAssertEqual(tabTray.viewModel.normalTabsCount, "2") -// XCTAssertEqual(tabTray.countLabel.text, "2") -// -// gridTab.tabDisplayManager.performCloseAction(for: tabToRemove) -// // Wait for notification of .TabClosed when tab is removed -// let expectation = expectation(description: "notificationReceived") -// NotificationCenter.default.addObserver( -// forName: .UpdateLabelOnTabClosed, -// object: nil, -// queue: nil -// ) { notification in -// expectation.fulfill() -// -// XCTAssertEqual(self.tabTray.viewModel.normalTabsCount, "1") -// XCTAssertEqual(self.tabTray.countLabel.text, "1") -// } -// -// waitForExpectations(timeout: 3.0) + let tabToRemove = manager.addTab() + manager.addTab() + + XCTAssertEqual(tabTray.viewModel.normalTabsCount, "2") + XCTAssertEqual(tabTray.countLabel.text, "2") + + gridTab.tabDisplayManager.performCloseAction(for: tabToRemove) + // Wait for notification of .TabClosed when tab is removed + let expectation = expectation(description: "notificationReceived") + NotificationCenter.default.addObserver( + forName: .UpdateLabelOnTabClosed, + object: nil, + queue: nil + ) { notification in + expectation.fulfill() + + XCTAssertEqual(self.tabTray.viewModel.normalTabsCount, "1") + XCTAssertEqual(self.tabTray.countLabel.text, "1") + } + + waitForExpectations(timeout: 3.0) } func testTabTrayRevertToRegular_ForNoPrivateTabSelected() { diff --git a/firefox-ios/nimbus-features/messaging/messaging-evergreen-messages.fml.yaml b/firefox-ios/nimbus-features/messaging/messaging-evergreen-messages.fml.yaml index 86eae683acf5..830c0c201d8f 100644 --- a/firefox-ios/nimbus-features/messaging/messaging-evergreen-messages.fml.yaml +++ b/firefox-ios/nimbus-features/messaging/messaging-evergreen-messages.fml.yaml @@ -53,3 +53,21 @@ import: action: OPEN_URL action-params: url: https://www.macrumors.com + + # Serves as an example of how microsurveys may look like + microsurvey-message: + surface: microsurvey + style: MICROSURVEY + trigger-if-all: + - NEVER + title: Microsurvey/Microsurvey.Prompt.TitleLabel.v127 + text: "How satisfied are you with printing in Firefox?" # Should not show this message if this is nil + button-label: Microsurvey/Microsurvey.Prompt.TakeSurveyButton.v127 + microsurveyConfig: + target-feature: printing + options: + - Microsurvey/Microsurvey.Survey.Options.LikertScaleOption1.v127 + - Microsurvey/Microsurvey.Survey.Options.LikertScaleOption2.v127 + - Microsurvey/Microsurvey.Survey.Options.LikertScaleOption3.v127 + - Microsurvey/Microsurvey.Survey.Options.LikertScaleOption4.v127 + - Microsurvey/Microsurvey.Survey.Options.LikertScaleOption5.v127 diff --git a/firefox-ios/nimbus-features/messaging/messaging-firefox-ios.fml.yaml b/firefox-ios/nimbus-features/messaging/messaging-firefox-ios.fml.yaml index b57ade9b44db..507e63388de0 100644 --- a/firefox-ios/nimbus-features/messaging/messaging-firefox-ios.fml.yaml +++ b/firefox-ios/nimbus-features/messaging/messaging-firefox-ios.fml.yaml @@ -63,7 +63,7 @@ import: DEFAULT: priority: 50 max-display-count: 5 - MICRO_SURVEY: + MICROSURVEY: priority: 50 max-display-count: 5 NOTIFICATION: diff --git a/firefox-ios/nimbus-features/messaging/messaging.fml.yaml b/firefox-ios/nimbus-features/messaging/messaging.fml.yaml index 81d73655810d..7d02294d75a2 100644 --- a/firefox-ios/nimbus-features/messaging/messaging.fml.yaml +++ b/firefox-ios/nimbus-features/messaging/messaging.fml.yaml @@ -163,6 +163,11 @@ objects: type: Option description: The experiment slug that this message is involved in. default: null + + microsurveyConfig: + type: Option + description: Optional configuration data for a microsurvey. + default: null StyleData: description: > @@ -182,6 +187,20 @@ objects: before it is expired. default: 5 + MicrosurveyConfig: + description: > + Attributes relating to microsurvey messaging. + fields: + target-feature: + type: MicrosurveyTargetFeature + description: > + The type of feature the microsurvey is targeted. + default: Unknown # Should not be defaulted + options: + description: The list of survey options to present to the user. + type: List + default: [] # Should not be defaulted + enums: MessageSurfaceId: description: > @@ -192,6 +211,8 @@ enums: description: This is the card that appears at the top on the Firefox Home Page. survey: description: This is a full-page that appears providing a survey to the user. + microsurvey: + description: This is a microsurvey that appears on top of the bottom toolbar to the user. notification: description: This is a local notification send to the user periodically with tips and updates. Unknown: @@ -204,3 +225,12 @@ enums: description: The next eligible message should be shown. show-none: description: The surface should show no message. + + MicrosurveyTargetFeature: + description: > + The specific feature the microsurvey is for. This is a label that matches across both Android and iOS. + variants: + printing: + description: The printing feature. + Unknown: + description: No target feature set. Only used in an invalid experiment configuration. diff --git a/focus-ios/.swiftlint.yml b/focus-ios/.swiftlint.yml index 2a9566da97c9..7d0b64bc4c1e 100644 --- a/focus-ios/.swiftlint.yml +++ b/focus-ios/.swiftlint.yml @@ -21,11 +21,11 @@ only_rules: # Only enforce these rules, ignore all others - inclusive_language - invalid_swiftlint_command - large_tuple - # - leading_whitespace + - leading_whitespace - legacy_cggeometry_functions - # - legacy_constant + - legacy_constant - legacy_constructor - # - legacy_hashing + - legacy_hashing - legacy_nsgeometry_functions - mark - no_space_in_method_call @@ -33,7 +33,7 @@ only_rules: # Only enforce these rules, ignore all others - operator_whitespace - orphaned_doc_comment - private_over_fileprivate - # - protocol_property_accessors_order + - protocol_property_accessors_order - redundant_discardable_let - redundant_objc_attribute - redundant_optional_initialization diff --git a/focus-ios/Blockzilla.xcodeproj/project.pbxproj b/focus-ios/Blockzilla.xcodeproj/project.pbxproj index 6a4425f987aa..46d4faa0d9f6 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.20240427050414; + version = 127.0.20240430050317; }; }; 8A0E7F2C2BA0F0E0006BC6B6 /* XCRemoteSwiftPackageReference "Fuzi" */ = { 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 a17a55fa325d..552b7bd1448f 100644 --- a/focus-ios/Blockzilla.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/focus-ios/Blockzilla.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -51,8 +51,8 @@ "repositoryURL": "https://github.com/mozilla/rust-components-swift", "state": { "branch": null, - "revision": "67ea48dede8487276ccd6531f9da29ca793c11cc", - "version": "127.0.20240427050414" + "revision": "fb6671d24023c79a1dc2afd3aac7c850167c5c63", + "version": "127.0.20240430050317" } }, { diff --git a/test-fixtures/generate-metrics.sh b/test-fixtures/generate-metrics.sh index d9ddbed9b5f5..ce28003166c2 100755 --- a/test-fixtures/generate-metrics.sh +++ b/test-fixtures/generate-metrics.sh @@ -5,7 +5,7 @@ set -e BUILD_LOG_FILE="$1" TYPE_LOG_FILE="$2" THRESHOLD_UNIT_TEST=9 -THRESHOLD_XCUITEST=12 +THRESHOLD_XCUITEST=13 WARNING_COUNT=$(grep -E -v "SourcePackages/checkouts" "$BUILD_LOG_FILE" | grep -E "(^|:)[0-9]+:[0-9]+:|warning:|ld: warning:|:0: warning:|fatal|===" | uniq | wc -l)