intermediate
This commit is contained in:
@@ -3,7 +3,7 @@ version = "1.0-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.9.22"
|
||||
ext.mozillaComponentsVersion = '133.0.20241024211615'
|
||||
ext.mozillaComponentsVersion = '134.0.20241030214633'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
@@ -74,48 +74,6 @@ android {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.mozilla.components:support-utils:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-ktx:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-webextensions:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-locale:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-fetch-httpurlconnection:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-crash:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-publicsuffixlist:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-dataprotect:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:concept-engine:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:concept-fetch:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-engine-gecko:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-state:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-session-storage:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-icons:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-thumbnails:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-addons:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-app-links:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-autofill:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-downloads:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-media:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-tabs:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-prompts:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-session:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-readerview:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-privatemode:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-sitepermissions:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-webcompat:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-webnotifications:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:service-digitalassetlinks:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:ui-widgets:$mozillaComponentsVersion"
|
||||
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
//https://stackoverflow.com/questions/73782320/onbackinvokedcallback-is-not-enabled-for-the-application-in-set-androidenableo
|
||||
implementation 'androidx.activity:activity:1.9.3'
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.mockito:mockito-core:5.1.0")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
@@ -129,6 +87,66 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
configurations.configureEach {
|
||||
exclude group: "org.mozilla.telemetry", module: "glean-native"
|
||||
}
|
||||
|
||||
// Select the Glean from GeckoView.
|
||||
// `service-sync-logins` requires Glean, which pulls in glean-native,
|
||||
// but that's also provided by geckoview-omni, so now we need to select which one to use.
|
||||
project.configurations.configureEach {
|
||||
resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") {
|
||||
def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module.contains('geckoview') }
|
||||
if (toBeSelected != null) {
|
||||
select(toBeSelected)
|
||||
}
|
||||
because 'use GeckoView Glean instead of standalone Glean'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.mozilla.components:support-utils:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-ktx:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-webextensions:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:support-locale:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-fetch-httpurlconnection:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-crash:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-publicsuffixlist:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:lib-dataprotect:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:concept-engine:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:concept-fetch:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:concept-toolbar:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:concept-awesomebar:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-engine-gecko:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-state:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-session-storage:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-storage-sync:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-icons:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:browser-thumbnails:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-addons:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-app-links:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-autofill:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-awesomebar:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-downloads:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-media:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-tabs:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-prompts:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-session:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-readerview:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-privatemode:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-sitepermissions:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-webcompat:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:feature-webnotifications:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:service-digitalassetlinks:$mozillaComponentsVersion"
|
||||
implementation "org.mozilla.components:ui-widgets:$mozillaComponentsVersion"
|
||||
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
//https://stackoverflow.com/questions/73782320/onbackinvokedcallback-is-not-enabled-for-the-application-in-set-androidenableo
|
||||
implementation 'androidx.activity:activity-ktx:1.9.3'
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.mockito:mockito-core:5.2.0")
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
const port = browser.runtime.connectNative("mozacReaderExtract");
|
||||
|
||||
let domChanged = false;
|
||||
let lastContent = '';
|
||||
|
||||
function parseContent() {
|
||||
const article = window.parseReaderable(document);
|
||||
|
||||
if (article && lastContent !== article.textContent) {
|
||||
port.postMessage(article);
|
||||
|
||||
lastContent = article.textContent;
|
||||
}
|
||||
|
||||
domChanged = false;
|
||||
}
|
||||
|
||||
port.onMessage.addListener((message) => {
|
||||
if (message.action === "parseContent") {
|
||||
if (domChanged) {
|
||||
parseContent();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
domChanged = true;
|
||||
});
|
||||
|
||||
const observerConfig = {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true,
|
||||
attributes: false
|
||||
};
|
||||
|
||||
// Wait for page to be fully loaded
|
||||
if (document.readyState === 'complete') {
|
||||
parseContent();
|
||||
observer.observe(document.body, observerConfig);
|
||||
} else {
|
||||
window.addEventListener('load', parseContent);
|
||||
}
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
observer.disconnect();
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "readability-extract@movenext.me"
|
||||
}
|
||||
},
|
||||
"name": "Extract text of Websites",
|
||||
"version": "1.0",
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": [
|
||||
"readability.min.js",
|
||||
"content.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
"mozillaAddons",
|
||||
"geckoViewAddons",
|
||||
"nativeMessaging",
|
||||
"nativeMessagingFromContent",
|
||||
"storage",
|
||||
"tabs",
|
||||
"webNavigation",
|
||||
"<all_urls>"
|
||||
]
|
||||
}
|
||||
Vendored
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../../../../../javascript/readability/dist/readability.min.js
|
||||
+41
-10
@@ -15,7 +15,9 @@ import androidx.fragment.app.Fragment
|
||||
import eu.lensai.flutter_mozilla_components.addons.WebExtensionPromptFeature
|
||||
import eu.lensai.flutter_mozilla_components.databinding.FragmentBrowserBinding
|
||||
import eu.lensai.flutter_mozilla_components.ext.getPreferenceKey
|
||||
import eu.lensai.flutter_mozilla_components.pip.PictureInPictureIntegration
|
||||
import eu.lensai.flutter_mozilla_components.services.DownloadService
|
||||
import mozilla.components.browser.state.selector.selectedTab
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.feature.app.links.AppLinksFeature
|
||||
import mozilla.components.feature.downloads.DownloadsFeature
|
||||
@@ -51,6 +53,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
private val appLinksFeature = ViewBoundFeatureWrapper<AppLinksFeature>()
|
||||
private val promptFeature = ViewBoundFeatureWrapper<PromptFeature>()
|
||||
private val webExtensionPromptFeature = ViewBoundFeatureWrapper<WebExtensionPromptFeature>()
|
||||
private val pictureInPictureIntegration = ViewBoundFeatureWrapper<PictureInPictureIntegration>()
|
||||
private val sitePermissionsFeature = ViewBoundFeatureWrapper<SitePermissionsFeature>()
|
||||
private val swipeRefreshFeature = ViewBoundFeatureWrapper<SwipeRefreshFeature>()
|
||||
private val secureWindowFeature = ViewBoundFeatureWrapper<SecureWindowFeature>()
|
||||
@@ -251,16 +254,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
|
||||
fullScreenFeature.set(
|
||||
feature = FullScreenFeature(
|
||||
components.core.store,
|
||||
components.useCases.sessionUseCases,
|
||||
sessionId,
|
||||
) { inFullScreen ->
|
||||
if (inFullScreen) {
|
||||
activity?.enterImmersiveMode()
|
||||
} else {
|
||||
activity?.exitImmersiveMode()
|
||||
}
|
||||
},
|
||||
store = components.core.store,
|
||||
sessionUseCases = components.useCases.sessionUseCases,
|
||||
tabId = sessionId,
|
||||
fullScreenChanged = ::fullScreenChanged,
|
||||
),
|
||||
owner = this,
|
||||
view = binding.root,
|
||||
)
|
||||
@@ -275,6 +273,16 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
view = binding.root,
|
||||
)
|
||||
|
||||
pictureInPictureIntegration.set(
|
||||
feature = PictureInPictureIntegration(
|
||||
components.core.store,
|
||||
requireActivity(),
|
||||
sessionId,
|
||||
),
|
||||
owner = this,
|
||||
view = view,
|
||||
)
|
||||
|
||||
secureWindowFeature.set(
|
||||
feature = SecureWindowFeature(
|
||||
window = requireActivity().window,
|
||||
@@ -312,11 +320,34 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun fullScreenChanged(enabled: Boolean) {
|
||||
if (enabled) {
|
||||
activity?.enterImmersiveMode()
|
||||
} else {
|
||||
activity?.exitImmersiveMode()
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onBackPressed(): Boolean {
|
||||
return backButtonHandler.any { it.onBackPressed() }
|
||||
}
|
||||
|
||||
final override fun onPause() {
|
||||
pictureInPictureIntegration.get()?.onHomePressed() ?: false
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
final override fun onPictureInPictureModeChanged(enabled: Boolean) {
|
||||
val session = components.core.store.state.selectedTab
|
||||
val fullScreenMode = session?.content?.fullScreen ?: false
|
||||
// If we're exiting PIP mode and we're in fullscreen mode, then we should exit fullscreen mode as well.
|
||||
if (!enabled && fullScreenMode) {
|
||||
onBackPressed()
|
||||
fullScreenChanged(false)
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onActivityResult(requestCode: Int, data: Intent?, resultCode: Int): Boolean {
|
||||
return activityResultHandler.any { it.onActivityResult(requestCode, data, resultCode) }
|
||||
|
||||
+13
-3
@@ -6,6 +6,7 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import eu.lensai.flutter_mozilla_components.addons.WebExtensionActionPopupActivity
|
||||
import eu.lensai.flutter_mozilla_components.feature.ReadabilityExtractFeature
|
||||
import eu.lensai.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
||||
import eu.lensai.flutter_mozilla_components.integration.ReaderViewIntegration
|
||||
import mozilla.components.browser.state.state.WebExtensionState
|
||||
@@ -19,15 +20,16 @@ import mozilla.components.support.webextensions.WebExtensionPopupObserver
|
||||
/**
|
||||
* Fragment used for browsing the web within the main app.
|
||||
*/
|
||||
class BrowserFragment(private val context: Context) : BaseBrowserFragment(), UserInteractionHandler {
|
||||
class BrowserFragment() : BaseBrowserFragment(), UserInteractionHandler {
|
||||
private val windowFeature = ViewBoundFeatureWrapper<WindowFeature>()
|
||||
private val thumbnailsFeature = ViewBoundFeatureWrapper<BrowserThumbnails>()
|
||||
private val readerViewFeature = ViewBoundFeatureWrapper<ReaderViewIntegration>()
|
||||
private val readabilityExtractFeature = ViewBoundFeatureWrapper<ReadabilityExtractFeature>()
|
||||
private val webExtensionPopupObserver = ViewBoundFeatureWrapper<WebExtensionPopupObserver>()
|
||||
private val webExtToolbarFeature = ViewBoundFeatureWrapper<WebExtensionToolbarFeature>()
|
||||
|
||||
override fun createEngine(components: Components): EngineView {
|
||||
return components.core.engine.createView(context).apply {
|
||||
return components.core.engine.createView(requireContext()).apply {
|
||||
selectionActionDelegate = components.selectionAction
|
||||
}
|
||||
}
|
||||
@@ -49,6 +51,12 @@ class BrowserFragment(private val context: Context) : BaseBrowserFragment(), Use
|
||||
view = view,
|
||||
)
|
||||
|
||||
readabilityExtractFeature.set(
|
||||
feature = components.features.readabilityExtractFeature,
|
||||
owner = this,
|
||||
view = view,
|
||||
)
|
||||
|
||||
windowFeature.set(
|
||||
feature = WindowFeature(components.core.store, components.useCases.tabsUseCases),
|
||||
owner = this,
|
||||
@@ -72,6 +80,8 @@ class BrowserFragment(private val context: Context) : BaseBrowserFragment(), Use
|
||||
owner = this,
|
||||
view = view,
|
||||
)
|
||||
|
||||
components.core.historyStorage.registerStorageMaintenanceWorker()
|
||||
}
|
||||
|
||||
private fun openPopup(webExtensionState: WebExtensionState) {
|
||||
@@ -86,7 +96,7 @@ class BrowserFragment(private val context: Context) : BaseBrowserFragment(), Use
|
||||
readerViewFeature.onBackPressed() || super.onBackPressed()
|
||||
|
||||
companion object {
|
||||
fun create(context: Context, sessionId: String? = null) = BrowserFragment(context).apply {
|
||||
fun create(sessionId: String? = null) = BrowserFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSessionId(sessionId)
|
||||
}
|
||||
|
||||
+7
-2
@@ -5,10 +5,13 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import eu.lensai.flutter_mozilla_components.components.Core
|
||||
import eu.lensai.flutter_mozilla_components.components.Events
|
||||
import eu.lensai.flutter_mozilla_components.components.Features
|
||||
import eu.lensai.flutter_mozilla_components.components.Search
|
||||
import eu.lensai.flutter_mozilla_components.components.Services
|
||||
import eu.lensai.flutter_mozilla_components.components.UseCases
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoAddonEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabContentEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.ReaderViewController
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.concept.engine.selection.SelectionActionDelegate
|
||||
@@ -18,13 +21,15 @@ class Components(private val context: Context,
|
||||
val flutterEvents: GeckoStateEvents,
|
||||
val readerViewController: ReaderViewController,
|
||||
val selectionAction: SelectionActionDelegate,
|
||||
val addonEvents: GeckoAddonEvents
|
||||
val addonEvents: GeckoAddonEvents,
|
||||
val tabContentEvents: GeckoTabContentEvents
|
||||
) {
|
||||
val core by lazy { Core(context, this, flutterEvents) }
|
||||
val events by lazy { Events(flutterEvents) }
|
||||
val useCases by lazy { UseCases(context, core.engine, core.store) }
|
||||
val services by lazy { Services(context, useCases.tabsUseCases) }
|
||||
val features by lazy { Features(core.store, addonEvents) }
|
||||
val features by lazy { Features(core.engine, core.store, addonEvents, tabContentEvents) }
|
||||
val search by lazy { Search(context, core, useCases) }
|
||||
|
||||
var engineView: EngineView? = null
|
||||
var engineReportedInitialized = false
|
||||
|
||||
+2
@@ -32,6 +32,8 @@ object EngineProvider {
|
||||
// About config it's no longer enabled by default
|
||||
builder.aboutConfigEnabled(true)
|
||||
builder.extensionsWebAPIEnabled(true)
|
||||
builder.consoleOutput(true)
|
||||
|
||||
runtime = GeckoRuntime.create(context, builder.build())
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -12,6 +12,7 @@ import eu.lensai.flutter_mozilla_components.api.GeckoFindApiImpl
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoIconsApiImpl
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoSelectionActionControllerImpl
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoSessionApiImpl
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoSuggestionApiImpl
|
||||
import eu.lensai.flutter_mozilla_components.api.GeckoTabsApiImpl
|
||||
import eu.lensai.flutter_mozilla_components.feature.DefaultSelectionActionDelegate
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoAddonEvents
|
||||
@@ -25,6 +26,9 @@ import eu.lensai.flutter_mozilla_components.pigeons.GeckoSelectionActionControll
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSelectionActionEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSessionApi
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionApi
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabContentEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabsApi
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.ReaderViewController
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.ReaderViewEvents
|
||||
@@ -63,13 +67,18 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
|
||||
ReaderViewController(_flutterPluginBinding.binaryMessenger)
|
||||
|
||||
val addonEvents = GeckoAddonEvents(_flutterPluginBinding.binaryMessenger)
|
||||
val tabContentEvents = GeckoTabContentEvents(_flutterPluginBinding.binaryMessenger)
|
||||
|
||||
val suggestionEvents = GeckoSuggestionEvents(_flutterPluginBinding.binaryMessenger)
|
||||
GeckoSuggestionApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoSuggestionApiImpl(suggestionEvents))
|
||||
|
||||
GlobalComponents.setUp(
|
||||
flutterPluginBinding.applicationContext,
|
||||
_flutterEvents,
|
||||
readerViewController,
|
||||
selectionActionDelegate,
|
||||
addonEvents
|
||||
addonEvents,
|
||||
tabContentEvents,
|
||||
)
|
||||
|
||||
GeckoBrowserApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoBrowserApiImpl {
|
||||
@@ -104,7 +113,7 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
|
||||
}
|
||||
|
||||
// Replace this with your actual Fragment
|
||||
val nativeFragment = BrowserFragment.create(activity!!)
|
||||
val nativeFragment = BrowserFragment.create()
|
||||
|
||||
val fm = (activity as FragmentActivity).supportFragmentManager
|
||||
fm.beginTransaction()
|
||||
|
||||
+8
-1
@@ -3,11 +3,14 @@ package eu.lensai.flutter_mozilla_components
|
||||
import android.content.Context
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoAddonEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabContentEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.ReaderViewController
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.storage.sync.GlobalPlacesDependencyProvider
|
||||
import mozilla.components.concept.engine.selection.SelectionActionDelegate
|
||||
import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider
|
||||
import mozilla.components.support.base.facts.Facts
|
||||
@@ -40,7 +43,8 @@ object GlobalComponents {
|
||||
flutterEvents: GeckoStateEvents,
|
||||
readerViewController: ReaderViewController,
|
||||
selectionAction: SelectionActionDelegate,
|
||||
addonEvents: GeckoAddonEvents
|
||||
addonEvents: GeckoAddonEvents,
|
||||
tabContentEvents: GeckoTabContentEvents
|
||||
) {
|
||||
Logger.debug("Creating new components")
|
||||
|
||||
@@ -50,6 +54,7 @@ object GlobalComponents {
|
||||
readerViewController,
|
||||
selectionAction,
|
||||
addonEvents,
|
||||
tabContentEvents
|
||||
)
|
||||
|
||||
//newComponents.crashReporter.install(applicationContext)
|
||||
@@ -65,6 +70,8 @@ object GlobalComponents {
|
||||
//newComponents.useCases.downloadsUseCases.restoreDownloads()
|
||||
|
||||
try {
|
||||
GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage)
|
||||
|
||||
GlobalAddonDependencyProvider.initialize(
|
||||
newComponents.core.addonManager,
|
||||
newComponents.core.addonUpdater,
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ import eu.lensai.flutter_mozilla_components.pigeons.TranslationOptions as Pigeon
|
||||
class GeckoSessionApiImpl : GeckoSessionApi {
|
||||
companion object {
|
||||
private const val TAG = "GeckoSessionApi"
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
}
|
||||
|
||||
private val components by lazy {
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package eu.lensai.flutter_mozilla_components.api
|
||||
|
||||
import eu.lensai.flutter_mozilla_components.GlobalComponents
|
||||
import eu.lensai.flutter_mozilla_components.ext.toWebPBytes
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestion
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionApi
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSuggestionType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import org.mozilla.gecko.util.ThreadUtils.runOnUiThread
|
||||
|
||||
class GeckoSuggestionApiImpl(
|
||||
private val suggestionEvents: GeckoSuggestionEvents
|
||||
) : GeckoSuggestionApi {
|
||||
companion object {
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
}
|
||||
|
||||
private val components by lazy {
|
||||
requireNotNull(GlobalComponents.components) { "Components not initialized" }
|
||||
}
|
||||
|
||||
override fun onInputChanged(text: String, providers: List<GeckoSuggestionType>) {
|
||||
for(provider in providers) {
|
||||
coroutineScope.launch {
|
||||
val results = when(provider) {
|
||||
GeckoSuggestionType.SESSION -> components.search.sessionSuggestions.onInputChanged(text)
|
||||
GeckoSuggestionType.CLIPBOARD -> components.search.clipboardSuggestions.onInputChanged(text)
|
||||
GeckoSuggestionType.HISTORY -> components.search.historySuggestions.onInputChanged(text)
|
||||
}
|
||||
|
||||
val mappedResults = results.map { result ->
|
||||
GeckoSuggestion(
|
||||
id = result.id,
|
||||
title = result.title,
|
||||
score = result.score.toLong(),
|
||||
description = result.description,
|
||||
type = provider,
|
||||
editSuggestion = result.editSuggestion,
|
||||
icon = result.icon?.toWebPBytes()
|
||||
)
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
suggestionEvents.onSuggestionResult(
|
||||
System.currentTimeMillis(),
|
||||
provider,
|
||||
mappedResults
|
||||
) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -20,6 +20,7 @@ import mozilla.components.browser.session.storage.SessionStorage
|
||||
import mozilla.components.browser.state.engine.EngineMiddleware
|
||||
import mozilla.components.browser.state.engine.middleware.SessionPrioritizationMiddleware
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
|
||||
import mozilla.components.browser.thumbnails.ThumbnailsMiddleware
|
||||
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
|
||||
import mozilla.components.concept.engine.DefaultSettings
|
||||
@@ -35,6 +36,7 @@ import mozilla.components.feature.media.MediaSessionFeature
|
||||
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
|
||||
import mozilla.components.feature.prompts.file.FileUploadsDirCleaner
|
||||
import mozilla.components.feature.readerview.ReaderViewMiddleware
|
||||
import mozilla.components.feature.session.HistoryDelegate
|
||||
import mozilla.components.feature.session.middleware.LastAccessMiddleware
|
||||
import mozilla.components.feature.sitepermissions.OnDiskSitePermissionsStorage
|
||||
import mozilla.components.feature.webnotifications.WebNotificationFeature
|
||||
@@ -51,12 +53,13 @@ class Core(private val context: Context,
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
val engineSettings by lazy {
|
||||
private val engineSettings by lazy {
|
||||
DefaultSettings().apply {
|
||||
//historyTrackingDelegate = HistoryDelegate(lazyHistoryStorage)
|
||||
requestInterceptor = AppRequestInterceptor(context)
|
||||
remoteDebuggingEnabled = prefs.getBoolean(context.getPreferenceKey(R.string.pref_key_remote_debugging), false)
|
||||
testingModeEnabled = prefs.getBoolean(context.getPreferenceKey(R.string.pref_key_testing_mode), false)
|
||||
historyTrackingDelegate = HistoryDelegate(lazyHistoryStorage)
|
||||
trackingProtectionPolicy = createTrackingProtectionPolicy(prefs)
|
||||
httpsOnlyMode = Engine.HttpsOnlyMode.ENABLED
|
||||
globalPrivacyControlEnabled = prefs.getBoolean(
|
||||
@@ -159,6 +162,17 @@ class Core(private val context: Context,
|
||||
SessionStorage(context, engine)
|
||||
}
|
||||
|
||||
/**
|
||||
* The storage component to persist browsing history (with the exception of
|
||||
* private sessions).
|
||||
*/
|
||||
val lazyHistoryStorage = lazy { PlacesHistoryStorage(context) }
|
||||
|
||||
/**
|
||||
* A convenience accessor to the [PlacesHistoryStorage].
|
||||
*/
|
||||
val historyStorage by lazy { lazyHistoryStorage.value }
|
||||
|
||||
/**
|
||||
* Constructs a [TrackingProtectionPolicy] based on current preferences.
|
||||
*
|
||||
|
||||
+14
-1
@@ -1,12 +1,17 @@
|
||||
package eu.lensai.flutter_mozilla_components.components
|
||||
|
||||
import eu.lensai.flutter_mozilla_components.feature.ReadabilityExtractFeature
|
||||
import eu.lensai.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoAddonEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabContentEvents
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.Engine
|
||||
|
||||
class Features(
|
||||
private val engine: Engine,
|
||||
private val store: BrowserStore,
|
||||
private val addonEvents: GeckoAddonEvents
|
||||
private val addonEvents: GeckoAddonEvents,
|
||||
private var tabContentEvents: GeckoTabContentEvents
|
||||
) {
|
||||
val webExtensionToolbarFeature by lazy {
|
||||
WebExtensionToolbarFeature(
|
||||
@@ -14,4 +19,12 @@ class Features(
|
||||
addonEvents
|
||||
)
|
||||
}
|
||||
|
||||
val readabilityExtractFeature by lazy {
|
||||
ReadabilityExtractFeature(
|
||||
engine,
|
||||
store,
|
||||
tabContentEvents
|
||||
)
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package eu.lensai.flutter_mozilla_components.components
|
||||
|
||||
import android.content.Context
|
||||
import mozilla.components.feature.awesomebar.provider.ClipboardSuggestionProvider
|
||||
import mozilla.components.feature.awesomebar.provider.HistoryStorageSuggestionProvider
|
||||
import mozilla.components.feature.awesomebar.provider.SessionSuggestionProvider
|
||||
|
||||
class Search(
|
||||
private val context: Context,
|
||||
private val core: Core,
|
||||
private val useCases: UseCases,
|
||||
) {
|
||||
val sessionSuggestions by lazy {
|
||||
SessionSuggestionProvider(
|
||||
context.resources,
|
||||
core.store,
|
||||
useCases.tabsUseCases.selectTab,
|
||||
)
|
||||
}
|
||||
|
||||
val clipboardSuggestions by lazy {
|
||||
ClipboardSuggestionProvider(
|
||||
context,
|
||||
useCases.sessionUseCases.loadUrl,
|
||||
)
|
||||
}
|
||||
|
||||
val historySuggestions by lazy {
|
||||
HistoryStorageSuggestionProvider(
|
||||
core.historyStorage,
|
||||
useCases.sessionUseCases.loadUrl,
|
||||
core.icons,
|
||||
core.engine,
|
||||
)
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package eu.lensai.flutter_mozilla_components.feature
|
||||
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabContentEvents
|
||||
import eu.lensai.flutter_mozilla_components.pigeons.TabContent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.map
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.concept.engine.webextension.MessageHandler
|
||||
import mozilla.components.concept.engine.webextension.Port
|
||||
import mozilla.components.lib.state.ext.flowScoped
|
||||
import mozilla.components.support.base.feature.LifecycleAwareFeature
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import mozilla.components.support.ktx.android.org.json.tryGetString
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.filterChanged
|
||||
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
|
||||
import mozilla.components.support.webextensions.WebExtensionController
|
||||
import org.json.JSONObject
|
||||
|
||||
class ReadabilityExtractFeature(
|
||||
private var engine: Engine,
|
||||
private var store: BrowserStore,
|
||||
private var tabContentEvents: GeckoTabContentEvents
|
||||
): LifecycleAwareFeature {
|
||||
companion object {
|
||||
private val logger = Logger("ReadabilityExtract")
|
||||
|
||||
internal const val READER_EXTRACT_EXTENSION_ID = "readability-extract@movenext.me"
|
||||
internal const val READER_EXTRACT_CONTENT_PORT = "mozacReaderExtract"
|
||||
internal const val READER_EXTRACT_EXTENSION_URL =
|
||||
"resource://android/assets/extensions/readability_extract/"
|
||||
}
|
||||
|
||||
private class ContentMessageHandler(
|
||||
private var tabContentEvents: GeckoTabContentEvents,
|
||||
private var tabId: String
|
||||
) : MessageHandler {
|
||||
override fun onPortConnected(port: Port) {
|
||||
logger.debug("Port connected: ${port.name()}")
|
||||
}
|
||||
|
||||
override fun onPortMessage(message: Any, port: Port) {
|
||||
if (message is JSONObject) {
|
||||
tabContentEvents.onContentUpdate(
|
||||
System.currentTimeMillis(),
|
||||
contentArg = TabContent(
|
||||
tabId = tabId,
|
||||
fullContentMarkdown = message.tryGetString("fullContentMarkdown"),
|
||||
fullContentPlain = message.tryGetString("fullContentPlain"),
|
||||
extractedContentMarkdown = message.tryGetString("extractedContentMarkdown"),
|
||||
extractedContentPlain = message.tryGetString("extractedContentPlain")
|
||||
)
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var sessionScope: CoroutineScope? = null
|
||||
private var historyScope: CoroutineScope? = null
|
||||
|
||||
private var extensionController = WebExtensionController(
|
||||
READER_EXTRACT_EXTENSION_ID,
|
||||
READER_EXTRACT_EXTENSION_URL,
|
||||
READER_EXTRACT_CONTENT_PORT,
|
||||
)
|
||||
|
||||
override fun start() {
|
||||
ensureExtensionInstalled()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
sessionScope?.cancel()
|
||||
historyScope?.cancel()
|
||||
}
|
||||
|
||||
private fun ensureExtensionInstalled() {
|
||||
extensionController.install(
|
||||
engine,
|
||||
onSuccess = { extension ->
|
||||
sessionScope = store.flowScoped { flow ->
|
||||
flow.map { it.tabs }
|
||||
.filterChanged { it.engineState.engineSession }
|
||||
.collect { state ->
|
||||
val engineSession = state.engineState.engineSession ?: return@collect
|
||||
|
||||
if (extension.hasContentMessageHandler(engineSession, READER_EXTRACT_CONTENT_PORT)) {
|
||||
return@collect
|
||||
}
|
||||
|
||||
val handler = ContentMessageHandler(tabContentEvents, state.id)
|
||||
extension.registerContentMessageHandler(engineSession, READER_EXTRACT_CONTENT_PORT, handler)
|
||||
}
|
||||
}
|
||||
|
||||
historyScope = store.flowScoped { flow ->
|
||||
flow.map { it.tabs }
|
||||
.filterChanged { it.content.history }
|
||||
.ifAnyChanged { arrayOf(it.content.history.currentIndex) }
|
||||
.collect { state ->
|
||||
val engineSession = state.engineState.engineSession ?: return@collect
|
||||
|
||||
if(state.content.loading) {
|
||||
return@collect
|
||||
}
|
||||
|
||||
if (extension.hasContentMessageHandler(engineSession, READER_EXTRACT_CONTENT_PORT)) {
|
||||
if(extensionController.portConnected(engineSession)) {
|
||||
extensionController.sendContentMessage(
|
||||
JSONObject().put("action", "parseContent"),
|
||||
engineSession
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = { throwable ->
|
||||
Logger.error("Could not install $READER_EXTRACT_EXTENSION_ID extension", throwable)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+48
-50
@@ -119,7 +119,7 @@ class WebExtensionToolbarFeature(
|
||||
extension.browserAction?.let { browserAction ->
|
||||
addOrUpdateAction(
|
||||
extension = extension,
|
||||
defaultAction = browserAction,
|
||||
globalAction = browserAction,
|
||||
tabAction = tab?.extensionState?.get(extension.id)?.browserAction,
|
||||
)
|
||||
}
|
||||
@@ -132,7 +132,7 @@ class WebExtensionToolbarFeature(
|
||||
if (pageAction.copyWithOverride(tabPageAction).enabled == true) {
|
||||
addOrUpdateAction(
|
||||
extension = extension,
|
||||
defaultAction = pageAction,
|
||||
globalAction = pageAction,
|
||||
tabAction = tabPageAction,
|
||||
isPageAction = true,
|
||||
)
|
||||
@@ -156,62 +156,60 @@ class WebExtensionToolbarFeature(
|
||||
|
||||
private fun addOrUpdateAction(
|
||||
extension: WebExtensionState,
|
||||
defaultAction: Action,
|
||||
globalAction: Action,
|
||||
tabAction: Action?,
|
||||
isPageAction: Boolean = false,
|
||||
) {
|
||||
var action = defaultAction
|
||||
val actionMap = if (isPageAction) webExtensionPageActions else webExtensionBrowserActions
|
||||
// Add the global page/browser action if it doesn't exist
|
||||
var toolbarAction = actionMap.getOrPut(extension.id) {
|
||||
CoroutineScope(iconJobDispatcher).launch {
|
||||
try {
|
||||
//TODO: make variable size
|
||||
val icon = globalAction.loadIcon?.invoke(128)
|
||||
icon?.let {
|
||||
val imageBytes = icon.toWebPBytes()
|
||||
runOnUiThread {
|
||||
addonEvents.onUpdateWebExtensionIcon(
|
||||
timestampArg = System.currentTimeMillis(),
|
||||
extensionIdArg = extension.id,
|
||||
actionTypeArg = if (isPageAction) WebExtensionActionType.PAGE else WebExtensionActionType.BROWSER,
|
||||
iconArg = imageBytes
|
||||
) { }
|
||||
}
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
Log.log(
|
||||
Log.Priority.ERROR,
|
||||
"mozac-webextensions",
|
||||
throwable,
|
||||
"Failed to load browser action icon, falling back to default.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
globalAction
|
||||
}
|
||||
|
||||
// Apply tab-specific override of browser/page action
|
||||
tabAction?.let {
|
||||
action = action.copyWithOverride(it)
|
||||
toolbarAction = toolbarAction.copyWithOverride(it)
|
||||
}
|
||||
|
||||
CoroutineScope(iconJobDispatcher).launch {
|
||||
try {
|
||||
//TODO: make variable size
|
||||
val icon = action.loadIcon?.invoke(128)
|
||||
icon?.let {
|
||||
val imageBytes = icon.toWebPBytes()
|
||||
runOnUiThread {
|
||||
addonEvents.onUpdateWebExtensionIcon(
|
||||
timestampArg = System.currentTimeMillis(),
|
||||
extensionIdArg = extension.id,
|
||||
actionTypeArg = if (isPageAction) WebExtensionActionType.PAGE else WebExtensionActionType.BROWSER,
|
||||
iconArg = imageBytes
|
||||
) { }
|
||||
}
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
Log.log(
|
||||
Log.Priority.ERROR,
|
||||
"mozac-webextensions",
|
||||
throwable,
|
||||
"Failed to load browser action icon, falling back to default.",
|
||||
)
|
||||
}
|
||||
}
|
||||
val data = WebExtensionData(
|
||||
extensionId = extension.id,
|
||||
title = toolbarAction.title,
|
||||
enabled = toolbarAction.enabled,
|
||||
badgeText = toolbarAction.badgeText,
|
||||
badgeTextColor = toolbarAction.badgeTextColor?.toLong(),
|
||||
badgeBackgroundColor = toolbarAction.badgeBackgroundColor?.toLong(),
|
||||
)
|
||||
|
||||
val actionMap = if (isPageAction) webExtensionPageActions else webExtensionBrowserActions
|
||||
// Add the global page/browser action if it doesn't exist
|
||||
val toolbarAction = actionMap.getOrPut(extension.id) {
|
||||
val toolbarAction = WebExtensionData(
|
||||
extensionId = extension.id,
|
||||
title = action.title,
|
||||
enabled = action.enabled,
|
||||
badgeText = action.badgeText,
|
||||
badgeTextColor = action.badgeTextColor?.toLong(),
|
||||
badgeBackgroundColor = action.badgeBackgroundColor?.toLong(),
|
||||
)
|
||||
|
||||
addonEvents.onUpsertWebExtensionAction(
|
||||
timestampArg = System.currentTimeMillis(),
|
||||
extensionIdArg = extension.id,
|
||||
actionTypeArg = if (isPageAction) WebExtensionActionType.PAGE else WebExtensionActionType.BROWSER,
|
||||
extensionDataArg = toolbarAction
|
||||
) { }
|
||||
|
||||
action
|
||||
}
|
||||
addonEvents.onUpsertWebExtensionAction(
|
||||
timestampArg = System.currentTimeMillis(),
|
||||
extensionIdArg = extension.id,
|
||||
actionTypeArg = if (isPageAction) WebExtensionActionType.PAGE else WebExtensionActionType.BROWSER,
|
||||
extensionDataArg = data
|
||||
) { }
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -30,12 +30,12 @@ class ReaderViewIntegration(
|
||||
if (enabled) {
|
||||
feature.showReaderView()
|
||||
|
||||
readerViewController.appearanceButtonVisibility(true) { _ -> };
|
||||
readerViewController.appearanceButtonVisibility(System.currentTimeMillis(),true) { _ -> };
|
||||
} else {
|
||||
feature.hideReaderView()
|
||||
feature.hideControls()
|
||||
|
||||
readerViewController.appearanceButtonVisibility(false) { _ -> };
|
||||
readerViewController.appearanceButtonVisibility(System.currentTimeMillis(),false) { _ -> };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+245
-53
@@ -154,6 +154,18 @@ enum class WebExtensionActionType(val raw: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
enum class GeckoSuggestionType(val raw: Int) {
|
||||
SESSION(0),
|
||||
CLIPBOARD(1),
|
||||
HISTORY(2);
|
||||
|
||||
companion object {
|
||||
fun ofRaw(raw: Int): GeckoSuggestionType? {
|
||||
return values().firstOrNull { it.raw == raw }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation options that map to the Gecko Translations Options.
|
||||
*
|
||||
@@ -968,6 +980,72 @@ data class WebExtensionData (
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
data class GeckoSuggestion (
|
||||
val id: String,
|
||||
val type: GeckoSuggestionType,
|
||||
val score: Long,
|
||||
val title: String? = null,
|
||||
val description: String? = null,
|
||||
val editSuggestion: String? = null,
|
||||
val icon: ByteArray? = null
|
||||
)
|
||||
{
|
||||
companion object {
|
||||
fun fromList(pigeonVar_list: List<Any?>): GeckoSuggestion {
|
||||
val id = pigeonVar_list[0] as String
|
||||
val type = pigeonVar_list[1] as GeckoSuggestionType
|
||||
val score = pigeonVar_list[2] as Long
|
||||
val title = pigeonVar_list[3] as String?
|
||||
val description = pigeonVar_list[4] as String?
|
||||
val editSuggestion = pigeonVar_list[5] as String?
|
||||
val icon = pigeonVar_list[6] as ByteArray?
|
||||
return GeckoSuggestion(id, type, score, title, description, editSuggestion, icon)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf(
|
||||
id,
|
||||
type,
|
||||
score,
|
||||
title,
|
||||
description,
|
||||
editSuggestion,
|
||||
icon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
data class TabContent (
|
||||
val tabId: String,
|
||||
val fullContentMarkdown: String? = null,
|
||||
val fullContentPlain: String? = null,
|
||||
val extractedContentMarkdown: String? = null,
|
||||
val extractedContentPlain: String? = null
|
||||
)
|
||||
{
|
||||
companion object {
|
||||
fun fromList(pigeonVar_list: List<Any?>): TabContent {
|
||||
val tabId = pigeonVar_list[0] as String
|
||||
val fullContentMarkdown = pigeonVar_list[1] as String?
|
||||
val fullContentPlain = pigeonVar_list[2] as String?
|
||||
val extractedContentMarkdown = pigeonVar_list[3] as String?
|
||||
val extractedContentPlain = pigeonVar_list[4] as String?
|
||||
return TabContent(tabId, fullContentMarkdown, fullContentPlain, extractedContentMarkdown, extractedContentPlain)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf(
|
||||
tabId,
|
||||
fullContentMarkdown,
|
||||
fullContentPlain,
|
||||
extractedContentMarkdown,
|
||||
extractedContentPlain,
|
||||
)
|
||||
}
|
||||
}
|
||||
private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||
return when (type) {
|
||||
@@ -1007,130 +1085,145 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
}
|
||||
}
|
||||
136.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
TranslationOptions.fromList(it)
|
||||
return (readValue(buffer) as Long?)?.let {
|
||||
GeckoSuggestionType.ofRaw(it.toInt())
|
||||
}
|
||||
}
|
||||
137.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ReaderState.fromList(it)
|
||||
TranslationOptions.fromList(it)
|
||||
}
|
||||
}
|
||||
138.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
LastMediaAccessState.fromList(it)
|
||||
ReaderState.fromList(it)
|
||||
}
|
||||
}
|
||||
139.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
HistoryMetadataKey.fromList(it)
|
||||
LastMediaAccessState.fromList(it)
|
||||
}
|
||||
}
|
||||
140.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
PackageCategoryValue.fromList(it)
|
||||
HistoryMetadataKey.fromList(it)
|
||||
}
|
||||
}
|
||||
141.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ExternalPackage.fromList(it)
|
||||
PackageCategoryValue.fromList(it)
|
||||
}
|
||||
}
|
||||
142.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
LoadUrlFlagsValue.fromList(it)
|
||||
ExternalPackage.fromList(it)
|
||||
}
|
||||
}
|
||||
143.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SourceValue.fromList(it)
|
||||
LoadUrlFlagsValue.fromList(it)
|
||||
}
|
||||
}
|
||||
144.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
TabState.fromList(it)
|
||||
SourceValue.fromList(it)
|
||||
}
|
||||
}
|
||||
145.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
RecoverableTab.fromList(it)
|
||||
TabState.fromList(it)
|
||||
}
|
||||
}
|
||||
146.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
RecoverableBrowserState.fromList(it)
|
||||
RecoverableTab.fromList(it)
|
||||
}
|
||||
}
|
||||
147.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
IconRequest.fromList(it)
|
||||
RecoverableBrowserState.fromList(it)
|
||||
}
|
||||
}
|
||||
148.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ResourceSize.fromList(it)
|
||||
IconRequest.fromList(it)
|
||||
}
|
||||
}
|
||||
149.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
Resource.fromList(it)
|
||||
ResourceSize.fromList(it)
|
||||
}
|
||||
}
|
||||
150.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
IconResult.fromList(it)
|
||||
Resource.fromList(it)
|
||||
}
|
||||
}
|
||||
151.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
CookiePartitionKey.fromList(it)
|
||||
IconResult.fromList(it)
|
||||
}
|
||||
}
|
||||
152.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
Cookie.fromList(it)
|
||||
CookiePartitionKey.fromList(it)
|
||||
}
|
||||
}
|
||||
153.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
HistoryItem.fromList(it)
|
||||
Cookie.fromList(it)
|
||||
}
|
||||
}
|
||||
154.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
HistoryState.fromList(it)
|
||||
HistoryItem.fromList(it)
|
||||
}
|
||||
}
|
||||
155.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ReaderableState.fromList(it)
|
||||
HistoryState.fromList(it)
|
||||
}
|
||||
}
|
||||
156.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
SecurityInfoState.fromList(it)
|
||||
ReaderableState.fromList(it)
|
||||
}
|
||||
}
|
||||
157.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
TabContentState.fromList(it)
|
||||
SecurityInfoState.fromList(it)
|
||||
}
|
||||
}
|
||||
158.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
FindResultState.fromList(it)
|
||||
TabContentState.fromList(it)
|
||||
}
|
||||
}
|
||||
159.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
CustomSelectionAction.fromList(it)
|
||||
FindResultState.fromList(it)
|
||||
}
|
||||
}
|
||||
160.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
CustomSelectionAction.fromList(it)
|
||||
}
|
||||
}
|
||||
161.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
WebExtensionData.fromList(it)
|
||||
}
|
||||
}
|
||||
162.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
GeckoSuggestion.fromList(it)
|
||||
}
|
||||
}
|
||||
163.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
TabContent.fromList(it)
|
||||
}
|
||||
}
|
||||
else -> super.readValueOfType(type, buffer)
|
||||
}
|
||||
}
|
||||
@@ -1164,106 +1257,118 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
|
||||
stream.write(135)
|
||||
writeValue(stream, value.raw)
|
||||
}
|
||||
is TranslationOptions -> {
|
||||
is GeckoSuggestionType -> {
|
||||
stream.write(136)
|
||||
writeValue(stream, value.toList())
|
||||
writeValue(stream, value.raw)
|
||||
}
|
||||
is ReaderState -> {
|
||||
is TranslationOptions -> {
|
||||
stream.write(137)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is LastMediaAccessState -> {
|
||||
is ReaderState -> {
|
||||
stream.write(138)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is HistoryMetadataKey -> {
|
||||
is LastMediaAccessState -> {
|
||||
stream.write(139)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is PackageCategoryValue -> {
|
||||
is HistoryMetadataKey -> {
|
||||
stream.write(140)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ExternalPackage -> {
|
||||
is PackageCategoryValue -> {
|
||||
stream.write(141)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is LoadUrlFlagsValue -> {
|
||||
is ExternalPackage -> {
|
||||
stream.write(142)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SourceValue -> {
|
||||
is LoadUrlFlagsValue -> {
|
||||
stream.write(143)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is TabState -> {
|
||||
is SourceValue -> {
|
||||
stream.write(144)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is RecoverableTab -> {
|
||||
is TabState -> {
|
||||
stream.write(145)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is RecoverableBrowserState -> {
|
||||
is RecoverableTab -> {
|
||||
stream.write(146)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is IconRequest -> {
|
||||
is RecoverableBrowserState -> {
|
||||
stream.write(147)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ResourceSize -> {
|
||||
is IconRequest -> {
|
||||
stream.write(148)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is Resource -> {
|
||||
is ResourceSize -> {
|
||||
stream.write(149)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is IconResult -> {
|
||||
is Resource -> {
|
||||
stream.write(150)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is CookiePartitionKey -> {
|
||||
is IconResult -> {
|
||||
stream.write(151)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is Cookie -> {
|
||||
is CookiePartitionKey -> {
|
||||
stream.write(152)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is HistoryItem -> {
|
||||
is Cookie -> {
|
||||
stream.write(153)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is HistoryState -> {
|
||||
is HistoryItem -> {
|
||||
stream.write(154)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ReaderableState -> {
|
||||
is HistoryState -> {
|
||||
stream.write(155)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is SecurityInfoState -> {
|
||||
is ReaderableState -> {
|
||||
stream.write(156)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is TabContentState -> {
|
||||
is SecurityInfoState -> {
|
||||
stream.write(157)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is FindResultState -> {
|
||||
is TabContentState -> {
|
||||
stream.write(158)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is CustomSelectionAction -> {
|
||||
is FindResultState -> {
|
||||
stream.write(159)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is WebExtensionData -> {
|
||||
is CustomSelectionAction -> {
|
||||
stream.write(160)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is WebExtensionData -> {
|
||||
stream.write(161)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is GeckoSuggestion -> {
|
||||
stream.write(162)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is TabContent -> {
|
||||
stream.write(163)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
else -> super.writeValue(stream, value)
|
||||
}
|
||||
}
|
||||
@@ -2553,12 +2658,12 @@ class ReaderViewController(private val binaryMessenger: BinaryMessenger, private
|
||||
GeckoPigeonCodec()
|
||||
}
|
||||
}
|
||||
fun appearanceButtonVisibility(visibleArg: Boolean, callback: (Result<Unit>) -> Unit)
|
||||
fun appearanceButtonVisibility(timestampArg: Long, visibleArg: Boolean, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.ReaderViewController.appearanceButtonVisibility$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(visibleArg)) {
|
||||
channel.send(listOf(timestampArg, visibleArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
@@ -2743,3 +2848,90 @@ class GeckoAddonEvents(private val binaryMessenger: BinaryMessenger, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface GeckoSuggestionApi {
|
||||
fun onInputChanged(text: String, providers: List<GeckoSuggestionType>)
|
||||
|
||||
companion object {
|
||||
/** The codec used by GeckoSuggestionApi. */
|
||||
val codec: MessageCodec<Any?> by lazy {
|
||||
GeckoPigeonCodec()
|
||||
}
|
||||
/** Sets up an instance of `GeckoSuggestionApi` to handle messages through the `binaryMessenger`. */
|
||||
@JvmOverloads
|
||||
fun setUp(binaryMessenger: BinaryMessenger, api: GeckoSuggestionApi?, messageChannelSuffix: String = "") {
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoSuggestionApi.onInputChanged$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val textArg = args[0] as String
|
||||
val providersArg = args[1] as List<GeckoSuggestionType>
|
||||
val wrapped: List<Any?> = try {
|
||||
api.onInputChanged(textArg, providersArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */
|
||||
class GeckoSuggestionEvents(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") {
|
||||
companion object {
|
||||
/** The codec used by GeckoSuggestionEvents. */
|
||||
val codec: MessageCodec<Any?> by lazy {
|
||||
GeckoPigeonCodec()
|
||||
}
|
||||
}
|
||||
fun onSuggestionResult(timestampArg: Long, suggestionTypeArg: GeckoSuggestionType, suggestionsArg: List<GeckoSuggestion>, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoSuggestionEvents.onSuggestionResult$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(timestampArg, suggestionTypeArg, suggestionsArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
} else {
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
} else {
|
||||
callback(Result.failure(createConnectionError(channelName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */
|
||||
class GeckoTabContentEvents(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") {
|
||||
companion object {
|
||||
/** The codec used by GeckoTabContentEvents. */
|
||||
val codec: MessageCodec<Any?> by lazy {
|
||||
GeckoPigeonCodec()
|
||||
}
|
||||
}
|
||||
fun onContentUpdate(timestampArg: Long, contentArg: TabContent, callback: (Result<Unit>) -> Unit)
|
||||
{
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoTabContentEvents.onContentUpdate$separatedMessageChannelSuffix"
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
|
||||
channel.send(listOf(timestampArg, contentArg)) {
|
||||
if (it is List<*>) {
|
||||
if (it.size > 1) {
|
||||
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
|
||||
} else {
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
} else {
|
||||
callback(Result.failure(createConnectionError(channelName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/* 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 eu.lensai.flutter_mozilla_components.pip
|
||||
|
||||
import android.app.Activity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.feature.session.PictureInPictureFeature
|
||||
import mozilla.components.lib.state.ext.flowScoped
|
||||
import mozilla.components.support.base.feature.LifecycleAwareFeature
|
||||
|
||||
class PictureInPictureIntegration(
|
||||
private val store: BrowserStore,
|
||||
activity: Activity,
|
||||
private val customTabId: String?,
|
||||
private val whiteList: List<String> = listOf("youtube.com/tv"),
|
||||
) : LifecycleAwareFeature {
|
||||
private var scope: CoroutineScope? = null
|
||||
private val pictureFeature = PictureInPictureFeature(store, activity)
|
||||
private var whiteListed = false
|
||||
|
||||
override fun start() {
|
||||
scope = store.flowScoped { flow ->
|
||||
flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(customTabId) }
|
||||
.distinctUntilChangedBy { it.content.url }
|
||||
.collect { whiteListed = isWhitelisted(it.content.url) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
scope?.cancel()
|
||||
}
|
||||
|
||||
fun onHomePressed() = if (whiteListed) {
|
||||
pictureFeature.enterPipModeCompat()
|
||||
} else {
|
||||
pictureFeature.onHomePressed()
|
||||
}
|
||||
|
||||
private fun isWhitelisted(url: String): Boolean {
|
||||
val exists = whiteList.firstOrNull { url.contains(it) }
|
||||
return exists != null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user