@capgo/capacitor-inappbrowser 8.13.3 → 8.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/SafeAreaInsetsSupport.java +16 -5
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +9 -3
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +45 -4
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +18 -5
- package/package.json +1 -1
|
@@ -97,18 +97,19 @@ final class SafeAreaInsetsSupport {
|
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* Bottom padding to apply to the WebView container (a SwipeRefreshLayout, which honours its own
|
|
100
|
-
* padding but ignores child margins).
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* regardless of the
|
|
100
|
+
* padding but ignores child margins). Takes the larger of the safe bottom inset and the keyboard
|
|
101
|
+
* (IME) inset via {@link #resolveBottomMargin}, then adds a compensation term: on Android 15+ the
|
|
102
|
+
* AppBarLayout is pushed down by the status-bar height, which shifts the container's bottom edge
|
|
103
|
+
* off-screen by that same amount, so it must be added back as padding regardless of the options.
|
|
104
104
|
*/
|
|
105
105
|
static int resolveContainerBottomPadding(
|
|
106
106
|
boolean enabledSafeBottomMargin,
|
|
107
107
|
int safeBottomInset,
|
|
108
|
+
int imeBottom,
|
|
108
109
|
boolean appBarHandlesTopInset,
|
|
109
110
|
int statusBarTop
|
|
110
111
|
) {
|
|
111
|
-
int base = enabledSafeBottomMargin
|
|
112
|
+
int base = resolveBottomMargin(enabledSafeBottomMargin, safeBottomInset, imeBottom);
|
|
112
113
|
int appBarCompensation = appBarHandlesTopInset ? Math.max(0, statusBarTop) : 0;
|
|
113
114
|
return base + appBarCompensation;
|
|
114
115
|
}
|
|
@@ -130,4 +131,14 @@ final class SafeAreaInsetsSupport {
|
|
|
130
131
|
|
|
131
132
|
return Math.max(0, statusBarTop);
|
|
132
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Whether the WebView container must be inset for the bottom system bar. On Android 15+ the dialog
|
|
137
|
+
* is forced edge-to-edge (decorFitsSystemWindows=false), so the window always draws under the
|
|
138
|
+
* navigation bar and insetting is mandatory to keep bottom content on-screen, regardless of the
|
|
139
|
+
* enabledSafeBottomMargin opt-in that older versions honour.
|
|
140
|
+
*/
|
|
141
|
+
static boolean shouldInsetBottomForContainer(boolean enabledSafeBottomMargin, boolean isEdgeToEdge) {
|
|
142
|
+
return enabledSafeBottomMargin || isEdgeToEdge;
|
|
143
|
+
}
|
|
133
144
|
}
|
|
@@ -3227,6 +3227,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
3227
3227
|
Insets systemGestures = windowInsets != null ? windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures()) : Insets.NONE;
|
|
3228
3228
|
Insets mandatoryGestures =
|
|
3229
3229
|
windowInsets != null ? windowInsets.getInsets(WindowInsetsCompat.Type.mandatorySystemGestures()) : Insets.NONE;
|
|
3230
|
+
Insets ime = windowInsets != null ? windowInsets.getInsets(WindowInsetsCompat.Type.ime()) : Insets.NONE;
|
|
3231
|
+
boolean keyboardVisible = windowInsets != null && windowInsets.isVisible(WindowInsetsCompat.Type.ime());
|
|
3230
3232
|
|
|
3231
3233
|
boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
|
|
3232
3234
|
View toolbarView = findViewById(R.id.tool_bar);
|
|
@@ -3237,8 +3239,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
3237
3239
|
toolbarView.getVisibility() == View.VISIBLE &&
|
|
3238
3240
|
toolbarView.getParent() instanceof com.google.android.material.appbar.AppBarLayout;
|
|
3239
3241
|
|
|
3242
|
+
boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(_options.getEnabledSafeMargin(), isAndroid15Plus);
|
|
3243
|
+
|
|
3240
3244
|
int statusBarTop = bars.top > 0 ? bars.top : getSystemStatusBarHeight();
|
|
3241
|
-
int fallbackBottomInset =
|
|
3245
|
+
int fallbackBottomInset = applyBottomInset ? getSystemNavigationBarHeight() : 0;
|
|
3242
3246
|
int safeBottomInset = SafeAreaInsetsSupport.resolveSafeBottomInsetWithFallback(
|
|
3243
3247
|
bars.bottom,
|
|
3244
3248
|
navigationBars.bottom,
|
|
@@ -3249,7 +3253,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
3249
3253
|
navigationBars.left,
|
|
3250
3254
|
navigationBars.right,
|
|
3251
3255
|
fallbackBottomInset,
|
|
3252
|
-
|
|
3256
|
+
applyBottomInset
|
|
3253
3257
|
);
|
|
3254
3258
|
|
|
3255
3259
|
int padTop = SafeAreaInsetsSupport.resolveContainerTopPadding(
|
|
@@ -3258,9 +3262,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
3258
3262
|
statusBarTop,
|
|
3259
3263
|
appBarHandlesTopInset
|
|
3260
3264
|
);
|
|
3265
|
+
int imeBottom = SafeAreaInsetsSupport.resolveImeBottomInset(keyboardVisible, ime.bottom, isAndroid15Plus);
|
|
3261
3266
|
int padBottom = SafeAreaInsetsSupport.resolveContainerBottomPadding(
|
|
3262
|
-
|
|
3267
|
+
applyBottomInset,
|
|
3263
3268
|
safeBottomInset,
|
|
3269
|
+
imeBottom,
|
|
3264
3270
|
appBarHandlesTopInset,
|
|
3265
3271
|
statusBarTop
|
|
3266
3272
|
);
|
|
@@ -110,6 +110,42 @@ enum CustomSchemeOpenSupport {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
enum SecureWindowRedirectSupport {
|
|
114
|
+
/// The auth session reports any navigation on the callback scheme, so the redirect is
|
|
115
|
+
/// identified component-wise. Query items configured in the redirect URI are matched,
|
|
116
|
+
/// while any extra items (e.g. provider code, state) are ignored, unless they reuse a
|
|
117
|
+
/// configured name: parsers disagree over which duplicate takes precedence.
|
|
118
|
+
static func matches(_ callbackURL: URL, redirectUri: String) -> Bool {
|
|
119
|
+
guard let expected = URLComponents(string: redirectUri),
|
|
120
|
+
let received = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false),
|
|
121
|
+
received.scheme?.lowercased() == expected.scheme?.lowercased(),
|
|
122
|
+
received.percentEncodedUser == expected.percentEncodedUser,
|
|
123
|
+
received.percentEncodedPassword == expected.percentEncodedPassword,
|
|
124
|
+
received.percentEncodedHost?.lowercased() == expected.percentEncodedHost?.lowercased(),
|
|
125
|
+
received.port == expected.port,
|
|
126
|
+
normalizedPath(received.percentEncodedPath) == normalizedPath(expected.percentEncodedPath) else {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let expectedItems = expected.percentEncodedQueryItems ?? []
|
|
131
|
+
let receivedItems = received.percentEncodedQueryItems ?? []
|
|
132
|
+
let configuredDecodedNames = Set((expected.queryItems ?? []).map(\.name))
|
|
133
|
+
let configuredReceivedItemCount = (received.queryItems ?? []).filter {
|
|
134
|
+
configuredDecodedNames.contains($0.name)
|
|
135
|
+
}.count
|
|
136
|
+
guard configuredReceivedItemCount == expectedItems.count else {
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
return Set(expectedItems.map(\.name)).allSatisfy { name in
|
|
140
|
+
receivedItems.filter { $0.name == name } == expectedItems.filter { $0.name == name }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private static func normalizedPath(_ path: String) -> String {
|
|
145
|
+
path == "/" ? "" : path
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
113
149
|
protocol ProxyRequestLocating {
|
|
114
150
|
func hasPendingProxyRequest(_ requestId: String) -> Bool
|
|
115
151
|
}
|
|
@@ -376,7 +412,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
376
412
|
case aware = "AWARE"
|
|
377
413
|
case fakeVisible = "FAKE_VISIBLE"
|
|
378
414
|
}
|
|
379
|
-
private let pluginVersion: String = "8.13.
|
|
415
|
+
private let pluginVersion: String = "8.13.4"
|
|
380
416
|
public let identifier = "CapgoInAppBrowserPlugin"
|
|
381
417
|
public let jsName = "CapgoInAppBrowser"
|
|
382
418
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -1268,7 +1304,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1268
1304
|
return
|
|
1269
1305
|
}
|
|
1270
1306
|
let titleFontFamily = call.getString("titleFontFamily")
|
|
1271
|
-
let isInspectable = call.getBool("isInspectable", false)
|
|
1307
|
+
let isInspectable = call.getBool("isInspectable", self.bridge?.config.isWebDebuggable ?? false)
|
|
1272
1308
|
let preventDeeplink = call.getBool("preventDeeplink", false)
|
|
1273
1309
|
let openBlankTargetInWebView = call.getBool("openBlankTargetInWebView", false)
|
|
1274
1310
|
let isAnimated = call.getBool("isAnimated", true)
|
|
@@ -2637,6 +2673,11 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2637
2673
|
return
|
|
2638
2674
|
}
|
|
2639
2675
|
|
|
2676
|
+
guard let callbackURLScheme = URL(string: redirectUri)?.scheme else {
|
|
2677
|
+
call.reject("Invalid Redirect URI")
|
|
2678
|
+
return
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2640
2681
|
// Store the call for later resolution
|
|
2641
2682
|
self.openSecureWindowCall = call
|
|
2642
2683
|
|
|
@@ -2644,7 +2685,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2644
2685
|
|
|
2645
2686
|
// Open the URL in a secure browser window
|
|
2646
2687
|
DispatchQueue.main.async {
|
|
2647
|
-
let session = ASWebAuthenticationSession(url: url, callbackURLScheme:
|
|
2688
|
+
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURLScheme) {
|
|
2648
2689
|
callbackURL, error in
|
|
2649
2690
|
|
|
2650
2691
|
// Clean up the stored call
|
|
@@ -2661,7 +2702,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2661
2702
|
return
|
|
2662
2703
|
}
|
|
2663
2704
|
|
|
2664
|
-
if !
|
|
2705
|
+
if !SecureWindowRedirectSupport.matches(callbackURL, redirectUri: redirectUri) {
|
|
2665
2706
|
call.reject("Redirect URI does not match, expected " + redirectUri + " but got " + callbackURL.absoluteString)
|
|
2666
2707
|
return
|
|
2667
2708
|
}
|
|
@@ -439,6 +439,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
439
439
|
open var httpMethod: String?
|
|
440
440
|
open var httpBody: String?
|
|
441
441
|
open var capBrowserPlugin: CapgoInAppBrowserPlugin?
|
|
442
|
+
open var isInspectable: Bool = false
|
|
442
443
|
var instanceId: String = ""
|
|
443
444
|
var shareDisclaimer: [String: Any]?
|
|
444
445
|
var shareSubject: String?
|
|
@@ -2032,11 +2033,12 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
2032
2033
|
}
|
|
2033
2034
|
}
|
|
2034
2035
|
|
|
2035
|
-
open func initWebview(isInspectable: Bool =
|
|
2036
|
+
open func initWebview(isInspectable: Bool = false) {
|
|
2036
2037
|
if self.isWebViewInitialized {
|
|
2037
2038
|
return
|
|
2038
2039
|
}
|
|
2039
2040
|
self.isWebViewInitialized = true
|
|
2041
|
+
self.isInspectable = isInspectable
|
|
2040
2042
|
self.startObservingKeyboardViewportChanges()
|
|
2041
2043
|
self.view.backgroundColor = UIColor.white
|
|
2042
2044
|
|
|
@@ -2183,9 +2185,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
2183
2185
|
// }
|
|
2184
2186
|
|
|
2185
2187
|
if #available(iOS 16.4, *) {
|
|
2186
|
-
webView.isInspectable =
|
|
2187
|
-
} else {
|
|
2188
|
-
// Fallback on earlier versions
|
|
2188
|
+
webView.isInspectable = isInspectable
|
|
2189
2189
|
}
|
|
2190
2190
|
|
|
2191
2191
|
// First add the webView to view hierarchy
|
|
@@ -2341,7 +2341,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
2341
2341
|
self.view.backgroundColor = parent.view.backgroundColor
|
|
2342
2342
|
self.title = parent.title ?? request.url?.host ?? "Popup Window"
|
|
2343
2343
|
self.navigationItem.title = self.title
|
|
2344
|
-
self.initWebview()
|
|
2344
|
+
self.initWebview(isInspectable: parent.isInspectable)
|
|
2345
2345
|
return self.capableWebView
|
|
2346
2346
|
}
|
|
2347
2347
|
|
|
@@ -3338,6 +3338,19 @@ extension WKWebViewController: WKUIDelegate {
|
|
|
3338
3338
|
// This allows websites to access location when opened with openWebView
|
|
3339
3339
|
decisionHandler(.grant)
|
|
3340
3340
|
}
|
|
3341
|
+
|
|
3342
|
+
@available(iOS 15.0, *)
|
|
3343
|
+
public func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
|
|
3344
|
+
print("[InAppBrowser] Media capture permission requested for origin: \(origin.host)")
|
|
3345
|
+
|
|
3346
|
+
// Grant media capture permission automatically, matching Capacitor core's
|
|
3347
|
+
// WebViewDelegationHandler. The OS-level camera/microphone permission
|
|
3348
|
+
// (Info.plist usage description + system prompt) still applies — this only
|
|
3349
|
+
// suppresses WebKit's extra per-origin prompt, which is not persisted
|
|
3350
|
+
// across app launches (WebKit bug 220416) and would otherwise re-prompt
|
|
3351
|
+
// users on every cold start.
|
|
3352
|
+
decisionHandler(.grant)
|
|
3353
|
+
}
|
|
3341
3354
|
}
|
|
3342
3355
|
|
|
3343
3356
|
// MARK: - Host Blocking Utilities
|