intermediate
This commit is contained in:
+85
@@ -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
|
||||
}
|
||||
});
|
||||
+23
@@ -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>"
|
||||
]
|
||||
}
|
||||
+1
@@ -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
|
||||
|
||||
+120
-8
@@ -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
|
||||
)
|
||||
) { _ -> }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-5
@@ -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() {
|
||||
|
||||
+3
-2
@@ -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())
|
||||
|
||||
+2
-4
@@ -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();
|
||||
}
|
||||
|
||||
+309
@@ -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")))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -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
|
||||
}
|
||||
}
|
||||
+15
-25
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-8
@@ -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) {
|
||||
|
||||
+17
@@ -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()
|
||||
}
|
||||
+109
@@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
-5
@@ -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
|
||||
|
||||
+878
-31
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
+6
-3
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user