Skip to content

Bugfix [Add new tab gesture] FXIOS issues on new Tab gesture #27993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public final class AddressToolbarAddTabView: UIView,

public func configure(_ configuration: AddressToolbarUXConfiguration) {
layer.cornerRadius = configuration.toolbarCornerRadius
layer.shadowRadius = configuration.toolbarCornerRadius
}

public func showHideAddTabIcon(shouldShow: Bool) {
Expand All @@ -41,6 +42,7 @@ public final class AddressToolbarAddTabView: UIView,
// MARK: - ThemeApplicable

public func applyTheme(theme: any Theme) {
layer.shadowColor = theme.colors.shadowStrong.cgColor
plusIconView.tintColor = theme.colors.textPrimary
backgroundColor = theme.colors.layerSurfaceMedium
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,16 @@ class BrowserCoordinator: BaseCoordinator,
}

func homepageScreenshotTool() -> (any Screenshotable)? {
if tabManager.selectedTab?.isPrivate == true {
return privateHomepageViewController
let newTabSettings = browserViewController.newTabSettings
switch newTabSettings {
case .blankPage, .homePage:
return nil
case .topSites:
if tabManager.selectedTab?.isPrivate == true {
return privateHomepageViewController
}
return homepageViewController ?? legacyHomepageViewController
}
return homepageViewController ?? legacyHomepageViewController
}

func setHomepageVisibility(isVisible: Bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,7 @@ class BrowserViewController: UIViewController,
statusBarOverlay.hasTopTabs = toolbarHelper.shouldShowTopTabs(for: traitCollection)
statusBarOverlay.applyTheme(theme: theme)
topTabsViewController?.applyTheme()
webPagePreview.applyTheme(theme: theme)

KeyboardHelper.defaultHelper.addDelegate(self)
listenForThemeChange(view)
Expand Down Expand Up @@ -1260,7 +1261,8 @@ class BrowserViewController: UIViewController,
statusBarOverlay: statusBarOverlay,
tabManager: tabManager,
windowUUID: windowUUID,
screenshotHelper: screenshotHelper
screenshotHelper: screenshotHelper,
prefs: profile.prefs
)
}

Expand Down Expand Up @@ -1301,6 +1303,9 @@ class BrowserViewController: UIViewController,
addressBarPanGestureHandler?.homepageScreenshotToolProvider = { [weak self] in
return self?.browserDelegate?.homepageScreenshotTool()
}
addressBarPanGestureHandler?.newTabSettingsProvider = { [weak self] in
return self?.newTabSettings
}
}
}

Expand Down Expand Up @@ -4804,8 +4809,8 @@ extension BrowserViewController: TopTabsDelegate {
}

func topTabsDidPressNewTab(_ isPrivate: Bool) {
let shouldLoadCustomHomePage = isToolbarRefactorEnabled && NewTabAccessors.getHomePage(profile.prefs) == .homePage
let homePageURL = HomeButtonHomePageAccessors.getHomePage(profile.prefs)
let shouldLoadCustomHomePage = isToolbarRefactorEnabled && newTabSettings == .homePage
let homePageURL = NewTabHomePageAccessors.getHomePage(profile.prefs)

if shouldLoadCustomHomePage, let url = homePageURL {
openBlankNewTab(focusLocationField: false, isPrivate: isPrivate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import UIKit
import Common
import Redux
import Shared

final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
typealias SubscriberStateType = ToolbarState
Expand All @@ -30,8 +31,10 @@ final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
private let windowUUID: WindowUUID
private let screenshotHelper: ScreenshotHelper?
var homepageScreenshotToolProvider: (() -> Screenshotable?)?
var newTabSettingsProvider: (() -> NewTabPage?)?
private var homepageScreenshot: UIImage?
private var toolbarState: ToolbarState?
private let prefs: Prefs

private var isRTL: Bool {
return UIView.userInterfaceLayoutDirection(
Expand All @@ -47,7 +50,8 @@ final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
statusBarOverlay: StatusBarOverlay,
tabManager: TabManager,
windowUUID: WindowUUID,
screenshotHelper: ScreenshotHelper?
screenshotHelper: ScreenshotHelper?,
prefs: Prefs
) {
self.addressToolbarContainer = addressToolbarContainer
self.contentContainer = contentContainer
Expand All @@ -56,6 +60,7 @@ final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
self.windowUUID = windowUUID
self.screenshotHelper = screenshotHelper
self.statusBarOverlay = statusBarOverlay
self.prefs = prefs
super.init()
subscribeToRedux()
setupGesture()
Expand Down Expand Up @@ -153,7 +158,8 @@ final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
)
)
statusBarOverlay.showOverlay(animated: !UIAccessibility.isReduceMotionEnabled)
if nextTab == nil {
case .changed:
if nextTab == nil, homepageScreenshot == nil {
let homepageScreenshotTool = homepageScreenshotToolProvider?()
homepageScreenshot = homepageScreenshotTool?.screenshot(bounds: CGRect(
x: 0.0,
Expand All @@ -162,7 +168,6 @@ final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
height: webPagePreview.frame.height
))
}
case .changed:
handleGestureChangedState(translation: translation, nextTab: nextTab)
case .ended, .cancelled, .failed:
let velocity = gesture.velocity(in: contentContainer)
Expand All @@ -172,7 +177,6 @@ final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
}

private func handleGestureChangedState(translation: CGPoint, nextTab: Tab?) {
webPagePreview.isHidden = false
let shouldAddNewTab = shouldAddNewTab(translation: translation.x, nextTab: nextTab)
applyCurrentTabTransform(translation.x, shouldAddNewTab: shouldAddNewTab)
applyPreviewTransform(translation: translation)
Expand All @@ -184,7 +188,15 @@ final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
let translation = width * (1 - progress)
webPagePreview.transform = CGAffineTransform(scaleX: scale, y: scale).translatedBy(x: translation, y: 0.0)
webPagePreview.alpha = progress
webPagePreview.setScreenshot(homepageScreenshot)
let pageSetting = newTabSettingsProvider?()
switch pageSetting {
case .homePage:
webPagePreview.setScreenshot(url: NewTabHomePageAccessors.getHomePage(prefs))
case .topSites:
webPagePreview.setScreenshot(homepageScreenshot)
case nil, .blankPage:
webPagePreview.setScreenshot(url: nil)
}
} else {
webPagePreview.alpha = 1.0
webPagePreview.setScreenshot(nextTab)
Expand Down Expand Up @@ -220,8 +232,8 @@ final class AddressBarPanGestureHandler: NSObject, StoreSubscriber {
webPagePreview.alpha = shouldCompleteTransition ? 1.0 : 0.0
webPagePreview.transform = shouldCompleteTransition ? .identity : previewTransform
} completion: { [self] _ in
webPagePreview.isHidden = true
webPagePreview.transitionDidEnd()
homepageScreenshot = nil

if shouldCompleteTransition {
store.dispatchLegacy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,9 @@ final class AddressToolbarContainer: UIView,
rightSkeletonAddressBar.transform = transform
if shouldAddNewTab {
let percentageTransform = abs(transform.tx) / bounds.width
if transform == .identity {
addNewTabView.showHideAddTabIcon(shouldShow: false)
}
UIView.animate(withDuration: UX.addNewTabFadeAnimationDuration) {
self.addNewTabView.showHideAddTabIcon(shouldShow:
percentageTransform > UX.addNewTabPercentageAnimationThreshold)
Expand Down
25 changes: 24 additions & 1 deletion firefox-ios/Client/TabManagement/TabWebViewPreview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import UIKit
import SiteImageView
import Common

final class TabWebViewPreview: UIView {
final class TabWebViewPreview: UIView, ThemeApplicable {
private struct UX {
static let faviconCornerRadius: CGFloat = 20.0
static let faviconImageViewSize: CGFloat = 45.0
static let backgroundShadowCornerRadius: CGFloat = 14.0
static let backgroundShadowOpacity: Float = 1
static let backgroundShadowOffset = CGSize(width: 0, height: 2)
}
private lazy var webPageScreenshotImageView: UIImageView = .build {
$0.contentMode = .top
Expand All @@ -34,6 +37,12 @@ final class TabWebViewPreview: UIView {

// MARK: - Layout
private func setupLayout() {
layer.shadowRadius = UX.backgroundShadowCornerRadius
layer.cornerRadius = screenCornerRadius
layer.shadowOpacity = UX.backgroundShadowOpacity
layer.shadowOffset = UX.backgroundShadowOffset
layer.masksToBounds = false

webPageScreenshotImageView.layer.cornerRadius = screenCornerRadius
addSubviews(webPageScreenshotImageView, faviconImageView)

Expand Down Expand Up @@ -83,6 +92,13 @@ final class TabWebViewPreview: UIView {
}
}

func setScreenshot(url: URL? = nil) {
faviconImageView.isHidden = url == nil
webPageScreenshotImageView.isHidden = true
faviconImageView.setFavicon(FaviconImageViewModel(siteURLString: url?.absoluteString,
faviconCornerRadius: UX.faviconCornerRadius))
}

func setScreenshot(_ image: UIImage?) {
faviconImageView.isHidden = true
webPageScreenshotImageView.isHidden = false
Expand All @@ -96,4 +112,11 @@ final class TabWebViewPreview: UIView {
func transitionDidEnd() {
layoutWasInvalidated = false
}

// MARK: - ThemeApplicable

func applyTheme(theme: any Theme) {
backgroundColor = theme.colors.layerSurfaceMedium
layer.shadowColor = theme.colors.shadowStrong.cgColor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ final class BrowserCoordinatorTests: XCTestCase, FeatureFlaggable {

func testHomepageScreenshotTool_returnsHomepage_forNormalTab() throws {
let subject = createSubject()
subject.browserViewController = browserViewController
subject.showHomepage(
overlayManager: overlayModeManager,
isZeroSearch: false,
Expand All @@ -157,8 +158,36 @@ final class BrowserCoordinatorTests: XCTestCase, FeatureFlaggable {
XCTAssertTrue(screenshotTool is HomepageViewController)
}

func testHomepageScreenshotTool_returnsNil_forNewBlankNewTab() throws {
let subject = createSubject()
subject.browserViewController = browserViewController
browserViewController.overrideNewTabSettings = .blankPage
subject.showHomepage(
overlayManager: overlayModeManager,
isZeroSearch: false,
statusBarScrollDelegate: scrollDelegate,
toastContainer: UIView()
)
XCTAssertNil(subject.homepageScreenshotTool())
}

func testHomepageScreenshotTool_returnsNil_forNewCustomURLNewTab() throws {
let subject = createSubject()
subject.browserViewController = browserViewController
browserViewController.overrideNewTabSettings = .homePage
subject.showHomepage(
overlayManager: overlayModeManager,
isZeroSearch: false,
statusBarScrollDelegate: scrollDelegate,
toastContainer: UIView()
)

XCTAssertNil(subject.homepageScreenshotTool())
}

func testHomepageScreenshotTool_returnsLegacyHomepage_forNormalTab() throws {
let subject = createSubject()
browserViewController.overrideNewTabSettings = .topSites
subject.showLegacyHomepage(
inline: false,
toastContainer: UIView(),
Expand All @@ -175,6 +204,7 @@ final class BrowserCoordinatorTests: XCTestCase, FeatureFlaggable {
@MainActor
func testHomepageScreenshotTool_returnsPrivateHomepage_forPrivateTab() throws {
let subject = createSubject()
browserViewController.overrideNewTabSettings = .topSites
let tab = tabManager.addTab(nil, afterTab: nil, zombie: false, isPrivate: true)
tabManager.selectTab(tab)
subject.showPrivateHomepage(overlayManager: overlayModeManager)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ class MockBrowserViewController: BrowserViewController {

var viewControllerToPresent: UIViewController?

var createWebViewCalled = 0
var runJavaScriptAlertPanelCalled = 0
var runJavaScriptConfirmPanelCalled = 0
var runJavaScriptTextInputPanelCalled = 0
var webViewDidCloseCalled = 0
var contextMenuConfigurationCalled = 0
var requestMediaCapturePermissionCalled = 0
var contextMenuDidEndForElementCalled = 0
override var newTabSettings: NewTabPage {
return overrideNewTabSettings
}
var overrideNewTabSettings: NewTabPage = .topSites

override var presentedViewController: UIViewController? {
return viewControllerToPresent
}
Expand Down
Loading