@capgo/capacitor-inappbrowser 8.21.2 → 8.22.0
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/README.md +2 -1
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/CapgoInAppBrowserPlugin.java +1 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/FileInputCaptureChooserSupport.java +150 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +10 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCustomFullscreenSupport.java +4 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +87 -132
- package/dist/docs.json +20 -0
- package/dist/esm/definitions.d.ts +12 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +1 -1
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +148 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Open web content inside your Capacitor app with a full-featured in-app browser:
|
|
|
5
5
|
<a href="https://capgo.app/?ref=plugin_inappbrowser"><img src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-inappbrowser" alt="Capgo - Instant updates for Capacitor" /></a>
|
|
6
6
|
|
|
7
7
|
<div align="center">
|
|
8
|
-
<p><b>Capgo</b>:
|
|
8
|
+
<p><b>Capgo</b>: push fixes to your Capacitor users in minutes, build signed iOS and Android apps without a Mac, and roll back in one click.</p>
|
|
9
9
|
<h2><a href="https://capgo.app/register/?ref=plugin_inappbrowser">➡️ Get started for free</a></h2>
|
|
10
10
|
<p>14-day unlimited free trial. No credit card required</p>
|
|
11
11
|
<p><a href="https://capgo.app/consulting/?ref=plugin_inappbrowser">Missing a feature? We'll build the plugin for you 💪</a></p>
|
|
@@ -1714,6 +1714,7 @@ See the README section "openSecureWindow (OAuth)" for full setup examples.
|
|
|
1714
1714
|
| **`backgroundColor`** | <code><a href="#backgroundcolor">BackgroundColor</a></code> | Background color of the browser | <code>BackgroundColor.BLACK</code> | 0.1.0 |
|
|
1715
1715
|
| **`activeNativeNavigationForWebview`** | <code>boolean</code> | If true, enables native navigation gestures within the webview. - Android: Native back button navigates within webview history - iOS: Enables swipe left/right gestures for back/forward navigation | <code>false (Android), true (iOS - enabled by default)</code> | |
|
|
1716
1716
|
| **`enableReloadGesture`** | <code>boolean</code> | Enable pull-to-refresh (overscroll from top) to reload the current page. - iOS: Uses UIRefreshControl on the WebView scroll view (reload commits on finger release) - Android: Uses SwipeRefreshLayout around the WebView On iOS, this requires overscroll bounce. If `disableOverscroll` is `true`, the reload gesture will not work. | <code>false</code> | 8.10.8 |
|
|
1717
|
+
| **`allowWebViewFullscreen`** | <code>boolean</code> | Allow pages to enter HTML5 fullscreen (e.g. `requestFullscreen()` or a video player's fullscreen button). When `false`, fullscreen requests are rejected and the toolbar stays visible, so the user can always close the browser. Android only. On iOS, element fullscreen is not enabled by the plugin, so this option has no effect. | <code>true</code> | 8.22.0 |
|
|
1717
1718
|
| **`disableGoBackOnNativeApplication`** | <code>boolean</code> | Disable the possibility to go back on native application, useful to force user to stay on the webview, Android only | <code>false</code> | |
|
|
1718
1719
|
| **`isPresentAfterPageLoad`** | <code>boolean</code> | Open url in a new window fullscreen isPresentAfterPageLoad: if true, the browser will be presented after the page is loaded, if false, the browser will be presented immediately. Promise timing: on Android, `openWebView()` resolves with the webview id when the webview is ready to be controlled (immediately for hidden/immediate presentation, after the first page load when `isPresentAfterPageLoad` is `true`). On iOS, the promise resolves with the id as soon as the native webview is created, even if presentation is deferred. | <code>false</code> | 0.1.0 |
|
|
1719
1720
|
| **`preferredContentMode`** | <code><a href="#preferredcontentmode">PreferredContentMode</a></code> | Web content rendering mode for the managed WKWebView. Controls whether pages load with mobile or desktop layout on iPad. Matches Capacitor's `ios.preferredContentMode` behavior. Resolution order when opening a webview: 1. This per-open value 2. Plugin config `plugins.CapgoInAppBrowser.preferredContentMode` 3. Legacy plugin config `plugins.InAppBrowser.preferredContentMode` 4. Capacitor config `ios.preferredContentMode` When unset at all levels, the system default applies (device-recommended). **iOS only** — ignored on Android and Web. | | 8.18.0 |
|
|
@@ -1286,6 +1286,7 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
|
|
|
1286
1286
|
|
|
1287
1287
|
options.setActiveNativeNavigationForWebview(Boolean.TRUE.equals(call.getBoolean("activeNativeNavigationForWebview", false)));
|
|
1288
1288
|
options.setEnableReloadGesture(Boolean.TRUE.equals(call.getBoolean("enableReloadGesture", false)));
|
|
1289
|
+
options.setAllowWebViewFullscreen(!Boolean.FALSE.equals(call.getBoolean("allowWebViewFullscreen", true)));
|
|
1289
1290
|
options.setDisableGoBackOnNativeApplication(Boolean.TRUE.equals(call.getBoolean("disableGoBackOnNativeApplication", false)));
|
|
1290
1291
|
options.setPresentAfterPageLoad(Boolean.TRUE.equals(call.getBoolean("isPresentAfterPageLoad", false)));
|
|
1291
1292
|
options.setPluginCall(call);
|
package/android/src/main/java/ee/forgr/capacitor_inappbrowser/FileInputCaptureChooserSupport.java
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
package ee.forgr.capacitor_inappbrowser;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Decides whether a file chooser request should use the direct camera capture path and
|
|
5
|
+
* builds JavaScript used to detect {@code capture} on file inputs (including shadow DOM).
|
|
6
|
+
*/
|
|
7
|
+
final class FileInputCaptureChooserSupport {
|
|
8
|
+
|
|
9
|
+
private FileInputCaptureChooserSupport() {}
|
|
10
|
+
|
|
11
|
+
static boolean usesCameraCapturePath(boolean imageOnlyAcceptTypes, boolean captureEnabled) {
|
|
12
|
+
return imageOnlyAcceptTypes && captureEnabled;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Document-level click hook installed at document start (and on navigation fallback).
|
|
17
|
+
*/
|
|
18
|
+
static String createFileInputCaptureHookScript() {
|
|
19
|
+
return (
|
|
20
|
+
"""
|
|
21
|
+
(function() {
|
|
22
|
+
if (window.__capgoFileInputCaptureHook) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
window.__capgoFileInputCaptureHook = true;
|
|
26
|
+
window.__capgoLastFileCapture = null;
|
|
27
|
+
""" +
|
|
28
|
+
captureDetectionHelpersJs() +
|
|
29
|
+
"""
|
|
30
|
+
var notifyCapture = function(value) {
|
|
31
|
+
var captureValue = value || 'environment';
|
|
32
|
+
window.__capgoLastFileCapture = captureValue;
|
|
33
|
+
try {
|
|
34
|
+
var bridge = window.AndroidInterface || window.mobileApp;
|
|
35
|
+
if (bridge && bridge.setFileInputCapture) {
|
|
36
|
+
bridge.setFileInputCapture(captureValue);
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {}
|
|
39
|
+
};
|
|
40
|
+
var register = function(doc) {
|
|
41
|
+
if (!doc || doc.__capgoFileInputCaptureRegistered) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
doc.__capgoFileInputCaptureRegistered = true;
|
|
45
|
+
doc.addEventListener('click', function(e) {
|
|
46
|
+
var captureValue = capgoFindCaptureFromClickEvent(e);
|
|
47
|
+
if (captureValue) {
|
|
48
|
+
notifyCapture(captureValue);
|
|
49
|
+
}
|
|
50
|
+
}, true);
|
|
51
|
+
};
|
|
52
|
+
register(document);
|
|
53
|
+
window.addEventListener('load', function(e) {
|
|
54
|
+
var el = e.target;
|
|
55
|
+
try {
|
|
56
|
+
if (el && el.tagName === 'IFRAME' && el.contentDocument) {
|
|
57
|
+
register(el.contentDocument);
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {}
|
|
60
|
+
}, true);
|
|
61
|
+
})();
|
|
62
|
+
"""
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Runs when the native chooser opens, after {@code consumeLastFileInputCaptureValue()}.
|
|
68
|
+
*/
|
|
69
|
+
static String createChooserFallbackCaptureResolutionScript() {
|
|
70
|
+
return (
|
|
71
|
+
"""
|
|
72
|
+
(function() {
|
|
73
|
+
try {
|
|
74
|
+
""" +
|
|
75
|
+
captureDetectionHelpersJs() +
|
|
76
|
+
"""
|
|
77
|
+
var cached = window.__capgoLastFileCapture;
|
|
78
|
+
window.__capgoLastFileCapture = null;
|
|
79
|
+
if (cached) {
|
|
80
|
+
return cached;
|
|
81
|
+
}
|
|
82
|
+
var fromActive = capgoFindCaptureFromActiveElementChain();
|
|
83
|
+
if (fromActive) {
|
|
84
|
+
return fromActive;
|
|
85
|
+
}
|
|
86
|
+
var inputs = document.querySelectorAll('input[type="file"][capture]');
|
|
87
|
+
if (inputs && inputs.length === 1) {
|
|
88
|
+
return inputs[0].getAttribute('capture') || 'environment';
|
|
89
|
+
}
|
|
90
|
+
return 'environment';
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return 'environment';
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
"""
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private static String captureDetectionHelpersJs() {
|
|
100
|
+
return """
|
|
101
|
+
var capgoNormalizeCapture = function(value) {
|
|
102
|
+
return value || 'environment';
|
|
103
|
+
};
|
|
104
|
+
var capgoIsCaptureFileInput = function(node) {
|
|
105
|
+
return node && node.tagName === 'INPUT' && node.type === 'file' && node.hasAttribute('capture');
|
|
106
|
+
};
|
|
107
|
+
var capgoFindCaptureFromClickEvent = function(e) {
|
|
108
|
+
if (!e) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
var path = e.composedPath ? e.composedPath() : null;
|
|
112
|
+
if (path && path.length) {
|
|
113
|
+
for (var i = 0; i < path.length; i++) {
|
|
114
|
+
var node = path[i];
|
|
115
|
+
if (capgoIsCaptureFileInput(node)) {
|
|
116
|
+
return capgoNormalizeCapture(node.getAttribute('capture'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (capgoIsCaptureFileInput(e.target)) {
|
|
121
|
+
return capgoNormalizeCapture(e.target.getAttribute('capture'));
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
};
|
|
125
|
+
var capgoFindCaptureFromActiveElementChain = function() {
|
|
126
|
+
var el = document.activeElement;
|
|
127
|
+
var depth = 0;
|
|
128
|
+
while (el && depth < 32) {
|
|
129
|
+
if (capgoIsCaptureFileInput(el)) {
|
|
130
|
+
return capgoNormalizeCapture(el.getAttribute('capture'));
|
|
131
|
+
}
|
|
132
|
+
if (el.shadowRoot) {
|
|
133
|
+
var inner = el.shadowRoot.activeElement;
|
|
134
|
+
if (inner) {
|
|
135
|
+
el = inner;
|
|
136
|
+
depth++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
var shadowInput = el.shadowRoot.querySelector('input[type="file"][capture]');
|
|
140
|
+
if (shadowInput) {
|
|
141
|
+
return capgoNormalizeCapture(shadowInput.getAttribute('capture'));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
};
|
|
148
|
+
""";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -164,6 +164,7 @@ public class Options {
|
|
|
164
164
|
private boolean disableGoBackOnNativeApplication;
|
|
165
165
|
private boolean activeNativeNavigationForWebview;
|
|
166
166
|
private boolean enableReloadGesture = false;
|
|
167
|
+
private boolean allowWebViewFullscreen = true;
|
|
167
168
|
private boolean isPresentAfterPageLoad;
|
|
168
169
|
private WebViewCallbacks callbacks;
|
|
169
170
|
private PluginCall pluginCall;
|
|
@@ -649,6 +650,14 @@ public class Options {
|
|
|
649
650
|
this.enableReloadGesture = enableReloadGesture;
|
|
650
651
|
}
|
|
651
652
|
|
|
653
|
+
public boolean getAllowWebViewFullscreen() {
|
|
654
|
+
return allowWebViewFullscreen;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
public void setAllowWebViewFullscreen(boolean allowWebViewFullscreen) {
|
|
658
|
+
this.allowWebViewFullscreen = allowWebViewFullscreen;
|
|
659
|
+
}
|
|
660
|
+
|
|
652
661
|
public boolean getDisableGoBackOnNativeApplication() {
|
|
653
662
|
return disableGoBackOnNativeApplication;
|
|
654
663
|
}
|
|
@@ -879,6 +888,7 @@ public class Options {
|
|
|
879
888
|
copy.setDisableGoBackOnNativeApplication(disableGoBackOnNativeApplication);
|
|
880
889
|
copy.setActiveNativeNavigationForWebview(activeNativeNavigationForWebview);
|
|
881
890
|
copy.setEnableReloadGesture(enableReloadGesture);
|
|
891
|
+
copy.setAllowWebViewFullscreen(allowWebViewFullscreen);
|
|
882
892
|
copy.setPresentAfterPageLoad(false);
|
|
883
893
|
copy.setVisibleTitle(VisibleTitle);
|
|
884
894
|
copy.setToolbarColor(ToolbarColor);
|
package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCustomFullscreenSupport.java
CHANGED
|
@@ -22,6 +22,10 @@ final class WebViewCustomFullscreenSupport {
|
|
|
22
22
|
return isActive;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
static boolean shouldRejectShow(boolean allowWebViewFullscreen, boolean isActive) {
|
|
26
|
+
return !allowWebViewFullscreen || shouldRejectDuplicateShow(isActive);
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
static boolean shouldConsumeBackPress(boolean isActive) {
|
|
26
30
|
return isActive;
|
|
27
31
|
}
|
|
@@ -591,6 +591,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
591
591
|
// Temporary URI for storing camera capture
|
|
592
592
|
public Uri tempCameraUri;
|
|
593
593
|
|
|
594
|
+
// Last file input capture attribute reported from any frame (including cross-origin iframes).
|
|
595
|
+
private volatile String lastFileInputCaptureValue;
|
|
596
|
+
|
|
594
597
|
public interface PermissionHandler {
|
|
595
598
|
void handleCameraPermissionRequest(PermissionRequest request);
|
|
596
599
|
|
|
@@ -1061,6 +1064,11 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
1061
1064
|
handleJavaScriptPostMessage(message);
|
|
1062
1065
|
}
|
|
1063
1066
|
|
|
1067
|
+
@JavascriptInterface
|
|
1068
|
+
public void setFileInputCapture(String captureValue) {
|
|
1069
|
+
storeLastFileInputCaptureValue(captureValue == null || captureValue.isEmpty() ? "environment" : captureValue);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1064
1072
|
@JavascriptInterface
|
|
1065
1073
|
public void close() {
|
|
1066
1074
|
try {
|
|
@@ -2505,6 +2513,8 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2505
2513
|
|
|
2506
2514
|
_webView.setWebChromeClient(
|
|
2507
2515
|
new WebChromeClient() {
|
|
2516
|
+
private Bitmap transparentVideoPoster;
|
|
2517
|
+
|
|
2508
2518
|
@Override
|
|
2509
2519
|
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
|
|
2510
2520
|
if (consoleMessage != null && _options != null && _options.getCaptureConsoleLogs() && _options.getCallbacks() != null) {
|
|
@@ -2521,6 +2531,15 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2521
2531
|
return super.onConsoleMessage(consoleMessage);
|
|
2522
2532
|
}
|
|
2523
2533
|
|
|
2534
|
+
// Android WebView otherwise paints a stretched grey play icon on <video> without poster or frames; Chrome paints nothing.
|
|
2535
|
+
@Override
|
|
2536
|
+
public Bitmap getDefaultVideoPoster() {
|
|
2537
|
+
if (transparentVideoPoster == null) {
|
|
2538
|
+
transparentVideoPoster = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
|
|
2539
|
+
}
|
|
2540
|
+
return transparentVideoPoster;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2524
2543
|
// Enable HTML5/iframe fullscreen (e.g. embedded YouTube player fullscreen button).
|
|
2525
2544
|
@Override
|
|
2526
2545
|
public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
|
|
@@ -2562,140 +2581,36 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2562
2581
|
beginFileChooserRequest(filePathCallback, acceptTypes, isMultiple);
|
|
2563
2582
|
final FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
|
|
2564
2583
|
|
|
2565
|
-
|
|
2566
|
-
boolean
|
|
2567
|
-
String
|
|
2568
|
-
String currentUrl = getUrl();
|
|
2584
|
+
final boolean imageOnlyAcceptTypes = FileChooserAcceptSupport.isImageOnlyAcceptTypes(acceptTypes);
|
|
2585
|
+
final boolean captureEnabled = fileChooserParams.isCaptureEnabled();
|
|
2586
|
+
final String nativeCaptureForRequest = consumeLastFileInputCaptureValue();
|
|
2569
2587
|
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
Log.d("InAppBrowser", "Found capture in URL: " + captureMode);
|
|
2575
|
-
} else {
|
|
2576
|
-
captureMode = null;
|
|
2577
|
-
isCaptureInUrl = false;
|
|
2588
|
+
if (!FileInputCaptureChooserSupport.usesCameraCapturePath(imageOnlyAcceptTypes, captureEnabled)) {
|
|
2589
|
+
clearFileInputCapturePageState(webView);
|
|
2590
|
+
openFileChooser(request);
|
|
2591
|
+
return true;
|
|
2578
2592
|
}
|
|
2579
2593
|
|
|
2580
|
-
// For image-only inputs
|
|
2581
|
-
// Mixed accept lists (e.g. "image/*,application/pdf")
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
Log.d("InAppBrowser", "Quick capture check: " + hasCaptureValue);
|
|
2589
|
-
if (Boolean.parseBoolean(hasCaptureValue.replace("\"", ""))) {
|
|
2590
|
-
Log.d("InAppBrowser", "Found capture attribute in quick check");
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
);
|
|
2594
|
-
|
|
2595
|
-
// Fixed JavaScript with proper error handling
|
|
2596
|
-
String js = """
|
|
2597
|
-
(function() {
|
|
2598
|
-
try {
|
|
2599
|
-
var captureAttr = null;
|
|
2600
|
-
// Check active element first
|
|
2601
|
-
if (document.activeElement &&
|
|
2602
|
-
document.activeElement.tagName === 'INPUT' &&
|
|
2603
|
-
document.activeElement.type === 'file') {
|
|
2604
|
-
if (document.activeElement.hasAttribute('capture')) {
|
|
2605
|
-
captureAttr = document.activeElement.getAttribute('capture') || 'environment';
|
|
2606
|
-
return captureAttr;
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
// Try to find any input with capture attribute
|
|
2610
|
-
var inputs = document.querySelectorAll('input[type="file"][capture]');
|
|
2611
|
-
if (inputs && inputs.length > 0) {
|
|
2612
|
-
captureAttr = inputs[0].getAttribute('capture') || 'environment';
|
|
2613
|
-
return captureAttr;
|
|
2614
|
-
}
|
|
2615
|
-
// Try to extract from HTML attributes
|
|
2616
|
-
var allInputs = document.getElementsByTagName('input');
|
|
2617
|
-
for (var i = 0; i < allInputs.length; i++) {
|
|
2618
|
-
var input = allInputs[i];
|
|
2619
|
-
if (input.type === 'file') {
|
|
2620
|
-
if (input.hasAttribute('capture')) {
|
|
2621
|
-
captureAttr = input.getAttribute('capture') || 'environment';
|
|
2622
|
-
return captureAttr;
|
|
2623
|
-
}
|
|
2624
|
-
// Look for the accept attribute containing image/* as this might be a camera input
|
|
2625
|
-
var acceptAttr = input.getAttribute('accept');
|
|
2626
|
-
if (acceptAttr && acceptAttr.indexOf('image/*') >= 0) {
|
|
2627
|
-
console.log('Found input with image/* accept');
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
return '';
|
|
2632
|
-
} catch(e) {
|
|
2633
|
-
console.error('Capture detection error:', e);
|
|
2634
|
-
return '';
|
|
2635
|
-
}
|
|
2636
|
-
})();
|
|
2637
|
-
""";
|
|
2638
|
-
|
|
2639
|
-
webView.evaluateJavascript(js, (value) -> {
|
|
2640
|
-
if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
|
|
2641
|
-
return;
|
|
2642
|
-
}
|
|
2643
|
-
|
|
2644
|
-
Log.d("InAppBrowser", "Capture attribute JS result: " + value);
|
|
2645
|
-
|
|
2646
|
-
// If we already found capture in URL, use that directly
|
|
2647
|
-
if (isCaptureInUrl) {
|
|
2648
|
-
Log.d("InAppBrowser", "Using capture from URL: " + captureMode);
|
|
2649
|
-
launchCamera(captureMode.equals("user"), request);
|
|
2650
|
-
return;
|
|
2651
|
-
}
|
|
2652
|
-
|
|
2653
|
-
// Process JavaScript result
|
|
2654
|
-
if (value != null && value.length() > 2) {
|
|
2655
|
-
// Clean up the value (remove quotes)
|
|
2656
|
-
String captureValue = value.replace("\"", "");
|
|
2657
|
-
Log.d("InAppBrowser", "Found capture attribute: " + captureValue);
|
|
2658
|
-
|
|
2659
|
-
if (!captureValue.isEmpty()) {
|
|
2660
|
-
activity.runOnUiThread(() -> launchCamera(captureValue.equals("user"), request));
|
|
2661
|
-
return;
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
|
|
2665
|
-
// Look for hints in the web page source
|
|
2666
|
-
Log.d("InAppBrowser", "Looking for camera hints in page content");
|
|
2667
|
-
webView.evaluateJavascript("(function() { return document.documentElement.innerHTML; })()", (htmlSource) -> {
|
|
2668
|
-
if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
|
|
2669
|
-
return;
|
|
2670
|
-
}
|
|
2671
|
-
|
|
2672
|
-
if (htmlSource != null && htmlSource.length() > 10) {
|
|
2673
|
-
boolean hasCameraOrSelfieKeyword =
|
|
2674
|
-
htmlSource.contains("capture=") || htmlSource.contains("camera") || htmlSource.contains("selfie");
|
|
2675
|
-
|
|
2676
|
-
Log.d("InAppBrowser", "Page contains camera keywords: " + hasCameraOrSelfieKeyword);
|
|
2677
|
-
|
|
2678
|
-
if (
|
|
2679
|
-
hasCameraOrSelfieKeyword &&
|
|
2680
|
-
currentUrl != null &&
|
|
2681
|
-
(currentUrl.contains("selfie") || currentUrl.contains("camera") || currentUrl.contains("photo"))
|
|
2682
|
-
) {
|
|
2683
|
-
Log.d("InAppBrowser", "URL suggests camera usage, launching camera");
|
|
2684
|
-
activity.runOnUiThread(() -> launchCamera(currentUrl.contains("selfie"), request));
|
|
2685
|
-
return;
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
|
|
2689
|
-
// If all detection methods fail, fall back to regular file picker
|
|
2690
|
-
Log.d("InAppBrowser", "No capture attribute detected, using file picker");
|
|
2691
|
-
openFileChooser(request);
|
|
2692
|
-
});
|
|
2693
|
-
});
|
|
2594
|
+
// For image-only inputs with capture enabled, launch the camera when possible.
|
|
2595
|
+
// Mixed accept lists (e.g. "image/*,application/pdf") use the regular picker above.
|
|
2596
|
+
if (nativeCaptureForRequest != null && !nativeCaptureForRequest.isEmpty()) {
|
|
2597
|
+
if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
|
|
2598
|
+
return true;
|
|
2599
|
+
}
|
|
2600
|
+
boolean useFrontCameraFromNative = nativeCaptureForRequest.equalsIgnoreCase("user");
|
|
2601
|
+
activity.runOnUiThread(() -> launchCamera(useFrontCameraFromNative, request));
|
|
2694
2602
|
return true;
|
|
2695
2603
|
}
|
|
2696
2604
|
|
|
2697
|
-
|
|
2698
|
-
|
|
2605
|
+
String js = FileInputCaptureChooserSupport.createChooserFallbackCaptureResolutionScript();
|
|
2606
|
+
|
|
2607
|
+
webView.evaluateJavascript(js, (value) -> {
|
|
2608
|
+
if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
boolean useFrontCamera = value != null && value.replace("\"", "").equalsIgnoreCase("user");
|
|
2612
|
+
activity.runOnUiThread(() -> launchCamera(useFrontCamera, request));
|
|
2613
|
+
});
|
|
2699
2614
|
return true;
|
|
2700
2615
|
}
|
|
2701
2616
|
|
|
@@ -3284,7 +3199,8 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3284
3199
|
|
|
3285
3200
|
// Shows a native fullscreen view requested via the HTML5 Fullscreen API (e.g. embedded YouTube).
|
|
3286
3201
|
private void showCustomFullscreenView(View view, WebChromeClient.CustomViewCallback callback) {
|
|
3287
|
-
|
|
3202
|
+
boolean allowWebViewFullscreen = _options == null || _options.getAllowWebViewFullscreen();
|
|
3203
|
+
if (WebViewCustomFullscreenSupport.shouldRejectShow(allowWebViewFullscreen, customFullscreenView != null)) {
|
|
3288
3204
|
callback.onCustomViewHidden();
|
|
3289
3205
|
return;
|
|
3290
3206
|
}
|
|
@@ -3571,6 +3487,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3571
3487
|
configureBlankToolbarLayout();
|
|
3572
3488
|
}
|
|
3573
3489
|
|
|
3490
|
+
if (toolbarView != null && toolbarView.getParent() instanceof com.google.android.material.appbar.AppBarLayout appBarLayout) {
|
|
3491
|
+
removeAppBarShadow(appBarLayout);
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3574
3494
|
// Special handling for Android 15+
|
|
3575
3495
|
if (isAndroid15Plus && !isBlankToolbar) {
|
|
3576
3496
|
refreshEdgeToEdgeChrome();
|
|
@@ -3828,6 +3748,13 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3828
3748
|
statusBarColorView.requestLayout();
|
|
3829
3749
|
}
|
|
3830
3750
|
|
|
3751
|
+
// AppBarLayout re-applies its elevation through the StateListAnimator, so clearing the elevation alone is not enough.
|
|
3752
|
+
private static void removeAppBarShadow(com.google.android.material.appbar.AppBarLayout appBarLayout) {
|
|
3753
|
+
appBarLayout.setElevation(0);
|
|
3754
|
+
appBarLayout.setStateListAnimator(null);
|
|
3755
|
+
appBarLayout.setOutlineProvider(null);
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3831
3758
|
private void refreshEdgeToEdgeChrome() {
|
|
3832
3759
|
if (_webView == null || _options == null) {
|
|
3833
3760
|
return;
|
|
@@ -3840,9 +3767,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3840
3767
|
return;
|
|
3841
3768
|
}
|
|
3842
3769
|
|
|
3843
|
-
appBarLayout
|
|
3844
|
-
appBarLayout.setStateListAnimator(null);
|
|
3845
|
-
appBarLayout.setOutlineProvider(null);
|
|
3770
|
+
removeAppBarShadow(appBarLayout);
|
|
3846
3771
|
|
|
3847
3772
|
final int finalBgColor = resolveWindowBackgroundColor();
|
|
3848
3773
|
_webView.post(() -> {
|
|
@@ -4392,6 +4317,23 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
4392
4317
|
_webView.post(() -> callback.onError(message));
|
|
4393
4318
|
}
|
|
4394
4319
|
|
|
4320
|
+
private synchronized void storeLastFileInputCaptureValue(String captureValue) {
|
|
4321
|
+
lastFileInputCaptureValue = captureValue;
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
private synchronized String consumeLastFileInputCaptureValue() {
|
|
4325
|
+
String value = lastFileInputCaptureValue;
|
|
4326
|
+
lastFileInputCaptureValue = null;
|
|
4327
|
+
return value;
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
private void clearFileInputCapturePageState(WebView webView) {
|
|
4331
|
+
if (webView == null) {
|
|
4332
|
+
return;
|
|
4333
|
+
}
|
|
4334
|
+
webView.evaluateJavascript("(function(){ window.__capgoLastFileCapture = null; })();", null);
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4395
4337
|
private void injectJavaScriptInterface() {
|
|
4396
4338
|
if (_webView == null) {
|
|
4397
4339
|
Log.w("InAppBrowser", "Cannot inject JavaScript interface - WebView is null");
|
|
@@ -4405,6 +4347,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
4405
4347
|
if (_webView != null) {
|
|
4406
4348
|
try {
|
|
4407
4349
|
_webView.evaluateJavascript(script, null);
|
|
4350
|
+
injectFileInputCaptureHookScript();
|
|
4408
4351
|
injectBlankTargetInCurrentWebViewScript();
|
|
4409
4352
|
} catch (Exception e) {
|
|
4410
4353
|
Log.e("InAppBrowser", "Error injecting JavaScript interface: " + e.getMessage());
|
|
@@ -4416,6 +4359,17 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
4416
4359
|
}
|
|
4417
4360
|
}
|
|
4418
4361
|
|
|
4362
|
+
private void injectFileInputCaptureHookScript() {
|
|
4363
|
+
if (_webView == null) {
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
4366
|
+
_webView.evaluateJavascript(createFileInputCaptureHookScript(), null);
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
private String createFileInputCaptureHookScript() {
|
|
4370
|
+
return FileInputCaptureChooserSupport.createFileInputCaptureHookScript();
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4419
4373
|
private void injectDocumentStartJavaScriptInterface() {
|
|
4420
4374
|
if (_webView == null) {
|
|
4421
4375
|
Log.w("InAppBrowser", "Cannot inject document-start JavaScript interface - WebView is null");
|
|
@@ -4428,6 +4382,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
4428
4382
|
|
|
4429
4383
|
try {
|
|
4430
4384
|
injectDocumentStartPostMessageBridge();
|
|
4385
|
+
WebViewCompat.addDocumentStartJavaScript(_webView, createFileInputCaptureHookScript(), Collections.singleton("*"));
|
|
4431
4386
|
WebViewCompat.addDocumentStartJavaScript(_webView, createMobileAppBridgeScript(), Collections.singleton("*"));
|
|
4432
4387
|
// Honor preShowScriptInjectionTime: "documentStart" (matches iOS WKUserScript
|
|
4433
4388
|
// .atDocumentStart). Runs before page scripts and persists across navigations,
|
package/dist/docs.json
CHANGED
|
@@ -1958,6 +1958,26 @@
|
|
|
1958
1958
|
"complexTypes": [],
|
|
1959
1959
|
"type": "boolean | undefined"
|
|
1960
1960
|
},
|
|
1961
|
+
{
|
|
1962
|
+
"name": "allowWebViewFullscreen",
|
|
1963
|
+
"tags": [
|
|
1964
|
+
{
|
|
1965
|
+
"text": "8.22.0",
|
|
1966
|
+
"name": "since"
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
"text": "true",
|
|
1970
|
+
"name": "default"
|
|
1971
|
+
},
|
|
1972
|
+
{
|
|
1973
|
+
"text": "allowWebViewFullscreen: false",
|
|
1974
|
+
"name": "example"
|
|
1975
|
+
}
|
|
1976
|
+
],
|
|
1977
|
+
"docs": "Allow pages to enter HTML5 fullscreen (e.g. `requestFullscreen()` or a video player's fullscreen button).\nWhen `false`, fullscreen requests are rejected and the toolbar stays visible, so the user can always close the browser.\n\nAndroid only. On iOS, element fullscreen is not enabled by the plugin, so this option has no effect.",
|
|
1978
|
+
"complexTypes": [],
|
|
1979
|
+
"type": "boolean | undefined"
|
|
1980
|
+
},
|
|
1961
1981
|
{
|
|
1962
1982
|
"name": "disableGoBackOnNativeApplication",
|
|
1963
1983
|
"tags": [
|
|
@@ -996,6 +996,18 @@ export interface OpenWebViewOptions {
|
|
|
996
996
|
* enableReloadGesture: true
|
|
997
997
|
*/
|
|
998
998
|
enableReloadGesture?: boolean;
|
|
999
|
+
/**
|
|
1000
|
+
* Allow pages to enter HTML5 fullscreen (e.g. `requestFullscreen()` or a video player's fullscreen button).
|
|
1001
|
+
* When `false`, fullscreen requests are rejected and the toolbar stays visible, so the user can always close the browser.
|
|
1002
|
+
*
|
|
1003
|
+
* Android only. On iOS, element fullscreen is not enabled by the plugin, so this option has no effect.
|
|
1004
|
+
*
|
|
1005
|
+
* @since 8.22.0
|
|
1006
|
+
* @default true
|
|
1007
|
+
* @example
|
|
1008
|
+
* allowWebViewFullscreen: false
|
|
1009
|
+
*/
|
|
1010
|
+
allowWebViewFullscreen?: boolean;
|
|
999
1011
|
/**
|
|
1000
1012
|
* Disable the possibility to go back on native application,
|
|
1001
1013
|
* useful to force user to stay on the webview, Android only
|