Refactor ExternalAppBrowserActivity and ExternalAppBrowserFragment to not use Session(Manager).

This commit is contained in:
Sebastian Kaspari 2021-01-21 13:29:29 +01:00 committed by Christian Sadilek
parent b419ff82af
commit 023ddcc131
5 changed files with 642 additions and 648 deletions

View File

@ -184,6 +184,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
protected var webAppToolbarShouldBeVisible = true
private val sharedViewModel: SharedViewModel by activityViewModels()
private val homeViewModel: HomeScreenViewModel by activityViewModels()
@VisibleForTesting
internal val onboarding by lazy { FenixOnboarding(requireContext()) }
@ -220,7 +221,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
}
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
browserInitialized = initializeUI(view) != null
initializeUI(view)
if (customTabSessionId == null) {
// We currently only need this observer to navigate to home
@ -238,12 +239,19 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
requireContext().accessibilityManager.addAccessibilityStateChangeListener(this)
}
private val homeViewModel: HomeScreenViewModel by activityViewModels()
private fun initializeUI(view: View) {
val tab = getCurrentTab()
browserInitialized = if (tab != null) {
initializeUI(view, tab)
true
} else {
false
}
}
@Suppress("ComplexMethod", "LongMethod")
@CallSuper
@VisibleForTesting
internal open fun initializeUI(view: View): Session? {
internal open fun initializeUI(view: View, tab: SessionState) {
val context = requireContext()
val sessionManager = context.components.core.sessionManager
val store = context.components.core.store
@ -260,456 +268,454 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
beginAnimateInIfNecessary()
}
return getSessionById()?.also { _ ->
val openInFenixIntent = Intent(context, IntentReceiverActivity::class.java).apply {
action = Intent.ACTION_VIEW
putExtra(HomeActivity.OPEN_TO_BROWSER, true)
}
val openInFenixIntent = Intent(context, IntentReceiverActivity::class.java).apply {
action = Intent.ACTION_VIEW
putExtra(HomeActivity.OPEN_TO_BROWSER, true)
}
val readerMenuController = DefaultReaderModeController(
readerViewFeature,
view.readerViewControlsBar,
isPrivate = activity.browsingModeManager.mode.isPrivate
)
val browserToolbarController = DefaultBrowserToolbarController(
store = store,
activity = activity,
navController = findNavController(),
metrics = requireComponents.analytics.metrics,
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
engineView = engineView,
homeViewModel = homeViewModel,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
onTabCounterClicked = {
thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalTabTrayDialogFragment()
)
},
onCloseTab = { closedSession ->
val tab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController
val snackbarMessage = if (tab.content.private) {
requireContext().getString(R.string.snackbar_private_tab_closed)
} else {
requireContext().getString(R.string.snackbar_tab_closed)
}
viewLifecycleOwner.lifecycleScope.allowUndo(
requireView().browserLayout,
snackbarMessage,
requireContext().getString(R.string.snackbar_deleted_undo),
{
requireComponents.useCases.tabsUseCases.undo.invoke()
},
paddedForBottomToolbar = true,
operation = { }
)
}
)
val browserToolbarMenuController = DefaultBrowserToolbarMenuController(
activity = activity,
navController = findNavController(),
metrics = requireComponents.analytics.metrics,
settings = context.settings(),
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
sessionFeature = sessionFeature,
findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
swipeRefresh = swipeRefresh,
browserAnimator = browserAnimator,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
openInFenixIntent = openInFenixIntent,
bookmarkTapped = { url: String, title: String ->
viewLifecycleOwner.lifecycleScope.launch {
bookmarkTapped(url, title)
}
},
scope = viewLifecycleOwner.lifecycleScope,
tabCollectionStorage = requireComponents.core.tabCollectionStorage,
topSitesStorage = requireComponents.core.topSitesStorage,
browserStore = store
)
_browserInteractor = BrowserInteractor(
browserToolbarController,
browserToolbarMenuController
)
_browserToolbarView = BrowserToolbarView(
container = view.browserLayout,
toolbarPosition = context.settings().toolbarPosition,
interactor = browserInteractor,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
lifecycleOwner = viewLifecycleOwner
)
toolbarIntegration.set(
feature = browserToolbarView.toolbarIntegration,
owner = this,
view = view
)
findInPageIntegration.set(
feature = FindInPageIntegration(
store = store,
sessionId = customTabSessionId,
stub = view.stubFindInPage,
engineView = view.engineView,
toolbar = browserToolbarView.view
),
owner = this,
view = view
)
browserToolbarView.view.display.setOnSiteSecurityClickedListener {
showQuickSettingsDialog()
}
browserToolbarView.view.display.setOnTrackingProtectionClickedListener {
context.metrics.track(Event.TrackingProtectionIconPressed)
showTrackingProtectionPanel()
}
contextMenuFeature.set(
feature = ContextMenuFeature(
fragmentManager = parentFragmentManager,
store = store,
candidates = getContextMenuCandidates(context, view.browserLayout),
engineView = view.engineView,
useCases = context.components.useCases.contextMenuUseCases,
tabId = customTabSessionId
),
owner = this,
view = view
)
val allowScreenshotsInPrivateMode = context.settings().allowScreenshotsInPrivateMode
secureWindowFeature.set(
feature = SecureWindowFeature(
window = requireActivity().window,
store = store,
customTabId = customTabSessionId,
isSecure = { !allowScreenshotsInPrivateMode && it.content.private }
),
owner = this,
view = view
)
if (newMediaSessionApi) {
fullScreenMediaSessionFeature.set(
feature = MediaSessionFullscreenFeature(
requireActivity(),
context.components.core.store
),
owner = this,
view = view
val readerMenuController = DefaultReaderModeController(
readerViewFeature,
view.readerViewControlsBar,
isPrivate = activity.browsingModeManager.mode.isPrivate
)
val browserToolbarController = DefaultBrowserToolbarController(
store = store,
activity = activity,
navController = findNavController(),
metrics = requireComponents.analytics.metrics,
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
engineView = engineView,
homeViewModel = homeViewModel,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
onTabCounterClicked = {
thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalTabTrayDialogFragment()
)
} else {
fullScreenMediaFeature.set(
feature = MediaFullscreenOrientationFeature(
requireActivity(),
context.components.core.store
),
owner = this,
view = view
},
onCloseTab = { closedSession ->
val closedTab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController
val snackbarMessage = if (closedTab.content.private) {
requireContext().getString(R.string.snackbar_private_tab_closed)
} else {
requireContext().getString(R.string.snackbar_tab_closed)
}
viewLifecycleOwner.lifecycleScope.allowUndo(
requireView().browserLayout,
snackbarMessage,
requireContext().getString(R.string.snackbar_deleted_undo),
{
requireComponents.useCases.tabsUseCases.undo.invoke()
},
paddedForBottomToolbar = true,
operation = { }
)
}
val downloadFeature = DownloadsFeature(
context.applicationContext,
store = store,
useCases = context.components.useCases.downloadUseCases,
fragmentManager = childFragmentManager,
tabId = customTabSessionId,
downloadManager = FetchDownloadManager(
context.applicationContext,
store,
DownloadService::class
),
shouldForwardToThirdParties = {
PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
context.getPreferenceKey(R.string.pref_key_external_download_manager), false
)
},
promptsStyling = DownloadsFeature.PromptsStyling(
gravity = Gravity.BOTTOM,
shouldWidthMatchParent = true,
positiveButtonBackgroundColor = ThemeManager.resolveAttribute(
R.attr.accent,
context
),
positiveButtonTextColor = ThemeManager.resolveAttribute(
R.attr.contrastText,
context
),
positiveButtonRadius = (resources.getDimensionPixelSize(R.dimen.tab_corner_radius)).toFloat()
),
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
)
val browserToolbarMenuController = DefaultBrowserToolbarMenuController(
activity = activity,
navController = findNavController(),
metrics = requireComponents.analytics.metrics,
settings = context.settings(),
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
sessionFeature = sessionFeature,
findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
swipeRefresh = swipeRefresh,
browserAnimator = browserAnimator,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
openInFenixIntent = openInFenixIntent,
bookmarkTapped = { url: String, title: String ->
viewLifecycleOwner.lifecycleScope.launch {
bookmarkTapped(url, title)
}
)
},
scope = viewLifecycleOwner.lifecycleScope,
tabCollectionStorage = requireComponents.core.tabCollectionStorage,
topSitesStorage = requireComponents.core.topSitesStorage,
browserStore = store
)
downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus ->
// If the download is just paused, don't show any in-app notification
if (downloadJobStatus == DownloadState.Status.COMPLETED ||
downloadJobStatus == DownloadState.Status.FAILED
) {
_browserInteractor = BrowserInteractor(
browserToolbarController,
browserToolbarMenuController
)
saveDownloadDialogState(
downloadState.sessionId,
downloadState,
downloadJobStatus
)
_browserToolbarView = BrowserToolbarView(
container = view.browserLayout,
toolbarPosition = context.settings().toolbarPosition,
interactor = browserInteractor,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
lifecycleOwner = viewLifecycleOwner
)
val dynamicDownloadDialog = DynamicDownloadDialog(
container = view.browserLayout,
downloadState = downloadState,
metrics = requireComponents.analytics.metrics,
didFail = downloadJobStatus == DownloadState.Status.FAILED,
tryAgain = downloadFeature::tryAgain,
onCannotOpenFile = {
FenixSnackbar.make(
view = view.browserLayout,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)
.setText(context.getString(R.string.mozac_feature_downloads_could_not_open_file))
.show()
},
view = view.viewDynamicDownloadDialog,
toolbarHeight = toolbarHeight,
onDismiss = { sharedViewModel.downloadDialogState.remove(downloadState.sessionId) }
)
toolbarIntegration.set(
feature = browserToolbarView.toolbarIntegration,
owner = this,
view = view
)
// Don't show the dialog if we aren't in the tab that started the download
if (downloadState.sessionId == sessionManager.selectedSession?.id) {
dynamicDownloadDialog.show()
browserToolbarView.expand()
}
}
}
resumeDownloadDialogState(
sessionManager.selectedSession?.id,
store, view, context, toolbarHeight
)
downloadsFeature.set(
downloadFeature,
owner = this,
view = view
)
pipFeature = PictureInPictureFeature(
findInPageIntegration.set(
feature = FindInPageIntegration(
store = store,
activity = requireActivity(),
crashReporting = context.components.analytics.crashReporter,
sessionId = customTabSessionId,
stub = view.stubFindInPage,
engineView = view.engineView,
toolbar = browserToolbarView.view
),
owner = this,
view = view
)
browserToolbarView.view.display.setOnSiteSecurityClickedListener {
showQuickSettingsDialog()
}
browserToolbarView.view.display.setOnTrackingProtectionClickedListener {
context.metrics.track(Event.TrackingProtectionIconPressed)
showTrackingProtectionPanel()
}
contextMenuFeature.set(
feature = ContextMenuFeature(
fragmentManager = parentFragmentManager,
store = store,
candidates = getContextMenuCandidates(context, view.browserLayout),
engineView = view.engineView,
useCases = context.components.useCases.contextMenuUseCases,
tabId = customTabSessionId
)
),
owner = this,
view = view
)
appLinksFeature.set(
feature = AppLinksFeature(
context,
store = store,
sessionId = customTabSessionId,
fragmentManager = parentFragmentManager,
launchInApp = { context.settings().openLinksInExternalApp },
loadUrlUseCase = context.components.useCases.sessionUseCases.loadUrl
val allowScreenshotsInPrivateMode = context.settings().allowScreenshotsInPrivateMode
secureWindowFeature.set(
feature = SecureWindowFeature(
window = requireActivity().window,
store = store,
customTabId = customTabSessionId,
isSecure = { !allowScreenshotsInPrivateMode && it.content.private }
),
owner = this,
view = view
)
if (newMediaSessionApi) {
fullScreenMediaSessionFeature.set(
feature = MediaSessionFullscreenFeature(
requireActivity(),
context.components.core.store
),
owner = this,
view = view
)
} else {
fullScreenMediaFeature.set(
feature = MediaFullscreenOrientationFeature(
requireActivity(),
context.components.core.store
),
owner = this,
view = view
)
}
promptsFeature.set(
feature = PromptFeature(
fragment = this,
store = store,
customTabId = customTabSessionId,
fragmentManager = parentFragmentManager,
loginValidationDelegate = DefaultLoginValidationDelegate(
context.components.core.lazyPasswordsStorage
),
isSaveLoginEnabled = {
context.settings().shouldPromptToSaveLogins
val downloadFeature = DownloadsFeature(
context.applicationContext,
store = store,
useCases = context.components.useCases.downloadUseCases,
fragmentManager = childFragmentManager,
tabId = customTabSessionId,
downloadManager = FetchDownloadManager(
context.applicationContext,
store,
DownloadService::class
),
shouldForwardToThirdParties = {
PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
context.getPreferenceKey(R.string.pref_key_external_download_manager), false
)
},
promptsStyling = DownloadsFeature.PromptsStyling(
gravity = Gravity.BOTTOM,
shouldWidthMatchParent = true,
positiveButtonBackgroundColor = ThemeManager.resolveAttribute(
R.attr.accent,
context
),
positiveButtonTextColor = ThemeManager.resolveAttribute(
R.attr.contrastText,
context
),
positiveButtonRadius = (resources.getDimensionPixelSize(R.dimen.tab_corner_radius)).toFloat()
),
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
}
)
downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus ->
// If the download is just paused, don't show any in-app notification
if (downloadJobStatus == DownloadState.Status.COMPLETED ||
downloadJobStatus == DownloadState.Status.FAILED
) {
saveDownloadDialogState(
downloadState.sessionId,
downloadState,
downloadJobStatus
)
val dynamicDownloadDialog = DynamicDownloadDialog(
container = view.browserLayout,
downloadState = downloadState,
metrics = requireComponents.analytics.metrics,
didFail = downloadJobStatus == DownloadState.Status.FAILED,
tryAgain = downloadFeature::tryAgain,
onCannotOpenFile = {
FenixSnackbar.make(
view = view.browserLayout,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)
.setText(context.getString(R.string.mozac_feature_downloads_could_not_open_file))
.show()
},
loginExceptionStorage = context.components.core.loginExceptionStorage,
shareDelegate = object : ShareDelegate {
override fun showShareSheet(
context: Context,
shareData: ShareData,
onDismiss: () -> Unit,
onSuccess: () -> Unit
) {
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(shareData),
showPage = true,
sessionId = getSessionById()?.id
)
findNavController().navigate(directions)
}
},
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_PROMPT_PERMISSIONS)
},
loginPickerView = loginSelectBar,
onManageLogins = {
browserAnimator.captureEngineViewAndDrawStatically {
val directions =
NavGraphDirections.actionGlobalSavedLoginsAuthFragment()
findNavController().navigate(directions)
}
view = view.viewDynamicDownloadDialog,
toolbarHeight = toolbarHeight,
onDismiss = { sharedViewModel.downloadDialogState.remove(downloadState.sessionId) }
)
// Don't show the dialog if we aren't in the tab that started the download
if (downloadState.sessionId == sessionManager.selectedSession?.id) {
dynamicDownloadDialog.show()
browserToolbarView.expand()
}
}
}
resumeDownloadDialogState(
sessionManager.selectedSession?.id,
store, view, context, toolbarHeight
)
downloadsFeature.set(
downloadFeature,
owner = this,
view = view
)
pipFeature = PictureInPictureFeature(
store = store,
activity = requireActivity(),
crashReporting = context.components.analytics.crashReporter,
tabId = customTabSessionId
)
appLinksFeature.set(
feature = AppLinksFeature(
context,
store = store,
sessionId = customTabSessionId,
fragmentManager = parentFragmentManager,
launchInApp = { context.settings().openLinksInExternalApp },
loadUrlUseCase = context.components.useCases.sessionUseCases.loadUrl
),
owner = this,
view = view
)
promptsFeature.set(
feature = PromptFeature(
fragment = this,
store = store,
customTabId = customTabSessionId,
fragmentManager = parentFragmentManager,
loginValidationDelegate = DefaultLoginValidationDelegate(
context.components.core.lazyPasswordsStorage
),
isSaveLoginEnabled = {
context.settings().shouldPromptToSaveLogins
},
loginExceptionStorage = context.components.core.loginExceptionStorage,
shareDelegate = object : ShareDelegate {
override fun showShareSheet(
context: Context,
shareData: ShareData,
onDismiss: () -> Unit,
onSuccess: () -> Unit
) {
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(shareData),
showPage = true,
sessionId = getSessionById()?.id
)
findNavController().navigate(directions)
}
},
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_PROMPT_PERMISSIONS)
},
loginPickerView = loginSelectBar,
onManageLogins = {
browserAnimator.captureEngineViewAndDrawStatically {
val directions =
NavGraphDirections.actionGlobalSavedLoginsAuthFragment()
findNavController().navigate(directions)
}
}
),
owner = this,
view = view
)
sessionFeature.set(
feature = SessionFeature(
requireComponents.core.store,
requireComponents.useCases.sessionUseCases.goBack,
view.engineView,
customTabSessionId
),
owner = this,
view = view
)
searchFeature.set(
feature = SearchFeature(store, customTabSessionId) { request, tabId ->
val parentSession = sessionManager.findSessionById(tabId)
val useCase = if (request.isPrivate) {
requireComponents.useCases.searchUseCases.newPrivateTabSearch
} else {
requireComponents.useCases.searchUseCases.newTabSearch
}
if (parentSession?.isCustomTabSession() == true) {
useCase.invoke(request.query)
requireActivity().startActivity(openInFenixIntent)
} else {
useCase.invoke(request.query, parentSessionId = parentSession?.id)
}
},
owner = this,
view = view
)
val accentHighContrastColor =
ThemeManager.resolveAttribute(R.attr.accentHighContrast, context)
sitePermissionsFeature.set(
feature = SitePermissionsFeature(
context = context,
storage = context.components.core.permissionStorage.permissionsStorage,
fragmentManager = parentFragmentManager,
promptsStyling = SitePermissionsFeature.PromptsStyling(
gravity = getAppropriateLayoutGravity(),
shouldWidthMatchParent = true,
positiveButtonBackgroundColor = accentHighContrastColor,
positiveButtonTextColor = R.color.photonWhite
),
sessionId = customTabSessionId,
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_APP_PERMISSIONS)
},
onShouldShowRequestPermissionRationale = {
shouldShowRequestPermissionRationale(
it
)
},
store = store
),
owner = this,
view = view
)
sitePermissionWifiIntegration.set(
feature = SitePermissionsWifiIntegration(
settings = context.settings(),
wifiConnectionMonitor = context.components.wifiConnectionMonitor
),
owner = this,
view = view
)
if (FeatureFlags.webAuthFeature) {
webAuthnFeature.set(
feature = WebAuthnFeature(
engine = requireComponents.core.engine,
activity = requireActivity()
),
owner = this,
view = view
)
}
sessionFeature.set(
feature = SessionFeature(
context.settings().setSitePermissionSettingListener(viewLifecycleOwner) {
// If the user connects to WIFI while on the BrowserFragment, this will update the
// SitePermissionsRules (specifically autoplay) accordingly
runIfFragmentIsAttached {
assignSitePermissionsRules()
}
}
assignSitePermissionsRules()
fullScreenFeature.set(
feature = FullScreenFeature(
requireComponents.core.store,
requireComponents.useCases.sessionUseCases,
customTabSessionId,
::viewportFitChange,
::fullScreenChanged
),
owner = this,
view = view
)
expandToolbarOnNavigation(store)
store.flowScoped(viewLifecycleOwner) { flow ->
flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(customTabSessionId) }
.ifChanged { tab -> tab.content.pictureInPictureEnabled }
.collect { tab -> pipModeChanged(tab) }
}
view.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled(false)
if (view.swipeRefresh.isEnabled) {
val primaryTextColor =
ThemeManager.resolveAttribute(R.attr.primaryText, context)
view.swipeRefresh.setColorSchemeColors(primaryTextColor)
swipeRefreshFeature.set(
feature = SwipeRefreshFeature(
requireComponents.core.store,
requireComponents.useCases.sessionUseCases.goBack,
view.engineView,
context.components.useCases.sessionUseCases.reload,
view.swipeRefresh,
customTabSessionId
),
owner = this,
view = view
)
searchFeature.set(
feature = SearchFeature(store, customTabSessionId) { request, tabId ->
val parentSession = sessionManager.findSessionById(tabId)
val useCase = if (request.isPrivate) {
requireComponents.useCases.searchUseCases.newPrivateTabSearch
} else {
requireComponents.useCases.searchUseCases.newTabSearch
}
if (parentSession?.isCustomTabSession() == true) {
useCase.invoke(request.query)
requireActivity().startActivity(openInFenixIntent)
} else {
useCase.invoke(request.query, parentSessionId = parentSession?.id)
}
},
owner = this,
view = view
)
val accentHighContrastColor =
ThemeManager.resolveAttribute(R.attr.accentHighContrast, context)
sitePermissionsFeature.set(
feature = SitePermissionsFeature(
context = context,
storage = context.components.core.permissionStorage.permissionsStorage,
fragmentManager = parentFragmentManager,
promptsStyling = SitePermissionsFeature.PromptsStyling(
gravity = getAppropriateLayoutGravity(),
shouldWidthMatchParent = true,
positiveButtonBackgroundColor = accentHighContrastColor,
positiveButtonTextColor = R.color.photonWhite
),
sessionId = customTabSessionId,
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_APP_PERMISSIONS)
},
onShouldShowRequestPermissionRationale = {
shouldShowRequestPermissionRationale(
it
)
},
store = store
),
owner = this,
view = view
)
sitePermissionWifiIntegration.set(
feature = SitePermissionsWifiIntegration(
settings = context.settings(),
wifiConnectionMonitor = context.components.wifiConnectionMonitor
),
owner = this,
view = view
)
if (FeatureFlags.webAuthFeature) {
webAuthnFeature.set(
feature = WebAuthnFeature(
engine = requireComponents.core.engine,
activity = requireActivity()
),
owner = this,
view = view
)
}
context.settings().setSitePermissionSettingListener(viewLifecycleOwner) {
// If the user connects to WIFI while on the BrowserFragment, this will update the
// SitePermissionsRules (specifically autoplay) accordingly
runIfFragmentIsAttached {
assignSitePermissionsRules()
}
}
assignSitePermissionsRules()
fullScreenFeature.set(
feature = FullScreenFeature(
requireComponents.core.store,
requireComponents.useCases.sessionUseCases,
customTabSessionId,
::viewportFitChange,
::fullScreenChanged
),
owner = this,
view = view
)
expandToolbarOnNavigation(store)
store.flowScoped(viewLifecycleOwner) { flow ->
flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(customTabSessionId) }
.ifChanged { tab -> tab.content.pictureInPictureEnabled }
.collect { tab -> pipModeChanged(tab) }
}
view.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled(false)
if (view.swipeRefresh.isEnabled) {
val primaryTextColor =
ThemeManager.resolveAttribute(R.attr.primaryText, context)
view.swipeRefresh.setColorSchemeColors(primaryTextColor)
swipeRefreshFeature.set(
feature = SwipeRefreshFeature(
requireComponents.core.store,
context.components.useCases.sessionUseCases.reload,
view.swipeRefresh,
customTabSessionId
),
owner = this,
view = view
)
}
webchannelIntegration.set(
feature = FxaWebChannelFeature(
requireContext(),
customTabSessionId,
requireComponents.core.engine,
requireComponents.core.store,
requireComponents.backgroundServices.accountManager,
requireComponents.backgroundServices.serverConfig,
setOf(FxaCapability.CHOOSE_WHAT_TO_SYNC)
),
owner = this,
view = view
)
initializeEngineView(toolbarHeight)
}
webchannelIntegration.set(
feature = FxaWebChannelFeature(
requireContext(),
customTabSessionId,
requireComponents.core.engine,
requireComponents.core.store,
requireComponents.backgroundServices.accountManager,
requireComponents.backgroundServices.serverConfig,
setOf(FxaCapability.CHOOSE_WHAT_TO_SYNC)
),
owner = this,
view = view
)
initializeEngineView(toolbarHeight)
}
@VisibleForTesting
@ -928,9 +934,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
resumeDownloadDialogState(selectedTab.id, context.components.core.store, view, context, toolbarHeight)
}
} else {
view?.let { view ->
browserInitialized = initializeUI(view) != null
}
view?.let { view -> initializeUI(view) }
}
}
@ -1070,7 +1074,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
sitePermissions: SitePermissions?
)
protected abstract fun navToTrackingProtectionPanel(session: Session)
protected abstract fun navToTrackingProtectionPanel(tab: SessionState)
/**
* Returns the layout [android.view.Gravity] for the quick settings and ETP dialog.
@ -1108,9 +1112,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
}
private fun showTrackingProtectionPanel() {
val session = getSessionById() ?: return
val tab = getCurrentTab() ?: return
view?.let {
navToTrackingProtectionPanel(session)
navToTrackingProtectionPanel(tab)
}
}

View File

@ -15,7 +15,6 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.SessionState
@ -56,107 +55,107 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
private var pwaOnboardingObserver: PwaOnboardingObserver? = null
@Suppress("LongMethod")
override fun initializeUI(view: View): Session? {
override fun initializeUI(view: View, tab: SessionState) {
super.initializeUI(view, tab)
val context = requireContext()
val components = context.components
return super.initializeUI(view)?.also {
if (context.settings().isSwipeToolbarToSwitchTabsEnabled) {
gestureLayout.addGestureListener(
ToolbarGestureHandler(
activity = requireActivity(),
contentLayout = browserLayout,
tabPreview = tabPreview,
toolbarLayout = browserToolbarView.view,
store = components.core.store,
sessionManager = components.core.sessionManager
)
)
}
val readerModeAction =
BrowserToolbar.ToggleButton(
image = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
imageSelected =
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode_selected)!!,
contentDescription = requireContext().getString(R.string.browser_menu_read),
contentDescriptionSelected = requireContext().getString(R.string.browser_menu_read_close),
visible = {
readerModeAvailable
},
selected = getSessionById()?.let {
activity?.components?.core?.store?.state?.findTab(it.id)?.readerState?.active
} ?: false,
listener = browserInteractor::onReaderModePressed
)
browserToolbarView.view.addPageAction(readerModeAction)
thumbnailsFeature.set(
feature = BrowserThumbnails(context, view.engineView, components.core.store),
owner = this,
view = view
)
readerViewFeature.set(
feature = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
ReaderViewFeature(
context,
components.core.engine,
components.core.store,
view.readerViewControlsBar
) { available, active ->
if (available) {
components.analytics.metrics.track(Event.ReaderModeAvailable)
}
readerModeAvailable = available
readerModeAction.setSelected(active)
safeInvalidateBrowserToolbarView()
}
},
owner = this,
view = view
)
windowFeature.set(
feature = WindowFeature(
if (context.settings().isSwipeToolbarToSwitchTabsEnabled) {
gestureLayout.addGestureListener(
ToolbarGestureHandler(
activity = requireActivity(),
contentLayout = browserLayout,
tabPreview = tabPreview,
toolbarLayout = browserToolbarView.view,
store = components.core.store,
tabsUseCases = components.useCases.tabsUseCases
sessionManager = components.core.sessionManager
)
)
}
val readerModeAction =
BrowserToolbar.ToggleButton(
image = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
imageSelected =
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode_selected)!!,
contentDescription = requireContext().getString(R.string.browser_menu_read),
contentDescriptionSelected = requireContext().getString(R.string.browser_menu_read_close),
visible = {
readerModeAvailable
},
selected = getSessionById()?.let {
activity?.components?.core?.store?.state?.findTab(it.id)?.readerState?.active
} ?: false,
listener = browserInteractor::onReaderModePressed
)
browserToolbarView.view.addPageAction(readerModeAction)
thumbnailsFeature.set(
feature = BrowserThumbnails(context, view.engineView, components.core.store),
owner = this,
view = view
)
readerViewFeature.set(
feature = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
ReaderViewFeature(
context,
components.core.engine,
components.core.store,
view.readerViewControlsBar
) { available, active ->
if (available) {
components.analytics.metrics.track(Event.ReaderModeAvailable)
}
readerModeAvailable = available
readerModeAction.setSelected(active)
safeInvalidateBrowserToolbarView()
}
},
owner = this,
view = view
)
windowFeature.set(
feature = WindowFeature(
store = components.core.store,
tabsUseCases = components.useCases.tabsUseCases
),
owner = this,
view = view
)
if (context.settings().shouldShowOpenInAppCfr) {
openInAppOnboardingObserver.set(
feature = OpenInAppOnboardingObserver(
context = context,
store = context.components.core.store,
lifecycleOwner = this,
navController = findNavController(),
settings = context.settings(),
appLinksUseCases = context.components.useCases.appLinksUseCases,
container = browserLayout as ViewGroup
),
owner = this,
view = view
)
}
if (context.settings().shouldShowTrackingProtectionCfr) {
trackingProtectionOverlayObserver.set(
feature = TrackingProtectionOverlay(
context = context,
store = context.components.core.store,
lifecycleOwner = viewLifecycleOwner,
settings = context.settings(),
metrics = context.components.analytics.metrics,
getToolbar = { browserToolbarView.view }
),
owner = this,
view = view
)
if (context.settings().shouldShowOpenInAppCfr) {
openInAppOnboardingObserver.set(
feature = OpenInAppOnboardingObserver(
context = context,
store = context.components.core.store,
lifecycleOwner = this,
navController = findNavController(),
settings = context.settings(),
appLinksUseCases = context.components.useCases.appLinksUseCases,
container = browserLayout as ViewGroup
),
owner = this,
view = view
)
}
if (context.settings().shouldShowTrackingProtectionCfr) {
trackingProtectionOverlayObserver.set(
feature = TrackingProtectionOverlay(
context = context,
store = context.components.core.store,
lifecycleOwner = viewLifecycleOwner,
settings = context.settings(),
metrics = context.components.analytics.metrics,
getToolbar = { browserToolbarView.view }
),
owner = this,
view = view
)
}
}
}
@ -219,15 +218,15 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
nav(R.id.browserFragment, directions)
}
override fun navToTrackingProtectionPanel(session: Session) {
override fun navToTrackingProtectionPanel(tab: SessionState) {
val navController = findNavController()
requireComponents.useCases.trackingProtectionUseCases.containsException(session.id) { contains ->
val isEnabled = session.trackerBlockingEnabled && !contains
requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
val isEnabled = tab.trackingProtection.enabled && !contains
val directions =
BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment(
sessionId = session.id,
url = session.url,
sessionId = tab.id,
url = tab.content.url,
trackingProtectionEnabled = isEnabled,
gravity = getAppropriateLayoutGravity()
)

View File

@ -9,7 +9,6 @@ import androidx.annotation.VisibleForTesting
import androidx.navigation.NavDestination
import androidx.navigation.NavDirections
import kotlinx.android.synthetic.main.activity_home.*
import mozilla.components.browser.session.runWithSession
import mozilla.components.browser.state.selector.findCustomTab
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.manifest.WebAppManifestParser
@ -102,12 +101,9 @@ open class ExternalAppBrowserActivity : HomeActivity() {
// When this activity finishes, the process is staying around and the session still
// exists then remove it now to free all its resources. Once this activity is finished
// then there's no way to get back to it other than relaunching it.
components.core.sessionManager.runWithSession(getExternalTabId()) { session ->
// If the custom tag config has been removed we are opening this in normal browsing
if (session.customTabConfig != null) {
remove(session)
}
true
val tabId = getExternalTabId()
if (tabId != null) {
components.useCases.customTabsUseCases.remove(tabId)
}
}
}

View File

@ -14,7 +14,6 @@ import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.component_browser_top_toolbar.*
import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.manifest.WebAppManifestParser
import mozilla.components.concept.engine.manifest.getOrNull
@ -52,113 +51,109 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
private val hideToolbarFeature = ViewBoundFeatureWrapper<WebAppHideToolbarFeature>()
@Suppress("LongMethod", "ComplexMethod")
override fun initializeUI(view: View): Session? {
return super.initializeUI(view)?.also {
val activity = requireActivity()
val components = activity.components
override fun initializeUI(view: View, tab: SessionState) {
super.initializeUI(view, tab)
val manifest = args.webAppManifest?.let { json ->
WebAppManifestParser().parse(json).getOrNull()
}
val customTabSessionId = customTabSessionId ?: return
val activity = requireActivity()
val components = activity.components
val manifest = args.webAppManifest?.let { json -> WebAppManifestParser().parse(json).getOrNull() }
customTabSessionId?.let { customTabSessionId ->
customTabsIntegration.set(
feature = CustomTabsIntegration(
sessionManager = requireComponents.core.sessionManager,
store = requireComponents.core.store,
useCases = requireComponents.useCases.customTabsUseCases,
toolbar = toolbar,
sessionId = customTabSessionId,
activity = activity,
onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) },
isPrivate = it.private,
shouldReverseItems = !activity.settings().shouldUseBottomToolbar
),
owner = this,
view = view
)
customTabsIntegration.set(
feature = CustomTabsIntegration(
sessionManager = requireComponents.core.sessionManager,
store = requireComponents.core.store,
useCases = requireComponents.useCases.customTabsUseCases,
toolbar = toolbar,
sessionId = customTabSessionId,
activity = activity,
onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) },
isPrivate = tab.content.private,
shouldReverseItems = !activity.settings().shouldUseBottomToolbar
),
owner = this,
view = view
)
windowFeature.set(
feature = CustomTabWindowFeature(
activity,
components.core.store,
customTabSessionId
) { uri ->
val intent =
Intent.parseUri("${BuildConfig.DEEP_LINK_SCHEME}://open?url=$uri", 0)
if (intent.action == Intent.ACTION_VIEW) {
intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.component = null
intent.selector = null
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
activity.startActivity(intent)
},
owner = this,
view = view
)
hideToolbarFeature.set(
feature = WebAppHideToolbarFeature(
store = requireComponents.core.store,
customTabsStore = requireComponents.core.customTabsStore,
tabId = customTabSessionId,
manifest = manifest
) { toolbarVisible ->
browserToolbarView.view.isVisible = toolbarVisible
webAppToolbarShouldBeVisible = toolbarVisible
if (!toolbarVisible) {
engineView.setDynamicToolbarMaxHeight(0)
val browserEngine =
swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams
browserEngine.bottomMargin = 0
}
},
owner = this,
view = toolbar
)
if (manifest != null) {
activity.lifecycle.addObservers(
WebAppActivityFeature(
activity,
components.core.icons,
manifest
),
ManifestUpdateFeature(
activity.applicationContext,
requireComponents.core.store,
requireComponents.core.webAppShortcutManager,
requireComponents.core.webAppManifestStorage,
customTabSessionId,
manifest
)
)
viewLifecycleOwner.lifecycle.addObserver(
WebAppSiteControlsFeature(
activity.applicationContext,
requireComponents.core.store,
requireComponents.useCases.sessionUseCases.reload,
customTabSessionId,
manifest,
WebAppSiteControlsBuilder(
requireComponents.core.sessionManager,
requireComponents.useCases.sessionUseCases.reload,
customTabSessionId,
manifest
)
)
)
} else {
viewLifecycleOwner.lifecycle.addObserver(
PoweredByNotification(
activity.applicationContext,
requireComponents.core.store,
customTabSessionId
)
)
windowFeature.set(
feature = CustomTabWindowFeature(
activity,
components.core.store,
customTabSessionId
) { uri ->
val intent =
Intent.parseUri("${BuildConfig.DEEP_LINK_SCHEME}://open?url=$uri", 0)
if (intent.action == Intent.ACTION_VIEW) {
intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.component = null
intent.selector = null
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
}
activity.startActivity(intent)
},
owner = this,
view = view
)
hideToolbarFeature.set(
feature = WebAppHideToolbarFeature(
store = requireComponents.core.store,
customTabsStore = requireComponents.core.customTabsStore,
tabId = customTabSessionId,
manifest = manifest
) { toolbarVisible ->
browserToolbarView.view.isVisible = toolbarVisible
webAppToolbarShouldBeVisible = toolbarVisible
if (!toolbarVisible) {
engineView.setDynamicToolbarMaxHeight(0)
val browserEngine =
swipeRefresh.layoutParams as CoordinatorLayout.LayoutParams
browserEngine.bottomMargin = 0
}
},
owner = this,
view = toolbar
)
if (manifest != null) {
activity.lifecycle.addObservers(
WebAppActivityFeature(
activity,
components.core.icons,
manifest
),
ManifestUpdateFeature(
activity.applicationContext,
requireComponents.core.store,
requireComponents.core.webAppShortcutManager,
requireComponents.core.webAppManifestStorage,
customTabSessionId,
manifest
)
)
viewLifecycleOwner.lifecycle.addObserver(
WebAppSiteControlsFeature(
activity.applicationContext,
requireComponents.core.store,
requireComponents.useCases.sessionUseCases.reload,
customTabSessionId,
manifest,
WebAppSiteControlsBuilder(
requireComponents.core.sessionManager,
requireComponents.useCases.sessionUseCases.reload,
customTabSessionId,
manifest
)
)
)
} else {
viewLifecycleOwner.lifecycle.addObserver(
PoweredByNotification(
activity.applicationContext,
requireComponents.core.store,
customTabSessionId
)
)
}
}
@ -197,14 +192,14 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
nav(R.id.externalAppBrowserFragment, directions)
}
override fun navToTrackingProtectionPanel(session: Session) {
requireComponents.useCases.trackingProtectionUseCases.containsException(session.id) { contains ->
val isEnabled = session.trackerBlockingEnabled && !contains
override fun navToTrackingProtectionPanel(tab: SessionState) {
requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
val isEnabled = tab.trackingProtection.enabled && !contains
val directions =
ExternalAppBrowserFragmentDirections
.actionGlobalTrackingProtectionPanelDialogFragment(
sessionId = session.id,
url = session.url,
sessionId = tab.id,
url = tab.content.url,
trackingProtectionEnabled = isEnabled,
gravity = getAppropriateLayoutGravity()
)

View File

@ -87,13 +87,13 @@ class BrowserFragmentTest {
every { browserFragment.onboarding } returns onboarding
every { browserFragment.requireContext() } returns context
every { browserFragment.initializeUI(any()) } returns mockk()
every { browserFragment.initializeUI(any(), any()) } returns mockk()
every { browserFragment.fullScreenChanged(any()) } returns Unit
every { browserFragment.resumeDownloadDialogState(any(), any(), any(), any(), any()) } returns Unit
testTab = createTab(url = "https://mozilla.org")
store = BrowserStore()
every { context.components.core.store } returns store
testTab = createTab(url = "https://mozilla.org")
}
@After
@ -122,10 +122,10 @@ class BrowserFragmentTest {
@Test
fun `GIVEN browser UI is not initialized WHEN selected tab changes THEN browser UI is initialized`() {
browserFragment.observeTabSelection(store)
verify(exactly = 0) { browserFragment.initializeUI(view) }
verify(exactly = 0) { browserFragment.initializeUI(view, testTab) }
addAndSelectTab(testTab)
verify(exactly = 1) { browserFragment.initializeUI(view) }
verify(exactly = 1) { browserFragment.initializeUI(view, testTab) }
}
@Test