From 501829083d81f2d7df603ad9ff9dac2c0cf02b90 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Tue, 5 Apr 2022 19:17:12 +0300 Subject: [PATCH] [fenix] For https://github.com/mozilla-mobile/fenix/issues/24618 - Add Fenix own ServiceWorkerSupport This replaces the default implementation from Android-Components to add the functionality to first navigate to the browser fragment before responding to service workers' requests of opening new tabs. This will register itself when the main activity is created and unregister itself when that activity is destroyed to support requests even when the activity is in background but prevent any leaks. --- app/build.gradle | 1 - .../org/mozilla/fenix/FenixApplication.kt | 5 - .../java/org/mozilla/fenix/HomeActivity.kt | 6 +- .../fenix/ServiceWorkerSupportFeature.kt | 47 ++++++++ .../mozilla/fenix/ServiceWorkerSupportTest.kt | 103 ++++++++++++++++++ buildSrc/src/main/java/Dependencies.kt | 1 - 6 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/ServiceWorkerSupportFeature.kt create mode 100644 app/src/test/java/org/mozilla/fenix/ServiceWorkerSupportTest.kt diff --git a/app/build.gradle b/app/build.gradle index c48a9fed0f..b0af372af4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -504,7 +504,6 @@ dependencies { implementation Deps.mozilla_feature_webcompat implementation Deps.mozilla_feature_webnotifications implementation Deps.mozilla_feature_webcompat_reporter - implementation Deps.mozilla_feature_serviceworker implementation Deps.mozilla_service_pocket implementation Deps.mozilla_service_contile diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 1def45caf7..0ef5975320 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -36,7 +36,6 @@ import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider import mozilla.components.feature.autofill.AutofillUseCases import mozilla.components.feature.search.ext.buildSearchUrl import mozilla.components.feature.search.ext.waitForSelectedOrDefaultSearchEngine -import mozilla.components.feature.serviceworker.ServiceWorkerSupport import mozilla.components.feature.top.sites.TopSitesProviderConfig import mozilla.components.lib.crash.CrashReporter import mozilla.components.service.fxa.manager.SyncEnginesStorage @@ -196,10 +195,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider { setupLeakCanary() startMetricsIfEnabled() - ServiceWorkerSupport.install( - components.core.engine, - components.useCases.tabsUseCases.addTab - ) setupPush() visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService()) diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 678a11b591..98d33dcd19 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -160,6 +160,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { WebExtensionPopupFeature(components.core.store, ::openPopup) } + private val serviceWorkerSupport by lazy { + ServiceWorkerSupportFeature(this) + } + private var inflater: LayoutInflater? = null private val navHost by lazy { @@ -264,7 +268,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } supportActionBar?.hide() - lifecycle.addObservers(webExtensionPopupFeature) + lifecycle.addObservers(webExtensionPopupFeature, serviceWorkerSupport) if (shouldAddToRecentsScreen(intent)) { intent.removeExtra(START_IN_RECENTS_SCREEN) diff --git a/app/src/main/java/org/mozilla/fenix/ServiceWorkerSupportFeature.kt b/app/src/main/java/org/mozilla/fenix/ServiceWorkerSupportFeature.kt new file mode 100644 index 0000000000..7334c9faf9 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/ServiceWorkerSupportFeature.kt @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import mozilla.components.browser.state.state.SessionState +import mozilla.components.concept.engine.EngineSession +import mozilla.components.concept.engine.EngineSession.LoadUrlFlags +import mozilla.components.concept.engine.serviceworker.ServiceWorkerDelegate +import org.mozilla.fenix.ext.components + +/** + * Fenix own version of the `ServiceWorkerSupportFeature` from Android-Components + * which adds the ability to navigate to the browser before opening a new tab. + * + * Will automatically register callbacks for service workers requests and cleanup when [homeActivity] is destroyed. + * + * @param homeActivity [HomeActivity] used for navigating to browser or accessing various app components. + */ +class ServiceWorkerSupportFeature( + private val homeActivity: HomeActivity +) : ServiceWorkerDelegate, DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + homeActivity.components.core.engine.unregisterServiceWorkerDelegate() + } + + override fun onCreate(owner: LifecycleOwner) { + homeActivity.components.core.engine.registerServiceWorkerDelegate(this) + } + + override fun addNewTab(engineSession: EngineSession): Boolean { + with(homeActivity) { + openToBrowser(BrowserDirection.FromHome) + + components.useCases.tabsUseCases.addTab( + flags = LoadUrlFlags.external(), + engineSession = engineSession, + source = SessionState.Source.Internal.None + ) + } + + return true + } +} diff --git a/app/src/test/java/org/mozilla/fenix/ServiceWorkerSupportTest.kt b/app/src/test/java/org/mozilla/fenix/ServiceWorkerSupportTest.kt new file mode 100644 index 0000000000..a3352d3953 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/ServiceWorkerSupportTest.kt @@ -0,0 +1,103 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix + +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import io.mockk.verifyOrder +import mozilla.components.browser.engine.gecko.GeckoEngine +import mozilla.components.browser.state.state.SessionState.Source.Internal.None +import mozilla.components.concept.engine.EngineSession.LoadUrlFlags +import mozilla.components.feature.tabs.TabsUseCases.AddNewTabUseCase +import mozilla.components.support.test.eq +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.BrowserDirection.FromHome +import org.mozilla.fenix.ext.components + +@RunWith(AndroidJUnit4::class) +class ServiceWorkerSupportTest { + @Before + fun setup() { + // Needed to mock the response of the "Context.components" extension property. + mockkStatic("org.mozilla.fenix.ext.ContextKt") + } + + @After + fun teardown() { + unmockkStatic("org.mozilla.fenix.ext.ContextKt") + } + + @Test + fun `GIVEN the feature is registered for lifecycle events WHEN the owner is created THEN register itself as a service worker delegate`() { + val engine: GeckoEngine = mockk(relaxed = true) + every { any().components.core.engine } returns engine + val feature = ServiceWorkerSupportFeature(mockk(relaxed = true)) + + feature.onCreate(mockk()) + + verify { engine.registerServiceWorkerDelegate(feature) } + } + + @Test + fun `GIVEN the feature is registered for lifecycle events WHEN the owner is destroyed THEN unregister itself as a service worker delegate`() { + val engine: GeckoEngine = mockk(relaxed = true) + every { any().components.core.engine } returns engine + val feature = ServiceWorkerSupportFeature(mockk(relaxed = true)) + + feature.onDestroy(mockk()) + + verify { engine.unregisterServiceWorkerDelegate() } + } + + @Test + fun `WHEN a new tab is requested THEN navigate to browser then add a new tab`() { + val addNewTabUseCase: AddNewTabUseCase = mockk(relaxed = true) + every { any().components.useCases.tabsUseCases.addTab } returns addNewTabUseCase + val activity: HomeActivity = mockk(relaxed = true) + val feature = ServiceWorkerSupportFeature(activity) + + feature.addNewTab(mockk()) + + verifyOrder { + activity.openToBrowser(FromHome) + + addNewTabUseCase( + url = eq("about:blank"), + selectTab = eq(true), // default + startLoading = eq(true), // default + parentId = eq(null), // default + flags = eq(LoadUrlFlags.external()), + contextId = eq(null), // default + engineSession = any(), + source = eq(None), + searchTerms = eq(""), // default + private = eq(false), // default + historyMetadata = eq(null) // default + ) + } + } + + @Test + fun `WHEN a new tab is requested THEN return true`() { + val addNewTabUseCase: AddNewTabUseCase = mockk(relaxed = true) + every { any().components.useCases.tabsUseCases.addTab } returns addNewTabUseCase + + val activity: HomeActivity = mockk(relaxed = true) + val feature = ServiceWorkerSupportFeature(activity) + + val result = feature.addNewTab(mockk()) + + assertTrue(result) + } +} diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 03acdabe6f..af5ea13a32 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -135,7 +135,6 @@ object Deps { const val mozilla_feature_webcompat = "org.mozilla.components:feature-webcompat:${Versions.mozilla_android_components}" const val mozilla_feature_webnotifications = "org.mozilla.components:feature-webnotifications:${Versions.mozilla_android_components}" const val mozilla_feature_webcompat_reporter = "org.mozilla.components:feature-webcompat-reporter:${Versions.mozilla_android_components}" - const val mozilla_feature_serviceworker = "org.mozilla.components:feature-serviceworker:${Versions.mozilla_android_components}" const val mozilla_service_pocket = "org.mozilla.components:service-pocket:${Versions.mozilla_android_components}" const val mozilla_service_contile =