2
0
mirror of https://github.com/fork-maintainers/iceraven-browser synced 2024-11-03 23:15:31 +00:00

[fenix] 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 a1246d05f8
commit 086174a280
5 changed files with 642 additions and 648 deletions

View File

@ -184,6 +184,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
protected var webAppToolbarShouldBeVisible = true protected var webAppToolbarShouldBeVisible = true
private val sharedViewModel: SharedViewModel by activityViewModels() private val sharedViewModel: SharedViewModel by activityViewModels()
private val homeViewModel: HomeScreenViewModel by activityViewModels()
@VisibleForTesting @VisibleForTesting
internal val onboarding by lazy { FenixOnboarding(requireContext()) } internal val onboarding by lazy { FenixOnboarding(requireContext()) }
@ -220,7 +221,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
} }
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) { final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
browserInitialized = initializeUI(view) != null initializeUI(view)
if (customTabSessionId == null) { if (customTabSessionId == null) {
// We currently only need this observer to navigate to home // We currently only need this observer to navigate to home
@ -238,12 +239,19 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
requireContext().accessibilityManager.addAccessibilityStateChangeListener(this) 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") @Suppress("ComplexMethod", "LongMethod")
@CallSuper @CallSuper
@VisibleForTesting internal open fun initializeUI(view: View, tab: SessionState) {
internal open fun initializeUI(view: View): Session? {
val context = requireContext() val context = requireContext()
val sessionManager = context.components.core.sessionManager val sessionManager = context.components.core.sessionManager
val store = context.components.core.store val store = context.components.core.store
@ -260,456 +268,454 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
beginAnimateInIfNecessary() beginAnimateInIfNecessary()
} }
return getSessionById()?.also { _ -> val openInFenixIntent = Intent(context, IntentReceiverActivity::class.java).apply {
val openInFenixIntent = Intent(context, IntentReceiverActivity::class.java).apply { action = Intent.ACTION_VIEW
action = Intent.ACTION_VIEW putExtra(HomeActivity.OPEN_TO_BROWSER, true)
putExtra(HomeActivity.OPEN_TO_BROWSER, true) }
}
val readerMenuController = DefaultReaderModeController( val readerMenuController = DefaultReaderModeController(
readerViewFeature, readerViewFeature,
view.readerViewControlsBar, view.readerViewControlsBar,
isPrivate = activity.browsingModeManager.mode.isPrivate isPrivate = activity.browsingModeManager.mode.isPrivate
) )
val browserToolbarController = DefaultBrowserToolbarController( val browserToolbarController = DefaultBrowserToolbarController(
store = store, store = store,
activity = activity, activity = activity,
navController = findNavController(), navController = findNavController(),
metrics = requireComponents.analytics.metrics, metrics = requireComponents.analytics.metrics,
readerModeController = readerMenuController, readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager, sessionManager = requireComponents.core.sessionManager,
engineView = engineView, engineView = engineView,
homeViewModel = homeViewModel, homeViewModel = homeViewModel,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) }, customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
onTabCounterClicked = { onTabCounterClicked = {
thumbnailsFeature.get()?.requestScreenshot() thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav( findNavController().nav(
R.id.browserFragment, R.id.browserFragment,
BrowserFragmentDirections.actionGlobalTabTrayDialogFragment() 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
) )
} else { },
fullScreenMediaFeature.set( onCloseTab = { closedSession ->
feature = MediaFullscreenOrientationFeature( val closedTab = store.state.findTab(closedSession.id) ?: return@DefaultBrowserToolbarController
requireActivity(),
context.components.core.store val snackbarMessage = if (closedTab.content.private) {
), requireContext().getString(R.string.snackbar_private_tab_closed)
owner = this, } else {
view = view 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( val browserToolbarMenuController = DefaultBrowserToolbarMenuController(
context.applicationContext, activity = activity,
store = store, navController = findNavController(),
useCases = context.components.useCases.downloadUseCases, metrics = requireComponents.analytics.metrics,
fragmentManager = childFragmentManager, settings = context.settings(),
tabId = customTabSessionId, readerModeController = readerMenuController,
downloadManager = FetchDownloadManager( sessionManager = requireComponents.core.sessionManager,
context.applicationContext, sessionFeature = sessionFeature,
store, findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
DownloadService::class swipeRefresh = swipeRefresh,
), browserAnimator = browserAnimator,
shouldForwardToThirdParties = { customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
PreferenceManager.getDefaultSharedPreferences(context).getBoolean( openInFenixIntent = openInFenixIntent,
context.getPreferenceKey(R.string.pref_key_external_download_manager), false bookmarkTapped = { url: String, title: String ->
) viewLifecycleOwner.lifecycleScope.launch {
}, bookmarkTapped(url, title)
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)
} }
) },
scope = viewLifecycleOwner.lifecycleScope,
tabCollectionStorage = requireComponents.core.tabCollectionStorage,
topSitesStorage = requireComponents.core.topSitesStorage,
browserStore = store
)
downloadFeature.onDownloadStopped = { downloadState, _, downloadJobStatus -> _browserInteractor = BrowserInteractor(
// If the download is just paused, don't show any in-app notification browserToolbarController,
if (downloadJobStatus == DownloadState.Status.COMPLETED || browserToolbarMenuController
downloadJobStatus == DownloadState.Status.FAILED )
) {
saveDownloadDialogState( _browserToolbarView = BrowserToolbarView(
downloadState.sessionId, container = view.browserLayout,
downloadState, toolbarPosition = context.settings().toolbarPosition,
downloadJobStatus interactor = browserInteractor,
) customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
lifecycleOwner = viewLifecycleOwner
)
val dynamicDownloadDialog = DynamicDownloadDialog( toolbarIntegration.set(
container = view.browserLayout, feature = browserToolbarView.toolbarIntegration,
downloadState = downloadState, owner = this,
metrics = requireComponents.analytics.metrics, view = view
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) }
)
// Don't show the dialog if we aren't in the tab that started the download findInPageIntegration.set(
if (downloadState.sessionId == sessionManager.selectedSession?.id) { feature = FindInPageIntegration(
dynamicDownloadDialog.show()
browserToolbarView.expand()
}
}
}
resumeDownloadDialogState(
sessionManager.selectedSession?.id,
store, view, context, toolbarHeight
)
downloadsFeature.set(
downloadFeature,
owner = this,
view = view
)
pipFeature = PictureInPictureFeature(
store = store, store = store,
activity = requireActivity(), sessionId = customTabSessionId,
crashReporting = context.components.analytics.crashReporter, 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 tabId = customTabSessionId
) ),
owner = this,
view = view
)
appLinksFeature.set( val allowScreenshotsInPrivateMode = context.settings().allowScreenshotsInPrivateMode
feature = AppLinksFeature( secureWindowFeature.set(
context, feature = SecureWindowFeature(
store = store, window = requireActivity().window,
sessionId = customTabSessionId, store = store,
fragmentManager = parentFragmentManager, customTabId = customTabSessionId,
launchInApp = { context.settings().openLinksInExternalApp }, isSecure = { !allowScreenshotsInPrivateMode && it.content.private }
loadUrlUseCase = context.components.useCases.sessionUseCases.loadUrl ),
owner = this,
view = view
)
if (newMediaSessionApi) {
fullScreenMediaSessionFeature.set(
feature = MediaSessionFullscreenFeature(
requireActivity(),
context.components.core.store
), ),
owner = this, owner = this,
view = view view = view
) )
} else {
fullScreenMediaFeature.set(
feature = MediaFullscreenOrientationFeature(
requireActivity(),
context.components.core.store
),
owner = this,
view = view
)
}
promptsFeature.set( val downloadFeature = DownloadsFeature(
feature = PromptFeature( context.applicationContext,
fragment = this, store = store,
store = store, useCases = context.components.useCases.downloadUseCases,
customTabId = customTabSessionId, fragmentManager = childFragmentManager,
fragmentManager = parentFragmentManager, tabId = customTabSessionId,
loginValidationDelegate = DefaultLoginValidationDelegate( downloadManager = FetchDownloadManager(
context.components.core.lazyPasswordsStorage context.applicationContext,
), store,
isSaveLoginEnabled = { DownloadService::class
context.settings().shouldPromptToSaveLogins ),
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, view = view.viewDynamicDownloadDialog,
shareDelegate = object : ShareDelegate { toolbarHeight = toolbarHeight,
override fun showShareSheet( onDismiss = { sharedViewModel.downloadDialogState.remove(downloadState.sessionId) }
context: Context, )
shareData: ShareData,
onDismiss: () -> Unit, // Don't show the dialog if we aren't in the tab that started the download
onSuccess: () -> Unit if (downloadState.sessionId == sessionManager.selectedSession?.id) {
) { dynamicDownloadDialog.show()
val directions = NavGraphDirections.actionGlobalShareFragment( browserToolbarView.expand()
data = arrayOf(shareData), }
showPage = true, }
sessionId = getSessionById()?.id }
)
findNavController().navigate(directions) resumeDownloadDialogState(
} sessionManager.selectedSession?.id,
}, store, view, context, toolbarHeight
onNeedToRequestPermissions = { permissions -> )
requestPermissions(permissions, REQUEST_CODE_PROMPT_PERMISSIONS)
}, downloadsFeature.set(
loginPickerView = loginSelectBar, downloadFeature,
onManageLogins = { owner = this,
browserAnimator.captureEngineViewAndDrawStatically { view = view
val directions = )
NavGraphDirections.actionGlobalSavedLoginsAuthFragment()
findNavController().navigate(directions) 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, owner = this,
view = view view = view
) )
}
sessionFeature.set( context.settings().setSitePermissionSettingListener(viewLifecycleOwner) {
feature = SessionFeature( // 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.core.store,
requireComponents.useCases.sessionUseCases.goBack, context.components.useCases.sessionUseCases.reload,
view.engineView, view.swipeRefresh,
customTabSessionId customTabSessionId
), ),
owner = this, owner = this,
view = view 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 @VisibleForTesting
@ -928,9 +934,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
resumeDownloadDialogState(selectedTab.id, context.components.core.store, view, context, toolbarHeight) resumeDownloadDialogState(selectedTab.id, context.components.core.store, view, context, toolbarHeight)
} }
} else { } else {
view?.let { view -> view?.let { view -> initializeUI(view) }
browserInitialized = initializeUI(view) != null
}
} }
} }
@ -1070,7 +1074,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
sitePermissions: SitePermissions? 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. * 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() { private fun showTrackingProtectionPanel() {
val session = getSessionById() ?: return val tab = getCurrentTab() ?: return
view?.let { 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.*
import kotlinx.android.synthetic.main.fragment_browser.view.* import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.SessionState
@ -56,107 +55,107 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
private var pwaOnboardingObserver: PwaOnboardingObserver? = null private var pwaOnboardingObserver: PwaOnboardingObserver? = null
@Suppress("LongMethod") @Suppress("LongMethod")
override fun initializeUI(view: View): Session? { override fun initializeUI(view: View, tab: SessionState) {
super.initializeUI(view, tab)
val context = requireContext() val context = requireContext()
val components = context.components val components = context.components
return super.initializeUI(view)?.also { if (context.settings().isSwipeToolbarToSwitchTabsEnabled) {
if (context.settings().isSwipeToolbarToSwitchTabsEnabled) { gestureLayout.addGestureListener(
gestureLayout.addGestureListener( ToolbarGestureHandler(
ToolbarGestureHandler( activity = requireActivity(),
activity = requireActivity(), contentLayout = browserLayout,
contentLayout = browserLayout, tabPreview = tabPreview,
tabPreview = tabPreview, toolbarLayout = browserToolbarView.view,
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(
store = components.core.store, 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, owner = this,
view = view 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) nav(R.id.browserFragment, directions)
} }
override fun navToTrackingProtectionPanel(session: Session) { override fun navToTrackingProtectionPanel(tab: SessionState) {
val navController = findNavController() val navController = findNavController()
requireComponents.useCases.trackingProtectionUseCases.containsException(session.id) { contains -> requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
val isEnabled = session.trackerBlockingEnabled && !contains val isEnabled = tab.trackingProtection.enabled && !contains
val directions = val directions =
BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment( BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment(
sessionId = session.id, sessionId = tab.id,
url = session.url, url = tab.content.url,
trackingProtectionEnabled = isEnabled, trackingProtectionEnabled = isEnabled,
gravity = getAppropriateLayoutGravity() gravity = getAppropriateLayoutGravity()
) )

View File

@ -9,7 +9,6 @@ import androidx.annotation.VisibleForTesting
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import kotlinx.android.synthetic.main.activity_home.* 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.selector.findCustomTab
import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.manifest.WebAppManifestParser 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 // 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 // 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. // then there's no way to get back to it other than relaunching it.
components.core.sessionManager.runWithSession(getExternalTabId()) { session -> val tabId = getExternalTabId()
// If the custom tag config has been removed we are opening this in normal browsing if (tabId != null) {
if (session.customTabConfig != null) { components.useCases.customTabsUseCases.remove(tabId)
remove(session)
}
true
} }
} }
} }

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.component_browser_top_toolbar.*
import kotlinx.android.synthetic.main.fragment_browser.* import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.manifest.WebAppManifestParser import mozilla.components.concept.engine.manifest.WebAppManifestParser
import mozilla.components.concept.engine.manifest.getOrNull import mozilla.components.concept.engine.manifest.getOrNull
@ -52,113 +51,109 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
private val hideToolbarFeature = ViewBoundFeatureWrapper<WebAppHideToolbarFeature>() private val hideToolbarFeature = ViewBoundFeatureWrapper<WebAppHideToolbarFeature>()
@Suppress("LongMethod", "ComplexMethod") @Suppress("LongMethod", "ComplexMethod")
override fun initializeUI(view: View): Session? { override fun initializeUI(view: View, tab: SessionState) {
return super.initializeUI(view)?.also { super.initializeUI(view, tab)
val activity = requireActivity()
val components = activity.components
val manifest = args.webAppManifest?.let { json -> val customTabSessionId = customTabSessionId ?: return
WebAppManifestParser().parse(json).getOrNull() val activity = requireActivity()
} val components = activity.components
val manifest = args.webAppManifest?.let { json -> WebAppManifestParser().parse(json).getOrNull() }
customTabSessionId?.let { customTabSessionId -> customTabsIntegration.set(
customTabsIntegration.set( feature = CustomTabsIntegration(
feature = CustomTabsIntegration( sessionManager = requireComponents.core.sessionManager,
sessionManager = requireComponents.core.sessionManager, store = requireComponents.core.store,
store = requireComponents.core.store, useCases = requireComponents.useCases.customTabsUseCases,
useCases = requireComponents.useCases.customTabsUseCases, toolbar = toolbar,
toolbar = toolbar, sessionId = customTabSessionId,
sessionId = customTabSessionId, activity = activity,
activity = activity, onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) },
onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) }, isPrivate = tab.content.private,
isPrivate = it.private, shouldReverseItems = !activity.settings().shouldUseBottomToolbar
shouldReverseItems = !activity.settings().shouldUseBottomToolbar ),
), owner = this,
owner = this, view = view
view = view )
)
windowFeature.set( windowFeature.set(
feature = CustomTabWindowFeature( feature = CustomTabWindowFeature(
activity, activity,
components.core.store, components.core.store,
customTabSessionId customTabSessionId
) { uri -> ) { uri ->
val intent = val intent =
Intent.parseUri("${BuildConfig.DEEP_LINK_SCHEME}://open?url=$uri", 0) Intent.parseUri("${BuildConfig.DEEP_LINK_SCHEME}://open?url=$uri", 0)
if (intent.action == Intent.ACTION_VIEW) { if (intent.action == Intent.ACTION_VIEW) {
intent.addCategory(Intent.CATEGORY_BROWSABLE) intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.component = null intent.component = null
intent.selector = null intent.selector = null
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK 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
)
)
} }
} 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) nav(R.id.externalAppBrowserFragment, directions)
} }
override fun navToTrackingProtectionPanel(session: Session) { override fun navToTrackingProtectionPanel(tab: SessionState) {
requireComponents.useCases.trackingProtectionUseCases.containsException(session.id) { contains -> requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
val isEnabled = session.trackerBlockingEnabled && !contains val isEnabled = tab.trackingProtection.enabled && !contains
val directions = val directions =
ExternalAppBrowserFragmentDirections ExternalAppBrowserFragmentDirections
.actionGlobalTrackingProtectionPanelDialogFragment( .actionGlobalTrackingProtectionPanelDialogFragment(
sessionId = session.id, sessionId = tab.id,
url = session.url, url = tab.content.url,
trackingProtectionEnabled = isEnabled, trackingProtectionEnabled = isEnabled,
gravity = getAppropriateLayoutGravity() gravity = getAppropriateLayoutGravity()
) )

View File

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