Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f21fff5
fix: align custom tabs with Fenix for prompts, downloads, and media
prirai Aug 23, 2026
db8aa3d
feat: persist recently closed tabs in the browser store
prirai Aug 23, 2026
e33559b
feat: wire homepage recents and replace bookmark grid
prirai Aug 23, 2026
ca975e6
feat: add download pause, resume, search, and parallel Range fetches
prirai Aug 23, 2026
db45cb0
fix: keep SessionFeature on the selected tab after tab switches
prirai Aug 23, 2026
812f847
fix: keep EngineView attached under the homepage overlay
prirai Aug 23, 2026
f2aeb65
feat: load search engines from Remote Settings
prirai Aug 23, 2026
d7f500b
feat: apply search engines live and add a private default
prirai Aug 23, 2026
f0ed2ae
feat: persist desktop mode and default it on large windows
prirai Aug 23, 2026
3e284ac
feat: add in-browser page translations
prirai Aug 23, 2026
1c2c941
feat: enable and disable addons per profile
prirai Aug 23, 2026
5caa273
fix: apply profile addon enable/disable on the main thread
prirai Aug 23, 2026
722c033
fix: slim ungrouped tab pills and center them in the row
prirai Aug 23, 2026
8a31f6d
feat: use Fenix mozac icons on menus, settings, and toolbar
prirai Aug 23, 2026
cb04be1
fix: inherit dynamic colors in settings dialogs and simplify theme pi…
prirai Aug 23, 2026
927ad12
fix: wait for two characters before listing extension search results
prirai Aug 23, 2026
273b6d9
fix: do not apply an empty profile addon allowlist
prirai Aug 23, 2026
2819a20
fix: list installed extensions from BrowserStore with action icons
prirai Aug 23, 2026
e350a05
feat: expose the per-profile addon allowlist on extension details
prirai Aug 23, 2026
a1b5fa7
feat: remove address-bar extension icons from the toolbar
prirai Aug 23, 2026
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
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ dependencies {
implementation "org.mozilla.components:support-webextensions:$mozComponentsVersion"
implementation "org.mozilla.components:support-ktx:$mozComponentsVersion"
implementation "org.mozilla.components:support-locale:$mozComponentsVersion"
implementation "org.mozilla.components:support-remotesettings:$mozComponentsVersion"
implementation "org.mozilla.appservices:remotesettings:153.0"

// Mozilla UI components
implementation "org.mozilla.components:ui-autocomplete:$mozComponentsVersion"
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@
android:exported="false"
/>

<service
android:name=".downloads.ParallelDownloadService"
android:foregroundServiceType="dataSync"
android:exported="false"
/>

<!-- Custom Tabs Service -->
<service
android:name=".customtabs.CustomTabsService"
Expand Down
17 changes: 10 additions & 7 deletions app/src/main/java/com/prirai/android/nira/BaseBrowserFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.withContext
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.selector.findCustomTab
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
Expand Down Expand Up @@ -156,16 +155,20 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit

private var _binding: FragmentBrowserBinding? = null
protected val binding get() = _binding!!
protected val bindingOrNull: FragmentBrowserBinding? get() = _binding

@CallSuper
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// For custom tabs: read EXTRA_SESSION_ID (used by external apps)
// For normal browsing: customTabSessionId should be null so SessionFeature follows selected tab
customTabSessionId = requireArguments().getString(EXTRA_SESSION_ID)
// Only pin SessionFeature for a real custom tab. A normal tab id in this arg
// freezes EngineView on that session after later selectTab() calls.
val argSessionId = requireArguments().getString(EXTRA_SESSION_ID)
customTabSessionId = argSessionId?.takeIf {
requireContext().components.store.state.findCustomTab(it) != null
}

_binding = FragmentBrowserBinding.inflate(inflater, container, false)
val view = binding.root
Expand Down Expand Up @@ -611,7 +614,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
internal fun expandToolbarOnNavigation(store: BrowserStore) {
consumeFlow(store) { flow ->
flow.mapNotNull { state ->
state.findCustomTabOrSelectedTab(customTabSessionId)
state.findTabOrCustomTabOrSelectedTab(customTabSessionId)
}
.ifAnyChanged { tab ->
arrayOf(tab.content.url, tab.content.loadRequest)
Expand Down Expand Up @@ -979,7 +982,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit

@VisibleForTesting
internal fun getCurrentTab(): SessionState? {
return requireContext().components.store.state.findCustomTabOrSelectedTab(customTabSessionId)
return requireContext().components.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId)
}

override fun onHomePressed() = pipFeature?.onHomePressed() ?: false
Expand Down Expand Up @@ -1184,7 +1187,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
// Lifecycle-aware features cleaned up automatically
webContentPositionManager?.destroy()
webContentPositionManager = null
binding.engineView.setActivityContext(null)
_binding?.engineView?.setActivityContext(null)
_browserInteractor = null
_unifiedToolbar = null
_binding = null
Expand Down
59 changes: 7 additions & 52 deletions app/src/main/java/com/prirai/android/nira/BrowserActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.prirai.android.nira.browser.BrowsingMode
import com.prirai.android.nira.browser.BrowsingModeManager
import com.prirai.android.nira.browser.DefaultBrowsingModeManager
import com.prirai.android.nira.browser.SearchEngineList
import com.prirai.android.nira.browser.SearchEnginePreferences
import com.prirai.android.nira.databinding.ActivityMainBinding
import com.prirai.android.nira.ext.alreadyOnDestination
import com.prirai.android.nira.ext.components
Expand All @@ -36,7 +37,6 @@ import com.prirai.android.nira.search.SearchDialogFragmentDirections
import com.prirai.android.nira.theme.applyAppTheme
import com.prirai.android.nira.utils.Utils
import kotlinx.coroutines.launch
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.state.action.AppLifecycleAction
import mozilla.components.browser.state.search.SearchEngine
import mozilla.components.browser.state.state.SessionState
Expand All @@ -45,7 +45,6 @@ import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
import mozilla.components.feature.contextmenu.ext.DefaultSelectionActionDelegate
import mozilla.components.feature.search.ext.createSearchEngine
import mozilla.components.support.base.feature.ActivityResultHandler
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.ktx.kotlin.isUrl
Expand Down Expand Up @@ -149,8 +148,12 @@ open class BrowserActivity : LocaleAwareAppCompatActivity(), ComponentCallbacks2
// OPTIMIZATION: Defer search engine setup to after first frame
// This was accessing components.store.state which triggers heavy initialization
view.post {
setupSearchEngines()
// Also prefetch publicSuffixList after UI is ready
lifecycleScope.launch {
SearchEnginePreferences.apply(this@BrowserActivity, private = false)
if (UserPreferences(this@BrowserActivity).privateSearchEngineChoice >= 0) {
SearchEnginePreferences.apply(this@BrowserActivity, private = true)
}
}
components.publicSuffixList.prefetch()
}

Expand Down Expand Up @@ -570,54 +573,6 @@ open class BrowserActivity : LocaleAwareAppCompatActivity(), ComponentCallbacks2
super.attachBaseContext(base)
}

/**
* Setup search engines - extracted to separate method for deferred initialization
*/
private fun setupSearchEngines() {
//TODO: Move to settings page so app restart no longer required
//TODO: Differentiate between using search engine / adding to list - the code below removes all from list as I don't support adding to list, only setting as default
for (i in components.store.state.search.customSearchEngines) {
components.searchUseCases.removeSearchEngine(i)
}

if (UserPreferences(this).customSearchEngine) {
// SECURITY: Use lifecycle-aware coroutine scope
lifecycleScope.launch {
val customSearch =
createSearchEngine(
name = "Custom Search",
url = UserPreferences(this@BrowserActivity).customSearchEngineURL,
icon = components.icons.loadIcon(IconRequest(UserPreferences(this@BrowserActivity).customSearchEngineURL))
.await().bitmap
)

runOnUiThread {
components.searchUseCases.addSearchEngine(
customSearch
)
components.searchUseCases.selectSearchEngine(
customSearch
)
}
}
} else {
if (SearchEngineList(this).getEngines()[UserPreferences(this).searchEngineChoice].type == SearchEngine.Type.BUNDLED) {
components.searchUseCases.selectSearchEngine(
SearchEngineList(this).getEngines()[UserPreferences(this).searchEngineChoice]
)
} else {
components.searchUseCases.addSearchEngine(
SearchEngineList(this).getEngines()[UserPreferences(
this
).searchEngineChoice]
)
components.searchUseCases.selectSearchEngine(
SearchEngineList(this).getEngines()[UserPreferences(this).searchEngineChoice]
)
}
}
}

private fun updateToolbarAndStatusBarTheme() {
val selectedTab = components.store.state.tabs.find { it.id == components.store.state.selectedTabId }
val isPrivate = selectedTab?.content?.private == true
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/prirai/android/nira/BrowserApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ class BrowserApp : Application() {
},
onExtensionsLoaded = { extensions ->
components.addonUpdater.registerForFutureUpdates(extensions)
applicationScope.launch(Dispatchers.IO) {
val profileId = com.prirai.android.nira.browser.profile.ProfileManager
.getInstance(this@BrowserApp).getActiveProfile().id
com.prirai.android.nira.browser.profile.ProfileAddonPolicy
.applyForProfile(this@BrowserApp, profileId)
}
},
onUpdatePermissionRequest = components.addonUpdater::onUpdatePermissionRequest,
)
Expand Down
Loading
Loading