Skip to content

Conversation

@0nko
Copy link
Member

@0nko 0nko commented Oct 23, 2025

Task/Issue URL: https://app.asana.com/1/137249556945/task/1211565479656353?focus=true

Description

This PR updates the storage layer and replaces the OmnibarPosition -> OmnibarType.

New OmnibarType enum has the following values:

  • SINGLE_TOP
  • SINGLE_BOTTOM
  • SPLIT_TOP

There's also a new splitOmnibar feature flag, although it's unused at the moment.

Steps to test this PR

Change position

  • Go to Settings -> Appearance
  • Verify the position option shows the correct value
  • Tap on the omnibar position option
  • Change the position
  • Go back to the browser and verify the omnibar position changed

Omnibar behavior

  • Test that the omnibar hiding works on scroll both when position at the top
  • Test thatthe correct browser menu shown when position at the top (navigation buttons are at the top of menu)
  • Test that the omnibar hiding works on scroll both when position at the bottom
  • Test that the correct browser menu shown when position at the bottom (navigation buttons are at the bottom of menu)

Copy link
Member Author

0nko commented Oct 23, 2025

@0nko 0nko changed the title Add OmnibarType and type resolver Split Omnibar: Data type & storage update Oct 23, 2025
@0nko 0nko requested a review from malmstein October 23, 2025 10:02
@0nko 0nko marked this pull request as ready for review October 23, 2025 10:03
@0nko 0nko force-pushed the feature/ondrej/split-omnibar-unified-layout branch from aa9f4d2 to 6b0dfce Compare October 24, 2025 17:16
@0nko 0nko force-pushed the feature/ondrej/split-omnibar-data-type branch from cee2296 to 86457ba Compare October 24, 2025 17:16
@0nko 0nko mentioned this pull request Oct 24, 2025
20 tasks
@0nko 0nko force-pushed the feature/ondrej/split-omnibar-unified-layout branch from 6b0dfce to 0f2ea5d Compare October 27, 2025 11:26
@0nko 0nko force-pushed the feature/ondrej/split-omnibar-data-type branch from 86457ba to e376ab4 Compare October 27, 2025 11:26
@0nko 0nko force-pushed the feature/ondrej/split-omnibar-unified-layout branch from d7dd418 to 69f6484 Compare October 27, 2025 17:17
@0nko 0nko force-pushed the feature/ondrej/split-omnibar-data-type branch from e376ab4 to be9ddaf Compare October 27, 2025 17:17
@0nko 0nko requested review from LukasPaczos and removed request for malmstein October 27, 2025 17:42
@0nko 0nko force-pushed the feature/ondrej/split-omnibar-data-type branch from 044f781 to 31606a2 Compare October 27, 2025 17:49
Base automatically changed from feature/ondrej/split-omnibar-unified-layout to develop October 28, 2025 09:21
Comment on lines 349 to 354
// safety check: if the omnibar type is SPLIT but the feature is disabled, reset to SINGLE_TOP
if (settingsDataStore.omnibarType == OmnibarType.SPLIT &&
(!browserFeatures.useUnifiedOmnibarLayout().isEnabled() || !browserFeatures.splitOmnibar().isEnabled())
) {
settingsDataStore.omnibarType = OmnibarType.SINGLE_TOP
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settingsDataStore can be mutated at any point in time, from a lot of places, so having this safety check in the BrowserActivity only looks fragile.

Maybe consider adding an OmnibarTypeResolver or something along these lines to inject into all places that initialize the omnibar, and do the flag checks there?

Copy link
Member Author

@0nko 0nko Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @LukasPaczos, that's a good point. However, there are a lot of places where this resolver would litter the code. Considering that this is just a temporary measure while the flags exist, I was thinking maybe we could avoid that and move this type reset somewhere higher up.

What do you think of adding a PrivacyConfigCallbackPlugin and checking/resetting in onPrivacyConfigDownloaded()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe using MainProcessLifecycleObserver would be better, because onStart() would be run every time the app starts, as opposed to just when the config is downloaded. This would take care of internal builds when the feature is toggled off in the dev settings.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The downside of this is that if:

  1. We release the feature and a number of users enable it.
  2. We have to temporarily disable it remotely.
  3. We re-enable the feature.
  4. All of the the users that previously opted into using the split omnibar would have to go and enable it again.

There's no friction-less way to manage a situation like that, but I think the preference should persist.

What if we added the flag check before returning the value from the SettingsSharedPreferences?

override var omnibarType: OmnibarType
    get() {
        val prefValue = OmnibarType.fromString(
            preferences.getString(KEY_OMNIBAR_TYPE, OmnibarType.SINGLE_TOP.typeName) ?: OmnibarType.SINGLE_TOP.typeName,
        )
        return if (prefValue == OmnibarType.SPLIT && browserFeatures.splitOmnibar().isEnabled()) {
            OmnibarType.SPLIT
        } else {
            OmnibarType.SINGLE_TOP
        }
    }
    set(value) = preferences.edit { putString(KEY_OMNIBAR_TYPE, value.typeName) }

There could be a concern about synchronous feature flag check. That said, pref access can already go to disk so all responsible callers should already move this off of the main thread 😁 We can also look into caching the flag value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already thought of your suggestion but unfortunately, it creates a dependency cycle. I've added a flag to remeber if the split omnibar was used when a feature is disabled. It's then used to restore the choice when it's re-enabled.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LukasPaczos You can test this fallback behavior in a subsequent PR where you can see the selection in the settings.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing in #6991 (comment).

@0nko 0nko force-pushed the feature/ondrej/split-omnibar-data-type branch 3 times, most recently from cdf44c0 to a863754 Compare October 28, 2025 20:39
@0nko 0nko requested a review from LukasPaczos October 29, 2025 12:36
@0nko 0nko mentioned this pull request Oct 29, 2025
14 tasks
@0nko 0nko force-pushed the feature/ondrej/split-omnibar-data-type branch from 83ce28b to 1965900 Compare October 30, 2025 09:45
Comment on lines 37 to 58
class SplitOmnibarFallbackObserver @Inject constructor(
private val settingsDataStore: SettingsDataStore,
private val browserFeatures: AndroidBrowserConfigFeature,
) : MainProcessLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)

if (settingsDataStore.omnibarType == OmnibarType.SPLIT &&
(!browserFeatures.useUnifiedOmnibarLayout().isEnabled() || !browserFeatures.splitOmnibar().isEnabled())
) {
settingsDataStore.isSplitOmnibarSelected = true
settingsDataStore.omnibarType = OmnibarType.SINGLE_TOP
} else if (settingsDataStore.isSplitOmnibarSelected &&
browserFeatures.useUnifiedOmnibarLayout().isEnabled() &&
browserFeatures.splitOmnibar().isEnabled()
) {
// Restore user's choice if the feature is re-enabled
settingsDataStore.omnibarType = OmnibarType.SPLIT
settingsDataStore.isSplitOmnibarSelected = false
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach feels a bit backwards to me. As discussed in this comment, whether the split omnibar is used isn’t determined solely by the user preference - it also depends on resolving a feature flag. That means it should be checked every time.

In this case, we effectively need a small "repository" layer in front of the settings data store to handle that logic cleanly. I understand the hesitation to add it since the flag might be short-lived, but I suspect it could stay around for a while during rollout.

My recommendation would still be to introduce this resolver/repository class to keep the code clean, but the current approach also works right now, so I won’t block the PR.

If you decide to move forward with the current version, please just make sure the isEnabled feature flag checks don’t run on the main thread.

Copy link
Member Author

@0nko 0nko Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LukasPaczos I understand the concern. However, the flags are used during omnibar initialization in the BrowserTabFragment, so a flag update would not take effect until a new tab is created. In fact, using the live value might create an inconsistent state when one tab might use a split omnibar and the other one would not. So using a "resolver" to get the most up-to-date omnibar type (based on the latest flag value) might create even more issues this omnibar type is used all over the place.

I think the easiest solution is to simply cache the latest known flag values at the start (and change the omnibar type, if needed) and then use them throughout the lifetime of the app. If there's a new config downloaded while the app's running, it will take effect the next time the app restarts.

I added OmnibarFeatureRepoitory, which loads the flags (on the background thread) when the app starts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, the flags are used during omnibar initialization in the BrowserTabFragment, so a flag update would not take effect until a new tab is created. In fact, using the live value might create an inconsistent state when one tab might use a split omnibar and the other one would not

That same issue can occur with the proposed OmnibarFeatureRepository. It updates the preference state in onStart, so if the user resumes the app after a config change, new tabs will fallback to top omnibar while existing tabs will still use the split version.

Either way, each fragment already checks for changes to the omnibar position/type in BrowserTabFragment#onResume, so they should recover correctly in both cases.

Let's merge and iterate as needed.

@0nko 0nko force-pushed the feature/ondrej/split-omnibar-data-type branch from cc81f57 to cff6c4f Compare October 30, 2025 18:41
@0nko 0nko merged commit 8ce9307 into develop Oct 31, 2025
9 checks passed
@0nko 0nko deleted the feature/ondrej/split-omnibar-data-type branch October 31, 2025 09:33
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.

2 participants