Skip to content
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

[MOB-3155] Review BrowserViewController upgrade leftovers #838

Open
wants to merge 5 commits into
base: mob-3113-firefox-upgrade-133
Choose a base branch
from

Conversation

lucaschifino
Copy link
Collaborator

@lucaschifino lucaschifino commented Feb 6, 2025

MOB-3155

Context

Some of Ecosia changes on BrowserViewController related files got lost on the old project path.

Approach

Review file by file and apply the relevant changes on the new project path files.

Other

Before merging

Checklist

  • I performed some relevant testing on a real device and/or simulator
  • I added the // Ecosia: helper comments where needed

Copy link

github-actions bot commented Feb 6, 2025

PR Reviewer Guide 🔍

(Review updated until commit 51408a1)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 5 🔵🔵🔵🔵🔵
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

The added logic in presentInsightfulSheetsIfNeeded (lines 106-124) includes a TODO comment indicating that the logic is not ideal and should be refactored in the future. This should be reviewed to ensure it does not introduce unintended side effects or performance issues.

        logger.log("Find and handle route called after didFinishLaunch after onboarding",
                   level: .info,
                   category: .coordinator)
        findAndHandle(route: savedRoute)
    }
}

// MARK: - BrowserDelegate

func showLegacyHomepage(
    inline: Bool,
    toastContainer: UIView,
    homepanelDelegate: HomePanelDelegate,
    libraryPanelDelegate: LibraryPanelDelegate,
    statusBarScrollDelegate: StatusBarScrollDelegate,
    overlayManager: OverlayModeManager
) {
    let legacyHomepageViewController = getHomepage(
        inline: inline,
Code Clarity

Several sections of the code (e.g., lines 393-396, 432-434, 760-763) include commented-out code with Ecosia-specific changes. These should be reviewed to ensure they are necessary and do not clutter the codebase unnecessarily.

    /* Ecosia: actionNeeded set to false
    let isActionNeeded = RustFirefoxAccounts.shared.isActionNeeded
     */
    let isActionNeeded = false
    let showWarningBadge = isActionNeeded

    if isToolbarRefactorEnabled {
        let action = ToolbarAction(
            showMenuWarningBadge: showWarningBadge,
            windowUUID: windowUUID,
            actionType: ToolbarActionType.showMenuWarningBadge
        )
        store.dispatch(action)
    } else {
        urlBar.warningMenuBadge(setVisible: showWarningBadge)
        toolbar.warningMenuBadge(setVisible: showWarningBadge)
    }
}

func updateToolbarStateForTraitCollection(_ newCollection: UITraitCollection) {
    let showNavToolbar = ToolbarHelper().shouldShowNavigationToolbar(for: newCollection)
    let showTopTabs = ToolbarHelper().shouldShowTopTabs(for: newCollection)

    switchToolbarIfNeeded()

    if isToolbarRefactorEnabled {
        if showNavToolbar {
            navigationToolbarContainer.isHidden = false
            navigationToolbarContainer.applyTheme(theme: currentTheme())
            updateTabCountUsingTabManager(self.tabManager)
        } else {
            navigationToolbarContainer.isHidden = true
        }

        updateToolbarStateTraitCollectionIfNecessary(newCollection)
    } else {
        urlBar.topTabsIsShowing = showTopTabs
        urlBar.setShowToolbar(!showNavToolbar)
        /* Ecosia: Change to circle button
        toolbar.addNewTabButton.isHidden = showNavToolbar
         */
        toolbar.circleButton.isHidden = false

        if showNavToolbar {
            toolbar.isHidden = false
            toolbar.tabToolbarDelegate = self
            toolbar.applyUIMode(
                isPrivate: tabManager.selectedTab?.isPrivate ?? false,
                theme: currentTheme()
            )
            toolbar.applyTheme(theme: currentTheme())
            handleMiddleButtonState(currentMiddleButtonState ?? .search)
            updateTabCountUsingTabManager(self.tabManager)
        } else {
            toolbar.tabToolbarDelegate = nil
            toolbar.isHidden = true
        }
    }

    appMenuBadgeUpdate()

    if showTopTabs, topTabsViewController == nil {
        let topTabsViewController = TopTabsViewController(tabManager: tabManager, profile: profile)
        topTabsViewController.delegate = self
        addChild(topTabsViewController)
        header.addArrangedViewToTop(topTabsViewController.view)
        self.topTabsViewController = topTabsViewController
        topTabsViewController.applyTheme()
    } else if showTopTabs, topTabsViewController != nil {
        topTabsViewController?.applyTheme()
    } else {
        if let topTabsView = topTabsViewController?.view {
            header.removeArrangedView(topTabsView)
        }
        topTabsViewController?.removeFromParent()
        topTabsViewController = nil
    }

    header.setNeedsLayout()
    view.layoutSubviews()

    if let tab = tabManager.selectedTab,
       let webView = tab.webView,
       !isToolbarRefactorEnabled {
        updateURLBarDisplayURL(tab)
        navigationToolbar.updateBackStatus(webView.canGoBack)
        navigationToolbar.updateForwardStatus(webView.canGoForward)
    }

    // Ecosia: Update toolbar search/add button
    toolbar.circleButton.config = isBottomSearchBar ? .newTab : .search
}

func dismissVisibleMenus() {
    displayedPopoverController?.dismiss(animated: true)
    if self.presentedViewController as? PhotonActionSheet != nil {
        self.presentedViewController?.dismiss(animated: true, completion: nil)
    }
}

@objc
func appDidEnterBackgroundNotification() {
    displayedPopoverController?.dismiss(animated: false) {
        self.updateDisplayedPopoverProperties = nil
        self.displayedPopoverController = nil
    }
    if self.presentedViewController as? PhotonActionSheet != nil {
        self.presentedViewController?.dismiss(animated: true, completion: nil)
    }
    if let tab = tabManager.selectedTab {
        screenshotHelper.takeScreenshot(tab)
    }
    // Formerly these calls were run during AppDelegate.didEnterBackground(), but we have
    // individual TabManager instances for each BVC, so we perform these here instead.
    tabManager.preserveTabs()
    logTelemetryForAppDidEnterBackground()
}

@objc
func tappedTopArea() {
    scrollController.showToolbars(animated: true)
}

@objc
func appWillResignActiveNotification() {
    // Dismiss any popovers that might be visible
    displayedPopoverController?.dismiss(animated: false) {
        self.updateDisplayedPopoverProperties = nil
        self.displayedPopoverController = nil
    }

    // If we are displaying a private tab, hide any elements in the tab that we wouldn't want shown
    // when the app is in the home switcher
    guard let privateTab = tabManager.selectedTab,
          privateTab.isPrivate,
          canShowPrivacyView
    else { return }

    contentStackView.alpha = 0
    /* Ecosia: Update background overlay for private tabs
    privacyWindowHelper.showWindow(withThemedColor: currentTheme().colors.layer3)
     */
    privacyWindowHelper.showWindow(withThemedColor: currentTheme().colors.ecosia.backgroundPrimary)

    if isToolbarRefactorEnabled {
        addressToolbarContainer.alpha = 0
    } else {
        urlBar.locationContainer.alpha = 0
    }
    presentedViewController?.popoverPresentationController?.containerView?.alpha = 0
    presentedViewController?.view.alpha = 0
}

var canShowPrivacyView: Bool {
    // Show privacy view if no view controller is presented
    // or if the presented view is a PhotonActionSheet.
    self.presentedViewController == nil || presentedViewController is PhotonActionSheet
}

@objc
func appDidBecomeActiveNotification() {
    // Re-show any components that might have been hidden because they were being displayed
    // as part of a private mode tab
    UIView.animate(
        withDuration: 0.2,
        delay: 0,
        options: UIView.AnimationOptions(),
        animations: {
            self.contentStackView.alpha = 1
            if self.isToolbarRefactorEnabled {
                self.addressToolbarContainer.alpha = 1
            } else {
                self.urlBar.locationContainer.alpha = 1
            }
            self.presentedViewController?.popoverPresentationController?.containerView?.alpha = 1
            self.presentedViewController?.view.alpha = 1
        }, completion: { _ in
            self.privacyWindowHelper.removeWindow()
        })

    if let tab = tabManager.selectedTab, !tab.isFindInPageMode {
        // Re-show toolbar which might have been hidden during scrolling (prior to app moving into the background)
        scrollController.showToolbars(animated: false)
    }

    browserDidBecomeActive()
}

func browserDidBecomeActive() {
    let uuid = tabManager.windowUUID
    AppEventQueue.started(.browserUpdatedForAppActivation(uuid))
    defer { AppEventQueue.completed(.browserUpdatedForAppActivation(uuid)) }

    nightModeUpdates()

    // Update lock icon without redrawing the whole locationView
    if let tab = tabManager.selectedTab, !isToolbarRefactorEnabled {
        // It appears this was added to fix an issue with the lock icon, so we're
        // calling into this for some kind of beneficial side effect. We should
        // probably explore a different solution; tab content blocking does not
        // change every time the app is brought forward. [FXIOS-10091]
        urlBar.locationView.tabDidChangeContentBlocking(tab)
    }

    dismissModalsIfStartAtHome()
}

private func nightModeUpdates() {
    if NightModeHelper.isActivated(),
       !featureFlags.isFeatureEnabled(.nightMode, checking: .buildOnly) {
        NightModeHelper.turnOff()
        themeManager.applyThemeUpdatesToWindows()
    }

    NightModeHelper.cleanNightModeDefaults()
}

private func dismissModalsIfStartAtHome() {
    guard tabManager.startAtHomeCheck() else { return }
    let action = FakespotAction(isOpen: false,
                                windowUUID: windowUUID,
                                actionType: FakespotActionType.setAppearanceTo)
    store.dispatch(action)

    guard presentedViewController != nil else { return }
    dismissVC()
}

// MARK: - Redux

func subscribeToRedux() {
    let action = ScreenAction(windowUUID: windowUUID,
                              actionType: ScreenActionType.showScreen,
                              screen: .browserViewController)
    store.dispatch(action)

    let browserAction = GeneralBrowserMiddlewareAction(
        toolbarPosition: searchBarPosition,
        windowUUID: windowUUID,
        actionType: GeneralBrowserMiddlewareActionType.browserDidLoad)
    store.dispatch(browserAction)

    let uuid = self.windowUUID
    store.subscribe(self, transform: {
        $0.select({ appState in
            return BrowserViewControllerState(appState: appState, uuid: uuid)
        })
    })
}

func unsubscribeFromRedux() {
    let action = ScreenAction(windowUUID: windowUUID,
                              actionType: ScreenActionType.closeScreen,
                              screen: .browserViewController)
    store.dispatch(action)
    // Note: actual `store.unsubscribe()` is not strictly needed; Redux uses weak subscribers
}

func newState(state: BrowserViewControllerState) {
    browserViewControllerState = state

    // opens or close sidebar/bottom sheet to match the saved state
    if state.fakespotState.isOpen {
        let productURL = isToolbarRefactorEnabled ?
            store.state.screenState(ToolbarState.self, for: .toolbar, window: windowUUID)?.addressToolbar.url :
            urlBar.currentURL
        guard let productURL else { return }
        handleFakespotFlow(productURL: productURL)
    } else if !state.fakespotState.isOpen {
        dismissFakespotIfNeeded()
    }

    if state.reloadWebView {
        updateContentInHomePanel(state.browserViewType)
    }

    setupMiddleButtonStatus(isLoading: false)

    if let toast = state.toast {
        self.showToastType(toast: toast)
    }

    if state.showOverlay == true {
        overlayManager.openNewTab(url: nil, newTabSettings: newTabSettings)
    } else if state.showOverlay == false {
        overlayManager.cancelEditing(shouldCancelLoading: false)
    }

    executeNavigationAndDisplayActions()

    handleMicrosurvey(state: state)
}

private func showToastType(toast: ToastType) {
    switch toast {
    case .addShortcut,
            .addToReadingList,
            .removeShortcut,
            .removeFromReadingList,
            .addBookmark:
        SimpleToast().showAlertWithText(
            toast.title,
            bottomContainer: contentContainer,
            theme: currentTheme()
        )
    default:
        let viewModel = ButtonToastViewModel(
            labelText: toast.title,
            buttonText: toast.buttonText)
        let uuid = windowUUID
        let toast = ButtonToast(viewModel: viewModel,
                                theme: currentTheme(),
                                completion: { buttonPressed in
            if let action = toast.reduxAction(for: uuid), buttonPressed {
                store.dispatch(action)
            }
        })

        show(toast: toast)
    }
}

private func handleMicrosurvey(state: BrowserViewControllerState) {
    if !state.microsurveyState.showPrompt {
        guard microsurvey != nil else { return }
        removeMicrosurveyPrompt()
    } else if state.microsurveyState.showSurvey {
        guard let model = state.microsurveyState.model else {
            logger.log("Microsurvey model should not be nil", level: .warning, category: .redux)
            return
        }
        navigationHandler?.showMicrosurvey(model: model)
    } else if state.microsurveyState.showPrompt {
        guard microsurvey == nil else { return }
        createMicrosurveyPrompt(with: state.microsurveyState)
    }
}

// MARK: - Lifecycle

override func viewDidLoad() {
    super.viewDidLoad()
    KeyboardHelper.defaultHelper.addDelegate(self)
    trackTelemetry()
    setupNotifications()
    addSubviews()
    listenForThemeChange(view)
    setupAccessibleActions()

    clipboardBarDisplayHandler = ClipboardBarDisplayHandler(prefs: profile.prefs, tabManager: tabManager)
    clipboardBarDisplayHandler?.delegate = self

    navigationToolbarContainer.toolbarDelegate = self

    scrollController.header = header
    scrollController.overKeyboardContainer = overKeyboardContainer
    scrollController.bottomContainer = bottomContainer

    updateToolbarStateForTraitCollection(traitCollection)

    setupConstraints()

    // Setup UIDropInteraction to handle dragging and dropping
    // links into the view from other apps.
    let dropInteraction = UIDropInteraction(delegate: self)
    view.addInteraction(dropInteraction)

    /* Ecosia: Remove SearchTelemetry
    searchTelemetry = SearchTelemetry(tabManager: tabManager)
     */

Logic Complexity
The presentInsightfulSheetsIfNeeded function (lines 106-150) introduces a complex decision-making process for presenting sheets. This logic should be validated for correctness and maintainability.

Copy link

github-actions bot commented Feb 6, 2025

PR Code Suggestions ✨

Latest suggestions up to 51408a1
Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Prevent simultaneous sheet presentations

Add a safeguard to ensure that presentInsightfulSheetsIfNeeded does not execute
multiple times simultaneously, which could lead to unexpected behavior.

firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+Ecosia.swift [106-124]

+private var isPresentingInsightfulSheets = false
+
 func presentInsightfulSheetsIfNeeded() {
-    guard isHomePage(),
+    guard !isPresentingInsightfulSheets,
+          isHomePage(),
           !showLoadingScreen(for: .shared) else { return }
+    isPresentingInsightfulSheets = true
+    defer { isPresentingInsightfulSheets = false }
     ...
 }
Suggestion importance[1-10]: 9

__

Why: Adding a safeguard to prevent simultaneous executions of presentInsightfulSheetsIfNeeded addresses potential race conditions and ensures reliable behavior. This is a significant enhancement to the code's robustness.

High
Add fallback for theme colors

Add a fallback mechanism for currentTheme().colors.ecosia.backgroundSecondary to
handle cases where the theme might not include the expected color.

firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift [3049-3050]

-keyboardBackdrop?.backgroundColor = currentTheme().colors.ecosia.backgroundSecondary
-navigationController?.view.backgroundColor = currentTheme().colors.ecosia.backgroundSecondary
+keyboardBackdrop?.backgroundColor = currentTheme().colors.ecosia.backgroundSecondary ?? currentTheme().colors.layer1
+navigationController?.view.backgroundColor = currentTheme().colors.ecosia.backgroundSecondary ?? currentTheme().colors.layer1
Suggestion importance[1-10]: 8

__

Why: Adding a fallback mechanism for theme colors enhances the code's resilience by ensuring a default value is used if the expected color is unavailable. This is a practical and effective improvement.

Medium
Possible issue
Validate inline variable initialization

Ensure that the inline variable is properly defined and initialized before being
used in the conditional statement to avoid potential runtime errors.

firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift [141-146]

-if inline, !User.shared.firstTime {
+if let inline = inline, !User.shared.firstTime {
     DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
         self?.browserViewController.presentInsightfulSheetsIfNeeded()
         EcosiaInstallType.evaluateCurrentEcosiaInstallType(storeUpgradeVersion: true)
     }
 }
Suggestion importance[1-10]: 8

__

Why: The suggestion ensures that the inline variable is properly initialized before use, which prevents potential runtime errors. This is a critical improvement for code safety and correctness.

Medium
Safeguard currentURL unwrapping

Ensure that the currentURL variable is safely unwrapped before being used in the
loadRequest method to prevent potential crashes.

firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift [1626-1629]

-if let currentURL = urlBar.currentURL, let nav = tab.loadRequest(URLRequest(url: currentURL)) {
+guard let currentURL = urlBar.currentURL else { return }
+if let nav = tab.loadRequest(URLRequest(url: currentURL)) {
     self.recordNavigationInTab(tab, navigation: nav, visitType: visitType)
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion ensures safe unwrapping of currentURL, which prevents potential crashes due to nil values. While the improvement is valuable, the existing code already handles the unwrapping in a concise manner.

Medium

Previous suggestions

Suggestions up to commit 693ceac
CategorySuggestion                                                                                                                                    Impact
Possible issue
Validate URL before creating request

Validate the currentURL in finishEditingAndSubmit to ensure it is not nil or
malformed before using it in URLRequest, to prevent potential crashes.

firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift [1628]

-if let currentURL = urlBar.currentURL, let nav = tab.loadRequest(URLRequest(url: currentURL)) {
+if let currentURL = urlBar.currentURL, let validURL = URL(string: currentURL.absoluteString), let nav = tab.loadRequest(URLRequest(url: validURL)) {
Suggestion importance[1-10]: 8

__

Why: Validating the URL before creating a request is crucial to prevent crashes due to malformed or nil URLs. This suggestion improves the robustness of the function and ensures safer handling of user input.

Medium
Handle empty presentation functions case

Ensure that the presentInsightfulSheetsIfNeeded function handles cases where
presentationFunctions contains no valid functions to execute, to avoid potential
unintended behavior or errors.

firefox-ios/Client/Ecosia/Extensions/BrowserViewController+Ecosia.swift [124]

-_ = presentationFunctions.first(where: { $0() })
+if presentationFunctions.first(where: { $0() }) == nil {
+    // Handle case where no presentation function is executed
+}
Suggestion importance[1-10]: 7

__

Why: The suggestion addresses a potential issue where no presentation function is executed, which could lead to unintended behavior. Adding a fallback ensures robustness, though the exact handling logic is not provided in the suggestion.

Medium
Handle nil navigation controller safely

Ensure that the popToBVC function properly handles cases where navigationController
or topViewController is nil to avoid runtime crashes.

firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift [2598]

-guard let currentViewController = navigationController?.topViewController else { return }
+guard let navigationController = navigationController, let currentViewController = navigationController.topViewController else { return }
Suggestion importance[1-10]: 6

__

Why: The suggestion ensures that the navigation controller is not nil before accessing its topViewController, which prevents potential runtime crashes. While useful, the improvement is minor as the existing code already handles nil topViewController.

Low
General
Add fallback for theme application

Add a fallback or default behavior for updateURLBarFollowingPrivateModeUI in case
tabManager.selectedTab or themeManager.getCurrentTheme returns nil, to prevent
potential crashes.

firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift [66]

-urlBar.applyUIMode(isPrivate: isPrivate, theme: themeManager.getCurrentTheme(for: windowUUID))
+if let theme = themeManager.getCurrentTheme(for: windowUUID) {
+    urlBar.applyUIMode(isPrivate: isPrivate, theme: theme)
+} else {
+    // Apply default theme or handle error
+}
Suggestion importance[1-10]: 7

__

Why: Adding a fallback for theme application ensures that the function handles cases where the theme is nil, preventing potential crashes. This enhances the robustness of the code, though the exact fallback behavior is not specified.

Medium

@lucaschifino lucaschifino marked this pull request as ready for review February 6, 2025 16:55
@lucaschifino lucaschifino requested a review from a team February 6, 2025 16:55
Copy link

github-actions bot commented Feb 6, 2025

Persistent review updated to latest commit 51408a1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant