@capgo/capacitor-inappbrowser 8.15.1 → 8.15.3
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/FileChooserAcceptSupport.java +111 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/FileChooserRequestSupport.java +3 -7
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +16 -55
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package ee.forgr.capacitor_inappbrowser;
|
|
2
|
+
|
|
3
|
+
import android.content.Intent;
|
|
4
|
+
import android.webkit.MimeTypeMap;
|
|
5
|
+
import java.util.LinkedHashSet;
|
|
6
|
+
import java.util.Locale;
|
|
7
|
+
import java.util.function.Predicate;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes WebView file-input accept attributes into Android file-chooser intents.
|
|
11
|
+
*/
|
|
12
|
+
final class FileChooserAcceptSupport {
|
|
13
|
+
|
|
14
|
+
private FileChooserAcceptSupport() {}
|
|
15
|
+
|
|
16
|
+
static String acceptEntryToMimeType(String accept) {
|
|
17
|
+
if (accept == null || accept.isEmpty() || accept.equals("undefined")) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
accept = accept.toLowerCase(Locale.ROOT);
|
|
21
|
+
if (accept.contains("/")) {
|
|
22
|
+
return accept;
|
|
23
|
+
}
|
|
24
|
+
if (!accept.startsWith(".")) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(accept.substring(1).toLowerCase(Locale.ROOT));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static LinkedHashSet<String> normalizeAcceptTypes(String[] acceptTypes) {
|
|
31
|
+
LinkedHashSet<String> mimeTypes = new LinkedHashSet<>();
|
|
32
|
+
if (acceptTypes != null) {
|
|
33
|
+
for (String entry : acceptTypes) {
|
|
34
|
+
if (entry == null) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
for (String part : entry.split(",")) {
|
|
38
|
+
String mime = acceptEntryToMimeType(part.trim());
|
|
39
|
+
if (mime != null) {
|
|
40
|
+
mimeTypes.add(mime);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (mimeTypes.contains("*/*")) {
|
|
46
|
+
mimeTypes.clear();
|
|
47
|
+
}
|
|
48
|
+
return mimeTypes;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static boolean isImageOnlyAcceptTypes(String[] acceptTypes) {
|
|
52
|
+
return matchesAllResolvedAcceptTypes(acceptTypes, (mime) -> mime.startsWith("image/"));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static boolean isMediaOnlyAcceptTypes(String[] acceptTypes) {
|
|
56
|
+
return matchesAllResolvedAcceptTypes(
|
|
57
|
+
acceptTypes,
|
|
58
|
+
(mime) -> mime.startsWith("image/") || mime.startsWith("video/") || mime.startsWith("audio/")
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private static boolean matchesAllResolvedAcceptTypes(String[] acceptTypes, Predicate<String> mimePredicate) {
|
|
63
|
+
boolean foundMatch = false;
|
|
64
|
+
if (acceptTypes == null) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
for (String entry : acceptTypes) {
|
|
68
|
+
if (entry == null) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (String part : entry.split(",")) {
|
|
72
|
+
String token = part.trim();
|
|
73
|
+
if (token.isEmpty() || token.equals("undefined")) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
String mime = acceptEntryToMimeType(token);
|
|
77
|
+
if (mime == null || mime.equals("*/*")) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (!mimePredicate.test(mime)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
foundMatch = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return foundMatch;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
static boolean useGetContentAction(String[] acceptTypes) {
|
|
90
|
+
LinkedHashSet<String> mimeTypes = normalizeAcceptTypes(acceptTypes);
|
|
91
|
+
return isMediaOnlyAcceptTypes(acceptTypes) && mimeTypes.size() == 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static Intent createFileChooserIntent(String[] acceptTypes, boolean multiple) {
|
|
95
|
+
LinkedHashSet<String> mimeTypes = normalizeAcceptTypes(acceptTypes);
|
|
96
|
+
Intent intent = new Intent(useGetContentAction(acceptTypes) ? Intent.ACTION_GET_CONTENT : Intent.ACTION_OPEN_DOCUMENT);
|
|
97
|
+
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
98
|
+
|
|
99
|
+
if (mimeTypes.size() == 1) {
|
|
100
|
+
intent.setType(mimeTypes.iterator().next());
|
|
101
|
+
} else {
|
|
102
|
+
intent.setType("*/*");
|
|
103
|
+
if (!mimeTypes.isEmpty()) {
|
|
104
|
+
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes.toArray(new String[0]));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
|
|
109
|
+
return intent;
|
|
110
|
+
}
|
|
111
|
+
}
|
package/android/src/main/java/ee/forgr/capacitor_inappbrowser/FileChooserRequestSupport.java
CHANGED
|
@@ -11,18 +11,14 @@ final class FileChooserRequestSupport {
|
|
|
11
11
|
|
|
12
12
|
static final class FileChooserRequest {
|
|
13
13
|
|
|
14
|
-
private static long nextId = 0;
|
|
15
|
-
|
|
16
|
-
final long id;
|
|
17
14
|
final ValueCallback<Uri[]> callback;
|
|
18
|
-
final String
|
|
15
|
+
final String[] acceptTypes;
|
|
19
16
|
final boolean multiple;
|
|
20
17
|
Uri tempCameraUri;
|
|
21
18
|
|
|
22
|
-
FileChooserRequest(ValueCallback<Uri[]> callback, String
|
|
23
|
-
this.id = ++nextId;
|
|
19
|
+
FileChooserRequest(ValueCallback<Uri[]> callback, String[] acceptTypes, boolean multiple) {
|
|
24
20
|
this.callback = callback;
|
|
25
|
-
this.
|
|
21
|
+
this.acceptTypes = acceptTypes;
|
|
26
22
|
this.multiple = multiple;
|
|
27
23
|
}
|
|
28
24
|
}
|
|
@@ -2323,21 +2323,14 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
2323
2323
|
ValueCallback<Uri[]> filePathCallback,
|
|
2324
2324
|
FileChooserParams fileChooserParams
|
|
2325
2325
|
) {
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
fileChooserParams.getAcceptTypes() != null &&
|
|
2330
|
-
fileChooserParams.getAcceptTypes().length > 0 &&
|
|
2331
|
-
!TextUtils.isEmpty(fileChooserParams.getAcceptTypes()[0])
|
|
2332
|
-
) {
|
|
2333
|
-
acceptType = fileChooserParams.getAcceptTypes()[0];
|
|
2334
|
-
} else {
|
|
2335
|
-
acceptType = "*/*";
|
|
2326
|
+
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
|
2327
|
+
if (acceptTypes == null || acceptTypes.length == 0) {
|
|
2328
|
+
acceptTypes = new String[] { "*/*" };
|
|
2336
2329
|
}
|
|
2337
2330
|
|
|
2338
2331
|
// DEBUG: Log details about the file chooser request
|
|
2339
2332
|
Log.d("InAppBrowser", "onShowFileChooser called");
|
|
2340
|
-
Log.d("InAppBrowser", "Accept
|
|
2333
|
+
Log.d("InAppBrowser", "Accept types: " + Arrays.toString(acceptTypes));
|
|
2341
2334
|
Log.d("InAppBrowser", "Current URL: " + getUrl());
|
|
2342
2335
|
Log.d("InAppBrowser", "Original URL: " + (webView.getOriginalUrl() != null ? webView.getOriginalUrl() : "null"));
|
|
2343
2336
|
Log.d(
|
|
@@ -2350,7 +2343,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
2350
2343
|
|
|
2351
2344
|
// Check if the file chooser is already open
|
|
2352
2345
|
final boolean isMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
|
|
2353
|
-
beginFileChooserRequest(filePathCallback,
|
|
2346
|
+
beginFileChooserRequest(filePathCallback, acceptTypes, isMultiple);
|
|
2354
2347
|
final FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
|
|
2355
2348
|
|
|
2356
2349
|
// Direct check for capture attribute in URL (fallback method)
|
|
@@ -2368,8 +2361,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
2368
2361
|
isCaptureInUrl = false;
|
|
2369
2362
|
}
|
|
2370
2363
|
|
|
2371
|
-
// For image inputs, try to detect capture attribute using JavaScript
|
|
2372
|
-
|
|
2364
|
+
// For image-only inputs, try to detect capture attribute using JavaScript.
|
|
2365
|
+
// Mixed accept lists (e.g. "image/*,application/pdf") must skip the camera
|
|
2366
|
+
// path entirely — the camera can only produce images.
|
|
2367
|
+
if (FileChooserAcceptSupport.isImageOnlyAcceptTypes(acceptTypes)) {
|
|
2373
2368
|
// Check if HTML content contains capture attribute on file inputs (synchronous check)
|
|
2374
2369
|
webView.evaluateJavascript(
|
|
2375
2370
|
"document.querySelector('input[type=\"file\"][capture]') !== null",
|
|
@@ -4091,11 +4086,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
4091
4086
|
}
|
|
4092
4087
|
}
|
|
4093
4088
|
|
|
4094
|
-
private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String
|
|
4089
|
+
private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String[] acceptTypes, boolean isMultiple) {
|
|
4095
4090
|
if (activeFileChooserRequest != null) {
|
|
4096
4091
|
FileChooserRequestSupport.cancel(activeFileChooserRequest);
|
|
4097
4092
|
}
|
|
4098
|
-
activeFileChooserRequest = new FileChooserRequestSupport.FileChooserRequest(filePathCallback,
|
|
4093
|
+
activeFileChooserRequest = new FileChooserRequestSupport.FileChooserRequest(filePathCallback, acceptTypes, isMultiple);
|
|
4099
4094
|
syncFileChooserPublicFields();
|
|
4100
4095
|
}
|
|
4101
4096
|
|
|
@@ -4126,45 +4121,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
4126
4121
|
if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
|
|
4127
4122
|
return;
|
|
4128
4123
|
}
|
|
4129
|
-
String
|
|
4130
|
-
|
|
4131
|
-
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
4132
|
-
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
4133
|
-
|
|
4134
|
-
// Fix MIME type handling
|
|
4135
|
-
if (acceptType == null || acceptType.isEmpty() || acceptType.equals("undefined")) {
|
|
4136
|
-
acceptType = "*/*";
|
|
4137
|
-
} else {
|
|
4138
|
-
// Handle common web input accept types
|
|
4139
|
-
if (acceptType.equals("image/*")) {
|
|
4140
|
-
// Keep as is - image/*
|
|
4141
|
-
} else if (acceptType.contains("image/")) {
|
|
4142
|
-
// Specific image type requested but keep it general for better compatibility
|
|
4143
|
-
acceptType = "image/*";
|
|
4144
|
-
} else if (acceptType.equals("audio/*") || acceptType.contains("audio/")) {
|
|
4145
|
-
acceptType = "audio/*";
|
|
4146
|
-
} else if (acceptType.equals("video/*") || acceptType.contains("video/")) {
|
|
4147
|
-
acceptType = "video/*";
|
|
4148
|
-
} else if (acceptType.startsWith(".") || acceptType.contains(",")) {
|
|
4149
|
-
// Handle file extensions like ".pdf, .docx" by using a general mime type
|
|
4150
|
-
if (acceptType.contains(".pdf")) {
|
|
4151
|
-
acceptType = "application/pdf";
|
|
4152
|
-
} else if (acceptType.contains(".doc") || acceptType.contains(".docx")) {
|
|
4153
|
-
acceptType = "application/msword";
|
|
4154
|
-
} else if (acceptType.contains(".xls") || acceptType.contains(".xlsx")) {
|
|
4155
|
-
acceptType = "application/vnd.ms-excel";
|
|
4156
|
-
} else if (acceptType.contains(".txt") || acceptType.contains(".text")) {
|
|
4157
|
-
acceptType = "text/plain";
|
|
4158
|
-
} else {
|
|
4159
|
-
// Default for extension lists
|
|
4160
|
-
acceptType = "*/*";
|
|
4161
|
-
}
|
|
4162
|
-
}
|
|
4163
|
-
}
|
|
4124
|
+
java.util.LinkedHashSet<String> mimeTypes = FileChooserAcceptSupport.normalizeAcceptTypes(request.acceptTypes);
|
|
4125
|
+
Intent intent = FileChooserAcceptSupport.createFileChooserIntent(request.acceptTypes, request.multiple);
|
|
4164
4126
|
|
|
4165
|
-
Log.d("InAppBrowser", "File picker using MIME
|
|
4166
|
-
intent.setType(acceptType);
|
|
4167
|
-
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
|
|
4127
|
+
Log.d("InAppBrowser", "File picker using action: " + intent.getAction() + ", MIME types: " + mimeTypes);
|
|
4168
4128
|
|
|
4169
4129
|
try {
|
|
4170
4130
|
if (activity instanceof androidx.activity.ComponentActivity) {
|
|
@@ -4207,8 +4167,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
4207
4167
|
}
|
|
4208
4168
|
} catch (ActivityNotFoundException e) {
|
|
4209
4169
|
// If no app can handle the specific MIME type, try with a more generic one
|
|
4210
|
-
Log.e("InAppBrowser", "No app available for
|
|
4170
|
+
Log.e("InAppBrowser", "No app available for types: " + mimeTypes + ", trying with */*");
|
|
4211
4171
|
intent.setType("*/*");
|
|
4172
|
+
intent.removeExtra(Intent.EXTRA_MIME_TYPES);
|
|
4212
4173
|
try {
|
|
4213
4174
|
if (activity instanceof androidx.activity.ComponentActivity) {
|
|
4214
4175
|
androidx.activity.ComponentActivity componentActivity = (androidx.activity.ComponentActivity) activity;
|
|
@@ -415,7 +415,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
415
415
|
case aware = "AWARE"
|
|
416
416
|
case fakeVisible = "FAKE_VISIBLE"
|
|
417
417
|
}
|
|
418
|
-
private let pluginVersion: String = "8.15.
|
|
418
|
+
private let pluginVersion: String = "8.15.3"
|
|
419
419
|
public let identifier = "CapgoInAppBrowserPlugin"
|
|
420
420
|
public let jsName = "CapgoInAppBrowser"
|
|
421
421
|
public let pluginMethods: [CAPPluginMethod] = [
|