diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c9c283..f99899c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,17 +4,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## [Unreleased]
+## [2.1.0]
-### Fixes
-
-- Fire `browserClosed` only when the Custom Tab actually terminates, using `ActivityResultLauncher` instead of inferring closure from `TAB_HIDDEN`. Prevents the event from firing prematurely when the tab is minimized to Picture-in-Picture or the app is backgrounded. This fix applies to Android 14 and higher, on older versions the previous behavior is preserved because the `ActivityResult` callback is not reliably delivered when the tab is dismissed from Picture-in-Picture.
+### Features
-## [2.0.4]
+- Add `successUrlPatterns` WebView option: a list of regex patterns checked against each finished page load, closing the browser natively as soon as one matches, independent of whether the host app's WebView is able to react to the completion event ([RMET-5394](https://outsystemsrd.atlassian.net/browse/RMET-5394)).
### Fixes
- Keep the main app process active while the WebView is in the foreground, by binding it to the WebView's process. Without this, Android can freeze the main process during a browser session, so browser events stop being processed and the app never reacts to the page finishing ([RMET-5394](https://outsystemsrd.atlassian.net/browse/RMET-5394)).
+- Fire `browserClosed` only when the Custom Tab actually terminates, using `ActivityResultLauncher` instead of inferring closure from `TAB_HIDDEN`. Prevents the event from firing prematurely when the tab is minimized to Picture-in-Picture or the app is backgrounded. This fix applies to Android 14 and higher, on older versions the previous behavior is preserved because the `ActivityResult` callback is not reliably delivered when the tab is dismissed from Picture-in-Picture.
## [2.0.3]
diff --git a/pom.xml b/pom.xml
index 41e4cf8..ccf7bf7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,5 +6,5 @@
4.0.0
io.ionic.libs
ioninappbrowser-android
- 2.0.3
+ 2.1.0
diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/models/OSIABWebViewOptions.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/models/OSIABWebViewOptions.kt
index 17a1711..512c84e 100644
--- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/models/OSIABWebViewOptions.kt
+++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/models/OSIABWebViewOptions.kt
@@ -17,5 +17,8 @@ data class OSIABWebViewOptions(
@SerializedName("hardwareBack") val hardwareBack: Boolean = true,
@SerializedName("pauseMedia") val pauseMedia: Boolean = true,
@SerializedName("customUserAgent") val customUserAgent: String? = null,
- @SerializedName("isIsolated") val isIsolated: Boolean = true
+ @SerializedName("isIsolated") val isIsolated: Boolean = true,
+ // RMET-5394: regex patterns checked against each finished page load; a match closes the
+ // browser natively (no dependency on MainActivity's WebView JS being able to react).
+ @SerializedName("successUrlPatterns") val successUrlPatterns: List? = null
) : OSIABOptions, Serializable
diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABWebViewActivity.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABWebViewActivity.kt
index 1c4f683..b6b60e7 100644
--- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABWebViewActivity.kt
+++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABWebViewActivity.kt
@@ -59,6 +59,7 @@ import java.io.IOException
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
+import java.util.regex.PatternSyntaxException
open class OSIABWebViewActivity : AppCompatActivity() {
@@ -548,6 +549,22 @@ open class OSIABWebViewActivity : AppCompatActivity() {
sendWebViewEvent(OSIABEvents.BrowserPageNavigationCompleted(browserId, resolvedUrl))
}
+ // RMET-5394: close natively the moment an app-configured "done" pattern matches,
+ // independent of whether MainActivity's WebView JS is able to react to the event
+ // above - sendWebViewEvent() above is synchronous (see #59), so the broadcast is
+ // already in flight by the time finish() runs below.
+ val matchesSuccessPattern = resolvedUrl != null && options.successUrlPatterns?.any { pattern ->
+ try {
+ Regex(pattern).containsMatchIn(resolvedUrl)
+ } catch (e: PatternSyntaxException) {
+ Log.d(LOG_TAG, "Invalid successUrlPatterns regex '$pattern': ${e.message}")
+ false
+ }
+ } == true
+ if (matchesSuccessPattern) {
+ finish()
+ }
+
if (url?.startsWith(PDF_VIEWER_URL_PREFIX) == true && options.clearCache) {
webView.evaluateJavascript(
"localStorage.clear(); sessionStorage.clear();", null