-
Notifications
You must be signed in to change notification settings - Fork 4
fix: separate local storage between WebView processes #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f9a3b3c
60c0758
0d1632e
e607a29
b811afa
4801756
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1,124 @@ | ||
| package com.outsystems.plugins.inappbrowser.osinappbrowserlib | ||
|
|
||
| import android.content.BroadcastReceiver | ||
| import android.content.Context | ||
| import com.outsystems.plugins.inappbrowser.osinappbrowserlib.views.OSIABWebViewActivity | ||
| import android.content.Intent | ||
| import android.content.IntentFilter | ||
| import androidx.core.content.ContextCompat | ||
| import androidx.core.content.IntentCompat | ||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||
| import kotlinx.coroutines.flow.asSharedFlow | ||
| import java.io.Serializable | ||
|
|
||
| sealed class OSIABEvents { | ||
| @RequiresOptIn( | ||
| level = RequiresOptIn.Level.WARNING, | ||
| message = "This API requires a prior call to OSIABEvents.registerReceiver(context) to work correctly with process isolation on Android 9+." | ||
| ) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) | ||
| annotation class RequiresEventBridgeRegistration | ||
|
|
||
| sealed class OSIABEvents : Serializable { | ||
|
OS-pedrogustavobilro marked this conversation as resolved.
|
||
| abstract val browserId: String | ||
|
|
||
| data class BrowserPageLoaded(override val browserId: String) : OSIABEvents() | ||
| data class BrowserFinished(override val browserId: String) : OSIABEvents() | ||
| data class BrowserPageNavigationCompleted(override val browserId: String, val url: String?) : OSIABEvents() | ||
|
|
||
| data class OSIABCustomTabsEvent( | ||
| override val browserId: String, | ||
| val action: String, | ||
| val context: Context | ||
| @Transient val context: Context? = null | ||
| ) : OSIABEvents() | ||
|
|
||
| data class OSIABWebViewEvent( | ||
| override val browserId: String, | ||
| val activity: OSIABWebViewActivity | ||
| override val browserId: String | ||
| ) : OSIABEvents() | ||
|
|
||
| companion object { | ||
| const val EXTRA_BROWSER_ID = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EXTRA_BROWSER_ID" | ||
| const val ACTION_IAB_EVENT = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.ACTION_IAB_EVENT" | ||
| const val ACTION_CLOSE_WEBVIEW = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.ACTION_CLOSE_WEBVIEW" | ||
| const val EXTRA_EVENT_DATA = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EXTRA_EVENT_DATA" | ||
|
|
||
| private val _events = MutableSharedFlow<OSIABEvents>() | ||
| // Buffer capacity is required because BroadcastReceiver.onReceive() is synchronous. | ||
| // We must use tryEmit() which would drop events without buffer space. | ||
| private val _events = MutableSharedFlow<OSIABEvents>(extraBufferCapacity = 64) | ||
|
ItsChaceD marked this conversation as resolved.
|
||
| val events = _events.asSharedFlow() | ||
|
|
||
| private var receiver: BroadcastReceiver? = null | ||
| private var receiverRefCount = 0 | ||
|
|
||
| /** | ||
| * Registers a BroadcastReceiver to listen for events from the isolated WebView process. | ||
| * This must be called before opening a WebView on Android 9+ to ensure events are received. | ||
| */ | ||
| @Synchronized | ||
| fun registerReceiver(context: Context) { | ||
|
ItsChaceD marked this conversation as resolved.
ItsChaceD marked this conversation as resolved.
|
||
| receiverRefCount++ | ||
| if (receiver != null) return | ||
|
|
||
| val appContext = context.applicationContext | ||
| receiver = object : BroadcastReceiver() { | ||
| override fun onReceive(context: Context?, intent: Intent?) { | ||
| if (intent?.action == ACTION_IAB_EVENT) { | ||
| val event = IntentCompat.getSerializableExtra( | ||
| intent, | ||
| EXTRA_EVENT_DATA, | ||
| OSIABEvents::class.java | ||
| ) | ||
| event?.let { | ||
| _events.tryEmit(it) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| val filter = IntentFilter(ACTION_IAB_EVENT) | ||
| ContextCompat.registerReceiver( | ||
| appContext, | ||
| receiver, | ||
| filter, | ||
| ContextCompat.RECEIVER_NOT_EXPORTED | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Unregisters the BroadcastReceiver. Should be called when the browser is closed. | ||
| * The receiver is only truly unregistered when all registered 'users' have unregistered. | ||
| */ | ||
| @Synchronized | ||
| fun unregisterReceiver(context: Context) { | ||
| if (receiverRefCount > 0) { | ||
| receiverRefCount-- | ||
| } | ||
|
|
||
| if (receiverRefCount == 0) { | ||
| receiver?.let { | ||
| try { | ||
| context.applicationContext.unregisterReceiver(it) | ||
| } catch (e: Exception) { | ||
| // Receiver may not be registered, ignore | ||
| } | ||
| receiver = null | ||
| } | ||
| } | ||
| } | ||
|
|
||
| suspend fun postEvent(event: OSIABEvents) { | ||
| _events.emit(event) | ||
| } | ||
|
|
||
| /** | ||
| * Broadcasts an event from the isolated WebView process to the main process. | ||
| * Only data-only events should be broadcast (BrowserPageLoaded, BrowserFinished, etc.). | ||
| */ | ||
| fun broadcastEvent(context: Context, event: OSIABEvents) { | ||
| val intent = Intent(ACTION_IAB_EVENT).apply { | ||
| setPackage(context.packageName) | ||
| putExtra(EXTRA_EVENT_DATA, event) | ||
| } | ||
| context.sendBroadcast(intent) | ||
| } | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,5 +16,6 @@ data class OSIABWebViewOptions( | |
| @SerializedName("allowZoom") val allowZoom: Boolean = true, | ||
| @SerializedName("hardwareBack") val hardwareBack: Boolean = true, | ||
| @SerializedName("pauseMedia") val pauseMedia: Boolean = true, | ||
| @SerializedName("customUserAgent") val customUserAgent: String? = null | ||
| @SerializedName("customUserAgent") val customUserAgent: String? = null, | ||
| @SerializedName("isIsolated") val isIsolated: Boolean = true | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to confirm here: By default the plugin will set "isIsolated" to true, meaning this will be a breaking change in the plugin as discussed some time ago? The reason why we are using default of true is because there are potential security concerns implicated? Because otherwise if we think there aren't security concerns, we can leave it at false to maintain backwards compatibility. |
||
| ) : OSIABOptions, Serializable | ||
Uh oh!
There was an error while loading. Please reload this page.