intermediate

This commit is contained in:
Fabian Freund
2024-09-13 09:11:06 +02:00
parent 3b047b5d8a
commit 37d2c84d7f
155 changed files with 5666 additions and 1816 deletions
@@ -0,0 +1,85 @@
'use strict';
console.log("Connecting to native port...")
const port = browser.runtime.connectNative("cookieManager");
console.log("Native port connected!")
function cookieToMap(cookie) {
let partitionKey = {}
if (cookie.partitionKey) {
partitionKey["topLevelSite"] = cookie.partitionKey.topLevelSite
}
return {
"domain": cookie.domain,
"expirationDate": cookie.expirationDate,
"firstPartyDomain": cookie.firstPartyDomain,
"hostOnly": cookie.hostOnly,
"httpOnly": cookie.httpOnly,
"name": cookie.name,
"partitionKey": partitionKey,
"path": cookie.path,
"secure": cookie.secure,
"session": cookie.session,
"sameSite": cookie.sameSite,
"storeId": cookie.storeId,
"value": cookie.value
}
}
function sendCookieResultForRequest(id) {
return function (cookie) {
port.postMessage({
"id": id,
"status": "success",
"result": cookieToMap(cookie)
})
}
}
function sendCookieListResultForRequest(id) {
return function (cookies) {
port.postMessage({
"id": id,
"status": "success",
"result": cookies.map((cookie) => cookieToMap(cookie))
})
}
}
function sendErrorForRequest(id) {
return function (error) {
console.error(error);
port.postMessage({
"id": id,
"status": "error",
"error": error
});
}
}
port.onMessage.addListener(message => {
let requestId = message["id"]
switch (message["action"]) {
case "get":
browser.cookies.get(message["args"])
.then(sendCookieResultForRequest(requestId))
.catch(sendErrorForRequest(requestId))
break
case "getAll":
browser.cookies.getAll(message["args"])
.then(sendCookieListResultForRequest(requestId))
.catch(sendErrorForRequest(requestId))
break
case "remove":
browser.cookies.remove(message["args"])
.then(sendCookieResultForRequest(requestId))
.catch(sendErrorForRequest(requestId))
break
case "set":
browser.cookies.set(message["args"])
.then(sendCookieResultForRequest(requestId))
.catch(sendErrorForRequest(requestId))
break
}
});
@@ -0,0 +1,23 @@
{
"manifest_version": 2,
"name": "cookie-manager",
"version": "1.0",
"description": "Cookie manager",
"browser_specific_settings": {
"gecko": {
"id": "cookie-manager@lensai.eu"
}
},
"background": {
"scripts": [
"background.js"
]
},
"permissions": [
"nativeMessaging",
"nativeMessagingFromContent",
"geckoViewAddons",
"cookies",
"<all_urls>"
]
}
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
import mozilla.components.browser.thumbnails.BrowserThumbnails
import mozilla.components.concept.engine.EngineView
import mozilla.components.feature.media.fullscreen.MediaSessionFullscreenFeature
@@ -9,9 +9,18 @@ import android.util.Log
import android.widget.Toast
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import eu.lensai.flutter_mozilla_components.ext.toWebPBytes
import eu.lensai.flutter_mozilla_components.middleware.FlutterEventMiddleware
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
import eu.lensai.flutter_mozilla_components.pigeons.HistoryItem
import eu.lensai.flutter_mozilla_components.pigeons.HistoryState
import eu.lensai.flutter_mozilla_components.pigeons.ReaderableState
import eu.lensai.flutter_mozilla_components.pigeons.SecurityInfoState
import eu.lensai.flutter_mozilla_components.pigeons.TabContentState
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import mozilla.components.browser.engine.gecko.GeckoEngine
@@ -66,7 +75,7 @@ private const val DAY_IN_MINUTES = 24 * 60L
@SuppressLint("NewApi")
@Suppress("LargeClass")
open class DefaultComponents(private val applicationContext: Context) {
open class DefaultComponents(private val applicationContext: Context, private val flutterEvents: GeckoStateEvents) {
companion object {
const val SAMPLE_BROWSER_PREFERENCES = "sample_browser_preferences"
const val PREF_LAUNCH_EXTERNAL_APP = "sample_browser_launch_external_app"
@@ -133,7 +142,7 @@ open class DefaultComponents(private val applicationContext: Context) {
val store by lazy {
BrowserStore(
middleware = listOf(
FlutterEventMiddleware(),
FlutterEventMiddleware(flutterEvents),
DownloadMiddleware(applicationContext, DownloadService::class.java),
ReaderViewMiddleware(),
ThumbnailsMiddleware(thumbnailStorage),
@@ -145,12 +154,63 @@ open class DefaultComponents(private val applicationContext: Context) {
) + EngineMiddleware.create(engine),
).apply {
this.flowScoped { flow ->
flow.mapNotNull { state -> state.selectedTab }
.distinctUntilChangedBy {
it.content.url
flow.map { state -> state.selectedTabId }
.collect { tabId ->
flutterEvents.onSelectedTabChange(
tabId
) { _ -> }
}
}
this.flowScoped { flow ->
flow.mapNotNull { state -> state.tabs }
.filterChanged {
it.content.icon
}
.collect { tab ->
Log.d("URL_CHANGE", tab.content.url)
val iconBytes = tab.content.icon?.toWebPBytes()
flutterEvents.onIconChange(
tab.id,
iconBytes
) { _ -> }
}
}
this.flowScoped { flow ->
flow.mapNotNull { state -> state.tabs }
.filterChanged {
it.content.securityInfo
}
.collect { tab ->
flutterEvents.onSecurityInfoStateChange(
tab.id,
SecurityInfoState(
tab.content.securityInfo.secure,
tab.content.securityInfo.host,
tab.content.securityInfo.issuer,
)
) { _ -> }
}
}
this.flowScoped { flow ->
flow.mapNotNull { state -> state.tabs }
.filterChanged {
it.readerState
}
.ifAnyChanged { arrayOf(
it.readerState.readerable,
it.readerState.active,
)
}
.collect { tab ->
flutterEvents.onReaderableStateChange(
tab.id,
ReaderableState(
tab.readerState.readerable,
tab.readerState.active,
)
) { _ -> }
}
}
@@ -159,9 +219,61 @@ open class DefaultComponents(private val applicationContext: Context) {
.filterChanged {
it.content
}
.ifAnyChanged { arrayOf(it.content.url) }
.ifAnyChanged { arrayOf(
it.content.history,
it.content.canGoBack,
it.content.canGoForward,
)
}
.collect { tab ->
Log.d("URL_CHANGE2", tab.content.url)
flutterEvents.onHistoryStateChange(
tab.id,
HistoryState(
items = tab.content.history.items.map { item -> HistoryItem(
url = item.uri,
title = item.title
) },
currentIndex = tab.content.history.currentIndex.toLong(),
canGoBack = tab.content.canGoBack,
canGoForward = tab.content.canGoForward,
)
) { _ -> }
}
}
this.flowScoped { flow ->
flow.mapNotNull { state -> state.tabs.map {tab -> tab.id} }
.distinctUntilChanged()
.collect { tabs ->
flutterEvents.onTabListChange(tabs) { _ -> }
}
}
this.flowScoped { flow ->
flow.mapNotNull { state -> state.tabs }
.filterChanged {
it.content
}
.ifAnyChanged { arrayOf(
it.content.url,
it.content.title,
it.content.private,
it.content.fullScreen,
it.content.progress,
it.content.loading) }
.collect { tab ->
flutterEvents.onTabContentStateChange(
TabContentState(
id = tab.id,
contextId = tab.contextId,
url = tab.content.url,
title = tab.content.title,
progress = tab.content.progress.toLong(),
isPrivate = tab.content.private,
isFullScreen = tab.content.fullScreen,
isLoading = tab.content.loading
)
) { _ -> }
}
}
@@ -1,8 +1,5 @@
package eu.lensai.flutter_mozilla_components
import GeckoBrowserApi
import GeckoSessionApi
import GeckoTabsApi
import android.app.Activity
import android.content.Context
import android.content.Intent
@@ -10,8 +7,17 @@ import androidx.annotation.NonNull
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import eu.lensai.flutter_mozilla_components.api.GeckoBrowserApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoCookieApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoIconsApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoSessionApiImpl
import eu.lensai.flutter_mozilla_components.api.GeckoTabsApiImpl
import eu.lensai.flutter_mozilla_components.feature.CookieManagerFeature
import eu.lensai.flutter_mozilla_components.pigeons.GeckoBrowserApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoCookieApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoIconsApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSessionApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabsApi
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
@@ -34,7 +40,7 @@ import org.mozilla.geckoview.GeckoRuntimeSettings
/**
* Helper class for lazily instantiating components needed by the application.
*/
class Components(private val applicationContext: Context) : DefaultComponents(applicationContext) {
class Components(private val applicationContext: Context, flutterEvents: GeckoStateEvents) : DefaultComponents(applicationContext, flutterEvents) {
private val runtime by lazy {
// Allow for exfiltrating Gecko metrics through the Glean SDK.
val builder = GeckoRuntimeSettings.Builder().aboutConfigEnabled(true)
@@ -56,6 +62,7 @@ class Components(private val applicationContext: Context) : DefaultComponents(ap
// }
WebCompatFeature.install(it)
CookieManagerFeature.install(it)
//WebCompatReporterFeature.install(it)
}
}
@@ -77,7 +84,9 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
_flutterPluginBinding = flutterPluginBinding
GlobalComponents.setUp(flutterPluginBinding.applicationContext)
val flutterEvents = GeckoStateEvents(_flutterPluginBinding.binaryMessenger)
GlobalComponents.setUp(flutterPluginBinding.applicationContext, flutterEvents)
val intent = Intent(flutterPluginBinding.applicationContext, NotificationActivity::class.java)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -89,6 +98,8 @@ class FlutterMozillaComponentsPlugin: FlutterPlugin, ActivityAware {
GeckoSessionApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoSessionApiImpl())
GeckoTabsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoTabsApiImpl())
GeckoIconsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoIconsApiImpl())
GeckoCookieApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoCookieApiImpl())
}
private fun showNativeFragment() {
@@ -1,6 +1,7 @@
package eu.lensai.flutter_mozilla_components
import android.content.Context
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@@ -28,8 +29,8 @@ object GlobalComponents {
.whenSessionsChange()
}
fun setUp(applicationContext: Context) {
val newComponents = Components(applicationContext)
fun setUp(applicationContext: Context, flutterEvents: GeckoStateEvents) {
val newComponents = Components(applicationContext, flutterEvents)
newComponents.crashReporter.install(applicationContext)
Facts.registerProcessor(LogFactProcessor())
@@ -1,10 +1,8 @@
package eu.lensai.flutter_mozilla_components.api
import GeckoBrowserApi
import eu.lensai.flutter_mozilla_components.pigeons.GeckoBrowserApi
class GeckoBrowserApiImpl(
private val showFragmentCallback: () -> Unit
) : GeckoBrowserApi {
class GeckoBrowserApiImpl(private val showFragmentCallback: () -> Unit) : GeckoBrowserApi {
override fun showNativeFragment() {
showFragmentCallback.invoke();
}
@@ -0,0 +1,309 @@
package eu.lensai.flutter_mozilla_components.api
import eu.lensai.flutter_mozilla_components.feature.CookieManagerFeature
import eu.lensai.flutter_mozilla_components.feature.ResultConsumer
import eu.lensai.flutter_mozilla_components.pigeons.Cookie
import eu.lensai.flutter_mozilla_components.pigeons.CookiePartitionKey
import eu.lensai.flutter_mozilla_components.pigeons.CookieSameSiteStatus
import eu.lensai.flutter_mozilla_components.pigeons.GeckoCookieApi
import org.json.JSONObject
class GeckoCookieApiImpl : GeckoCookieApi {
private fun toValueOrNull(json: JSONObject, key: String): Any? {
if (!json.has(key)) {
throw RuntimeException("Invalid map key")
}
if (!json.isNull(key)) {
return json.get(key)
}
return null
}
private fun cookiePartitionKeyFromJSON(inputJSON: JSONObject): CookiePartitionKey {
return CookiePartitionKey(
topLevelSite = inputJSON.getString("topLevelSite")
)
}
private fun CookiePartitionKey.toJSON(): JSONObject {
val json = JSONObject()
json.put("topLevelSite", topLevelSite)
return json
}
private fun cookieFromJSON(inputJSON: JSONObject): Cookie {
val partitionKeyRaw = toValueOrNull(inputJSON, "partitionKey") as JSONObject?
val partitionKey = if (partitionKeyRaw == null || partitionKeyRaw.length() == 0) null
else cookiePartitionKeyFromJSON(partitionKeyRaw)
return Cookie (
domain = inputJSON.getString("domain"),
expirationDate = (toValueOrNull(inputJSON, "expirationDate") as Int?)?.toLong(),
firstPartyDomain = inputJSON.getString("firstPartyDomain"),
hostOnly = inputJSON.getBoolean("hostOnly"),
httpOnly = inputJSON.getBoolean("httpOnly"),
name = inputJSON.getString("name"),
partitionKey = partitionKey,
path = inputJSON.getString("path"),
secure = inputJSON.getBoolean("secure"),
session = inputJSON.getBoolean("session"),
sameSite = when(inputJSON.getString("sameSite")) {
"no_restriction" -> CookieSameSiteStatus.NO_RESTRICTION
"lax" -> CookieSameSiteStatus.LAX
"strict" -> CookieSameSiteStatus.STRICT
else -> CookieSameSiteStatus.UNSPECIFIED
},
storeId = inputJSON.getString("storeId"),
value = inputJSON.getString("value")
)
}
override fun getCookie(
firstPartyDomain: String?,
name: String,
partitionKey: CookiePartitionKey?,
storeId: String?,
url: String,
callback: (Result<Cookie>) -> Unit
) {
val args = JSONObject()
if (firstPartyDomain == null) {
args.put("firstPartyDomain", JSONObject.NULL)
} else {
args.put("firstPartyDomain", firstPartyDomain)
}
args.put("name", name)
if (partitionKey == null) {
args.put("partitionKey", JSONObject.NULL)
} else {
args.put("partitionKey", partitionKey.toJSON())
}
if (storeId == null) {
args.put("storeId", JSONObject.NULL)
} else {
args.put("storeId", storeId)
}
args.put("url", url)
CookieManagerFeature.scheduleRequest("get", args, object: ResultConsumer<JSONObject> {
override fun success(result: JSONObject) {
callback(Result.success(cookieFromJSON(result.getJSONObject("result"))))
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
callback(Result.failure(Exception("$errorCode $errorMessage $errorDetails")))
}
})
}
override fun getAllCookies(
domain: String?,
firstPartyDomain: String?,
name: String?,
partitionKey: CookiePartitionKey?,
storeId: String?,
url: String,
callback: (Result<List<Cookie>>) -> Unit
) {
val args = JSONObject()
if (domain == null) {
args.put("domain", JSONObject.NULL)
} else {
args.put("domain", domain)
}
if (firstPartyDomain == null) {
args.put("firstPartyDomain", JSONObject.NULL)
} else {
args.put("firstPartyDomain", firstPartyDomain)
}
args.put("name", name)
if (partitionKey == null) {
args.put("partitionKey", JSONObject.NULL)
} else {
args.put("partitionKey", partitionKey.toJSON())
}
if (storeId == null) {
args.put("storeId", JSONObject.NULL)
} else {
args.put("storeId", storeId)
}
args.put("url", url)
CookieManagerFeature.scheduleRequest("getAll", args, object: ResultConsumer<JSONObject> {
override fun success(result: JSONObject) {
val jsonArray = result.getJSONArray("result")
val cookies: MutableList<Cookie> = mutableListOf()
repeat(jsonArray.length()) {
index ->
cookies.add(cookieFromJSON(jsonArray.getJSONObject(index)))
}
callback(Result.success(cookies))
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
callback(Result.failure(Exception("$errorCode $errorMessage $errorDetails")))
}
})
}
override fun setCookie(
domain: String?,
expirationDate: Long?,
firstPartyDomain: String?,
httpOnly: Boolean?,
name: String?,
partitionKey: CookiePartitionKey?,
path: String?,
sameSite: CookieSameSiteStatus?,
secure: Boolean?,
storeId: String?,
url: String,
value: String?,
callback: (Result<Unit>) -> Unit
) {
val args = JSONObject()
if (domain == null) {
args.put("domain", JSONObject.NULL)
} else {
args.put("domain", domain)
}
if (expirationDate == null) {
args.put("expirationDate", JSONObject.NULL)
} else {
args.put("expirationDate", domain)
}
if (firstPartyDomain == null) {
args.put("firstPartyDomain", JSONObject.NULL)
} else {
args.put("firstPartyDomain", firstPartyDomain)
}
if (httpOnly == null) {
args.put("httpOnly", JSONObject.NULL)
} else {
args.put("httpOnly", httpOnly)
}
if (name == null) {
args.put("name", JSONObject.NULL)
} else {
args.put("name", name)
}
if (partitionKey == null) {
args.put("partitionKey", JSONObject.NULL)
} else {
args.put("partitionKey", partitionKey.toJSON())
}
if (path == null) {
args.put("path", JSONObject.NULL)
} else {
args.put("path", path)
}
if (sameSite == null) {
args.put("sameSite", JSONObject.NULL)
} else {
args.put("sameSite", when(sameSite) {
CookieSameSiteStatus.NO_RESTRICTION -> "no_restriction"
CookieSameSiteStatus.LAX -> "lax"
CookieSameSiteStatus.STRICT -> "strict"
CookieSameSiteStatus.UNSPECIFIED -> ""
})
}
if (secure == null) {
args.put("secure", JSONObject.NULL)
} else {
args.put("secure", secure)
}
if (storeId == null) {
args.put("storeId", JSONObject.NULL)
} else {
args.put("storeId", storeId)
}
args.put("url", url)
if (value == null) {
args.put("value", JSONObject.NULL)
} else {
args.put("value", value)
}
CookieManagerFeature.scheduleRequest("set", args, object: ResultConsumer<JSONObject> {
override fun success(result: JSONObject) {
callback(Result.success(Unit))
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
callback(Result.failure(Exception("$errorCode $errorMessage $errorDetails")))
}
})
}
override fun removeCookie(
firstPartyDomain: String?,
name: String,
partitionKey: CookiePartitionKey?,
storeId: String?,
url: String,
callback: (Result<Unit>) -> Unit
) {
val args = JSONObject()
if (firstPartyDomain == null) {
args.put("firstPartyDomain", JSONObject.NULL)
} else {
args.put("firstPartyDomain", firstPartyDomain)
}
args.put("name", name)
if (partitionKey == null) {
args.put("partitionKey", JSONObject.NULL)
} else {
args.put("partitionKey", partitionKey.toJSON())
}
if (storeId == null) {
args.put("storeId", JSONObject.NULL)
} else {
args.put("storeId", storeId)
}
args.put("url", url)
CookieManagerFeature.scheduleRequest("remove", args, object: ResultConsumer<JSONObject> {
override fun success(result: JSONObject) {
callback(Result.success(Unit))
}
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
callback(Result.failure(Exception("$errorCode $errorMessage $errorDetails")))
}
})
}
}
@@ -0,0 +1,96 @@
package eu.lensai.flutter_mozilla_components.api
import android.graphics.Bitmap
import android.os.Build
import eu.lensai.flutter_mozilla_components.GlobalComponents
import eu.lensai.flutter_mozilla_components.ext.toWebPBytes
import eu.lensai.flutter_mozilla_components.pigeons.*
import kotlinx.coroutines.*
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.icons.Icon
import mozilla.components.concept.engine.manifest.Size as HtmlSize
import java.io.ByteArrayOutputStream
typealias MozillaIconRequest = mozilla.components.browser.icons.IconRequest
typealias MozillaIconSize = mozilla.components.browser.icons.IconRequest.Size
typealias MozillaIconResource = mozilla.components.browser.icons.IconRequest.Resource
typealias MozillaIconResourceType = mozilla.components.browser.icons.IconRequest.Resource.Type
class GeckoIconsApiImpl() : GeckoIconsApi {
private val icons: BrowserIcons by lazy { GlobalComponents.components!!.icons }
override fun loadIcon(request: IconRequest, callback: (Result<IconResult>) -> Unit) {
CoroutineScope(Dispatchers.Default).launch {
runCatching {
loadIconAsync(request.toMozillaIconRequest())
}.fold(
onSuccess = { result ->
withContext(Dispatchers.Main) {
callback(Result.success(result))
}
},
onFailure = { error ->
withContext(Dispatchers.Main) {
callback(Result.failure(error))
}
}
)
}
}
private suspend fun loadIconAsync(request: MozillaIconRequest): IconResult {
val result = icons.loadIcon(request).await()
val imageBytes = result.bitmap.toWebPBytes()
return IconResult(
image = imageBytes,
maskable = result.maskable,
color = result.color?.toLong(),
source = result.source.toApiSource()
)
}
private fun IconRequest.toMozillaIconRequest(): MozillaIconRequest =
MozillaIconRequest(
url = url,
size = size.toMozillaSize(),
color = color?.toInt(),
waitOnNetworkLoad = waitOnNetworkLoad,
isPrivate = isPrivate,
resources = resources.filterNotNull().map { it.toMozillaResource() }
)
private fun IconSize.toMozillaSize(): MozillaIconSize = when (this) {
IconSize.DEFAULT_SIZE -> MozillaIconSize.DEFAULT
IconSize.LAUNCHER -> MozillaIconSize.LAUNCHER
IconSize.LAUNCHER_ADAPTIVE -> MozillaIconSize.LAUNCHER_ADAPTIVE
}
private fun Resource.toMozillaResource(): MozillaIconResource =
MozillaIconResource(
url = url,
mimeType = mimeType,
maskable = maskable,
type = type.toMozillaType(),
sizes = sizes.filterNotNull().map { HtmlSize(it.height.toInt(), it.width.toInt()) }
)
private fun IconType.toMozillaType(): MozillaIconResourceType = when (this) {
IconType.FAVICON -> MozillaIconResourceType.FAVICON
IconType.APPLE_TOUCH_ICON -> MozillaIconResourceType.APPLE_TOUCH_ICON
IconType.FLUID_ICON -> MozillaIconResourceType.FLUID_ICON
IconType.IMAGE_SRC -> MozillaIconResourceType.IMAGE_SRC
IconType.OPEN_GRAPH -> MozillaIconResourceType.OPENGRAPH
IconType.TWITTER -> MozillaIconResourceType.TWITTER
IconType.MICROSOFT_TILE -> MozillaIconResourceType.MICROSOFT_TILE
IconType.TIPPY_TOP -> MozillaIconResourceType.TIPPY_TOP
IconType.MANIFEST_ICON -> MozillaIconResourceType.MANIFEST_ICON
}
private fun Icon.Source.toApiSource(): Source = when (this) {
Icon.Source.GENERATOR -> Source.GENERATOR
Icon.Source.DOWNLOAD -> Source.DOWNLOAD
Icon.Source.INLINE -> Source.INLINE
Icon.Source.MEMORY -> Source.MEMORY
Icon.Source.DISK -> Source.DISK
}
}
@@ -1,16 +1,16 @@
package eu.lensai.flutter_mozilla_components.api
import GeckoSessionApi
import LoadUrlFlagsValue
import android.util.Log
import eu.lensai.flutter_mozilla_components.GlobalComponents
import eu.lensai.flutter_mozilla_components.pigeons.GeckoSessionApi
import eu.lensai.flutter_mozilla_components.pigeons.LoadUrlFlagsValue
import eu.lensai.flutter_mozilla_components.pigeons.TranslationOptions as PigeonTranslationOptions
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.translate.TranslationOptions
import TranslationOptions as PigeonTranslationOptions
import mozilla.components.feature.session.SessionUseCases
class GeckoSessionApiImpl() : GeckoSessionApi {
class GeckoSessionApiImpl : GeckoSessionApi {
private val sessionUseCases: SessionUseCases by lazy { GlobalComponents.components!!.sessionUseCases }
private val state: BrowserState by lazy { GlobalComponents.components!!.store.state }
@@ -33,21 +33,19 @@ class GeckoSessionApiImpl() : GeckoSessionApi {
data = data,
tabId = tabId ?: state.selectedTabId,
mimeType = mimeType,
encoding = encoding,
encoding = encoding
)
}
override fun reload(tabId: String?, flags: LoadUrlFlagsValue) {
sessionUseCases.reload(
tabId = tabId ?: state.selectedTabId,
flags = EngineSession.LoadUrlFlags.select(flags.value.toInt()),
flags = EngineSession.LoadUrlFlags.select(flags.value.toInt())
)
}
override fun stopLoading(tabId: String?) {
sessionUseCases.stopLoading(
tabId = tabId
)
sessionUseCases.stopLoading(tabId = tabId ?: state.selectedTabId)
}
override fun goBack(tabId: String?, userInteraction: Boolean) {
@@ -74,26 +72,20 @@ class GeckoSessionApiImpl() : GeckoSessionApi {
override fun requestDesktopSite(tabId: String?, enable: Boolean) {
sessionUseCases.requestDesktopSite(
tabId = tabId ?: state.selectedTabId,
enable = enable,
enable = enable
)
}
override fun exitFullscreen(tabId: String?) {
sessionUseCases.exitFullscreen(
tabId = tabId ?: state.selectedTabId,
)
sessionUseCases.exitFullscreen(tabId = tabId ?: state.selectedTabId)
}
override fun saveToPdf(tabId: String?) {
sessionUseCases.saveToPdf(
tabId = tabId ?: state.selectedTabId,
)
sessionUseCases.saveToPdf(tabId = tabId ?: state.selectedTabId)
}
override fun printContent(tabId: String?) {
sessionUseCases.printContent(
tabId = tabId ?: state.selectedTabId,
)
sessionUseCases.printContent(tabId = tabId ?: state.selectedTabId)
}
override fun translate(
@@ -106,18 +98,16 @@ class GeckoSessionApiImpl() : GeckoSessionApi {
tabId = tabId ?: state.selectedTabId,
fromLanguage = fromLanguage,
toLanguage = toLanguage,
options = if (options != null) TranslationOptions(downloadModel = options.downloadModel) else null,
options = options?.let { TranslationOptions(downloadModel = it.downloadModel) }
)
}
override fun translateRestore(tabId: String?) {
sessionUseCases.translateRestore(
tabId = tabId ?: state.selectedTabId,
)
sessionUseCases.translateRestore(tabId = tabId ?: state.selectedTabId)
}
override fun crashRecovery(tabIds: List<String>?) {
if(tabIds != null) {
if (tabIds != null) {
sessionUseCases.crashRecovery.invoke(tabIds = tabIds)
} else {
sessionUseCases.crashRecovery.invoke()
@@ -134,4 +124,4 @@ class GeckoSessionApiImpl() : GeckoSessionApi {
lastAccess = lastAccess ?: System.currentTimeMillis()
)
}
}
}
@@ -1,13 +1,14 @@
package eu.lensai.flutter_mozilla_components.api
import GeckoTabsApi
import HistoryMetadataKey as PigeonHistoryMetadataKey
import LoadUrlFlagsValue
import RecoverableBrowserState as PigeonRecoverableBrowserState
import RestoreLocation as PigeonRestoreLocation
import RecoverableTab as PigeonRecoverableTab
import SourceValue
import eu.lensai.flutter_mozilla_components.pigeons.GeckoTabsApi
import eu.lensai.flutter_mozilla_components.pigeons.HistoryMetadataKey as PigeonHistoryMetadataKey
import eu.lensai.flutter_mozilla_components.pigeons.LoadUrlFlagsValue
import eu.lensai.flutter_mozilla_components.pigeons.RecoverableBrowserState as PigeonRecoverableBrowserState
import eu.lensai.flutter_mozilla_components.pigeons.RestoreLocation as PigeonRestoreLocation
import eu.lensai.flutter_mozilla_components.pigeons.RecoverableTab as PigeonRecoverableTab
import eu.lensai.flutter_mozilla_components.pigeons.SourceValue
import eu.lensai.flutter_mozilla_components.GlobalComponents
import eu.lensai.flutter_mozilla_components.pigeons.RestoreLocation
import mozilla.components.browser.session.storage.RecoverableBrowserState
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.selector.findTab
@@ -80,7 +81,11 @@ class GeckoTabsApiImpl() : GeckoTabsApi {
}
private fun mapRestoreLocation(t: PigeonRestoreLocation) : TabListAction.RestoreAction.RestoreLocation {
return TabListAction.RestoreAction.RestoreLocation.entries.first { it.ordinal == t.ordinal }
return when(t) {
RestoreLocation.BEGINNING -> TabListAction.RestoreAction.RestoreLocation.BEGINNING
RestoreLocation.END -> TabListAction.RestoreAction.RestoreLocation.END
RestoreLocation.AT_INDEX -> TabListAction.RestoreAction.RestoreLocation.AT_INDEX
}
}
override fun selectTab(tabId: String) {
@@ -0,0 +1,17 @@
package eu.lensai.flutter_mozilla_components.ext
import android.graphics.Bitmap
import android.os.Build
import java.io.ByteArrayOutputStream
fun Bitmap.toWebPBytes(): ByteArray {
val stream = ByteArrayOutputStream()
val compressFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Bitmap.CompressFormat.WEBP_LOSSLESS
} else {
@Suppress("DEPRECATION")
Bitmap.CompressFormat.WEBP
}
compress(compressFormat, 100, stream)
return stream.toByteArray()
}
@@ -0,0 +1,109 @@
package eu.lensai.flutter_mozilla_components.feature
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import mozilla.components.concept.engine.webextension.MessageHandler
import mozilla.components.concept.engine.webextension.Port
import mozilla.components.concept.engine.webextension.WebExtensionRuntime
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.webextensions.WebExtensionController
import org.json.JSONObject
interface ResultConsumer<T> {
fun success(result: T)
fun error(errorCode: String, errorMessage: String?, errorDetails: Any?)
}
/**
* A feature that enables users to report site issues to Mozilla's Web Compatibility team for
* further diagnosis.
*/
object CookieManagerFeature {
private val logger = Logger("cookie-manager")
private const val COOKIE_MANAGER_REPORTER_EXTENSION_ID = "cookie-manager@lensai.eu"
private const val COOKIE_MANAGER_REPORTER_EXTENSION_URL = "resource://android/assets/extensions/cookie-manager/"
private const val COOKIE_MANAGER_REPORTER_MESSAGING_ID = "cookieManager"
private var nextRequestId: Int = 0
private val requestHandlers = HashMap<Int, ResultConsumer<JSONObject>>()
private val mutex = Mutex()
@VisibleForTesting
// This is an internal var to make it mutable for unit testing purposes only
internal var extensionController = WebExtensionController(
COOKIE_MANAGER_REPORTER_EXTENSION_ID,
COOKIE_MANAGER_REPORTER_EXTENSION_URL,
COOKIE_MANAGER_REPORTER_MESSAGING_ID,
)
fun scheduleRequest(command: String, args: JSONObject, callback: ResultConsumer<JSONObject>) {
val message = JSONObject()
message.put("action", command);
message.put("args", args)
runBlocking {
withContext(Dispatchers.Default) {
mutex.withLock {
message.put("id", nextRequestId)
requestHandlers[nextRequestId] = callback
nextRequestId += 1
extensionController.sendBackgroundMessage(message)
}
}
}
}
private class CookieManagerReporterBackgroundMessageHandler() : MessageHandler {
override fun onPortMessage(message: Any, port: Port) {
runBlocking {
withContext(Dispatchers.Default) {
mutex.withLock {
val messageJSON = message as JSONObject;
val requestId = messageJSON.getInt("id")
val status = messageJSON.getString("status")
if (status == "success") {
requestHandlers[requestId]?.success(message)
} else {
requestHandlers[requestId]?.error(
"Cookie Manager",
"Failed to perform operation",
message.getString("error")
)
}
}
}
}
}
}
/**
* Installs the web extension in the runtime through the WebExtensionRuntime install method
*
* @param runtime a WebExtensionRuntime.
* @param productName a custom product name used to automatically label reports. Defaults to
* "android-components".
*/
fun install(runtime: WebExtensionRuntime) {
extensionController.registerBackgroundMessageHandler(
CookieManagerReporterBackgroundMessageHandler(),
)
extensionController.install(
runtime,
onSuccess = {
logger.debug("Installed CookieManager webextension: ${it.id}")
},
onError = { throwable ->
logger.error("Failed to install CookieManager webextension: ", throwable)
},
)
}
}
@@ -3,18 +3,24 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package eu.lensai.flutter_mozilla_components.middleware
import android.graphics.Bitmap
import android.util.Log
import eu.lensai.flutter_mozilla_components.ext.toWebPBytes
import eu.lensai.flutter_mozilla_components.pigeons.GeckoStateEvents
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import java.io.ByteArrayOutputStream
/**
* [Middleware] implementation for handling [ContentAction.UpdateThumbnailAction] and storing
* the thumbnail to the disk cache.
*/
class FlutterEventMiddleware() : Middleware<BrowserState, BrowserAction> {
class FlutterEventMiddleware(private val flutterEvents: GeckoStateEvents) : Middleware<BrowserState, BrowserAction> {
@Suppress("ComplexMethod")
override fun invoke(
context: MiddlewareContext<BrowserState, BrowserAction>,
@@ -23,10 +29,8 @@ class FlutterEventMiddleware() : Middleware<BrowserState, BrowserAction> {
) {
when (action) {
is ContentAction.UpdateThumbnailAction -> {
Log.d("UpdateThumbnailAction", action.sessionId)
}
is ContentAction.UpdateTitleAction -> {
Log.d("UpdateTitleAction", action.title)
val bytes = action.thumbnail.toWebPBytes()
flutterEvents.onThumbnailChange(action.sessionId, bytes) { _ -> }
}
else -> {
// no-op
@@ -29,13 +29,13 @@ class MyAppState extends State<MyApp> {
menuChildren: [
MenuItemButton(
onPressed: () async {
await GeckoSessionService.forCurrentTab().reload();
await GeckoSessionService.forActiveTab().reload();
},
child: const Text('Reload'),
),
MenuItemButton(
onPressed: () async {
await GeckoSessionService.forCurrentTab().goBack();
await GeckoSessionService.forActiveTab().goBack();
},
child: const Text('Back'),
)
@@ -1,4 +1,19 @@
export 'src/geckoview_widget.dart';
//export 'src/domain/services/gecko_browser.dart';
export 'src/data/models/load_url_flags.dart';
export 'src/data/models/source.dart';
export 'src/domain/services/gecko_session.dart';
export 'src/domain/services/gecko_tabs.dart';
export 'src/domain/services/gecko_tab.dart';
export 'src/domain/services/gecko_icon.dart';
export 'src/domain/services/gecko_cookie.dart';
export 'src/domain/services/gecko_event.dart';
export 'src/pigeons/gecko.g.dart'
show
Resource,
IconType,
ResourceSize,
IconSource,
CookieSameSiteStatus,
TabContentState,
SecurityInfoState,
HistoryMetadataKey;
@@ -1,4 +1,3 @@
import 'package:flutter_mozilla_components/src/helper/helper.dart';
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
/// Describes a category of an external package.
@@ -16,6 +15,13 @@ enum PackageCategory {
final int id;
const PackageCategory(this.id);
factory PackageCategory.fromInt(int? id) {
return PackageCategory.values.firstWhere(
(category) => category.id == id,
orElse: () => PackageCategory.unknown,
);
}
PackageCategoryValue toValue() {
return PackageCategoryValue(value: id);
}
@@ -36,7 +42,7 @@ abstract class Source {
final caller = packageId != null
? ExternalPackage(
packageId: packageId,
category: packageCategoryfromInt(packageCategory).toValue())
category: PackageCategory.fromInt(packageCategory).toValue())
: null;
switch (sourceId) {
@@ -1,9 +1,11 @@
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
final _apiInstance = GeckoBrowserApi();
class GeckoBrowserService {
final GeckoBrowserApi _api;
GeckoBrowserService({GeckoBrowserApi? api}) : _api = api ?? GeckoBrowserApi();
GeckoBrowserService({GeckoBrowserApi? api}) : _api = api ?? _apiInstance;
Future<void> showNativeFragment() {
return _api.showNativeFragment();
@@ -0,0 +1,99 @@
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
final _apiInstance = GeckoCookieApi();
class GeckoCookieService {
final GeckoCookieApi _api;
GeckoCookieService({GeckoCookieApi? api}) : _api = api ?? _apiInstance;
Future<Cookie> getCookie({
Uri? firstPartyDomain,
required String name,
String? partitionKey,
String? storeId,
required Uri url,
}) {
return _api.getCookie(
firstPartyDomain?.host,
name,
(partitionKey != null)
? CookiePartitionKey(topLevelSite: partitionKey)
: null,
storeId,
url.toString(),
);
}
Future<List<Cookie>> getAllCookies({
Uri? domain,
Uri? firstPartyDomain,
String? name,
String? partitionKey,
String? storeId,
required Uri url,
}) {
return _api
.getAllCookies(
domain?.host,
firstPartyDomain?.host,
name,
(partitionKey != null)
? CookiePartitionKey(topLevelSite: partitionKey)
: null,
storeId,
url.toString(),
)
.then((value) => value.nonNulls.toList());
}
Future<void> setCookie({
Uri? domain,
int? expirationDate,
Uri? firstPartyDomain,
bool? httpOnly,
String? name,
String? partitionKey,
String? path,
CookieSameSiteStatus? sameSite,
bool? secure,
String? storeId,
required Uri url,
String? value,
}) {
return _api.setCookie(
domain?.host,
expirationDate,
firstPartyDomain?.host,
httpOnly,
name,
(partitionKey != null)
? CookiePartitionKey(topLevelSite: partitionKey)
: null,
path,
sameSite,
secure,
storeId,
url.toString(),
value,
);
}
Future<void> removeCookie({
Uri? firstPartyDomain,
required String name,
String? partitionKey,
String? storeId,
required Uri url,
}) {
return _api.removeCookie(
firstPartyDomain?.host,
name,
(partitionKey != null)
? CookiePartitionKey(topLevelSite: partitionKey)
: null,
storeId,
url.toString(),
);
}
}
@@ -0,0 +1,93 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
// Typedefs for record types
typedef HistoryEvent = ({String tabId, HistoryState history});
typedef ReaderableEvent = ({String tabId, ReaderableState readerable});
typedef SecurityInfoEvent = ({String tabId, SecurityInfoState securityInfo});
typedef IconEvent = ({String tabId, Uint8List? bytes});
typedef ThumbnailEvent = ({String tabId, Uint8List? bytes});
class GeckoEventService extends GeckoStateEvents {
// Stream controllers
final _tabListController = StreamController<List<String>>.broadcast();
final _selectedTabController = StreamController<String?>.broadcast();
final _tabContentController = StreamController<TabContentState>.broadcast();
final _historyController = StreamController<HistoryEvent>.broadcast();
final _readerableController = StreamController<ReaderableEvent>.broadcast();
final _securityInfoController =
StreamController<SecurityInfoEvent>.broadcast();
final _iconController = StreamController<IconEvent>.broadcast();
final _thumbnailController = StreamController<ThumbnailEvent>.broadcast();
// Event streams
Stream<List<String>> get tabListEvents => _tabListController.stream;
Stream<String?> get selectedTabEvents => _selectedTabController.stream;
Stream<TabContentState> get tabContentEvents => _tabContentController.stream;
Stream<HistoryEvent> get historyEvents => _historyController.stream;
Stream<ReaderableEvent> get readerableEvents => _readerableController.stream;
Stream<SecurityInfoEvent> get securityInfoEvents =>
_securityInfoController.stream;
Stream<IconEvent> get iconEvents => _iconController.stream;
Stream<ThumbnailEvent> get thumbnailEvents => _thumbnailController.stream;
// Overridden methods
@override
void onTabListChange(List<String?> tabIds) {
_tabListController.add(tabIds.nonNulls.toList());
}
@override
void onSelectedTabChange(String? id) {
_selectedTabController.add(id);
}
@override
void onTabContentStateChange(TabContentState state) {
_tabContentController.add(state);
}
@override
void onHistoryStateChange(String id, HistoryState state) {
_historyController.add((tabId: id, history: state));
}
@override
void onReaderableStateChange(String id, ReaderableState state) {
_readerableController.add((tabId: id, readerable: state));
}
@override
void onSecurityInfoStateChange(String id, SecurityInfoState state) {
_securityInfoController.add((tabId: id, securityInfo: state));
}
@override
void onIconChange(String id, Uint8List? bytes) {
_iconController.add((tabId: id, bytes: bytes));
}
@override
void onThumbnailChange(String id, Uint8List? bytes) {
_thumbnailController.add((tabId: id, bytes: bytes));
}
GeckoEventService.setUp({
BinaryMessenger? binaryMessenger,
String messageChannelSuffix = '',
}) {
GeckoStateEvents.setUp(this);
}
void dispose() {
_tabListController.close();
_selectedTabController.close();
_tabContentController.close();
_historyController.close();
_readerableController.close();
_securityInfoController.close();
_iconController.close();
_thumbnailController.close();
}
}
@@ -0,0 +1,20 @@
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
final _apiInstance = GeckoIconsApi();
class GeckoIconService {
final GeckoIconsApi _api;
GeckoIconService({GeckoIconsApi? api}) : _api = api ?? _apiInstance;
Future<IconResult> loadIcon(
{required Uri url, List<Resource> resources = const []}) async {
return _api.loadIcon(IconRequest(
url: url.toString(),
size: IconSize.defaultSize,
resources: resources,
isPrivate: false,
waitOnNetworkLoad: true,
));
}
}
@@ -1,16 +1,18 @@
import 'package:flutter_mozilla_components/src/data/models/load_url_flags.dart';
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
final _apiInstance = GeckoSessionApi();
class GeckoSessionService {
final String? tabId;
final GeckoSessionApi _api;
GeckoSessionService.forCurrentTab({GeckoSessionApi? api})
: _api = api ?? GeckoSessionApi(),
GeckoSessionService.forActiveTab({GeckoSessionApi? api})
: _api = api ?? _apiInstance,
tabId = null;
GeckoSessionService({required String this.tabId, GeckoSessionApi? api})
: _api = api ?? GeckoSessionApi();
: _api = api ?? _apiInstance;
Future<void> loadUrl({
required Uri url,
@@ -1,11 +1,14 @@
import 'package:flutter_mozilla_components/src/data/models/load_url_flags.dart';
import 'package:flutter_mozilla_components/src/data/models/source.dart';
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart'
hide IconSource;
class GeckoTabsService {
final _apiInstance = GeckoTabsApi();
class GeckoTabService {
final GeckoTabsApi _api;
GeckoTabsService({GeckoTabsApi? api}) : _api = api ?? GeckoTabsApi();
GeckoTabService({GeckoTabsApi? api}) : _api = api ?? _apiInstance;
Future<void> selectTab({required String tabId}) {
return _api.selectTab(tabId: tabId);
@@ -1,9 +0,0 @@
import 'package:flutter_mozilla_components/src/data/models/source.dart';
/// Maps an int category (as it can be obtained from a package manager) to our internal representation.
PackageCategory packageCategoryfromInt(int? id) {
return PackageCategory.values.firstWhere(
(category) => category.id == id,
orElse: () => PackageCategory.unknown,
);
}
File diff suppressed because it is too large Load Diff
@@ -50,7 +50,7 @@ class ReaderState {
/// Details about the last playing media in this tab.
class LastMediaAccessState {
/// [ContentState.url] when media started playing.
/// [TabContentState.url] when media started playing.
/// This is not the URL of the media but of the page when media started.
/// Defaults to "" (an empty String) if media hasn't started playing.
/// This value is only updated when media starts playing.
@@ -173,7 +173,7 @@ class TabState {
/// The last [HistoryMetadataKey] of the tab.
final HistoryMetadataKey? historyMetadata;
/// The last [Source] of the tab.
/// The last [IconSource] of the tab.
final SourceValue source;
/// The index the tab should be restored at.
@@ -241,12 +241,228 @@ enum RestoreLocation {
atIndex,
}
/// An icon resource type.
enum IconType {
favicon,
appleTouchIcon,
fluidIcon,
imageSrc,
openGraph,
twitter,
microsoftTile,
tippyTop,
manifestIcon,
}
/// Supported sizes.
///
/// We are trying to limit the supported sizes in order to optimize our caching strategy.
enum IconSize {
defaultSize,
launcher,
launcherAdaptive,
}
/// A request to load an [Icon].
class IconRequest {
final String url;
final IconSize size;
final List<Resource?> resources;
final int? color;
final bool isPrivate;
final bool waitOnNetworkLoad;
IconRequest({
required this.url,
this.size = IconSize.defaultSize,
this.resources = const [],
this.color,
this.isPrivate = false,
this.waitOnNetworkLoad = true,
});
}
class ResourceSize {
final int height;
final int width;
const ResourceSize({required this.height, required this.width});
}
/// An icon resource that can be loaded.
class Resource {
final String url;
final IconType type;
final List<ResourceSize?> sizes;
final String? mimeType;
final bool maskable;
Resource({
required this.url,
required this.type,
this.sizes = const [],
this.mimeType,
this.maskable = false,
});
}
/// An [Icon] returned by [BrowserIcons] after processing an [IconRequest]
class IconResult {
/// The loaded icon as an [Uint8List].
final Uint8List image;
/// The dominant color of the icon. Will be null if no color could be extracted.
final int? color;
/// The source of the icon.
final IconSource source;
/// True if the icon represents as full-bleed icon that can be cropped to other shapes.
final bool maskable;
IconResult({
required this.image,
this.color,
required this.source,
this.maskable = false,
});
}
/// The source of an [Icon].
enum IconSource {
/// This icon was generated.
generator,
/// This icon was downloaded.
download,
/// This icon was inlined in the document.
inline,
/// This icon was loaded from an in-memory cache.
memory,
/// This icon was loaded from a disk cache.
disk,
}
enum CookieSameSiteStatus {
noRestriction,
lax,
strict,
unspecified;
}
class CookiePartitionKey {
final String topLevelSite;
CookiePartitionKey(this.topLevelSite);
}
class Cookie {
final String domain;
final int? expirationDate;
final String firstPartyDomain;
final bool hostOnly;
final bool httpOnly;
final String name;
final CookiePartitionKey? partitionKey;
final String path;
final bool secure;
final bool session;
final CookieSameSiteStatus sameSite;
final String storeId;
final String value;
Cookie(
this.domain,
this.expirationDate,
this.firstPartyDomain,
this.hostOnly,
this.httpOnly,
this.name,
this.partitionKey,
this.path,
this.secure,
this.session,
this.sameSite,
this.storeId,
this.value);
}
class HistoryItem {
final String url;
final String title;
HistoryItem(this.url, this.title);
}
class HistoryState {
final List<HistoryItem?> items;
final int currentIndex;
final bool canGoBack;
final bool canGoForward;
HistoryState(
this.items,
this.currentIndex,
this.canGoBack,
this.canGoForward,
);
}
class ReaderableState {
/// Whether or not the current page can be transformed to
/// be displayed in a reader view.
final bool readerable;
/// Whether or not reader view is active.
final bool active;
ReaderableState(this.readerable, this.active);
}
class SecurityInfoState {
final bool secure;
final String host;
final String issuer;
SecurityInfoState(this.secure, this.host, this.issuer);
}
class TabContentState {
final String id;
final String? contextId;
final String url;
final String title;
final int progress;
final bool isPrivate;
final bool isFullScreen;
final bool isLoading;
TabContentState(
this.id,
this.contextId,
this.url,
this.title,
this.progress,
this.isPrivate,
this.isFullScreen,
this.isLoading,
);
}
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/src/pigeons/gecko.g.dart',
dartOptions: DartOptions(),
kotlinOut:
'android/src/main/kotlin/eu/lensai/flutter_mozilla_components/pigeons/Gecko.g.kt',
kotlinOptions: KotlinOptions(),
kotlinOptions:
KotlinOptions(package: 'eu.lensai.flutter_mozilla_components.pigeons'),
dartPackageName: 'flutter_mozilla_components',
))
@HostApi()
@@ -412,3 +628,69 @@ abstract class GeckoTabsApi {
required String? alternativeUrl,
});
}
@HostApi()
abstract class GeckoIconsApi {
@async
IconResult loadIcon(IconRequest request);
}
@HostApi()
abstract class GeckoCookieApi {
@async
Cookie getCookie(
String? firstPartyDomain,
String name,
CookiePartitionKey? partitionKey,
String? storeId,
String url,
);
@async
List<Cookie> getAllCookies(
String? domain,
String? firstPartyDomain,
String? name,
CookiePartitionKey? partitionKey,
String? storeId,
String url,
);
@async
void setCookie(
String? domain,
int? expirationDate,
String? firstPartyDomain,
bool? httpOnly,
String? name,
CookiePartitionKey? partitionKey,
String? path,
CookieSameSiteStatus? sameSite,
bool? secure,
String? storeId,
String url,
String? value,
);
@async
void removeCookie(
String? firstPartyDomain,
String name,
CookiePartitionKey? partitionKey,
String? storeId,
String url,
);
}
@FlutterApi()
abstract class GeckoStateEvents {
void onTabListChange(List<String> tabIds);
void onSelectedTabChange(String? id);
void onTabContentStateChange(TabContentState state);
void onHistoryStateChange(String id, HistoryState state);
void onReaderableStateChange(String id, ReaderableState state);
void onSecurityInfoStateChange(String id, SecurityInfoState state);
void onIconChange(String id, Uint8List? bytes);
void onThumbnailChange(String id, Uint8List? bytes);
}