From 639d406789fb77f60cb355a5a9e791864580249b Mon Sep 17 00:00:00 2001
From: jworreth
Date: Tue, 17 Feb 2026 21:52:36 +0100
Subject: [PATCH 01/69] fix!: replace deprecated `@UIApplicationMain` with
`@main` (#8346)
Co-authored-by: jwo
Co-authored-by: Mark Anderson
---
ios-pods-template/App/App/AppDelegate.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ios-pods-template/App/App/AppDelegate.swift b/ios-pods-template/App/App/AppDelegate.swift
index c3cd83b5c0..0b31ad6866 100644
--- a/ios-pods-template/App/App/AppDelegate.swift
+++ b/ios-pods-template/App/App/AppDelegate.swift
@@ -1,7 +1,7 @@
import UIKit
import Capacitor
-@UIApplicationMain
+@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
From d5c129ff938b4b798d9ce112630cf10b92ad293d Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Wed, 18 Feb 2026 15:13:52 +0100
Subject: [PATCH 02/69] fix(spm-template)!: replace deprecated
@UIApplicationMain with @main (#8349)
---
ios-spm-template/App/App/AppDelegate.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ios-spm-template/App/App/AppDelegate.swift b/ios-spm-template/App/App/AppDelegate.swift
index c3cd83b5c0..0b31ad6866 100644
--- a/ios-spm-template/App/App/AppDelegate.swift
+++ b/ios-spm-template/App/App/AppDelegate.swift
@@ -1,7 +1,7 @@
import UIKit
import Capacitor
-@UIApplicationMain
+@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
From 0594ac541ea07817e0aa298e847097d89dfd6094 Mon Sep 17 00:00:00 2001
From: Terence Honles
Date: Mon, 23 Feb 2026 13:14:03 +0100
Subject: [PATCH 03/69] feat(android-template)!: update Android template to
support AGP 9.0 (#8318)
---
android-template/app/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/android-template/app/build.gradle b/android-template/app/build.gradle
index df6c4d55a1..363eb2f77d 100644
--- a/android-template/app/build.gradle
+++ b/android-template/app/build.gradle
@@ -19,7 +19,7 @@ android {
buildTypes {
release {
minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
From bb5569848d305f6f05a2de69bdc7ce682a469031 Mon Sep 17 00:00:00 2001
From: Pedro Bilro
Date: Tue, 24 Feb 2026 13:00:29 +0000
Subject: [PATCH 04/69] chore(cli)!: Remove unused livereload method and
parameter (#8350)
---
cli/src/util/livereload.ts | 40 ++------------------------------------
1 file changed, 2 insertions(+), 38 deletions(-)
diff --git a/cli/src/util/livereload.ts b/cli/src/util/livereload.ts
index c3b15ee9fa..6aceb166ad 100644
--- a/cli/src/util/livereload.ts
+++ b/cli/src/util/livereload.ts
@@ -112,13 +112,7 @@ class CapLiveReload {
return !all.length ? loopback(family) : all[0];
}
- // TODO remove on next major as it's unused
- async editExtConfigForLiveReload(
- config: Config,
- platformName: string,
- options: RunCommandOptions,
- rootConfigChange = false,
- ): Promise {
+ async editCapConfigForLiveReload(config: Config, platformName: string, options: RunCommandOptions): Promise {
const platformAbsPath =
platformName == config.ios.name
? config.ios.nativeTargetDirAbs
@@ -126,37 +120,7 @@ class CapLiveReload {
? config.android.assetsDirAbs
: null;
if (platformAbsPath == null) throw new Error('Platform not found.');
- const capConfigPath = rootConfigChange
- ? config.app.extConfigFilePath
- : join(platformAbsPath, 'capacitor.config.json');
-
- const configJson = { ...config.app.extConfig };
- this.configJsonToRevertTo.json = JSON.stringify(configJson, null, 2);
- this.configJsonToRevertTo.platformPath = capConfigPath;
- const url = `http://${options.host}:${options.port}`;
- configJson.server = {
- url,
- };
- return configJson;
- }
-
- // TODO remove rootConfigChange param on next major as it's unused
- async editCapConfigForLiveReload(
- config: Config,
- platformName: string,
- options: RunCommandOptions,
- rootConfigChange = false,
- ): Promise {
- const platformAbsPath =
- platformName == config.ios.name
- ? config.ios.nativeTargetDirAbs
- : platformName == config.android.name
- ? config.android.assetsDirAbs
- : null;
- if (platformAbsPath == null) throw new Error('Platform not found.');
- const capConfigPath = rootConfigChange
- ? config.app.extConfigFilePath
- : join(platformAbsPath, 'capacitor.config.json');
+ const capConfigPath = join(platformAbsPath, 'capacitor.config.json');
const configJson = readJSONSync(capConfigPath);
this.configJsonToRevertTo.json = JSON.stringify(configJson, null, 2);
From 07439cf63cc6ecc366eba9a741e62fc252cf6596 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Wed, 25 Feb 2026 20:02:16 +0100
Subject: [PATCH 05/69] refactor(ios)!: remove deprecated
tmpWindow/TmpViewController (#8362)
---
ios/Capacitor/Capacitor.xcodeproj/project.pbxproj | 4 ----
ios/Capacitor/Capacitor/CapacitorBridge.swift | 7 -------
ios/Capacitor/Capacitor/TmpViewController.swift | 8 --------
3 files changed, 19 deletions(-)
delete mode 100644 ios/Capacitor/Capacitor/TmpViewController.swift
diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
index b060d9f7d0..ed784d5bdd 100644
--- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
+++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
@@ -52,7 +52,6 @@
62959B3A2524DA7800A3D7F1 /* CAPLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B082524DA7700A3D7F1 /* CAPLog.swift */; };
62959B3B2524DA7800A3D7F1 /* CAPPluginMethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 62959B092524DA7700A3D7F1 /* CAPPluginMethod.h */; settings = {ATTRIBUTES = (Public, ); }; };
62959B3C2524DA7800A3D7F1 /* CAPBridgeDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B0A2524DA7700A3D7F1 /* CAPBridgeDelegate.swift */; };
- 62959B402524DA7800A3D7F1 /* TmpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B0E2524DA7700A3D7F1 /* TmpViewController.swift */; };
62959B412524DA7800A3D7F1 /* Capacitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 62959B0F2524DA7700A3D7F1 /* Capacitor.h */; settings = {ATTRIBUTES = (Public, ); }; };
62959B422524DA7800A3D7F1 /* DocLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B102524DA7700A3D7F1 /* DocLinks.swift */; };
62959B432524DA7800A3D7F1 /* Data+Capacitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B112524DA7700A3D7F1 /* Data+Capacitor.swift */; };
@@ -204,7 +203,6 @@
62959B082524DA7700A3D7F1 /* CAPLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPLog.swift; sourceTree = ""; };
62959B092524DA7700A3D7F1 /* CAPPluginMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAPPluginMethod.h; sourceTree = ""; };
62959B0A2524DA7700A3D7F1 /* CAPBridgeDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPBridgeDelegate.swift; sourceTree = ""; };
- 62959B0E2524DA7700A3D7F1 /* TmpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TmpViewController.swift; sourceTree = ""; };
62959B0F2524DA7700A3D7F1 /* Capacitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Capacitor.h; sourceTree = ""; };
62959B102524DA7700A3D7F1 /* DocLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocLinks.swift; sourceTree = ""; };
62959B112524DA7700A3D7F1 /* Data+Capacitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Capacitor.swift"; sourceTree = ""; };
@@ -395,7 +393,6 @@
6214934625509C3F006C36F9 /* CAPInstanceConfiguration.swift */,
62959B082524DA7700A3D7F1 /* CAPLog.swift */,
62959AE72524DA7700A3D7F1 /* CAPFile.swift */,
- 62959B0E2524DA7700A3D7F1 /* TmpViewController.swift */,
62959B102524DA7700A3D7F1 /* DocLinks.swift */,
62959B152524DA7700A3D7F1 /* CAPNotifications.swift */,
62959B072524DA7700A3D7F1 /* CapacitorExtension.swift */,
@@ -710,7 +707,6 @@
A7D9312F2B23710300FF59A2 /* JSValueDecoder.swift in Sources */,
62959B362524DA7800A3D7F1 /* CAPBridgeViewController.swift in Sources */,
621ECCB72542045900D3D615 /* CAPBridgedJSTypes.m in Sources */,
- 62959B402524DA7800A3D7F1 /* TmpViewController.swift in Sources */,
621ECCD6254205BD00D3D615 /* CAPBridgeProtocol.swift in Sources */,
62D43AF02581817500673C24 /* WKWebView+Capacitor.swift in Sources */,
62959B432524DA7800A3D7F1 /* Data+Capacitor.swift in Sources */,
diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift
index 7b8b9b99dc..3a186d4e89 100644
--- a/ios/Capacitor/Capacitor/CapacitorBridge.swift
+++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift
@@ -93,10 +93,6 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
}
}
}
- @available(*, deprecated, message: "obsolete")
- var tmpWindow: UIWindow?
- @available(*, deprecated, message: "obsolete")
- static let tmpVCAppeared = Notification(name: Notification.Name(rawValue: "tmpViewControllerAppeared"))
public static let capacitorSite = "https://capacitorjs.com/"
public static let fileStartIdentifier = "/_capacitor_file_"
public static let httpInterceptorStartIdentifier = "/_capacitor_http_interceptor_"
@@ -223,9 +219,6 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
setupCordovaCompatibility()
exportMiscJS()
canInjectJS = false
- observers.append(NotificationCenter.default.addObserver(forName: type(of: self).tmpVCAppeared.name, object: .none, queue: .none) { [weak self] _ in
- self?.tmpWindow = nil
- })
self.setupWebDebugging(configuration: configuration)
}
diff --git a/ios/Capacitor/Capacitor/TmpViewController.swift b/ios/Capacitor/Capacitor/TmpViewController.swift
deleted file mode 100644
index 3511d18aed..0000000000
--- a/ios/Capacitor/Capacitor/TmpViewController.swift
+++ /dev/null
@@ -1,8 +0,0 @@
-import UIKit
-
-internal class TmpViewController: UIViewController {
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- NotificationCenter.default.post(CapacitorBridge.tmpVCAppeared)
- }
-}
From b5e61daecf67047ff76345841c14fb4a919a3db2 Mon Sep 17 00:00:00 2001
From: Pedro Bilro
Date: Tue, 17 Mar 2026 17:46:51 +0000
Subject: [PATCH 06/69] refactor(android)!: Remove deprecated
`PathHandler#getResponseHeaders` (#8374)
---
.../java/com/getcapacitor/WebViewLocalServer.java | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java b/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java
index a39483de91..1304770703 100755
--- a/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java
@@ -128,19 +128,6 @@ public String getReasonPhrase() {
return reasonPhrase;
}
- /**
- * @deprecated This method may return incorrect headers in concurrent range requests.
- *
- * Use {@link #buildDefaultResponseHeaders()} instead, which returns a copy of the map.
- *
- * This method will be removed in a future major version of Capacitor.
- *
- */
- @Deprecated(forRemoval = true) // adjust version as appropriate
- public Map getResponseHeaders() {
- return responseHeaders;
- }
-
public Map buildDefaultResponseHeaders() {
return new HashMap<>(responseHeaders);
}
From 6881a4feb69c8b7ecfa8c37a62e081c44ef6d7e8 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Fri, 13 Mar 2026 09:54:59 +0100
Subject: [PATCH 07/69] chore(ci): close issues after 7 days (#8385)
---
.github/workflows/needs-reply.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/needs-reply.yml b/.github/workflows/needs-reply.yml
index 40d823c739..c4fd4a7444 100644
--- a/.github/workflows/needs-reply.yml
+++ b/.github/workflows/needs-reply.yml
@@ -13,7 +13,7 @@ jobs:
with:
repo-token: ${{ secrets.BOT_TOKEN }}
issue-label: 'needs reply'
- days-before-close: 15
+ days-before-close: 7
close-message: |
It looks like this issue didn't get the information it needed, so I'll close it for now. If I made a mistake, sorry! I am just a bot.
From 0aac1f0aee8083dc5013f0d8e3c180eb02f21141 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Mon, 16 Mar 2026 17:46:06 +0100
Subject: [PATCH 08/69] fix(http): handle URL objects on fetch (#8386)
---
android/capacitor/src/main/assets/native-bridge.js | 4 ++++
core/native-bridge.ts | 3 +++
ios/Capacitor/Capacitor/assets/native-bridge.js | 4 ++++
3 files changed, 11 insertions(+)
diff --git a/android/capacitor/src/main/assets/native-bridge.js b/android/capacitor/src/main/assets/native-bridge.js
index 40daf2a484..f5e7cc4403 100644
--- a/android/capacitor/src/main/assets/native-bridge.js
+++ b/android/capacitor/src/main/assets/native-bridge.js
@@ -505,6 +505,10 @@ var nativeBridge = (function (exports) {
if (typeof resource === 'string') {
return await win.CapacitorWebFetch(createProxyUrl(resource, win), options);
}
+ else if (resource instanceof URL) {
+ const modifiedURL = new URL(createProxyUrl(resource.toString(), win));
+ return await win.CapacitorWebFetch(modifiedURL, options);
+ }
else if (resource instanceof Request) {
const modifiedRequest = new Request(createProxyUrl(resource.url, win), resource);
return await win.CapacitorWebFetch(modifiedRequest, options);
diff --git a/core/native-bridge.ts b/core/native-bridge.ts
index f988399be8..4fc028f379 100644
--- a/core/native-bridge.ts
+++ b/core/native-bridge.ts
@@ -532,6 +532,9 @@ const initBridge = (w: any): void => {
if (typeof resource === 'string') {
return await win.CapacitorWebFetch(createProxyUrl(resource, win), options);
+ } else if (resource instanceof URL) {
+ const modifiedURL = new URL(createProxyUrl(resource.toString(), win));
+ return await win.CapacitorWebFetch(modifiedURL, options);
} else if (resource instanceof Request) {
const modifiedRequest = new Request(createProxyUrl(resource.url, win), resource);
return await win.CapacitorWebFetch(modifiedRequest, options);
diff --git a/ios/Capacitor/Capacitor/assets/native-bridge.js b/ios/Capacitor/Capacitor/assets/native-bridge.js
index 40daf2a484..f5e7cc4403 100644
--- a/ios/Capacitor/Capacitor/assets/native-bridge.js
+++ b/ios/Capacitor/Capacitor/assets/native-bridge.js
@@ -505,6 +505,10 @@ var nativeBridge = (function (exports) {
if (typeof resource === 'string') {
return await win.CapacitorWebFetch(createProxyUrl(resource, win), options);
}
+ else if (resource instanceof URL) {
+ const modifiedURL = new URL(createProxyUrl(resource.toString(), win));
+ return await win.CapacitorWebFetch(modifiedURL, options);
+ }
else if (resource instanceof Request) {
const modifiedRequest = new Request(createProxyUrl(resource.url, win), resource);
return await win.CapacitorWebFetch(modifiedRequest, options);
From d55f4f859bd88fa5617699e874c48c451bcb5e5f Mon Sep 17 00:00:00 2001
From: Joey Pender
Date: Mon, 16 Mar 2026 16:38:42 -0500
Subject: [PATCH 09/69] fix(SystemBars): use native safe area insets on Android
(#8384)
---
.../com/getcapacitor/plugin/SystemBars.java | 96 +++++++++++++------
cli/src/declarations.ts | 2 +-
core/system-bars.md | 2 +-
3 files changed, 67 insertions(+), 33 deletions(-)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
index 3f04adf976..c84923ca8b 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
@@ -7,7 +7,6 @@
import android.os.Build;
import android.util.TypedValue;
import android.view.View;
-import android.view.ViewGroup;
import android.view.Window;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
@@ -36,6 +35,11 @@ public class SystemBars extends Plugin {
static final String INSETS_HANDLING_CSS = "css";
static final String INSETS_HANDLING_DISABLE = "disable";
+ // https://issues.chromium.org/issues/40699457
+ private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140;
+ // https://issues.chromium.org/issues/457682720
+ private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144;
+
static final String viewportMetaJSFunction = """
function capacitorSystemBarsCheckMetaViewport() {
const meta = document.querySelectorAll("meta[name=viewport]");
@@ -94,7 +98,7 @@ private void initSystemBars() {
}
initWindowInsetsListener();
- initSafeAreaInsets();
+ initSafeAreaCSSVariables();
getBridge().executeOnMainThread(() -> {
setStyle(style, "");
@@ -157,7 +161,7 @@ private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
}
- private void initSafeAreaInsets() {
+ private void initSafeAreaCSSVariables() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
View v = (View) this.getBridge().getWebView().getParent();
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v);
@@ -169,41 +173,57 @@ private void initSafeAreaInsets() {
}
private void initWindowInsetsListener() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
- ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
- boolean hasBrokenWebViewVersion = getWebViewMajorVersion() <= 139;
+ ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
+ boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
+
+ Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
+ Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
+ boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
- if (hasViewportCover) {
+ if (shouldPassthroughInsets) {
+ // We need to correct for a possible shown IME
+ v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
Insets safeAreaInsets = calcSafeAreaInsets(insets);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
}
- if (hasBrokenWebViewVersion) {
- if (hasViewportCover && v.hasWindowFocus() && v.isShown()) {
- boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
- if (keyboardVisible) {
- Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
- setViewMargins(v, Insets.of(0, 0, 0, imeInsets.bottom));
- } else {
- setViewMargins(v, Insets.NONE);
- }
-
- return WindowInsetsCompat.CONSUMED;
- }
- }
+ return new WindowInsetsCompat.Builder(insets)
+ .setInsets(
+ WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
+ Insets.of(
+ systemBarsInsets.left,
+ systemBarsInsets.top,
+ systemBarsInsets.right,
+ getBottomInset(systemBarsInsets, keyboardVisible)
+ )
+ )
+ .build();
+ }
- return insets;
- });
- }
- }
+ // We need to correct for a possible shown IME
+ v.setPadding(
+ systemBarsInsets.left,
+ systemBarsInsets.top,
+ systemBarsInsets.right,
+ keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
+ );
+
+ // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
+ // So we have to explicitly set insets to `0`
+ // See: https://issues.chromium.org/issues/461332423
+ WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets)
+ .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
+ .build();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
+ Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
+ }
- private void setViewMargins(View v, Insets insets) {
- ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
- mlp.leftMargin = insets.left;
- mlp.bottomMargin = insets.bottom;
- mlp.rightMargin = insets.right;
- mlp.topMargin = insets.top;
- v.setLayoutParams(mlp);
+ return newInsets;
+ });
}
private void injectSafeAreaCSS(int top, int right, int bottom, int left) {
@@ -305,4 +325,18 @@ private Integer getWebViewMajorVersion() {
return 0;
}
+
+ private int getBottomInset(Insets systemBarsInsets, boolean keyboardVisible) {
+ if (getWebViewMajorVersion() < WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX) {
+ // This is a workaround for webview versions that have a bug
+ // that causes the bottom inset to be incorrect if the IME is visible
+ // See: https://issues.chromium.org/issues/457682720
+
+ if (keyboardVisible) {
+ return 0;
+ }
+ }
+
+ return systemBarsInsets.bottom;
+ }
}
diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts
index 6612eec777..5eacf5e654 100644
--- a/cli/src/declarations.ts
+++ b/cli/src/declarations.ts
@@ -713,7 +713,7 @@ export interface PluginsConfig {
*
* `css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
*
- * `disable` = Disable all inset handling.
+ * `disable` = Disable CSS variables injection.
*
* @default "css"
*/
diff --git a/core/system-bars.md b/core/system-bars.md
index c7654063bc..67275077af 100644
--- a/core/system-bars.md
+++ b/core/system-bars.md
@@ -73,7 +73,7 @@ const setStatusBarAnimation = async () => {
## Configuration
| Prop | Type | Description | Default |
| ------------- | -------------------- | ------------------------------------------------------------------------- | ------------------ |
-| **`insetsHandling`** | string | Specifies how to handle problematic insets on Android. This option is only supported on Android.
`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
`disable` = Disable all inset handling. | css |
+| **`insetsHandling`** | string | Specifies how to handle problematic insets on Android. This option is only supported on Android.
`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
`disable` = Disable CSS variables injection. | css |
| **`style`** | string | The style of the text and icons of the system bars. | DEFAULT |
| **`hidden`** | boolean | Hide the system bars on start. | false |
| **`animation`** | string | The type of status bar animation used when showing or hiding. This option is only supported on iOS. | FADE |
From a6b3e82bb62b4a5352e2c2c9a0cab0d8dd664284 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Tue, 17 Mar 2026 12:30:17 +0100
Subject: [PATCH 10/69] chore: change needs reproduction bot message (#8390)
---
.github/ionic-issue-bot.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ionic-issue-bot.yml b/.github/ionic-issue-bot.yml
index fef86a40f0..7adf8c4573 100644
--- a/.github/ionic-issue-bot.yml
+++ b/.github/ionic-issue-bot.yml
@@ -20,7 +20,7 @@ comment:
message: >
This issue needs more information before it can be addressed.
In particular, the reporter needs to provide a minimal sample app that demonstrates the issue.
- If no sample app is provided within 15 days, the issue will be closed.
+ If no sample app is provided within 7 days, the issue will be closed.
Please see the Contributing Guide for [how to create a Sample App](https://github.com/ionic-team/capacitor/blob/HEAD/CONTRIBUTING.md#creating-a-code-reproduction).
From ef57c773e70af6b94685a2dc0f808db3ed4cda6e Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Wed, 18 Mar 2026 18:53:47 +0100
Subject: [PATCH 11/69] fix(ios): make getArray accesible on Objective-C
plugins (#8392)
---
ios/Capacitor/Capacitor/CAPBridgedJSTypes.h | 1 +
ios/Capacitor/Capacitor/CAPBridgedJSTypes.m | 8 ++++++++
2 files changed, 9 insertions(+)
diff --git a/ios/Capacitor/Capacitor/CAPBridgedJSTypes.h b/ios/Capacitor/Capacitor/CAPBridgedJSTypes.h
index 516c8648b8..67f8a9501d 100644
--- a/ios/Capacitor/Capacitor/CAPBridgedJSTypes.h
+++ b/ios/Capacitor/Capacitor/CAPBridgedJSTypes.h
@@ -10,6 +10,7 @@
- (NSString * _Nullable)getString:(NSString * _Nonnull)key defaultValue:(NSString * _Nullable)defaultValue;
- (NSDate * _Nullable)getDate:(NSString * _Nonnull)key defaultValue:(NSDate * _Nullable)defaultValue;
- (NSDictionary * _Nullable)getObject:(NSString * _Nonnull)key defaultValue:(NSDictionary * _Nullable)defaultValue;
+- (NSArray * _Nullable)getArray:(NSString * _Nonnull)key defaultValue:(NSArray * _Nullable)defaultValue;
- (NSNumber * _Nullable)getNumber:(NSString * _Nonnull)key defaultValue:(NSNumber * _Nullable)defaultValue;
- (BOOL)getBool:(NSString * _Nonnull)key defaultValue:(BOOL)defaultValue;
@end
diff --git a/ios/Capacitor/Capacitor/CAPBridgedJSTypes.m b/ios/Capacitor/Capacitor/CAPBridgedJSTypes.m
index fb6da4586f..c8ec07da7f 100644
--- a/ios/Capacitor/Capacitor/CAPBridgedJSTypes.m
+++ b/ios/Capacitor/Capacitor/CAPBridgedJSTypes.m
@@ -29,6 +29,14 @@ - (NSDictionary * _Nullable)getObject:(NSString * _Nonnull)key defaultValue:(NSD
return defaultValue;
}
+- (NSArray * _Nullable)getArray:(NSString * _Nonnull)key defaultValue:(NSArray * _Nullable)defaultValue; {
+ id value = [[self dictionaryRepresentation] objectForKey:key];
+ if (value != nil && [value isKindOfClass:[NSArray class]]) {
+ return value;
+ }
+ return defaultValue;
+}
+
- (NSNumber * _Nullable)getNumber:(NSString * _Nonnull)key defaultValue:(NSNumber * _Nullable)defaultValue {
id value = [[self dictionaryRepresentation] objectForKey:key];
if (value != nil && [value isKindOfClass:[NSNumber class]]) {
From 9b0f1c8dc21ab7ed19c9c77294128eba6b94b06f Mon Sep 17 00:00:00 2001
From: Mark Anderson
Date: Thu, 19 Mar 2026 08:51:53 -0400
Subject: [PATCH 12/69] feat!: make Cordova support optional (#8328)
Co-authored-by: Steven Sherry
Co-authored-by: jcesarmobile
Co-authored-by: Joseph Pender
---
.../main/java/com/getcapacitor/Bridge.java | 180 ++++----
.../java/com/getcapacitor/MessageHandler.java | 62 +--
.../getcapacitor/cordova/CordovaPlugin.java | 148 +++++++
.../cordova/MockCordovaInterfaceImpl.java | 2 +-
.../cordova/MockCordovaWebViewImpl.java | 4 +-
cli/src/android/update.ts | 20 +-
cli/src/ios/update.ts | 419 +++---------------
cli/src/tasks/migrate.ts | 2 +-
cli/src/util/cordova-ios.ts | 365 +++++++++++++++
cli/src/util/iosplugin.ts | 1 +
cli/src/util/spm.ts | 10 +-
ios/Capacitor.podspec | 1 -
.../Capacitor.xcodeproj/project.pbxproj | 8 -
.../CAPApplicationDelegateProxy.swift | 1 -
.../Capacitor/CAPBridgeProtocol.swift | 3 +
...wController+CDVScreenOrientationDelegate.h | 6 -
...wController+CDVScreenOrientationDelegate.m | 5 -
.../Capacitor/CAPBridgeViewController.swift | 18 +-
.../Capacitor/CAPInstanceDescriptor.h | 4 +-
.../Capacitor/CAPInstanceDescriptor.m | 1 -
.../Capacitor/CAPInstanceDescriptor.swift | 19 +-
ios/Capacitor/Capacitor/CapacitorBridge.swift | 126 ++----
ios/Capacitor/Capacitor/JS.swift | 10 +-
ios/Capacitor/Capacitor/JSExport.swift | 40 --
.../Capacitor/WebViewDelegationHandler.swift | 13 +-
.../CapacitorTests/ConfigurationTests.swift | 1 -
.../CapacitorTests/PluginCallAccessorTests.m | 2 +-
ios/CapacitorCordova.podspec | 3 +-
.../Classes/Public/Plugin.swift | 126 ++++++
29 files changed, 885 insertions(+), 715 deletions(-)
create mode 100644 android/capacitor/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
create mode 100644 cli/src/util/cordova-ios.ts
delete mode 100644 ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h
delete mode 100644 ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m
create mode 100644 ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift
diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
index 2a72a4304a..6008c961fc 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
@@ -35,8 +35,6 @@
import com.getcapacitor.android.R;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
-import com.getcapacitor.cordova.MockCordovaInterfaceImpl;
-import com.getcapacitor.cordova.MockCordovaWebViewImpl;
import com.getcapacitor.util.HostMask;
import com.getcapacitor.util.InternalUtils;
import com.getcapacitor.util.PermissionHelper;
@@ -53,13 +51,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.apache.cordova.ConfigXmlParser;
-import org.apache.cordova.CordovaPreferences;
-import org.apache.cordova.CordovaWebView;
-import org.apache.cordova.PluginEntry;
-import org.apache.cordova.PluginManager;
import org.json.JSONException;
/**
@@ -125,9 +119,6 @@ public class Bridge {
private Boolean canInjectJS = true;
// A reference to the main WebView for the app
private final WebView webView;
- public final MockCordovaInterfaceImpl cordovaInterface;
- private CordovaWebView cordovaWebView;
- private CordovaPreferences preferences;
private BridgeWebViewClient webViewClient;
private App app;
@@ -147,6 +138,8 @@ public class Bridge {
// A map of Plugin Id's to PluginHandle's
private Map plugins = new HashMap<>();
+ private Map interceptors = new HashMap<>();
+
// Stored plugin calls that we're keeping around to call again someday
private Map savedCalls = new HashMap<>();
@@ -169,26 +162,6 @@ public class Bridge {
// A pre-determined path to load the bridge
private ServerPath serverPath;
- /**
- * Create the Bridge with a reference to the main {@link Activity} for the
- * app, and a reference to the {@link WebView} our app will use.
- * @param context
- * @param webView
- * @deprecated Use {@link Bridge.Builder} to create Bridge instances
- */
- @Deprecated
- public Bridge(
- AppCompatActivity context,
- WebView webView,
- List> initialPlugins,
- MockCordovaInterfaceImpl cordovaInterface,
- PluginManager pluginManager,
- CordovaPreferences preferences,
- CapConfig config
- ) {
- this(context, null, null, webView, initialPlugins, new ArrayList<>(), cordovaInterface, pluginManager, preferences, config);
- }
-
private Bridge(
AppCompatActivity context,
ServerPath serverPath,
@@ -196,9 +169,6 @@ private Bridge(
WebView webView,
List> initialPlugins,
List pluginInstances,
- MockCordovaInterfaceImpl cordovaInterface,
- PluginManager pluginManager,
- CordovaPreferences preferences,
CapConfig config
) {
this.app = new App();
@@ -209,8 +179,6 @@ private Bridge(
this.webViewClient = new BridgeWebViewClient(this);
this.initialPlugins = initialPlugins;
this.pluginInstances = pluginInstances;
- this.cordovaInterface = cordovaInterface;
- this.preferences = preferences;
// Start our plugin execution threads and handlers
handlerThread.start();
@@ -218,11 +186,10 @@ private Bridge(
this.config = config != null ? config : CapConfig.loadDefault(getActivity());
Logger.init(this.config);
-
// Initialize web view and message handler for it
this.initWebView();
this.setAllowedOriginRules();
- this.msgHandler = new MessageHandler(this, webView, pluginManager);
+ this.msgHandler = new MessageHandler(this, webView);
// Grab any intent info that our app was launched with
Intent intent = context.getIntent();
@@ -450,12 +417,30 @@ private boolean isNewBinary() {
return false;
}
+ private Plugin cordova() {
+ PluginHandle handle = getPlugin("__CordovaHandle");
+ if (handle != null) {
+ return handle.getInstance();
+ }
+ return null;
+ }
+
public boolean isDeployDisabled() {
- return preferences.getBoolean("DisableDeploy", false);
+ Plugin cordova = this.cordova();
+ if (cordova != null) {
+ return cordova.hasPermission("DisableDeploy");
+ } else {
+ return false;
+ }
}
public boolean shouldKeepRunning() {
- return preferences.getBoolean("KeepRunning", true);
+ Plugin cordova = this.cordova();
+ if (cordova != null) {
+ return cordova.hasPermission("KeepRunning");
+ } else {
+ return false;
+ }
}
public void handleAppUrlLoadError(Exception ex) {
@@ -474,10 +459,6 @@ public boolean isDevMode() {
return (getActivity().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
- protected void setCordovaWebView(CordovaWebView cordovaWebView) {
- this.cordovaWebView = cordovaWebView;
- }
-
/**
* Get the Context for the App
* @return
@@ -567,6 +548,14 @@ public CapConfig getConfig() {
return this.config;
}
+ public MessageHandler.Interceptor getCallInterceptor(String type) {
+ return this.interceptors.get(type);
+ }
+
+ public void registerInterceptor(String type, MessageHandler.Interceptor interceptor) {
+ this.interceptors.put(type, interceptor);
+ }
+
public void reset() {
savedCalls = new HashMap<>();
for (PluginHandle handle : this.plugins.values()) {
@@ -1132,11 +1121,14 @@ boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[]
if (plugin == null) {
boolean permissionHandled = false;
Logger.debug("Unable to find a Capacitor plugin to handle permission requestCode, trying Cordova plugins " + requestCode);
- try {
- permissionHandled = cordovaInterface.handlePermissionResult(requestCode, permissions, grantResults);
- } catch (JSONException e) {
- Logger.debug("Error on Cordova plugin permissions request " + e.getMessage());
+ PluginHandle cordovaHandle = getPlugin("__CordovaPlugin");
+
+ if (cordovaHandle != null) {
+ Plugin cordovaPlugin = cordovaHandle.getInstance();
+ cordovaPlugin.handleRequestPermissionsResult(requestCode, permissions, grantResults);
+ permissionHandled = cordovaPlugin.hasDefinedRequiredPermissions();
}
+
return permissionHandled;
}
@@ -1271,7 +1263,13 @@ boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (plugin == null || plugin.getInstance() == null) {
Logger.debug("Unable to find a Capacitor plugin to handle requestCode, trying Cordova plugins " + requestCode);
- return cordovaInterface.onActivityResult(requestCode, resultCode, data);
+ PluginHandle cordovaHandle = getPlugin("__CordovaPlugin");
+ if (cordovaHandle != null) {
+ Plugin cordovaPlugin = cordovaHandle.getInstance();
+ cordovaPlugin.handleOnActivityResult(requestCode, resultCode, data);
+ // This is a bit hacky but required to return the boolean out of the cordova interface
+ return cordovaPlugin.hasRequiredPermissions();
+ }
}
// deprecated, to be removed
@@ -1301,10 +1299,6 @@ public void onNewIntent(Intent intent) {
for (PluginHandle plugin : plugins.values()) {
plugin.getInstance().handleOnNewIntent(intent);
}
-
- if (cordovaWebView != null) {
- cordovaWebView.onNewIntent(intent);
- }
}
/**
@@ -1333,10 +1327,6 @@ public void onStart() {
for (PluginHandle plugin : plugins.values()) {
plugin.getInstance().handleOnStart();
}
-
- if (cordovaWebView != null) {
- cordovaWebView.handleStart();
- }
}
/**
@@ -1346,10 +1336,6 @@ public void onResume() {
for (PluginHandle plugin : plugins.values()) {
plugin.getInstance().handleOnResume();
}
-
- if (cordovaWebView != null) {
- cordovaWebView.handleResume(this.shouldKeepRunning());
- }
}
/**
@@ -1359,11 +1345,6 @@ public void onPause() {
for (PluginHandle plugin : plugins.values()) {
plugin.getInstance().handleOnPause();
}
-
- if (cordovaWebView != null) {
- boolean keepRunning = this.shouldKeepRunning() || cordovaInterface.getActivityResultCallback() != null;
- cordovaWebView.handlePause(keepRunning);
- }
}
/**
@@ -1373,10 +1354,6 @@ public void onStop() {
for (PluginHandle plugin : plugins.values()) {
plugin.getInstance().handleOnStop();
}
-
- if (cordovaWebView != null) {
- cordovaWebView.handleStop();
- }
}
/**
@@ -1388,10 +1365,6 @@ public void onDestroy() {
}
handlerThread.quitSafely();
-
- if (cordovaWebView != null) {
- cordovaWebView.handleDestroy();
- }
}
/**
@@ -1578,49 +1551,54 @@ public Builder setServerPath(ServerPath serverPath) {
public Bridge create() {
// Cordova initialization
- ConfigXmlParser parser = new ConfigXmlParser();
- parser.parse(activity.getApplicationContext());
- CordovaPreferences preferences = parser.getPreferences();
- preferences.setPreferencesBundle(activity.getIntent().getExtras());
- List pluginEntries = parser.getPluginEntries();
-
- MockCordovaInterfaceImpl cordovaInterface = new MockCordovaInterfaceImpl(activity);
- if (instanceState != null) {
- cordovaInterface.restoreInstanceState(instanceState);
- }
-
WebView webView = this.fragment != null ? fragment.getView().findViewById(R.id.webview) : activity.findViewById(R.id.webview);
- MockCordovaWebViewImpl mockWebView = new MockCordovaWebViewImpl(activity.getApplicationContext());
- mockWebView.init(cordovaInterface, pluginEntries, preferences, webView);
- PluginManager pluginManager = mockWebView.getPluginManager();
- cordovaInterface.onCordovaInit(pluginManager);
// Bridge initialization
- Bridge bridge = new Bridge(
- activity,
- serverPath,
- fragment,
- webView,
- plugins,
- pluginInstances,
- cordovaInterface,
- pluginManager,
- preferences,
- config
- );
+ Bridge bridge = new Bridge(activity, serverPath, fragment, webView, plugins, pluginInstances, config);
if (webView instanceof CapacitorWebView capacitorWebView) {
capacitorWebView.setBridge(bridge);
}
- bridge.setCordovaWebView(mockWebView);
bridge.setWebViewListeners(webViewListeners);
bridge.setRouteProcessor(routeProcessor);
if (instanceState != null) {
+ PluginHandle maybeCordova = bridge.getPlugin("__CordovaPlugin");
+ if (maybeCordova != null) {
+ maybeCordova.getInstance().restoreState(instanceState);
+ }
bridge.restoreInstanceState(instanceState);
}
+ bridge.registerInterceptor("message", (postData) -> {
+ try {
+ String callbackId = postData.getString("callbackId");
+ String pluginId = postData.getString("pluginId");
+ String methodName = postData.getString("methodName");
+ JSObject methodData = postData.getJSObject("options", new JSObject());
+
+ Logger.verbose(
+ Logger.tags("Plugin"),
+ "To native (Capacitor plugin): callbackId: " +
+ callbackId +
+ ", pluginId: " +
+ pluginId +
+ ", methodName: " +
+ methodName
+ );
+
+ PluginCall call = new PluginCall(bridge.msgHandler, pluginId, callbackId, methodName, methodData);
+ bridge.callPluginMethod(pluginId, methodName, call);
+ } catch (JSONException e) {
+ Logger.error(e.getMessage());
+ }
+ });
+
+ bridge.registerInterceptor("js.error", (postData) -> {
+ Logger.error("JavaScript Error: " + postData.toString());
+ });
+
return bridge;
}
}
diff --git a/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java b/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java
index dc91c9b01e..4de485b6fe 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/MessageHandler.java
@@ -5,7 +5,7 @@
import androidx.webkit.JavaScriptReplyProxy;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
-import org.apache.cordova.PluginManager;
+import java.util.function.Function;
/**
* MessageHandler handles messages from the WebView, dispatching them
@@ -13,15 +13,18 @@
*/
public class MessageHandler {
+ @FunctionalInterface
+ public interface Interceptor {
+ void intercept(JSObject object);
+ }
+
private Bridge bridge;
private WebView webView;
- private PluginManager cordovaPluginManager;
private JavaScriptReplyProxy javaScriptReplyProxy;
- public MessageHandler(Bridge bridge, WebView webView, PluginManager cordovaPluginManager) {
+ public MessageHandler(Bridge bridge, WebView webView) {
this.bridge = bridge;
this.webView = webView;
- this.cordovaPluginManager = cordovaPluginManager;
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER) && !bridge.getConfig().isUsingLegacyBridge()) {
WebViewCompat.WebMessageListener capListener = (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
@@ -42,6 +45,11 @@ public MessageHandler(Bridge bridge, WebView webView, PluginManager cordovaPlugi
}
}
+ @Deprecated
+ public MessageHandler(Bridge bridge, WebView webView, Object cordovaPluginManager) {
+ this(bridge, webView);
+ }
+
/**
* The main message handler that will be called from JavaScript
* to send a message to the native bridge.
@@ -55,43 +63,11 @@ public void postMessage(String jsonStr) {
String type = postData.getString("type");
- boolean typeIsNotNull = type != null;
- boolean isCordovaPlugin = typeIsNotNull && type.equals("cordova");
- boolean isJavaScriptError = typeIsNotNull && type.equals("js.error");
-
- String callbackId = postData.getString("callbackId");
-
- if (isCordovaPlugin) {
- String service = postData.getString("service");
- String action = postData.getString("action");
- String actionArgs = postData.getString("actionArgs");
-
- Logger.verbose(
- Logger.tags("Plugin"),
- "To native (Cordova plugin): callbackId: " +
- callbackId +
- ", service: " +
- service +
- ", action: " +
- action +
- ", actionArgs: " +
- actionArgs
- );
-
- this.callCordovaPluginMethod(callbackId, service, action, actionArgs);
- } else if (isJavaScriptError) {
- Logger.error("JavaScript Error: " + jsonStr);
- } else {
- String pluginId = postData.getString("pluginId");
- String methodName = postData.getString("methodName");
- JSObject methodData = postData.getJSObject("options", new JSObject());
-
- Logger.verbose(
- Logger.tags("Plugin"),
- "To native (Capacitor plugin): callbackId: " + callbackId + ", pluginId: " + pluginId + ", methodName: " + methodName
- );
+ if (type == null) type = "message";
- this.callPluginMethod(callbackId, pluginId, methodName, methodData);
+ Interceptor interceptor = bridge.getCallInterceptor(type);
+ if (interceptor != null) {
+ interceptor.intercept(postData);
}
} catch (Exception ex) {
Logger.error("Post message error:", ex);
@@ -148,10 +124,4 @@ private void callPluginMethod(String callbackId, String pluginId, String methodN
PluginCall call = new PluginCall(this, pluginId, callbackId, methodName, methodData);
bridge.callPluginMethod(pluginId, methodName, call);
}
-
- private void callCordovaPluginMethod(String callbackId, String service, String action, String actionArgs) {
- bridge.execute(() -> {
- cordovaPluginManager.exec(service, action, callbackId, actionArgs);
- });
- }
}
diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java b/android/capacitor/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
new file mode 100644
index 0000000000..efe101034d
--- /dev/null
+++ b/android/capacitor/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
@@ -0,0 +1,148 @@
+package com.getcapacitor.cordova;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.webkit.WebView;
+import com.getcapacitor.Logger;
+import com.getcapacitor.Plugin;
+import com.getcapacitor.android.R;
+import com.getcapacitor.annotation.CapacitorPlugin;
+import java.util.List;
+import org.apache.cordova.ConfigXmlParser;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPreferences;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.PluginEntry;
+import org.apache.cordova.PluginManager;
+import org.json.JSONException;
+
+@CapacitorPlugin(name = "__CordovaPlugin")
+public class CordovaPlugin extends Plugin {
+
+ private MockCordovaInterfaceImpl cordovaInterface;
+ private CordovaWebView webView;
+ private CordovaPreferences preferences;
+
+ private boolean pluginHadActivityResult = false;
+ private boolean pluginHadPermissionResult = false;
+
+ @Override
+ public void load() {
+ ConfigXmlParser parser = new ConfigXmlParser();
+ parser.parse(getActivity().getApplicationContext());
+ preferences = parser.getPreferences();
+ preferences.setPreferencesBundle(getActivity().getIntent().getExtras());
+ List pluginEntries = parser.getPluginEntries();
+
+ cordovaInterface = new MockCordovaInterfaceImpl(getActivity());
+
+ MockCordovaWebViewImpl mockWebView = new MockCordovaWebViewImpl(getActivity().getApplicationContext());
+ mockWebView.init(cordovaInterface, pluginEntries, preferences, bridge.getWebView());
+ webView = mockWebView;
+ PluginManager pluginManager = mockWebView.getPluginManager();
+ cordovaInterface.onCordovaInit(pluginManager);
+
+ bridge.registerInterceptor("cordova", (postData) -> {
+ String callbackId = postData.getString("callbackId");
+
+ String service = postData.getString("service");
+ String action = postData.getString("action");
+ String actionArgs = postData.getString("actionArgs");
+
+ Logger.verbose(
+ Logger.tags("Plugin"),
+ "To native (Cordova plugin): callbackId: " +
+ callbackId +
+ ", service: " +
+ service +
+ ", action: " +
+ action +
+ ", actionArgs: " +
+ actionArgs
+ );
+
+ bridge.execute(() -> {
+ pluginManager.exec(service, action, callbackId, actionArgs);
+ });
+ });
+ }
+
+ @Override
+ protected void restoreState(Bundle state) {
+ if (state != null) {
+ cordovaInterface.restoreInstanceState(state);
+ }
+ }
+
+ @Override
+ protected void handleRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ try {
+ pluginHadPermissionResult = cordovaInterface.handlePermissionResult(requestCode, permissions, grantResults);
+ } catch (JSONException e) {
+ Logger.debug("Error on Cordova plugin permissions request " + e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean hasPermission(String permission) {
+ if (permission.equals("DisableDeploy")) {
+ return preferences.getBoolean(permission, false);
+ }
+
+ if (permission.equals("KeepRunning")) {
+ return preferences.getBoolean(permission, true);
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean hasDefinedRequiredPermissions() {
+ boolean currentPermissionResult = pluginHadPermissionResult;
+ pluginHadPermissionResult = false;
+ return currentPermissionResult;
+ }
+
+ @Override
+ protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) {
+ pluginHadActivityResult = cordovaInterface.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public boolean hasRequiredPermissions() {
+ boolean currentActivityResult = pluginHadActivityResult;
+ pluginHadActivityResult = false;
+ return currentActivityResult;
+ }
+
+ @Override
+ protected void handleOnNewIntent(Intent intent) {
+ webView.onNewIntent(intent);
+ }
+
+ @Override
+ protected void handleOnStart() {
+ webView.handleStart();
+ }
+
+ @Override
+ protected void handleOnResume() {
+ webView.handleResume(bridge.shouldKeepRunning());
+ }
+
+ @Override
+ protected void handleOnPause() {
+ boolean keepRunning = bridge.shouldKeepRunning() || cordovaInterface.getActivityResultCallback() != null;
+ webView.handlePause(keepRunning);
+ }
+
+ @Override
+ protected void handleOnStop() {
+ webView.handleStop();
+ }
+
+ @Override
+ protected void handleOnDestroy() {
+ webView.handleDestroy();
+ }
+}
diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java
index 7e8358da5e..11116bf131 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java
@@ -7,7 +7,7 @@
import org.apache.cordova.CordovaPlugin;
import org.json.JSONException;
-public class MockCordovaInterfaceImpl extends CordovaInterfaceImpl {
+class MockCordovaInterfaceImpl extends CordovaInterfaceImpl {
public MockCordovaInterfaceImpl(AppCompatActivity activity) {
super(activity, Executors.newCachedThreadPool());
diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
index 1115429d7b..72136c496d 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
@@ -20,7 +20,7 @@
import org.apache.cordova.PluginManager;
import org.apache.cordova.PluginResult;
-public class MockCordovaWebViewImpl implements CordovaWebView {
+class MockCordovaWebViewImpl implements CordovaWebView {
private Context context;
private PluginManager pluginManager;
@@ -32,7 +32,7 @@ public class MockCordovaWebViewImpl implements CordovaWebView {
private WebView webView;
private boolean hasPausedEver;
- public MockCordovaWebViewImpl(Context context) {
+ MockCordovaWebViewImpl(Context context) {
this.context = context;
}
diff --git a/cli/src/android/update.ts b/cli/src/android/update.ts
index 16e8c4935f..fe22659b1a 100644
--- a/cli/src/android/update.ts
+++ b/cli/src/android/update.ts
@@ -7,6 +7,7 @@ import { checkPlatformVersions, runTask } from '../common';
import { checkPluginDependencies, handleCordovaPluginsJS, writeCordovaAndroidManifest } from '../cordova';
import type { Config } from '../definitions';
import { fatal } from '../errors';
+import { logger } from '../log';
import {
PluginType,
getAllElements,
@@ -35,10 +36,13 @@ export async function updateAndroid(config: Config): Promise {
printPlugins(capacitorPlugins, 'android');
- await writePluginsJson(config, capacitorPlugins);
- await removePluginsNativeFiles(config);
const cordovaPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Cordova);
- if (cordovaPlugins.length > 0) {
+ const enableCordova = cordovaPlugins.length > 0;
+
+ await writePluginsJson(config, capacitorPlugins, enableCordova);
+ await removePluginsNativeFiles(config);
+ if (enableCordova) {
+ logger.info('Found Cordova Plugins: Including Android Cordova Support');
await copyPluginsNativeFiles(config, cordovaPlugins);
}
if (!(await pathExists(config.android.webDirAbs))) {
@@ -64,8 +68,16 @@ interface PluginsJsonEntry {
classpath: string;
}
-async function writePluginsJson(config: Config, plugins: Plugin[]): Promise {
+async function writePluginsJson(config: Config, plugins: Plugin[], includeCordova: boolean): Promise {
const classes = await findAndroidPluginClasses(plugins);
+
+ if (includeCordova) {
+ classes.push({
+ pkg: '@capacitor/android',
+ classpath: 'com.getcapacitor.cordova.CordovaPlugin',
+ });
+ }
+
const pluginsJsonPath = resolve(config.android.assetsDirAbs, 'capacitor.plugins.json');
await writeJSON(pluginsJsonPath, classes, { spaces: '\t' });
diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts
index cd9c82a00d..961f6ca922 100644
--- a/cli/src/ios/update.ts
+++ b/cli/src/ios/update.ts
@@ -1,31 +1,29 @@
-import { copy, remove, pathExists, readFile, realpath, writeFile } from 'fs-extra';
+import { pathExists, readFile, realpath, writeFile } from 'fs-extra';
import { basename, dirname, join, relative } from 'path';
import c from '../colors';
-import { checkPlatformVersions, getCapacitorPackageVersion, runTask } from '../common';
-import { checkPluginDependencies, handleCordovaPluginsJS, logCordovaManualSteps, needsStaticPod } from '../cordova';
+import { checkPlatformVersions, runTask } from '../common';
+import { checkPluginDependencies, handleCordovaPluginsJS, logCordovaManualSteps } from '../cordova';
import type { Config } from '../definitions';
import { fatal } from '../errors';
import { logger } from '../log';
-import {
- PluginType,
- getAllElements,
- getFilePath,
- getPlatformElement,
- getPluginType,
- getPlugins,
- printPlugins,
-} from '../plugin';
+import { PluginType, getPluginType, getPlugins, printPlugins } from '../plugin';
import type { Plugin } from '../plugin';
import { copy as copyTask } from '../tasks/copy';
+import {
+ generateCordovaPodspecs,
+ generateCordovaPackageFiles,
+ copyPluginsNativeFiles,
+ removePluginsNativeFiles,
+ cordovaPodfileLines,
+} from '../util/cordova-ios';
import { convertToUnixPath } from '../util/fs';
import { generateIOSPackageJSON } from '../util/iosplugin';
import { resolveNode } from '../util/node';
import { generatePackageFile, checkPluginsForPackageSwift } from '../util/spm';
import { runCommand, isInstalled } from '../util/subprocess';
-import { extractTemplate } from '../util/template';
-import { getIOSPlugins, getMajoriOSVersion } from './common';
+import { getIOSPlugins } from './common';
const platform = 'ios';
@@ -43,89 +41,65 @@ export async function updateIOS(config: Config, deployment: boolean): Promise getPluginType(p, platform) === PluginType.Cordova);
- if (cordovaPlugins.length > 0) {
+ const enableCordova = cordovaPlugins.length > 0;
+
+ if (enableCordova) {
+ logger.info('Found Cordova Plugins: Including iOS Cordova Support');
await copyPluginsNativeFiles(config, cordovaPlugins);
}
+
if (!(await pathExists(await config.ios.webDirAbs))) {
await copyTask(config, platform);
}
+
await handleCordovaPluginsJS(cordovaPlugins, config, platform);
await checkPluginDependencies(plugins, platform, config.app.extConfig.cordova?.failOnUninstalledPlugins);
if ((await config.ios.packageManager) === 'SPM') {
- await generateCordovaPackageFiles(cordovaPlugins, config);
+ if (enableCordova) {
+ await generateCordovaPackageFiles(cordovaPlugins, config);
+ }
const validSPMPackages = await checkPluginsForPackageSwift(config, plugins);
await generatePackageFile(config, validSPMPackages.concat(cordovaPlugins));
} else {
- await generateCordovaPodspecs(cordovaPlugins, config);
- await installCocoaPodsPlugins(config, plugins, deployment);
+ await installCocoaPodsPlugins(config, plugins, deployment, enableCordova);
}
- await logCordovaManualSteps(cordovaPlugins, config, platform);
-
- const incompatibleCordovaPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Incompatible);
- printPlugins(incompatibleCordovaPlugins, platform, 'incompatible');
-}
-async function generateCordovaPackageFiles(cordovaPlugins: Plugin[], config: Config) {
- cordovaPlugins.map((plugin: any) => {
- generateCordovaPackageFile(plugin, config);
- });
+ if (enableCordova) {
+ await logCordovaManualSteps(cordovaPlugins, config, platform);
+ }
}
-async function generateCordovaPackageFile(p: Plugin, config: Config) {
- const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
- const iosVersion = getMajoriOSVersion(config);
- const headerFiles = getPlatformElement(p, platform, 'header-file');
- let headersText = '';
- if (headerFiles.length > 0) {
- headersText = `,
- publicHeadersPath: "."`;
+export async function installCocoaPodsPlugins(
+ config: Config,
+ plugins: Plugin[],
+ deployment: boolean,
+ enableCordova: boolean,
+): Promise {
+ if (enableCordova) {
+ const cordovaPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Cordova);
+ await generateCordovaPodspecs(cordovaPlugins, config);
}
- const content = `// swift-tools-version: 5.9
-
-import PackageDescription
-
-let package = Package(
- name: "${p.name}",
- platforms: [.iOS(.v${iosVersion})],
- products: [
- .library(
- name: "${p.name}",
- targets: ["${p.name}"]
- )
- ],
- dependencies: [
- .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "${iosPlatformVersion}")
- ],
- targets: [
- .target(
- name: "${p.name}",
- dependencies: [
- .product(name: "Cordova", package: "capacitor-swift-pm")
- ],
- path: "."${headersText}
- )
- ]
-)`;
- await writeFile(join(config.ios.cordovaPluginsDirAbs, 'sources', p.name, 'Package.swift'), content);
-}
-
-export async function installCocoaPodsPlugins(config: Config, plugins: Plugin[], deployment: boolean): Promise {
await runTask(`Updating iOS native dependencies with ${c.input(`${await config.ios.podPath} install`)}`, () => {
- return updatePodfile(config, plugins, deployment);
+ return updatePodfile(config, plugins, deployment, enableCordova);
});
}
-async function updatePodfile(config: Config, plugins: Plugin[], deployment: boolean): Promise {
- const dependenciesContent = await generatePodFile(config, plugins);
+async function updatePodfile(
+ config: Config,
+ plugins: Plugin[],
+ deployment: boolean,
+ enableCordova: boolean,
+): Promise {
+ const dependenciesContent = await generatePodFile(config, plugins, enableCordova);
const relativeCapacitoriOSPath = await getRelativeCapacitoriOSPath(config);
const podfilePath = join(config.ios.nativeProjectDirAbs, 'Podfile');
let podfileContent = await readFile(podfilePath, { encoding: 'utf-8' });
podfileContent = podfileContent.replace(/(def capacitor_pods)[\s\S]+?(\nend)/, `$1${dependenciesContent}$2`);
podfileContent = podfileContent.replace(
- /(require_relative)[\s\S]+?(@capacitor\/ios\/scripts\/pods_helpers')/,
+ /(require_relative)[\s\S]+?(ios\/scripts\/pods_helpers')/,
`require_relative '${relativeCapacitoriOSPath}/scripts/pods_helpers'`,
);
await writeFile(podfilePath, podfileContent, { encoding: 'utf-8' });
@@ -167,10 +141,12 @@ async function getRelativeCapacitoriOSPath(config: Config) {
return convertToUnixPath(relative(config.ios.nativeProjectDirAbs, await realpath(dirname(capacitoriOSPath))));
}
-async function generatePodFile(config: Config, plugins: Plugin[]): Promise {
+async function generatePodFile(config: Config, plugins: Plugin[], enableCordova: boolean): Promise {
const relativeCapacitoriOSPath = await getRelativeCapacitoriOSPath(config);
const capacitorPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Core);
+ const cordovaPodlines = cordovaPodfileLines(config, plugins);
+
const pods = await Promise.all(
capacitorPlugins.map(async (p) => {
if (!p.ios) {
@@ -182,297 +158,18 @@ async function generatePodFile(config: Config, plugins: Plugin[]): Promise getPluginType(p, platform) === PluginType.Cordova);
- cordovaPlugins.map(async (p) => {
- const podspecs = getPlatformElement(p, platform, 'podspec');
- podspecs.map((podspec: any) => {
- podspec.pods.map((pPods: any) => {
- pPods.pod.map((pod: any) => {
- if (pod.$.git) {
- let gitRef = '';
- if (pod.$.tag) {
- gitRef = `, :tag => '${pod.$.tag}'`;
- } else if (pod.$.branch) {
- gitRef = `, :branch => '${pod.$.branch}'`;
- } else if (pod.$.commit) {
- gitRef = `, :commit => '${pod.$.commit}'`;
- }
- pods.push(` pod '${pod.$.name}', :git => '${pod.$.git}'${gitRef}\n`);
- }
- });
- });
- });
- });
- const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
- const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
- if (noStaticPlugins.length > 0) {
- pods.push(` pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'\n`);
- }
- if (staticPlugins.length > 0) {
- pods.push(` pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'\n`);
- }
- const resourcesPlugins = cordovaPlugins.filter(filterResources);
- if (resourcesPlugins.length > 0) {
- pods.push(` pod 'CordovaPluginsResources', :path => '../capacitor-cordova-ios-plugins'\n`);
- }
- return `
- pod 'Capacitor', :path => '${relativeCapacitoriOSPath}'
- pod 'CapacitorCordova', :path => '${relativeCapacitoriOSPath}'
-${pods.join('').trimRight()}`;
-}
-
-function getFrameworkName(framework: any) {
- if (isFramework(framework)) {
- if (framework.$.custom && framework.$.custom === 'true') {
- return framework.$.src;
- }
- return framework.$.src.substr(0, framework.$.src.indexOf('.'));
- }
- return framework.$.src.substr(0, framework.$.src.indexOf('.')).replace('lib', '');
-}
-
-function isFramework(framework: any) {
- return framework.$.src.split('.').pop().includes('framework');
-}
-
-async function generateCordovaPodspecs(cordovaPlugins: Plugin[], config: Config) {
- const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
- const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
- generateCordovaPodspec(noStaticPlugins, config, false);
- generateCordovaPodspec(staticPlugins, config, true);
-}
-
-async function generateCordovaPodspec(cordovaPlugins: Plugin[], config: Config, isStatic: boolean) {
- const weakFrameworks: string[] = [];
- const linkedFrameworks: string[] = [];
- const customFrameworks: string[] = [];
- const systemLibraries: string[] = [];
- const sourceFrameworks: string[] = [];
- const frameworkDeps: string[] = [];
- const compilerFlags: string[] = [];
- let prefsArray: any[] = [];
- let name = 'CordovaPlugins';
- let sourcesFolderName = 'sources';
- if (isStatic) {
- name += 'Static';
- frameworkDeps.push('s.static_framework = true');
- sourcesFolderName += 'static';
- }
- cordovaPlugins.map((plugin: any) => {
- const frameworks = getPlatformElement(plugin, platform, 'framework');
- frameworks.map((framework: any) => {
- if (!framework.$.type) {
- const name = getFrameworkName(framework);
- if (isFramework(framework)) {
- if (framework.$.weak && framework.$.weak === 'true') {
- if (!weakFrameworks.includes(name)) {
- weakFrameworks.push(name);
- }
- } else if (framework.$.custom && framework.$.custom === 'true') {
- const frameworktPath = join(sourcesFolderName, plugin.name, name);
- if (!customFrameworks.includes(frameworktPath)) {
- customFrameworks.push(frameworktPath);
- }
- } else {
- if (!linkedFrameworks.includes(name)) {
- linkedFrameworks.push(name);
- }
- }
- } else {
- if (!systemLibraries.includes(name)) {
- systemLibraries.push(name);
- }
- }
- } else if (framework.$.type && framework.$.type === 'podspec') {
- let depString = `s.dependency '${framework.$.src}'`;
- if (framework.$.spec && framework.$.spec !== '') {
- depString += `, '${framework.$.spec}'`;
- }
- if (!frameworkDeps.includes(depString)) {
- frameworkDeps.push(depString);
- }
- }
- });
- prefsArray = prefsArray.concat(getAllElements(plugin, platform, 'preference'));
- const podspecs = getPlatformElement(plugin, platform, 'podspec');
- podspecs.map((podspec: any) => {
- podspec.pods.map((pods: any) => {
- pods.pod.map((pod: any) => {
- let depString = `s.dependency '${pod.$.name}'`;
- if (pod.$.spec && pod.$.spec !== '') {
- depString += `, '${pod.$.spec}'`;
- }
- if (!frameworkDeps.includes(depString)) {
- frameworkDeps.push(depString);
- }
- });
- });
- });
- const sourceFiles = getPlatformElement(plugin, platform, 'source-file');
- sourceFiles.map((sourceFile: any) => {
- if (sourceFile.$.framework && sourceFile.$.framework === 'true') {
- let fileName = sourceFile.$.src.split('/').pop();
- if (!fileName.startsWith('lib')) {
- fileName = 'lib' + fileName;
- }
- const frameworktPath = join(sourcesFolderName, plugin.name, fileName);
- if (!sourceFrameworks.includes(frameworktPath)) {
- sourceFrameworks.push(frameworktPath);
- }
- } else if (sourceFile.$['compiler-flags']) {
- const cFlag = sourceFile.$['compiler-flags'];
- if (!compilerFlags.includes(cFlag)) {
- compilerFlags.push(cFlag);
- }
- }
- });
- });
- const onlySystemLibraries = systemLibraries.filter((library) => removeNoSystem(library, sourceFrameworks));
- if (weakFrameworks.length > 0) {
- frameworkDeps.push(`s.weak_frameworks = '${weakFrameworks.join(`', '`)}'`);
- }
- if (linkedFrameworks.length > 0) {
- frameworkDeps.push(`s.frameworks = '${linkedFrameworks.join(`', '`)}'`);
- }
- if (onlySystemLibraries.length > 0) {
- frameworkDeps.push(`s.libraries = '${onlySystemLibraries.join(`', '`)}'`);
- }
- if (customFrameworks.length > 0) {
- frameworkDeps.push(`s.vendored_frameworks = '${customFrameworks.join(`', '`)}'`);
- frameworkDeps.push(
- `s.exclude_files = 'sources/**/*.framework/Headers/*.h', 'sources/**/*.framework/PrivateHeaders/*.h'`,
- );
- }
- if (sourceFrameworks.length > 0) {
- frameworkDeps.push(`s.vendored_libraries = '${sourceFrameworks.join(`', '`)}'`);
- }
- if (compilerFlags.length > 0) {
- frameworkDeps.push(`s.compiler_flags = '${compilerFlags.join(' ')}'`);
- }
- const arcPlugins = cordovaPlugins.filter(filterARCFiles);
- if (arcPlugins.length > 0) {
- frameworkDeps.push(`s.subspec 'noarc' do |sna|
- sna.requires_arc = false
- sna.source_files = 'noarc/**/*.{swift,h,m,c,cc,mm,cpp}'
- end`);
- }
- let frameworksString = frameworkDeps.join('\n ');
- frameworksString = await replaceFrameworkVariables(config, prefsArray, frameworksString);
- const content = `
- Pod::Spec.new do |s|
- s.name = '${name}'
- s.version = '${config.cli.package.version}'
- s.summary = 'Autogenerated spec'
- s.license = 'Unknown'
- s.homepage = 'https://example.com'
- s.authors = { 'Capacitor Generator' => 'hi@example.com' }
- s.source = { :git => 'https://github.com/ionic-team/does-not-exist.git', :tag => '${config.cli.package.version}' }
- s.source_files = '${sourcesFolderName}/**/*.{swift,h,m,c,cc,mm,cpp}'
- s.ios.deployment_target = '${config.ios.minVersion}'
- s.xcconfig = {'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) COCOAPODS=1 WK_WEB_VIEW_ONLY=1' }
- s.dependency 'CapacitorCordova'${getLinkerFlags(config)}
- s.swift_version = '5.1'
- ${frameworksString}
- end`;
- await writeFile(join(config.ios.cordovaPluginsDirAbs, `${name}.podspec`), content);
-}
-function getLinkerFlags(config: Config) {
- if (config.app.extConfig.ios?.cordovaLinkerFlags) {
- return `\n s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '${config.app.extConfig.ios.cordovaLinkerFlags.join(
- ' ',
- )}' }`;
- }
- return '';
-}
+ let podfileString = '\n';
+ podfileString += ` pod 'Capacitor', :path => '${relativeCapacitoriOSPath}'\n`;
-async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[]) {
- for (const p of cordovaPlugins) {
- const sourceFiles = getPlatformElement(p, platform, 'source-file');
- const headerFiles = getPlatformElement(p, platform, 'header-file');
- const codeFiles = sourceFiles.concat(headerFiles);
- const frameworks = getPlatformElement(p, platform, 'framework');
- let sourcesFolderName = 'sources';
- if (needsStaticPod(p)) {
- sourcesFolderName += 'static';
- }
- const sourcesFolder = join(config.ios.cordovaPluginsDirAbs, sourcesFolderName, p.name);
- for (const codeFile of codeFiles) {
- let fileName = codeFile.$.src.split('/').pop();
- const fileExt = codeFile.$.src.split('.').pop();
- if (fileExt === 'a' && !fileName.startsWith('lib')) {
- fileName = 'lib' + fileName;
- }
- let destFolder = sourcesFolderName;
- if (codeFile.$['compiler-flags'] && codeFile.$['compiler-flags'] === '-fno-objc-arc') {
- destFolder = 'noarc';
- }
- const filePath = getFilePath(config, p, codeFile.$.src);
- const fileDest = join(config.ios.cordovaPluginsDirAbs, destFolder, p.name, fileName);
- await copy(filePath, fileDest);
- if (!codeFile.$.framework) {
- let fileContent = await readFile(fileDest, { encoding: 'utf-8' });
- if (fileExt === 'swift') {
- fileContent = 'import Cordova\n' + fileContent;
- await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
- } else {
- if (fileContent.includes('@import Firebase;')) {
- fileContent = fileContent.replace('@import Firebase;', '#import ');
- await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
- }
- if (
- fileContent.includes('[NSBundle bundleForClass:[self class]]') ||
- fileContent.includes('[NSBundle bundleForClass:[CDVCapture class]]')
- ) {
- fileContent = fileContent.replace('[NSBundle bundleForClass:[self class]]', '[NSBundle mainBundle]');
- fileContent = fileContent.replace('[NSBundle bundleForClass:[CDVCapture class]]', '[NSBundle mainBundle]');
- await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
- }
- if (fileContent.includes('[self.webView superview]') || fileContent.includes('self.webView.superview')) {
- fileContent = fileContent.replace(/\[self.webView superview\]/g, 'self.viewController.view');
- fileContent = fileContent.replace(/self.webView.superview/g, 'self.viewController.view');
- await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
- }
- }
- }
- }
- const resourceFiles = getPlatformElement(p, platform, 'resource-file');
- for (const resourceFile of resourceFiles) {
- const fileName = resourceFile.$.src.split('/').pop();
- await copy(
- getFilePath(config, p, resourceFile.$.src),
- join(config.ios.cordovaPluginsDirAbs, 'resources', fileName),
- );
- }
- for (const framework of frameworks) {
- if (framework.$.custom && framework.$.custom === 'true') {
- await copy(getFilePath(config, p, framework.$.src), join(sourcesFolder, framework.$.src));
- }
- }
+ if (enableCordova) {
+ podfileString += ` pod 'CapacitorCordova', :path => '${relativeCapacitoriOSPath}'\n`;
+ podfileString += cordovaPodlines.join('').trimEnd() + '\n';
}
-}
-async function removePluginsNativeFiles(config: Config) {
- await remove(config.ios.cordovaPluginsDirAbs);
- await extractTemplate(config.cli.assets.ios.cordovaPluginsTemplateArchiveAbs, config.ios.cordovaPluginsDirAbs);
-}
+ podfileString += pods.join('').trimEnd();
-function filterResources(plugin: Plugin) {
- const resources = getPlatformElement(plugin, platform, 'resource-file');
- return resources.length > 0;
-}
-
-function filterARCFiles(plugin: Plugin) {
- const sources = getPlatformElement(plugin, platform, 'source-file');
- const sourcesARC = sources.filter(
- (sourceFile: any) => sourceFile.$['compiler-flags'] && sourceFile.$['compiler-flags'] === '-fno-objc-arc',
- );
- return sourcesARC.length > 0;
-}
-
-function removeNoSystem(library: string, sourceFrameworks: string[]) {
- const libraries = sourceFrameworks.filter((framework) => framework.includes(library));
- return libraries.length === 0;
+ return podfileString;
}
async function getPluginsTask(config: Config) {
@@ -482,13 +179,3 @@ async function getPluginsTask(config: Config) {
return iosPlugins;
});
}
-
-async function replaceFrameworkVariables(config: Config, prefsArray: any[], frameworkString: string) {
- prefsArray.map((preference: any) => {
- frameworkString = frameworkString.replace(
- new RegExp(('$' + preference.$.name).replace('$', '\\$&'), 'g'),
- preference.$.default,
- );
- });
- return frameworkString;
-}
diff --git a/cli/src/tasks/migrate.ts b/cli/src/tasks/migrate.ts
index b2c419c080..dbc09549a4 100644
--- a/cli/src/tasks/migrate.ts
+++ b/cli/src/tasks/migrate.ts
@@ -1,6 +1,6 @@
import { writeFileSync, readFileSync, existsSync } from 'fs-extra';
import { join } from 'path';
-import { rimraf } from 'rimraf';
+import rimraf from 'rimraf';
import { coerce, gte, lt } from 'semver';
import c from '../colors';
diff --git a/cli/src/util/cordova-ios.ts b/cli/src/util/cordova-ios.ts
new file mode 100644
index 0000000000..52dee60147
--- /dev/null
+++ b/cli/src/util/cordova-ios.ts
@@ -0,0 +1,365 @@
+import { copy, readFile, writeFile, remove } from 'fs-extra';
+import { join } from 'path';
+
+import { getCapacitorPackageVersion } from '../common';
+import { needsStaticPod } from '../cordova';
+import type { Config } from '../definitions';
+import { getMajoriOSVersion } from '../ios/common';
+import { PluginType, getPlatformElement, getPluginType, getAllElements, getFilePath } from '../plugin';
+import type { Plugin } from '../plugin';
+import { extractTemplate } from '../util/template';
+
+const platform = 'ios';
+
+export async function generateCordovaPodspecs(cordovaPlugins: Plugin[], config: Config): Promise {
+ const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
+ const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
+ generateCordovaPodspec(noStaticPlugins, config, false);
+ generateCordovaPodspec(staticPlugins, config, true);
+}
+
+export async function generateCordovaPodspec(
+ cordovaPlugins: Plugin[],
+ config: Config,
+ isStatic: boolean,
+): Promise {
+ const weakFrameworks: string[] = [];
+ const linkedFrameworks: string[] = [];
+ const customFrameworks: string[] = [];
+ const systemLibraries: string[] = [];
+ const sourceFrameworks: string[] = [];
+ const frameworkDeps: string[] = [];
+ const compilerFlags: string[] = [];
+ let prefsArray: any[] = [];
+ let name = 'CordovaPlugins';
+ let sourcesFolderName = 'sources';
+ if (isStatic) {
+ name += 'Static';
+ frameworkDeps.push('s.static_framework = true');
+ sourcesFolderName += 'static';
+ }
+ cordovaPlugins.map((plugin: any) => {
+ const frameworks = getPlatformElement(plugin, platform, 'framework');
+ frameworks.map((framework: any) => {
+ if (!framework.$.type) {
+ const name = getFrameworkName(framework);
+ if (isFramework(framework)) {
+ if (framework.$.weak && framework.$.weak === 'true') {
+ if (!weakFrameworks.includes(name)) {
+ weakFrameworks.push(name);
+ }
+ } else if (framework.$.custom && framework.$.custom === 'true') {
+ const frameworktPath = join(sourcesFolderName, plugin.name, name);
+ if (!customFrameworks.includes(frameworktPath)) {
+ customFrameworks.push(frameworktPath);
+ }
+ } else {
+ if (!linkedFrameworks.includes(name)) {
+ linkedFrameworks.push(name);
+ }
+ }
+ } else {
+ if (!systemLibraries.includes(name)) {
+ systemLibraries.push(name);
+ }
+ }
+ } else if (framework.$.type && framework.$.type === 'podspec') {
+ let depString = `s.dependency '${framework.$.src}'`;
+ if (framework.$.spec && framework.$.spec !== '') {
+ depString += `, '${framework.$.spec}'`;
+ }
+ if (!frameworkDeps.includes(depString)) {
+ frameworkDeps.push(depString);
+ }
+ }
+ });
+ prefsArray = prefsArray.concat(getAllElements(plugin, platform, 'preference'));
+ const podspecs = getPlatformElement(plugin, platform, 'podspec');
+ podspecs.map((podspec: any) => {
+ podspec.pods.map((pods: any) => {
+ pods.pod.map((pod: any) => {
+ let depString = `s.dependency '${pod.$.name}'`;
+ if (pod.$.spec && pod.$.spec !== '') {
+ depString += `, '${pod.$.spec}'`;
+ }
+ if (!frameworkDeps.includes(depString)) {
+ frameworkDeps.push(depString);
+ }
+ });
+ });
+ });
+ const sourceFiles = getPlatformElement(plugin, platform, 'source-file');
+ sourceFiles.map((sourceFile: any) => {
+ if (sourceFile.$.framework && sourceFile.$.framework === 'true') {
+ let fileName = sourceFile.$.src.split('/').pop();
+ if (!fileName.startsWith('lib')) {
+ fileName = 'lib' + fileName;
+ }
+ const frameworktPath = join(sourcesFolderName, plugin.name, fileName);
+ if (!sourceFrameworks.includes(frameworktPath)) {
+ sourceFrameworks.push(frameworktPath);
+ }
+ } else if (sourceFile.$['compiler-flags']) {
+ const cFlag = sourceFile.$['compiler-flags'];
+ if (!compilerFlags.includes(cFlag)) {
+ compilerFlags.push(cFlag);
+ }
+ }
+ });
+ });
+ const onlySystemLibraries = systemLibraries.filter((library) => removeNoSystem(library, sourceFrameworks));
+ if (weakFrameworks.length > 0) {
+ frameworkDeps.push(`s.weak_frameworks = '${weakFrameworks.join(`', '`)}'`);
+ }
+ if (linkedFrameworks.length > 0) {
+ frameworkDeps.push(`s.frameworks = '${linkedFrameworks.join(`', '`)}'`);
+ }
+ if (onlySystemLibraries.length > 0) {
+ frameworkDeps.push(`s.libraries = '${onlySystemLibraries.join(`', '`)}'`);
+ }
+ if (customFrameworks.length > 0) {
+ frameworkDeps.push(`s.vendored_frameworks = '${customFrameworks.join(`', '`)}'`);
+ frameworkDeps.push(
+ `s.exclude_files = 'sources/**/*.framework/Headers/*.h', 'sources/**/*.framework/PrivateHeaders/*.h'`,
+ );
+ }
+ if (sourceFrameworks.length > 0) {
+ frameworkDeps.push(`s.vendored_libraries = '${sourceFrameworks.join(`', '`)}'`);
+ }
+ if (compilerFlags.length > 0) {
+ frameworkDeps.push(`s.compiler_flags = '${compilerFlags.join(' ')}'`);
+ }
+ const arcPlugins = cordovaPlugins.filter(filterARCFiles);
+ if (arcPlugins.length > 0) {
+ frameworkDeps.push(`s.subspec 'noarc' do |sna|
+ sna.requires_arc = false
+ sna.source_files = 'noarc/**/*.{swift,h,m,c,cc,mm,cpp}'
+ end`);
+ }
+ let frameworksString = frameworkDeps.join('\n ');
+ frameworksString = await replaceFrameworkVariables(config, prefsArray, frameworksString);
+ const content = `
+ Pod::Spec.new do |s|
+ s.name = '${name}'
+ s.version = '${config.cli.package.version}'
+ s.summary = 'Autogenerated spec'
+ s.license = 'Unknown'
+ s.homepage = 'https://example.com'
+ s.authors = { 'Capacitor Generator' => 'hi@example.com' }
+ s.source = { :git => 'https://github.com/ionic-team/does-not-exist.git', :tag => '${config.cli.package.version}' }
+ s.source_files = '${sourcesFolderName}/**/*.{swift,h,m,c,cc,mm,cpp}'
+ s.ios.deployment_target = '${config.ios.minVersion}'
+ s.xcconfig = {'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) COCOAPODS=1 WK_WEB_VIEW_ONLY=1' }
+ s.dependency 'CapacitorCordova'${getLinkerFlags(config)}
+ s.swift_version = '5.1'
+ ${frameworksString}
+ end`;
+ await writeFile(join(config.ios.cordovaPluginsDirAbs, `${name}.podspec`), content);
+}
+
+function getLinkerFlags(config: Config) {
+ if (config.app.extConfig.ios?.cordovaLinkerFlags) {
+ return `\n s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '${config.app.extConfig.ios.cordovaLinkerFlags.join(
+ ' ',
+ )}' }`;
+ }
+ return '';
+}
+
+export async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[]): Promise {
+ for (const p of cordovaPlugins) {
+ const sourceFiles = getPlatformElement(p, platform, 'source-file');
+ const headerFiles = getPlatformElement(p, platform, 'header-file');
+ const codeFiles = sourceFiles.concat(headerFiles);
+ const frameworks = getPlatformElement(p, platform, 'framework');
+ let sourcesFolderName = 'sources';
+ if (needsStaticPod(p)) {
+ sourcesFolderName += 'static';
+ }
+ const sourcesFolder = join(config.ios.cordovaPluginsDirAbs, sourcesFolderName, p.name);
+ for (const codeFile of codeFiles) {
+ let fileName = codeFile.$.src.split('/').pop();
+ const fileExt = codeFile.$.src.split('.').pop();
+ if (fileExt === 'a' && !fileName.startsWith('lib')) {
+ fileName = 'lib' + fileName;
+ }
+ let destFolder = sourcesFolderName;
+ if (codeFile.$['compiler-flags'] && codeFile.$['compiler-flags'] === '-fno-objc-arc') {
+ destFolder = 'noarc';
+ }
+ const filePath = getFilePath(config, p, codeFile.$.src);
+ const fileDest = join(config.ios.cordovaPluginsDirAbs, destFolder, p.name, fileName);
+ await copy(filePath, fileDest);
+ if (!codeFile.$.framework) {
+ let fileContent = await readFile(fileDest, { encoding: 'utf-8' });
+ if (fileExt === 'swift') {
+ fileContent = 'import Cordova\n' + fileContent;
+ await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
+ } else {
+ if (fileContent.includes('@import Firebase;')) {
+ fileContent = fileContent.replace('@import Firebase;', '#import ');
+ await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
+ }
+ if (
+ fileContent.includes('[NSBundle bundleForClass:[self class]]') ||
+ fileContent.includes('[NSBundle bundleForClass:[CDVCapture class]]')
+ ) {
+ fileContent = fileContent.replace('[NSBundle bundleForClass:[self class]]', '[NSBundle mainBundle]');
+ fileContent = fileContent.replace('[NSBundle bundleForClass:[CDVCapture class]]', '[NSBundle mainBundle]');
+ await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
+ }
+ if (fileContent.includes('[self.webView superview]') || fileContent.includes('self.webView.superview')) {
+ fileContent = fileContent.replace(/\[self.webView superview\]/g, 'self.viewController.view');
+ fileContent = fileContent.replace(/self.webView.superview/g, 'self.viewController.view');
+ await writeFile(fileDest, fileContent, { encoding: 'utf-8' });
+ }
+ }
+ }
+ }
+ const resourceFiles = getPlatformElement(p, platform, 'resource-file');
+ for (const resourceFile of resourceFiles) {
+ const fileName = resourceFile.$.src.split('/').pop();
+ await copy(
+ getFilePath(config, p, resourceFile.$.src),
+ join(config.ios.cordovaPluginsDirAbs, 'resources', fileName),
+ );
+ }
+ for (const framework of frameworks) {
+ if (framework.$.custom && framework.$.custom === 'true') {
+ await copy(getFilePath(config, p, framework.$.src), join(sourcesFolder, framework.$.src));
+ }
+ }
+ }
+}
+
+export async function removePluginsNativeFiles(config: Config): Promise {
+ await remove(config.ios.cordovaPluginsDirAbs);
+ await extractTemplate(config.cli.assets.ios.cordovaPluginsTemplateArchiveAbs, config.ios.cordovaPluginsDirAbs);
+}
+
+export function filterARCFiles(plugin: Plugin): boolean {
+ const sources = getPlatformElement(plugin, platform, 'source-file');
+ const sourcesARC = sources.filter(
+ (sourceFile: any) => sourceFile.$['compiler-flags'] && sourceFile.$['compiler-flags'] === '-fno-objc-arc',
+ );
+ return sourcesARC.length > 0;
+}
+
+function removeNoSystem(library: string, sourceFrameworks: string[]): boolean {
+ const libraries = sourceFrameworks.filter((framework) => framework.includes(library));
+ return libraries.length === 0;
+}
+
+async function replaceFrameworkVariables(config: Config, prefsArray: any[], frameworkString: string): Promise {
+ prefsArray.map((preference: any) => {
+ frameworkString = frameworkString.replace(
+ new RegExp(('$' + preference.$.name).replace('$', '\\$&'), 'g'),
+ preference.$.default,
+ );
+ });
+ return frameworkString;
+}
+
+function getFrameworkName(framework: any): string {
+ if (isFramework(framework)) {
+ if (framework.$.custom && framework.$.custom === 'true') {
+ return framework.$.src;
+ }
+ return framework.$.src.substr(0, framework.$.src.indexOf('.'));
+ }
+ return framework.$.src.substr(0, framework.$.src.indexOf('.')).replace('lib', '');
+}
+
+function isFramework(framework: any) {
+ return framework.$.src.split('.').pop().includes('framework');
+}
+
+export function cordovaPodfileLines(config: Config, plugins: Plugin[]): string[] {
+ const pods: string[] = [];
+
+ const cordovaPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Cordova);
+ cordovaPlugins.map(async (p) => {
+ const podspecs = getPlatformElement(p, platform, 'podspec');
+ podspecs.map((podspec: any) => {
+ podspec.pods.map((pPods: any) => {
+ pPods.pod.map((pod: any) => {
+ if (pod.$.git) {
+ let gitRef = '';
+ if (pod.$.tag) {
+ gitRef = `, :tag => '${pod.$.tag}'`;
+ } else if (pod.$.branch) {
+ gitRef = `, :branch => '${pod.$.branch}'`;
+ } else if (pod.$.commit) {
+ gitRef = `, :commit => '${pod.$.commit}'`;
+ }
+ pods.push(` pod '${pod.$.name}', :git => '${pod.$.git}'${gitRef}\n`);
+ }
+ });
+ });
+ });
+ });
+ const staticPlugins = cordovaPlugins.filter((p) => needsStaticPod(p));
+ const noStaticPlugins = cordovaPlugins.filter((el) => !staticPlugins.includes(el));
+ if (noStaticPlugins.length > 0) {
+ pods.push(` pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'\n`);
+ }
+ if (staticPlugins.length > 0) {
+ pods.push(` pod 'CordovaPluginsStatic', :path => '../capacitor-cordova-ios-plugins'\n`);
+ }
+ const resourcesPlugins = cordovaPlugins.filter(filterResources);
+ if (resourcesPlugins.length > 0) {
+ pods.push(` pod 'CordovaPluginsResources', :path => '../capacitor-cordova-ios-plugins'\n`);
+ }
+
+ return pods;
+}
+
+function filterResources(plugin: Plugin) {
+ const resources = getPlatformElement(plugin, platform, 'resource-file');
+ return resources.length > 0;
+}
+
+export async function generateCordovaPackageFiles(cordovaPlugins: Plugin[], config: Config): Promise {
+ cordovaPlugins.map((plugin: any) => {
+ generateCordovaPackageFile(plugin, config);
+ });
+}
+
+export async function generateCordovaPackageFile(p: Plugin, config: Config): Promise {
+ const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
+ const iosVersion = getMajoriOSVersion(config);
+ const headerFiles = getPlatformElement(p, platform, 'header-file');
+ let headersText = '';
+ if (headerFiles.length > 0) {
+ headersText = `,
+ publicHeadersPath: "."`;
+ }
+
+ const content = `// swift-tools-version: 5.9
+
+import PackageDescription
+
+let package = Package(
+ name: "${p.name}",
+ platforms: [.iOS(.v${iosVersion})],
+ products: [
+ .library(
+ name: "${p.name}",
+ targets: ["${p.name}"]
+ )
+ ],
+ dependencies: [
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "${iosPlatformVersion}")
+ ],
+ targets: [
+ .target(
+ name: "${p.name}",
+ dependencies: [
+ .product(name: "Cordova", package: "capacitor-swift-pm")
+ ],
+ path: "."${headersText}
+ )
+ ]
+)`;
+ await writeFile(join(config.ios.cordovaPluginsDirAbs, 'sources', p.name, 'Package.swift'), content);
+}
diff --git a/cli/src/util/iosplugin.ts b/cli/src/util/iosplugin.ts
index 8ce42b35e7..ed4f4fd54b 100644
--- a/cli/src/util/iosplugin.ts
+++ b/cli/src/util/iosplugin.ts
@@ -68,6 +68,7 @@ export async function generateIOSPackageJSON(config: Config, plugins: Plugin[]):
const cordovaPlugins = await getCordovaPlugins(config, 'ios');
if (cordovaPlugins.length > 0) {
classList.push('CDVPlugin');
+ classList.push('CordovaPlugin');
}
writePluginJSON(config, classList);
}
diff --git a/cli/src/util/spm.ts b/cli/src/util/spm.ts
index 30e51cf1a4..58cc7ecd31 100644
--- a/cli/src/util/spm.ts
+++ b/cli/src/util/spm.ts
@@ -6,6 +6,7 @@ import { build, parse } from 'plist';
import { extract } from 'tar';
import { getCapacitorPackageVersion } from '../common';
+import { getCordovaPlugins } from '../cordova';
import type { Config } from '../definitions';
import { fatal } from '../errors';
import { getMajoriOSVersion } from '../ios/common';
@@ -98,6 +99,8 @@ export async function removeCocoapodsFiles(config: Config): Promise {
export async function generatePackageText(config: Config, plugins: Plugin[]): Promise {
const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
const iosVersion = getMajoriOSVersion(config);
+ const cordovaPlugins = await getCordovaPlugins(config, 'ios');
+ const enableCordova = cordovaPlugins.length > 0;
let packageSwiftText = `// swift-tools-version: 5.9
import PackageDescription
@@ -129,8 +132,11 @@ let package = Package(
.target(
name: "CapApp-SPM",
dependencies: [
- .product(name: "Capacitor", package: "capacitor-swift-pm"),
- .product(name: "Cordova", package: "capacitor-swift-pm")`;
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),`;
+
+ if (enableCordova) {
+ packageSwiftText += ` .product(name: "Cordova", package: "capacitor-swift-pm")`;
+ }
for (const plugin of plugins) {
packageSwiftText += `,\n .product(name: "${plugin.ios?.name}", package: "${plugin.ios?.name}")`;
diff --git a/ios/Capacitor.podspec b/ios/Capacitor.podspec
index 9d392728d2..1b92962750 100644
--- a/ios/Capacitor.podspec
+++ b/ios/Capacitor.podspec
@@ -19,6 +19,5 @@ Pod::Spec.new do |s|
s.module_map = "#{prefix}Capacitor/Capacitor/Capacitor.modulemap"
s.resources = ["#{prefix}Capacitor/Capacitor/assets/native-bridge.js"]
s.resource_bundles = { 'Capacitor' => ["#{prefix}Capacitor/Capacitor/PrivacyInfo.xcprivacy"] }
- s.dependency 'CapacitorCordova'
s.swift_version = '5.1'
end
diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
index ed784d5bdd..9e5ac570fe 100644
--- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
+++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
@@ -9,8 +9,6 @@
/* Begin PBXBuildFile section */
0F83E885285A332E006C43CB /* AppUUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F83E884285A332D006C43CB /* AppUUID.swift */; };
0F8F33B327DA980A003F49D6 /* PluginConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F33B127DA980A003F49D6 /* PluginConfig.swift */; };
- 2F81F5C926FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */; };
- 2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */; };
373A69C1255C9360000A6F44 /* NotificationHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */; };
373A69F2255C95D0000A6F44 /* NotificationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373A69F1255C95D0000A6F44 /* NotificationRouter.swift */; };
501CBAA71FC0A723009B0D4D /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 501CBAA61FC0A723009B0D4D /* WebKit.framework */; };
@@ -157,8 +155,6 @@
/* Begin PBXFileReference section */
0F83E884285A332D006C43CB /* AppUUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUUID.swift; sourceTree = ""; };
0F8F33B127DA980A003F49D6 /* PluginConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginConfig.swift; sourceTree = ""; };
- 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CAPBridgeViewController+CDVScreenOrientationDelegate.h"; sourceTree = ""; };
- 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CAPBridgeViewController+CDVScreenOrientationDelegate.m"; sourceTree = ""; };
373A69C0255C9360000A6F44 /* NotificationHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandlerProtocol.swift; sourceTree = ""; };
373A69F1255C95D0000A6F44 /* NotificationRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRouter.swift; sourceTree = ""; };
501CBAA61FC0A723009B0D4D /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
@@ -400,8 +396,6 @@
62FABD1925AE5C01007B3814 /* Array+Capacitor.swift */,
62D43AEF2581817500673C24 /* WKWebView+Capacitor.swift */,
62D43B642582A13D00673C24 /* WKWebView+Capacitor.m */,
- 2F81F5C726FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h */,
- 2F81F5C826FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m */,
62959AE92524DA7700A3D7F1 /* UIColor.swift */,
62959AFE2524DA7700A3D7F1 /* UIStatusBarManager+CAPHandleTapAction.m */,
62959B122524DA7700A3D7F1 /* Info.plist */,
@@ -512,7 +506,6 @@
623D6909254C6FDF002D01D1 /* CAPInstanceDescriptor.h in Headers */,
62959B192524DA7800A3D7F1 /* CAPBridgedPlugin.h in Headers */,
621ECCB82542045900D3D615 /* CAPBridgedJSTypes.h in Headers */,
- 2F81F5C926FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -724,7 +717,6 @@
62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */,
62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */,
62959B1C2524DA7800A3D7F1 /* CAPPluginMethod.m in Sources */,
- 2F81F5CA26FB7CB400DD35BE /* CAPBridgeViewController+CDVScreenOrientationDelegate.m in Sources */,
62ADC0CA25CB678000E914DE /* PluginCallResult.swift in Sources */,
62959B472524DA7800A3D7F1 /* CAPNotifications.swift in Sources */,
62D43B652582A13D00673C24 /* WKWebView+Capacitor.m in Sources */,
diff --git a/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift b/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift
index 7f510bc5ac..d687953fea 100644
--- a/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift
+++ b/ios/Capacitor/Capacitor/CAPApplicationDelegateProxy.swift
@@ -11,7 +11,6 @@ public class ApplicationDelegateProxy: NSObject, UIApplicationDelegate {
"url": url,
"options": options
])
- NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: url)
lastURL = url
return true
}
diff --git a/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift b/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
index 24ee52a75d..98ac66d965 100644
--- a/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
+++ b/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
@@ -79,6 +79,9 @@ import WebKit
func registerPluginType(_ pluginType: CAPPlugin.Type)
func registerPluginInstance(_ pluginInstance: CAPPlugin)
+ // MARK: - Interceptors
+ func registerCallInterceptor(_ name: String, handler: @escaping ([String: Any]) -> Void)
+
// MARK: - View Presentation
func showAlertWith(title: String, message: String, buttonTitle: String)
@available(*, deprecated, message: "Use self?.bridge?.viewController?.present")
diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h b/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h
deleted file mode 100644
index aeda727d5f..0000000000
--- a/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#import
-
-@interface CAPBridgeViewController (CDVScreenOrientationDelegate)
-
-@end
-
diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m b/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m
deleted file mode 100644
index 94c169a0cc..0000000000
--- a/ios/Capacitor/Capacitor/CAPBridgeViewController+CDVScreenOrientationDelegate.m
+++ /dev/null
@@ -1,5 +0,0 @@
-#import "CAPBridgeViewController+CDVScreenOrientationDelegate.h"
-
-@implementation CAPBridgeViewController (CDVScreenOrientationDelegate)
-
-@end
diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift
index a7285213d8..567d6c69a9 100644
--- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift
+++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift
@@ -1,6 +1,5 @@
import UIKit
import WebKit
-import Cordova
@objc open class CAPBridgeViewController: UIViewController {
private var capacitorBridge: CapacitorBridge?
@@ -47,10 +46,10 @@ import Cordova
// create the bridge
capacitorBridge = CapacitorBridge(with: configuration,
delegate: self,
- cordovaConfiguration: configDescriptor.cordovaConfiguration,
assetHandler: assetHandler,
delegationHandler: delegationHandler)
capacitorDidLoad()
+ updateAppLocationIfNeeded()
if configDescriptor.instanceType == .fixed {
updateBinaryVersion()
@@ -89,18 +88,25 @@ import Cordova
- Note: This is called early in the View Controller's lifecycle. Not all properties will be set at invocation.
*/
open func instanceDescriptor() -> InstanceDescriptor {
- let descriptor = InstanceDescriptor.init()
- if !isNewBinary && !descriptor.cordovaDeployDisabled {
+ return InstanceDescriptor()
+ }
+
+ /// This function must be called after plugins are loaded or it will have no effect.
+ open func updateAppLocationIfNeeded() {
+ let cordovaPlugin = bridge?.plugin(withName: "__CordovaPlugin")
+ let cordovaDeployDisabled = cordovaPlugin?.perform(Selector(("cordovaDeployDisabled"))).takeUnretainedValue() as? Bool ?? false
+
+ if !isNewBinary && !cordovaDeployDisabled {
if let persistedPath = KeyValueStore.standard["serverBasePath", as: String.self], !persistedPath.isEmpty {
if let libPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first {
- descriptor.appLocation = URL(fileURLWithPath: libPath, isDirectory: true)
+ let serverBasePath = URL(fileURLWithPath: libPath, isDirectory: true)
.appendingPathComponent("NoCloud")
.appendingPathComponent("ionic_built_snapshots")
.appendingPathComponent(URL(fileURLWithPath: persistedPath, isDirectory: true).lastPathComponent)
+ setServerBasePath(path: serverBasePath.path)
}
}
}
- return descriptor
}
open func router() -> Router {
diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h
index f477ef55be..3ca447e6f8 100644
--- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h
+++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h
@@ -2,7 +2,7 @@
#define CAPInstanceDescriptor_h
@import UIKit;
-@import Cordova;
+
typedef NS_ENUM(NSInteger, CAPInstanceType) {
CAPInstanceTypeFixed NS_SWIFT_NAME(fixed),
@@ -137,7 +137,7 @@ NS_SWIFT_NAME(InstanceDescriptor)
/**
@brief The parser used to load the cofiguration for Cordova plugins.
*/
-@property (nonatomic, copy, nonnull) CDVConfigParser *cordovaConfiguration;
+@property (nonatomic, copy, nonnull) NSObject *cordovaConfiguration;
/**
@brief Warnings generated during initialization.
*/
diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m
index 2d7a4fba91..acdbc08caf 100644
--- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m
+++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m
@@ -45,7 +45,6 @@ - (void)_setDefaultsWithAppLocation:(NSURL*)location {
_contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
_appLocation = location;
_limitsNavigationsToAppBoundDomains = FALSE;
- _cordovaConfiguration = [[CDVConfigParser alloc] init];
_warnings = 0;
if (location == nil) {
_warnings |= CAPInstanceWarningMissingAppDir;
diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift
index a7eff5f623..495de0634d 100644
--- a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift
+++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift
@@ -52,23 +52,6 @@ internal extension InstanceDescriptor {
warnings.update(with: .missingFile)
}
- // parse the cordova configuration
- var configParser: XMLParser?
- if let cordovaURL = cordovaURL,
- FileManager.default.fileExists(atPath: cordovaURL.path, isDirectory: &isDirectory),
- isDirectory.boolValue == false {
- configParser = XMLParser(contentsOf: cordovaURL)
- } else {
- warnings.update(with: .missingCordovaFile)
- // we don't want to break up string literals
- // swiftlint:disable:next line_length
- if let cordovaXML = "".data(using: .utf8) {
- configParser = XMLParser(data: cordovaXML)
- }
- }
- configParser?.delegate = cordovaConfiguration
- configParser?.parse()
-
// extract our configuration values
if let config = config {
// to be removed
@@ -160,7 +143,7 @@ internal extension InstanceDescriptor {
extension InstanceDescriptor {
@objc public var cordovaDeployDisabled: Bool {
- return (cordovaConfiguration.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false
+ return false
}
@objc public func normalize() {
diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift
index 3a186d4e89..de2beb22e3 100644
--- a/ios/Capacitor/Capacitor/CapacitorBridge.swift
+++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift
@@ -1,12 +1,11 @@
import Foundation
import Dispatch
import WebKit
-import Cordova
internal typealias CapacitorPlugin = CAPPlugin & CAPBridgedPlugin
struct RegistrationList: Codable {
- let packageClassList: Set
+ var packageClassList: Set
}
/**
@@ -113,18 +112,16 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
@objc public var config: InstanceConfiguration
// Map of all loaded and instantiated plugins by pluginId -> instance
var plugins = [String: CapacitorPlugin]()
- // Manager for getting Cordova plugins
- var cordovaPluginManager: CDVPluginManager?
// Calls we are storing to resolve later
var storedCalls = ConcurrentDictionary()
// Whether to inject the Cordova files
- private var injectCordovaFiles = false
- private var cordovaParser: CDVConfigParser?
+ private var cordovaIsPresent = false
private var injectMiscFiles: [String] = []
private var canInjectJS: Bool = true
// Background dispatch queue for plugin calls
open private(set) var dispatchQueue = DispatchQueue(label: "bridge")
+ internal private(set) var callInterceptors: [String: ([String: Any]) -> Void] = [:]
// Array of block based observers
var observers: [NSObjectProtocol] = []
@@ -201,12 +198,17 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
// MARK: - Initialization
- public init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: CDVConfigParser, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler, autoRegisterPlugins: Bool = true) {
+ @available(*, deprecated, renamed: "init", message: "Use different init")
+ public convenience init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: Any, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler, autoRegisterPlugins: Bool = true) {
+ self.init(with: configuration, delegate: bridgeDelegate, assetHandler: assetHandler, delegationHandler: delegationHandler, autoRegisterPlugins: autoRegisterPlugins)
+ }
+
+ public init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler, autoRegisterPlugins: Bool = true) {
+
self.bridgeDelegate = bridgeDelegate
self.webViewAssetHandler = assetHandler
self.webViewDelegationHandler = delegationHandler
self.config = configuration
- self.cordovaParser = cordovaConfiguration
self.notificationRouter = NotificationRouter()
self.notificationRouter.handleApplicationNotifications = configuration.handleApplicationNotifications
self.autoRegisterPlugins = autoRegisterPlugins
@@ -216,7 +218,7 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
exportCoreJS(localUrl: configuration.localURL.absoluteString)
registerPlugins()
- setupCordovaCompatibility()
+ setupListeners()
exportMiscJS()
canInjectJS = false
@@ -255,31 +257,27 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
injectMiscFiles.removeAll()
}
- /**
- Set up our Cordova compat by loading all known Cordova plugins and injecting their JS.
- */
- func setupCordovaCompatibility() {
- if injectCordovaFiles {
- exportCordovaJS()
- registerCordovaPlugins()
- } else {
- observers.append(NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in
- self?.triggerDocumentJSEvent(eventName: "resume")
- })
- observers.append(NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: OperationQueue.main) { [weak self] (_) in
- self?.triggerDocumentJSEvent(eventName: "pause")
- })
- }
- }
-
- /**
- Export the core Cordova JS runtime
- */
- func exportCordovaJS() {
- do {
- try JSExport.exportCordovaJS(userContentController: webViewDelegationHandler.contentController)
- } catch {
- type(of: self).fatalError(error, error)
+ func setupListeners() {
+ if !cordovaIsPresent {
+ observers.append(
+ NotificationCenter.default.addObserver(
+ forName: UIApplication.willEnterForegroundNotification,
+ object: nil,
+ queue: .main
+ ) { [weak self] _ in
+ self?.triggerDocumentJSEvent(eventName: "resume")
+ }
+ )
+
+ observers.append(
+ NotificationCenter.default.addObserver(
+ forName: UIApplication.didEnterBackgroundNotification,
+ object: nil,
+ queue: .main
+ ) { [weak self] _ in
+ self?.triggerDocumentJSEvent(eventName: "pause")
+ }
+ )
}
}
@@ -302,14 +300,13 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
do {
if let pluginJSON = Bundle.main.url(forResource: "capacitor.config", withExtension: "json") {
let pluginData = try Data(contentsOf: pluginJSON)
- let registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData)
+ var registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData)
for plugin in registrationList.packageClassList {
- if let pluginClass = NSClassFromString(plugin) {
- if pluginClass == CDVPlugin.self {
- injectCordovaFiles = true
- } else {
- pluginList.append(pluginClass)
+ if let pluginClass = NSClassFromString(plugin), pluginClass is CAPPlugin.Type {
+ pluginList.append(pluginClass)
+ if plugin == "CordovaPlugin" {
+ cordovaIsPresent = true
}
}
}
@@ -386,6 +383,10 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
// MARK: - CAPBridgeProtocol: Call Management
+ public func registerCallInterceptor(_ name: String, handler: @escaping ([String: Any]) -> Void) {
+ callInterceptors[name] = handler
+ }
+
@objc public func saveCall(_ call: CAPPluginCall) {
storedCalls[call.callbackId] = call
}
@@ -418,23 +419,6 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
return self.dispatchQueue
}
- func registerCordovaPlugins() {
- guard let cordovaParser = cordovaParser else {
- return
- }
- cordovaPluginManager = CDVPluginManager.init(parser: cordovaParser, viewController: self.viewController, webView: self.getWebView())
- if cordovaParser.startupPluginNames.count > 0 {
- for pluginName in cordovaParser.startupPluginNames {
- _ = cordovaPluginManager?.getCommandInstance(pluginName as? String)
- }
- }
- do {
- try JSExport.exportCordovaPluginsJS(userContentController: webViewDelegationHandler.contentController)
- } catch {
- type(of: self).fatalError(error, error)
- }
- }
-
func reload() {
self.getWebView()?.reload()
}
@@ -533,34 +517,6 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
}
}
- /**
- Handle a Cordova call from JavaScript. First, find the corresponding plugin,
- construct a selector, and perform that selector on the plugin instance.
- */
- func handleCordovaJSCall(call: JSCall) {
- // Create a selector to send to the plugin
-
- if let plugin = self.cordovaPluginManager?.getCommandInstance(call.pluginId.lowercased()) {
- let selector = NSSelectorFromString("\(call.method):")
- if !plugin.responds(to: selector) {
- CAPLog.print("Error: Plugin \(plugin.className ?? "") does not respond to method call \(selector).")
- CAPLog.print("Ensure plugin method exists and uses @objc in its declaration")
- return
- }
-
- let arguments: [Any] = call.options["options"] as? [Any] ?? []
- let pluginCall = CDVInvokedUrlCommand(arguments: arguments,
- callbackId: call.callbackId,
- className: plugin.className,
- methodName: call.method)
- plugin.perform(selector, with: pluginCall)
-
- } else {
- CAPLog.print("Error: Cordova Plugin mapping not found")
- return
- }
- }
-
func removeAllPluginListeners() {
for plugin in plugins.values {
plugin.perform(#selector(CAPPlugin.removeAllListeners(_:)), with: nil)
diff --git a/ios/Capacitor/Capacitor/JS.swift b/ios/Capacitor/Capacitor/JS.swift
index 46972c48a5..b1198b1786 100644
--- a/ios/Capacitor/Capacitor/JS.swift
+++ b/ios/Capacitor/Capacitor/JS.swift
@@ -14,11 +14,11 @@ public typealias JSResultBody = [String: Any]
/**
* A call originating from JavaScript land
*/
-internal struct JSCall {
- let options: [String: Any]
- let pluginId: String
- let method: String
- let callbackId: String
+public struct JSCall {
+ public let options: [String: Any]
+ public let pluginId: String
+ public let method: String
+ public let callbackId: String
}
internal protocol JSResultProtocol {
diff --git a/ios/Capacitor/Capacitor/JSExport.swift b/ios/Capacitor/Capacitor/JSExport.swift
index f6934c17a7..c51da733df 100644
--- a/ios/Capacitor/Capacitor/JSExport.swift
+++ b/ios/Capacitor/Capacitor/JSExport.swift
@@ -35,24 +35,6 @@ internal class JSExport {
}
}
- static func exportCordovaJS(userContentController: WKUserContentController) throws {
- guard let cordovaUrl = Bundle.main.url(forResource: "public/cordova", withExtension: "js") else {
- CAPLog.print("ERROR: Required cordova.js file not found. Cordova plugins will not function!")
- throw CapacitorBridgeError.errorExportingCoreJS
- }
- guard let cordovaPluginsUrl = Bundle.main.url(forResource: "public/cordova_plugins", withExtension: "js") else {
- CAPLog.print("ERROR: Required cordova_plugins.js file not found. Cordova plugins will not function!")
- throw CapacitorBridgeError.errorExportingCoreJS
- }
- do {
- try self.injectFile(fileURL: cordovaUrl, userContentController: userContentController)
- try self.injectFile(fileURL: cordovaPluginsUrl, userContentController: userContentController)
- } catch {
- CAPLog.print("ERROR: Unable to read required cordova files. Cordova plugins will not function!")
- throw CapacitorBridgeError.errorExportingCoreJS
- }
- }
-
static func exportMiscFileJS(paths: [String], userContentController: WKUserContentController) {
for path in paths {
if let miscJSFilePath = Bundle.main.url(forResource: "public/\(path.replacingOccurrences(of: ".js", with: ""))", withExtension: "js") {
@@ -184,28 +166,6 @@ internal class JSExport {
return lines.joined(separator: "\n")
}
- static func exportCordovaPluginsJS(userContentController: WKUserContentController) throws {
- if let pluginsJSFolder = Bundle.main.url(forResource: "public/plugins", withExtension: nil) {
- self.injectFilesForFolder(folder: pluginsJSFolder, userContentController: userContentController)
- }
- }
-
- static func injectFilesForFolder(folder: URL, userContentController: WKUserContentController) {
- let fileManager = FileManager.default
- do {
- let fileURLs = try fileManager.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil, options: [])
- for fileURL in fileURLs {
- if fileURL.hasDirectoryPath {
- injectFilesForFolder(folder: fileURL, userContentController: userContentController)
- } else {
- try self.injectFile(fileURL: fileURL, userContentController: userContentController)
- }
- }
- } catch {
- CAPLog.print("Error while enumerating files")
- }
- }
-
static func injectFile(fileURL: URL, userContentController: WKUserContentController) throws {
do {
let data = try String(contentsOf: fileURL, encoding: .utf8)
diff --git a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift
index 2b4a256d00..37deace97b 100644
--- a/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift
+++ b/ios/Capacitor/Capacitor/WebViewDelegationHandler.swift
@@ -208,17 +208,8 @@ open class WebViewDelegationHandler: NSObject, WKNavigationDelegate, WKUIDelegat
}
bridge.handleJSCall(call: JSCall(options: options, pluginId: pluginId, method: method, callbackId: callbackId))
- } else if type == "cordova" {
- let pluginId = dict["service"] as? String ?? ""
- let method = dict["action"] as? String ?? ""
- let callbackId = dict["callbackId"] as? String ?? ""
-
- let args = dict["actionArgs"] as? Array ?? []
- let options = ["options": args]
-
- CAPLog.print("To Native Cordova -> ", pluginId, method, callbackId, options)
-
- bridge.handleCordovaJSCall(call: JSCall(options: options, pluginId: pluginId, method: method, callbackId: callbackId))
+ } else if let handler = bridge.callInterceptors[type] {
+ handler(dict)
}
}
}
diff --git a/ios/Capacitor/CapacitorTests/ConfigurationTests.swift b/ios/Capacitor/CapacitorTests/ConfigurationTests.swift
index 778bfd688d..177f211e7d 100644
--- a/ios/Capacitor/CapacitorTests/ConfigurationTests.swift
+++ b/ios/Capacitor/CapacitorTests/ConfigurationTests.swift
@@ -33,7 +33,6 @@ class ConfigurationTests: XCTestCase {
let descriptor = InstanceDescriptor.init()
XCTAssertTrue(descriptor.warnings.contains(.missingAppDir))
XCTAssertTrue(descriptor.warnings.contains(.missingFile))
- XCTAssertTrue(descriptor.warnings.contains(.missingCordovaFile))
}
func testMissingAppDetection() throws {
diff --git a/ios/Capacitor/CapacitorTests/PluginCallAccessorTests.m b/ios/Capacitor/CapacitorTests/PluginCallAccessorTests.m
index 0295cfa822..4d665aeec1 100644
--- a/ios/Capacitor/CapacitorTests/PluginCallAccessorTests.m
+++ b/ios/Capacitor/CapacitorTests/PluginCallAccessorTests.m
@@ -21,7 +21,7 @@ - (void)setUp {
@"testDateString": [formatter stringFromDate:date],
@"testBoolTrue": @TRUE,
@"testBoolFalse": @FALSE};
- [self setCall:[[CAPPluginCall alloc] initWithCallbackId:@"test" options:options success:NULL error:NULL]];
+ [self setCall:[[CAPPluginCall alloc] initWithCallbackId:@"test" methodName:@"test" options:options success:NULL error:NULL]];
}
- (void)testStringAccessor {
diff --git a/ios/CapacitorCordova.podspec b/ios/CapacitorCordova.podspec
index 65fcce86d7..b4aa014f30 100644
--- a/ios/CapacitorCordova.podspec
+++ b/ios/CapacitorCordova.podspec
@@ -16,11 +16,12 @@ Pod::Spec.new do |s|
s.authors = { 'Ionic Team' => 'hi@ionicframework.com' }
s.source = { git: 'https://github.com/ionic-team/capacitor', tag: s.version.to_s }
s.platform = :ios, 15.0
- s.source_files = "#{prefix}CapacitorCordova/CapacitorCordova/**/*.{h,m}"
+ s.source_files = "#{prefix}CapacitorCordova/CapacitorCordova/**/*.{h,m,swift}"
s.public_header_files = "#{prefix}CapacitorCordova/CapacitorCordova/Classes/Public/*.h",
"#{prefix}CapacitorCordova/CapacitorCordova/CapacitorCordova.h"
s.module_map = "#{prefix}CapacitorCordova/CapacitorCordova/CapacitorCordova.modulemap"
s.resource_bundles = { 'CapacitorCordova' => ["#{prefix}CapacitorCordova/CapacitorCordova/PrivacyInfo.xcprivacy"] }
s.requires_arc = true
+ s.dependency 'Capacitor', s.version.to_s
s.framework = 'WebKit'
end
diff --git a/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift
new file mode 100644
index 0000000000..d58c4ca33f
--- /dev/null
+++ b/ios/CapacitorCordova/CapacitorCordova/Classes/Public/Plugin.swift
@@ -0,0 +1,126 @@
+import Capacitor
+
+@objc(CordovaPlugin)
+public class CordovaPlugin: CAPPlugin, CAPBridgedPlugin {
+ public let jsName = "__CordovaPlugin"
+ public let pluginMethods: [CAPPluginMethod] = []
+ public var identifier: String { jsName }
+
+ private var _cordovaDeployDisabled = false
+
+ override public func load() {
+ injectJavascript()
+ configureRuntime()
+ }
+
+ func configureRuntime() {
+ guard let configURL = Bundle.main.url(forResource: "config", withExtension: "xml") else { fatalError() }
+ guard let bridge, let webView = bridge.webView else { return }
+
+ let cordovaConfigParser = CDVConfigParser()
+
+ let xmlParser = XMLParser(contentsOf: configURL)
+ xmlParser?.delegate = cordovaConfigParser
+ xmlParser?.parse()
+
+ guard let pluginManager = CDVPluginManager(
+ parser: cordovaConfigParser,
+ viewController: bridge.viewController,
+ webView: bridge.webView
+ ) else { return }
+
+ for plugin in cordovaConfigParser.startupPluginNames.compactMap({ $0 as? String }) {
+ _ = pluginManager.getCommandInstance(plugin)
+ }
+
+ exportCordovaPluginsJS(userContentController: webView.configuration.userContentController)
+
+ bridge.registerCallInterceptor("cordova") { [pluginManager] dict in
+ let pluginId = dict["service"] as? String ?? ""
+ let method = dict["action"] as? String ?? ""
+ let callbackId = dict["callbackId"] as? String ?? ""
+
+ let args = dict["actionArgs"] as? Array ?? []
+ let options = ["options": args]
+
+ CAPLog.print("To Native Cordova -> ", pluginId, method, callbackId, options)
+
+ if let plugin = pluginManager.getCommandInstance(pluginId.lowercased()) {
+ let selector = NSSelectorFromString("\(method):")
+ if !plugin.responds(to: selector) {
+ CAPLog.print("Error: Plugin \(plugin.className ?? "") does not respond to method call \(selector).")
+ CAPLog.print("Ensure plugin method exists and uses @objc in its declaration")
+ return
+ }
+
+ let arguments = options["options"] ?? []
+ let pluginCall = CDVInvokedUrlCommand(
+ arguments: arguments,
+ callbackId: callbackId,
+ className: plugin.className,
+ methodName: method
+ )
+
+ plugin.perform(selector, with: pluginCall)
+
+ } else {
+ CAPLog.print("Error: Cordova Plugin mapping not found")
+ return
+ }
+ }
+
+ _cordovaDeployDisabled = (cordovaConfigParser.settings?["DisableDeploy".lowercased()] as? NSString)?.boolValue ?? false
+ }
+
+ @objc
+ func cordovaDeployDisabled() -> NSNumber {
+ return _cordovaDeployDisabled as NSNumber
+ }
+
+ func injectJavascript() {
+ guard let cordovaUrl = Bundle.main.url(forResource: "public/cordova", withExtension: "js") else {
+ fatalError("ERROR: Required cordova.js file not found. Cordova plugins will not function!")
+ }
+
+ guard let cordovaPluginsUrl = Bundle.main.url(forResource: "public/cordova_plugins", withExtension: "js") else {
+ fatalError("ERROR: Required cordova_plugins.js file not found. Cordova plugins will not function!")
+ }
+
+ guard let webView = bridge?.webView else { return }
+
+ injectFile(fileURL: cordovaUrl, userContentController: webView.configuration.userContentController)
+ injectFile(fileURL: cordovaPluginsUrl, userContentController: webView.configuration.userContentController)
+ }
+
+ func exportCordovaPluginsJS(userContentController: WKUserContentController) {
+ if let pluginsJSFolder = Bundle.main.url(forResource: "public/plugins", withExtension: nil) {
+ injectFilesForFolder(folder: pluginsJSFolder, userContentController: userContentController)
+ }
+ }
+
+ func injectFile(fileURL: URL, userContentController: WKUserContentController) {
+ do {
+ let data = try String(contentsOf: fileURL, encoding: .utf8)
+ let userScript = WKUserScript(source: data, injectionTime: .atDocumentStart, forMainFrameOnly: true)
+ userContentController.addUserScript(userScript)
+ } catch {
+ fatalError("Unable to inject js file")
+ }
+ }
+
+ func injectFilesForFolder(folder: URL, userContentController: WKUserContentController) {
+ let fileManager = FileManager.default
+ do {
+ let fileURLs = try fileManager.contentsOfDirectory(at: folder, includingPropertiesForKeys: nil, options: [])
+ for fileURL in fileURLs {
+ if fileURL.hasDirectoryPath {
+ injectFilesForFolder(folder: fileURL, userContentController: userContentController)
+ } else {
+ injectFile(fileURL: fileURL, userContentController: userContentController)
+ }
+ }
+ } catch {
+ CAPLog.print("Error while enumerating files")
+ }
+ }
+}
From dd83b9920788d7c976475263e2befce95a7fbb54 Mon Sep 17 00:00:00 2001
From: Mark Anderson
Date: Thu, 19 Mar 2026 12:27:24 -0400
Subject: [PATCH 13/69] Upgrade to major version pre-release
---
android/package.json | 4 ++--
cli/package.json | 2 +-
core/package.json | 2 +-
ios/package.json | 4 ++--
lerna.json | 2 +-
5 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/android/package.json b/android/package.json
index 9555484901..d1f6155219 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "8.2.0",
+ "version": "9.0.0-dev.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -23,7 +23,7 @@
"verify": "./gradlew clean lint build test -b capacitor/build.gradle"
},
"peerDependencies": {
- "@capacitor/core": "^8.2.0"
+ "@capacitor/core": "^9.0.0-dev.0"
},
"publishConfig": {
"access": "public"
diff --git a/cli/package.json b/cli/package.json
index d488db7dbc..9f9d89eed9 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "8.2.0",
+ "version": "9.0.0-dev.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/package.json b/core/package.json
index 98e5b1b7e0..674c151eff 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "8.2.0",
+ "version": "9.0.0-dev.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/package.json b/ios/package.json
index 86e5c661d6..477e3c881b 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "8.2.0",
+ "version": "9.0.0-dev.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -25,7 +25,7 @@
"xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd .."
},
"peerDependencies": {
- "@capacitor/core": "^8.2.0"
+ "@capacitor/core": "^9.0.0-dev.0"
},
"publishConfig": {
"access": "public"
diff --git a/lerna.json b/lerna.json
index 1b708a45da..cac40342b5 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "8.2.0",
+ "version": "9.0.0-dev.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From 05730dedb2283d7a4d03ece908711e822e91dde4 Mon Sep 17 00:00:00 2001
From: Pedro Bilro
Date: Mon, 23 Mar 2026 18:16:46 +0000
Subject: [PATCH 14/69] fix(cli)!: Remove default port for --live-reload
(#8376)
---
cli/src/tasks/run.ts | 3 ---
1 file changed, 3 deletions(-)
diff --git a/cli/src/tasks/run.ts b/cli/src/tasks/run.ts
index 2abdb9cd2c..8abe6fa47e 100644
--- a/cli/src/tasks/run.ts
+++ b/cli/src/tasks/run.ts
@@ -43,9 +43,6 @@ export async function runCommand(
options: RunCommandOptions,
): Promise {
options.host = options.host ?? CapLiveReloadHelper.getIpAddress() ?? 'localhost';
- if (!options.https && !options.port) {
- options.port = '3000';
- }
if (selectedPlatformName && !(await isValidPlatform(selectedPlatformName))) {
const platformDir = resolvePlatform(config, selectedPlatformName);
if (platformDir) {
From 65ad1fd55b9793a3e078e4a837a00e2f28c75c17 Mon Sep 17 00:00:00 2001
From: Pedro Bilro
Date: Fri, 24 Apr 2026 10:36:02 +0100
Subject: [PATCH 15/69] fix(android): Handle activity result without plugin
handle (#8436)
---
android/capacitor/src/main/java/com/getcapacitor/Bridge.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
index 4868db3d0e..0a787ffe29 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
@@ -1270,6 +1270,7 @@ boolean onActivityResult(int requestCode, int resultCode, Intent data) {
// This is a bit hacky but required to return the boolean out of the cordova interface
return cordovaPlugin.hasRequiredPermissions();
}
+ return false;
}
// deprecated, to be removed
From e37aad87bb22afc6d974190e0a59221dcc863b5b Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Wed, 29 Apr 2026 19:52:12 +0200
Subject: [PATCH 16/69] refactor(android-template)!: remove
capacitor-cordova-android-plugins module (#8446)
---
android-template/app/build.gradle | 7 -------
android-template/settings.gradle | 2 --
2 files changed, 9 deletions(-)
diff --git a/android-template/app/build.gradle b/android-template/app/build.gradle
index 363eb2f77d..b701096d4b 100644
--- a/android-template/app/build.gradle
+++ b/android-template/app/build.gradle
@@ -24,12 +24,6 @@ android {
}
}
-repositories {
- flatDir{
- dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
- }
-}
-
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
@@ -39,7 +33,6 @@ dependencies {
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
- implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
diff --git a/android-template/settings.gradle b/android-template/settings.gradle
index 3b4431d772..fb68af1dfb 100644
--- a/android-template/settings.gradle
+++ b/android-template/settings.gradle
@@ -1,5 +1,3 @@
include ':app'
-include ':capacitor-cordova-android-plugins'
-project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
apply from: 'capacitor.settings.gradle'
\ No newline at end of file
From 9e85905bf36c1e2300994e231cf10e829f2074ed Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Thu, 30 Apr 2026 10:11:41 +0200
Subject: [PATCH 17/69] fix(cli): conditionally add
capacitor-cordova-android-plugins module (#8449)
---
cli/src/android/update.ts | 39 ++++++++++++++++++++++++++++++---------
1 file changed, 30 insertions(+), 9 deletions(-)
diff --git a/cli/src/android/update.ts b/cli/src/android/update.ts
index fe22659b1a..d2e5fcabd1 100644
--- a/cli/src/android/update.ts
+++ b/cli/src/android/update.ts
@@ -51,8 +51,10 @@ export async function updateAndroid(config: Config): Promise {
await handleCordovaPluginsJS(cordovaPlugins, config, platform);
await checkPluginDependencies(plugins, platform, config.app.extConfig.cordova?.failOnUninstalledPlugins);
await installGradlePlugins(config, capacitorPlugins, cordovaPlugins);
- await handleCordovaPluginsGradle(config, cordovaPlugins);
- await writeCordovaAndroidManifest(cordovaPlugins, config, platform);
+ if (enableCordova) {
+ await handleCordovaPluginsGradle(config, cordovaPlugins);
+ await writeCordovaAndroidManifest(cordovaPlugins, config, platform);
+ }
const incompatibleCordovaPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Incompatible);
printPlugins(incompatibleCordovaPlugins, platform, 'incompatible');
@@ -155,6 +157,13 @@ export async function installGradlePlugins(
);
}
+ const enableCordova = cordovaPlugins.length > 0;
+ const cdvIncludeText = enableCordova
+ ? `
+include ':capacitor-cordova-android-plugins'
+project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')`
+ : '';
+
const capacitorAndroidPath = resolve(dirname(capacitorAndroidPackagePath), 'capacitor');
const settingsPath = config.android.platformDirAbs;
@@ -163,6 +172,7 @@ export async function installGradlePlugins(
const settingsLines = `// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('${relativeCapcitorAndroidPath}')
+${cdvIncludeText}
${capacitorPlugins
.map((p) => {
if (!p.android) {
@@ -202,6 +212,18 @@ project(':${getGradlePackageName(p.id)}').projectDir = new File('${relativePlugi
});
prefsArray = prefsArray.concat(getAllElements(p, platform, 'preference'));
});
+ const cdvRepoApplyText = enableCordova
+ ? `
+repositories {
+ flatDir{
+ dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
+ }
+}
+
+apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
+`
+ : '';
+ const cdvDependencyText = enableCordova ? ` implementation project(':capacitor-cordova-android-plugins')\n` : '';
let frameworkString = frameworksArray.join('\n');
frameworkString = await replaceFrameworkVariables(config, prefsArray, frameworkString);
const dependencyLines = `// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
@@ -212,14 +234,13 @@ android {
targetCompatibility JavaVersion.VERSION_21
}
}
-
-apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
+${cdvRepoApplyText}
dependencies {
-${capacitorPlugins
- .map((p) => {
- return ` implementation project(':${getGradlePackageName(p.id)}')`;
- })
- .join('\n')}
+${cdvDependencyText}${capacitorPlugins
+ .map((p) => {
+ return ` implementation project(':${getGradlePackageName(p.id)}')`;
+ })
+ .join('\n')}
${frameworkString}
}
${applyArray.join('\n')}
From e136b3f0cdd1437cac5234576301ed388edf85e7 Mon Sep 17 00:00:00 2001
From: "Alex J."
Date: Tue, 5 May 2026 10:22:40 -0400
Subject: [PATCH 18/69] fix(android): conditionally add Cordova (#8453)
---
android/capacitor-cordova/build.gradle | 45 +++++++++++++++++++
android/capacitor-cordova/proguard-rules.pro | 4 ++
.../src/main/AndroidManifest.xml | 3 ++
.../CapacitorCordovaCookieManager.java | 0
.../getcapacitor/cordova/CordovaPlugin.java | 1 -
.../cordova/MockCordovaInterfaceImpl.java | 0
.../cordova/MockCordovaWebViewImpl.java | 0
android/capacitor/build.gradle | 4 +-
android/capacitor/proguard-rules.pro | 6 ---
.../main/java/com/getcapacitor/Bridge.java | 2 +-
android/package.json | 5 ++-
android/settings.gradle | 5 +++
cli/src/android/update.ts | 19 +++++---
13 files changed, 76 insertions(+), 18 deletions(-)
create mode 100644 android/capacitor-cordova/build.gradle
create mode 100644 android/capacitor-cordova/proguard-rules.pro
create mode 100644 android/capacitor-cordova/src/main/AndroidManifest.xml
rename android/{capacitor => capacitor-cordova}/src/main/java/com/getcapacitor/cordova/CapacitorCordovaCookieManager.java (100%)
rename android/{capacitor => capacitor-cordova}/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java (99%)
rename android/{capacitor => capacitor-cordova}/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java (100%)
rename android/{capacitor => capacitor-cordova}/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java (100%)
diff --git a/android/capacitor-cordova/build.gradle b/android/capacitor-cordova/build.gradle
new file mode 100644
index 0000000000..717c6b1aa2
--- /dev/null
+++ b/android/capacitor-cordova/build.gradle
@@ -0,0 +1,45 @@
+ext {
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
+ cordovaAndroidVersion = project.hasProperty('cordovaAndroidVersion') ? rootProject.ext.cordovaAndroidVersion : '14.0.1'
+}
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.13.0'
+ }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+ namespace = "com.getcapacitor.cordova"
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
+ defaultConfig {
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
+ versionCode 1
+ versionName "1.0"
+ consumerProguardFiles 'proguard-rules.pro'
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_21
+ targetCompatibility JavaVersion.VERSION_21
+ }
+ publishing {
+ singleVariant("release")
+ }
+}
+
+repositories {
+ google()
+ mavenCentral()
+}
+
+dependencies {
+ implementation project(':capacitor-android')
+ implementation "org.apache.cordova:framework:$cordovaAndroidVersion"
+}
diff --git a/android/capacitor-cordova/proguard-rules.pro b/android/capacitor-cordova/proguard-rules.pro
new file mode 100644
index 0000000000..9a21635529
--- /dev/null
+++ b/android/capacitor-cordova/proguard-rules.pro
@@ -0,0 +1,4 @@
+-keep public class * extends org.apache.cordova.* {
+ public ;
+ public ;
+}
diff --git a/android/capacitor-cordova/src/main/AndroidManifest.xml b/android/capacitor-cordova/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..9a40236b94
--- /dev/null
+++ b/android/capacitor-cordova/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/CapacitorCordovaCookieManager.java b/android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/CapacitorCordovaCookieManager.java
similarity index 100%
rename from android/capacitor/src/main/java/com/getcapacitor/cordova/CapacitorCordovaCookieManager.java
rename to android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/CapacitorCordovaCookieManager.java
diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java b/android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
similarity index 99%
rename from android/capacitor/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
rename to android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
index efe101034d..5fe4e74b5b 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
+++ b/android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/CordovaPlugin.java
@@ -5,7 +5,6 @@
import android.webkit.WebView;
import com.getcapacitor.Logger;
import com.getcapacitor.Plugin;
-import com.getcapacitor.android.R;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.util.List;
import org.apache.cordova.ConfigXmlParser;
diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java b/android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java
similarity index 100%
rename from android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java
rename to android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java
diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java b/android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
similarity index 100%
rename from android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
rename to android/capacitor-cordova/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
diff --git a/android/capacitor/build.gradle b/android/capacitor/build.gradle
index 6ece3eda05..5841ded35d 100644
--- a/android/capacitor/build.gradle
+++ b/android/capacitor/build.gradle
@@ -8,7 +8,6 @@ ext {
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
- cordovaAndroidVersion = project.hasProperty('cordovaAndroidVersion') ? rootProject.ext.cordovaAndroidVersion : '14.0.1'
}
@@ -87,8 +86,7 @@ dependencies {
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
- implementation "org.apache.cordova:framework:$cordovaAndroidVersion"
- testImplementation 'org.json:json:20250517'
+testImplementation 'org.json:json:20250517'
testImplementation 'org.mockito:mockito-core:5.20.0'
}
diff --git a/android/capacitor/proguard-rules.pro b/android/capacitor/proguard-rules.pro
index 96db065b39..d34e7df58e 100644
--- a/android/capacitor/proguard-rules.pro
+++ b/android/capacitor/proguard-rules.pro
@@ -20,9 +20,3 @@
-keep @com.getcapacitor.NativePlugin public class * {
@com.getcapacitor.PluginMethod public ;
}
-
-# Rules for Cordova plugins
--keep public class * extends org.apache.cordova.* {
- public ;
- public ;
-}
\ No newline at end of file
diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
index 0a787ffe29..0ca600aff6 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
@@ -418,7 +418,7 @@ private boolean isNewBinary() {
}
private Plugin cordova() {
- PluginHandle handle = getPlugin("__CordovaHandle");
+ PluginHandle handle = getPlugin("__CordovaPlugin");
if (handle != null) {
return handle.getInstance();
}
diff --git a/android/package.json b/android/package.json
index d1f6155219..483d629955 100644
--- a/android/package.json
+++ b/android/package.json
@@ -17,7 +17,10 @@
"capacitor/lint-baseline.xml",
"capacitor/lint.xml",
"capacitor/proguard-rules.pro",
- "capacitor/src/main/"
+ "capacitor/src/main/",
+ "capacitor-cordova/build.gradle",
+ "capacitor-cordova/proguard-rules.pro",
+ "capacitor-cordova/src/main/"
],
"scripts": {
"verify": "./gradlew clean lint build test -b capacitor/build.gradle"
diff --git a/android/settings.gradle b/android/settings.gradle
index e69de29bb2..85956d0c11 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -0,0 +1,5 @@
+include ':capacitor-android'
+project(':capacitor-android').projectDir = new File('capacitor')
+
+include ':capacitor-cordova-android'
+project(':capacitor-cordova-android').projectDir = new File('capacitor-cordova')
diff --git a/cli/src/android/update.ts b/cli/src/android/update.ts
index d2e5fcabd1..5053f639d5 100644
--- a/cli/src/android/update.ts
+++ b/cli/src/android/update.ts
@@ -158,17 +158,22 @@ export async function installGradlePlugins(
}
const enableCordova = cordovaPlugins.length > 0;
- const cdvIncludeText = enableCordova
- ? `
-include ':capacitor-cordova-android-plugins'
-project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')`
- : '';
const capacitorAndroidPath = resolve(dirname(capacitorAndroidPackagePath), 'capacitor');
+ const capacitorCordovaAndroidPath = resolve(dirname(capacitorAndroidPackagePath), 'capacitor-cordova');
const settingsPath = config.android.platformDirAbs;
const dependencyPath = config.android.appDirAbs;
const relativeCapcitorAndroidPath = convertToUnixPath(relative(settingsPath, capacitorAndroidPath));
+ const relativeCapacitorCordovaAndroidPath = convertToUnixPath(relative(settingsPath, capacitorCordovaAndroidPath));
+ const cdvIncludeText = enableCordova
+ ? `
+include ':capacitor-cordova-android'
+project(':capacitor-cordova-android').projectDir = new File('${relativeCapacitorCordovaAndroidPath}')
+
+include ':capacitor-cordova-android-plugins'
+project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')`
+ : '';
const settingsLines = `// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('${relativeCapcitorAndroidPath}')
@@ -223,7 +228,9 @@ repositories {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
`
: '';
- const cdvDependencyText = enableCordova ? ` implementation project(':capacitor-cordova-android-plugins')\n` : '';
+ const cdvDependencyText = enableCordova
+ ? ` implementation project(':capacitor-cordova-android')\n implementation project(':capacitor-cordova-android-plugins')\n`
+ : '';
let frameworkString = frameworksArray.join('\n');
frameworkString = await replaceFrameworkVariables(config, prefsArray, frameworkString);
const dependencyLines = `// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
From f1e9e15e0474f807513ca6cf793df7a283f311b5 Mon Sep 17 00:00:00 2001
From: Mark Anderson
Date: Thu, 7 May 2026 09:56:41 -0400
Subject: [PATCH 19/69] chore: fixed errors introduced by merge
---
cli/src/ios/update.ts | 11 +----------
cli/src/util/cordova-ios.ts | 5 +++--
2 files changed, 4 insertions(+), 12 deletions(-)
diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts
index afdd389c61..4bce181464 100644
--- a/cli/src/ios/update.ts
+++ b/cli/src/ios/update.ts
@@ -8,12 +8,7 @@ import type { Config } from '../definitions';
import { fatal } from '../errors';
import { logger } from '../log';
import type { Plugin } from '../plugin';
-import {
- PluginType,
- getPluginType,
- getPlugins,
- printPlugins,
-} from '../plugin';
+import { PluginType, getPluginType, getPlugins, printPlugins } from '../plugin';
import { copy as copyTask } from '../tasks/copy';
import {
generateCordovaPodspecs,
@@ -22,8 +17,6 @@ import {
removePluginsNativeFiles,
cordovaPodfileLines,
} from '../util/cordova-ios';
-
-import { setAllStringIn } from '../tasks/migrate';
import { convertToUnixPath } from '../util/fs';
import { generateIOSPackageJSON } from '../util/iosplugin';
import { resolveNode } from '../util/node';
@@ -186,5 +179,3 @@ async function getPluginsTask(config: Config) {
return iosPlugins;
});
}
-
-
diff --git a/cli/src/util/cordova-ios.ts b/cli/src/util/cordova-ios.ts
index f16bffee98..5fc3c1a8c4 100644
--- a/cli/src/util/cordova-ios.ts
+++ b/cli/src/util/cordova-ios.ts
@@ -169,10 +169,10 @@ function getLinkerFlags(config: Config) {
return '';
}
-export async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[]) {
+export async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[]): Promise {
const isSPM = (await config.ios.packageManager) === 'SPM';
for (const p of cordovaPlugins) {
- const platformTag = getPluginPla tform(p, platform);
+ const platformTag = getPluginPlatform(p, platform);
if (platformTag.$?.package) {
continue;
}
@@ -245,6 +245,7 @@ export async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plu
}
}
}
+
export async function removePluginsNativeFiles(config: Config): Promise {
await remove(config.ios.cordovaPluginsDirAbs);
await extractTemplate(config.cli.assets.ios.cordovaPluginsTemplateArchiveAbs, config.ios.cordovaPluginsDirAbs);
From 23ad7f8a4231a3b78abec32bb67bbd0c415b5011 Mon Sep 17 00:00:00 2001
From: "Github Workflow (on behalf of markemer)"
Date: Thu, 7 May 2026 16:55:19 +0000
Subject: [PATCH 20/69] Release 8.3.2
---
CHANGELOG.md | 14 ++++++++++++++
android/CHANGELOG.md | 6 ++++++
android/package.json | 2 +-
cli/CHANGELOG.md | 13 +++++++++++++
cli/package.json | 2 +-
core/CHANGELOG.md | 4 ++++
core/package.json | 2 +-
ios/CHANGELOG.md | 4 ++++
ios/package.json | 2 +-
lerna.json | 2 +-
10 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 95ee704f66..3851a6663d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
+
+### Bug Fixes
+
+- **cli:** add cSettings support for compiler flags in generated Package.swift ([#8448](https://github.com/ionic-team/capacitor/issues/8448)) ([0bd0676](https://github.com/ionic-team/capacitor/commit/0bd0676315c5fd77e50312dd7b5bf4990dcbd7d0))
+- **cli:** add system framework and weak framework support in SPM Package.swift ([#8447](https://github.com/ionic-team/capacitor/issues/8447)) ([3232f0f](https://github.com/ionic-team/capacitor/commit/3232f0fe1d9811b0b5c500e3dc05cb8a250177f8))
+- **cli:** correct Capacitor plugin SPM compat check ([#8440](https://github.com/ionic-team/capacitor/issues/8440)) ([e5ccc45](https://github.com/ionic-team/capacitor/commit/e5ccc451dda27d56bca824ed644bd20fe4d988cb))
+- **cli:** generate binaryTarget entries for custom xcframeworks in Package.swift ([#8445](https://github.com/ionic-team/capacitor/issues/8445)) ([1f7e33f](https://github.com/ionic-team/capacitor/commit/1f7e33fca43d183332ec19d22b0d75ef81d8cc6d))
+- **cli:** generate resource entries in Package.swift ([#8455](https://github.com/ionic-team/capacitor/issues/8455)) ([790bd27](https://github.com/ionic-team/capacitor/commit/790bd27123497111984227010c3162cec94a108e))
+- **cli:** handle Cordova plugins without iOS source files ([#8443](https://github.com/ionic-team/capacitor/issues/8443)) ([0da130e](https://github.com/ionic-team/capacitor/commit/0da130eb7a861bee4e2c35bc0aac53ba9c983fc3))
+- **cli:** link plugin dependencies in Package.swift ([#8457](https://github.com/ionic-team/capacitor/issues/8457)) ([b3c769e](https://github.com/ionic-team/capacitor/commit/b3c769e856c826b1174518877cf86ac7ce73bf09))
+- **ios:** support Cordova plugins with Package.swift ([#8438](https://github.com/ionic-team/capacitor/issues/8438)) ([139943b](https://github.com/ionic-team/capacitor/commit/139943b0c05fddb2d1ce2d6f468800fddf17b4cf))
+- **SystemBars:** avoid extra view padding on API <= 34 ([#8439](https://github.com/ionic-team/capacitor/issues/8439)) ([5b135a7](https://github.com/ionic-team/capacitor/commit/5b135a70217be560e7176c8d5b514cc92ed3e4e4))
+
## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16)
### Bug Fixes
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index 5812a48328..ca3b6f0ef1 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
+
+### Bug Fixes
+
+- **SystemBars:** avoid extra view padding on API <= 34 ([#8439](https://github.com/ionic-team/capacitor/issues/8439)) ([5b135a7](https://github.com/ionic-team/capacitor/commit/5b135a70217be560e7176c8d5b514cc92ed3e4e4))
+
## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16)
### Bug Fixes
diff --git a/android/package.json b/android/package.json
index f55229c313..5ecf01e071 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "8.3.1",
+ "version": "8.3.2",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index ac66f222fc..1cc5720a18 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
+
+### Bug Fixes
+
+- **cli:** add cSettings support for compiler flags in generated Package.swift ([#8448](https://github.com/ionic-team/capacitor/issues/8448)) ([0bd0676](https://github.com/ionic-team/capacitor/commit/0bd0676315c5fd77e50312dd7b5bf4990dcbd7d0))
+- **cli:** add system framework and weak framework support in SPM Package.swift ([#8447](https://github.com/ionic-team/capacitor/issues/8447)) ([3232f0f](https://github.com/ionic-team/capacitor/commit/3232f0fe1d9811b0b5c500e3dc05cb8a250177f8))
+- **cli:** correct Capacitor plugin SPM compat check ([#8440](https://github.com/ionic-team/capacitor/issues/8440)) ([e5ccc45](https://github.com/ionic-team/capacitor/commit/e5ccc451dda27d56bca824ed644bd20fe4d988cb))
+- **cli:** generate binaryTarget entries for custom xcframeworks in Package.swift ([#8445](https://github.com/ionic-team/capacitor/issues/8445)) ([1f7e33f](https://github.com/ionic-team/capacitor/commit/1f7e33fca43d183332ec19d22b0d75ef81d8cc6d))
+- **cli:** generate resource entries in Package.swift ([#8455](https://github.com/ionic-team/capacitor/issues/8455)) ([790bd27](https://github.com/ionic-team/capacitor/commit/790bd27123497111984227010c3162cec94a108e))
+- **cli:** handle Cordova plugins without iOS source files ([#8443](https://github.com/ionic-team/capacitor/issues/8443)) ([0da130e](https://github.com/ionic-team/capacitor/commit/0da130eb7a861bee4e2c35bc0aac53ba9c983fc3))
+- **cli:** link plugin dependencies in Package.swift ([#8457](https://github.com/ionic-team/capacitor/issues/8457)) ([b3c769e](https://github.com/ionic-team/capacitor/commit/b3c769e856c826b1174518877cf86ac7ce73bf09))
+- **ios:** support Cordova plugins with Package.swift ([#8438](https://github.com/ionic-team/capacitor/issues/8438)) ([139943b](https://github.com/ionic-team/capacitor/commit/139943b0c05fddb2d1ce2d6f468800fddf17b4cf))
+
## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16)
### Bug Fixes
diff --git a/cli/package.json b/cli/package.json
index b9f5d1cccc..abc3774df3 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "8.3.1",
+ "version": "8.3.2",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index 60a9ca82bf..08c8815c37 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
+
+**Note:** Version bump only for package @capacitor/core
+
## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16)
**Note:** Version bump only for package @capacitor/core
diff --git a/core/package.json b/core/package.json
index 54e8c71108..bc3bebc0f0 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "8.3.1",
+ "version": "8.3.2",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index a16975c795..111b642acd 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
+
+**Note:** Version bump only for package @capacitor/ios
+
## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16)
### Bug Fixes
diff --git a/ios/package.json b/ios/package.json
index 4f7200cada..0972ed9467 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "8.3.1",
+ "version": "8.3.2",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/lerna.json b/lerna.json
index 5f513f6567..d0a1ebc027 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "8.3.1",
+ "version": "8.3.2",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From 3f18cb70f9e8d0c3d5c68acb91640fdc1792f566 Mon Sep 17 00:00:00 2001
From: Mark Anderson
Date: Thu, 7 May 2026 13:13:44 -0400
Subject: [PATCH 21/69] chore: update lerna.json for alpha publish
---
lerna.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lerna.json b/lerna.json
index cac40342b5..0a1a0886fe 100644
--- a/lerna.json
+++ b/lerna.json
@@ -4,7 +4,7 @@
"hoist": true
},
"version": {
- "allowBranch": "main",
+ "allowBranch": "next",
"conventionalCommits": true,
"createRelease": "github",
"message": "Release %s",
From 52e0cd7160e895273e30f45f177a3d42f00c9c83 Mon Sep 17 00:00:00 2001
From: "Github Workflow (on behalf of markemer)"
Date: Thu, 7 May 2026 17:15:50 +0000
Subject: [PATCH 22/69] Release 9.0.0-alpha.0
---
CHANGELOG.md | 11 +++++++++++
android/CHANGELOG.md | 9 +++++++++
android/package.json | 4 ++--
cli/CHANGELOG.md | 8 ++++++++
cli/package.json | 2 +-
core/CHANGELOG.md | 7 +++++++
core/package.json | 2 +-
ios/CHANGELOG.md | 7 +++++++
ios/package.json | 4 ++--
lerna.json | 2 +-
10 files changed, 49 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3851a6663d..802047e8f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
+
+### Bug Fixes
+
+- **android:** conditionally add Cordova ([#8453](https://github.com/ionic-team/capacitor/issues/8453)) ([e136b3f](https://github.com/ionic-team/capacitor/commit/e136b3f0cdd1437cac5234576301ed388edf85e7))
+- **android:** Handle activity result without plugin handle ([#8436](https://github.com/ionic-team/capacitor/issues/8436)) ([65ad1fd](https://github.com/ionic-team/capacitor/commit/65ad1fd55b9793a3e078e4a837a00e2f28c75c17))
+- **cli:** conditionally add capacitor-cordova-android-plugins module ([#8449](https://github.com/ionic-team/capacitor/issues/8449)) ([9e85905](https://github.com/ionic-team/capacitor/commit/9e85905bf36c1e2300994e231cf10e829f2074ed))
+- **http:** handle URL objects on fetch ([#8386](https://github.com/ionic-team/capacitor/issues/8386)) ([0aac1f0](https://github.com/ionic-team/capacitor/commit/0aac1f0aee8083dc5013f0d8e3c180eb02f21141))
+- **ios:** make getArray accesible on Objective-C plugins ([#8392](https://github.com/ionic-team/capacitor/issues/8392)) ([ef57c77](https://github.com/ionic-team/capacitor/commit/ef57c773e70af6b94685a2dc0f808db3ed4cda6e))
+- **SystemBars:** use native safe area insets on Android ([#8384](https://github.com/ionic-team/capacitor/issues/8384)) ([d55f4f8](https://github.com/ionic-team/capacitor/commit/d55f4f859bd88fa5617699e874c48c451bcb5e5f))
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
### Bug Fixes
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index ca3b6f0ef1..4abb7c931b 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
+
+### Bug Fixes
+
+- **android:** conditionally add Cordova ([#8453](https://github.com/ionic-team/capacitor/issues/8453)) ([e136b3f](https://github.com/ionic-team/capacitor/commit/e136b3f0cdd1437cac5234576301ed388edf85e7))
+- **android:** Handle activity result without plugin handle ([#8436](https://github.com/ionic-team/capacitor/issues/8436)) ([65ad1fd](https://github.com/ionic-team/capacitor/commit/65ad1fd55b9793a3e078e4a837a00e2f28c75c17))
+- **http:** handle URL objects on fetch ([#8386](https://github.com/ionic-team/capacitor/issues/8386)) ([0aac1f0](https://github.com/ionic-team/capacitor/commit/0aac1f0aee8083dc5013f0d8e3c180eb02f21141))
+- **SystemBars:** use native safe area insets on Android ([#8384](https://github.com/ionic-team/capacitor/issues/8384)) ([d55f4f8](https://github.com/ionic-team/capacitor/commit/d55f4f859bd88fa5617699e874c48c451bcb5e5f))
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
### Bug Fixes
diff --git a/android/package.json b/android/package.json
index 483d629955..794b343d38 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "9.0.0-dev.0",
+ "version": "9.0.0-alpha.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -26,7 +26,7 @@
"verify": "./gradlew clean lint build test -b capacitor/build.gradle"
},
"peerDependencies": {
- "@capacitor/core": "^9.0.0-dev.0"
+ "@capacitor/core": "^9.0.0-alpha.0"
},
"publishConfig": {
"access": "public"
diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index 1cc5720a18..b90561f8c2 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
+
+### Bug Fixes
+
+- **android:** conditionally add Cordova ([#8453](https://github.com/ionic-team/capacitor/issues/8453)) ([e136b3f](https://github.com/ionic-team/capacitor/commit/e136b3f0cdd1437cac5234576301ed388edf85e7))
+- **cli:** conditionally add capacitor-cordova-android-plugins module ([#8449](https://github.com/ionic-team/capacitor/issues/8449)) ([9e85905](https://github.com/ionic-team/capacitor/commit/9e85905bf36c1e2300994e231cf10e829f2074ed))
+- **SystemBars:** use native safe area insets on Android ([#8384](https://github.com/ionic-team/capacitor/issues/8384)) ([d55f4f8](https://github.com/ionic-team/capacitor/commit/d55f4f859bd88fa5617699e874c48c451bcb5e5f))
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
### Bug Fixes
diff --git a/cli/package.json b/cli/package.json
index 9f9d89eed9..5341b2bb1d 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "9.0.0-dev.0",
+ "version": "9.0.0-alpha.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index 08c8815c37..4448742a8f 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
+
+### Bug Fixes
+
+- **http:** handle URL objects on fetch ([#8386](https://github.com/ionic-team/capacitor/issues/8386)) ([0aac1f0](https://github.com/ionic-team/capacitor/commit/0aac1f0aee8083dc5013f0d8e3c180eb02f21141))
+- **SystemBars:** use native safe area insets on Android ([#8384](https://github.com/ionic-team/capacitor/issues/8384)) ([d55f4f8](https://github.com/ionic-team/capacitor/commit/d55f4f859bd88fa5617699e874c48c451bcb5e5f))
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
**Note:** Version bump only for package @capacitor/core
diff --git a/core/package.json b/core/package.json
index 674c151eff..e462f243da 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "9.0.0-dev.0",
+ "version": "9.0.0-alpha.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 111b642acd..20ea880360 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
+
+### Bug Fixes
+
+- **http:** handle URL objects on fetch ([#8386](https://github.com/ionic-team/capacitor/issues/8386)) ([0aac1f0](https://github.com/ionic-team/capacitor/commit/0aac1f0aee8083dc5013f0d8e3c180eb02f21141))
+- **ios:** make getArray accesible on Objective-C plugins ([#8392](https://github.com/ionic-team/capacitor/issues/8392)) ([ef57c77](https://github.com/ionic-team/capacitor/commit/ef57c773e70af6b94685a2dc0f808db3ed4cda6e))
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
**Note:** Version bump only for package @capacitor/ios
diff --git a/ios/package.json b/ios/package.json
index 477e3c881b..f3dca61381 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "9.0.0-dev.0",
+ "version": "9.0.0-alpha.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -25,7 +25,7 @@
"xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd .."
},
"peerDependencies": {
- "@capacitor/core": "^9.0.0-dev.0"
+ "@capacitor/core": "^9.0.0-alpha.0"
},
"publishConfig": {
"access": "public"
diff --git a/lerna.json b/lerna.json
index 0a1a0886fe..fdc0f9716e 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "9.0.0-dev.0",
+ "version": "9.0.0-alpha.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From b2d771926a180e60deea31992d7d4abcd5ca3bc7 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Fri, 8 May 2026 18:44:22 +0200
Subject: [PATCH 23/69] fix(cli): copy plugin files in CocoaPods projects
(#8467)
---
cli/src/ios/update.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts
index 9fcd1c7184..82ca85a88f 100644
--- a/cli/src/ios/update.ts
+++ b/cli/src/ios/update.ts
@@ -564,7 +564,7 @@ async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[])
const isSPM = (await config.ios.packageManager) === 'SPM';
for (const p of cordovaPlugins) {
const platformTag = getPluginPlatform(p, platform);
- if (platformTag.$?.package) {
+ if (isSPM && platformTag.$?.package) {
continue;
}
const sourceFiles = getPlatformElement(p, platform, 'source-file');
From 0d5848d1669a95da25a77e233044678986dc30bb Mon Sep 17 00:00:00 2001
From: Mark Anderson
Date: Fri, 8 May 2026 12:57:41 -0400
Subject: [PATCH 24/69] Fix from main to copy plugin files in CocoaPods
found in: 5c63a58c88cefa7ec882536975ccaf86c6b0d7e2
---
cli/src/util/cordova-ios.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cli/src/util/cordova-ios.ts b/cli/src/util/cordova-ios.ts
index 5fc3c1a8c4..a54c059aaa 100644
--- a/cli/src/util/cordova-ios.ts
+++ b/cli/src/util/cordova-ios.ts
@@ -173,7 +173,7 @@ export async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plu
const isSPM = (await config.ios.packageManager) === 'SPM';
for (const p of cordovaPlugins) {
const platformTag = getPluginPlatform(p, platform);
- if (platformTag.$?.package) {
+ if (isSPM && platformTag.$?.package) {
continue;
}
const sourceFiles = getPlatformElement(p, platform, 'source-file');
From 76ff70e0e40bbbca63c5195c572f166e5ce82089 Mon Sep 17 00:00:00 2001
From: "Github Workflow (on behalf of markemer)"
Date: Fri, 8 May 2026 17:08:40 +0000
Subject: [PATCH 25/69] Release 8.3.3
---
CHANGELOG.md | 6 ++++++
android/CHANGELOG.md | 4 ++++
android/package.json | 2 +-
cli/CHANGELOG.md | 6 ++++++
cli/package.json | 2 +-
core/CHANGELOG.md | 4 ++++
core/package.json | 2 +-
ios/CHANGELOG.md | 4 ++++
ios/package.json | 2 +-
lerna.json | 2 +-
10 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3851a6663d..d4ad41ff2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
+
+### Bug Fixes
+
+- **cli:** copy plugin files in CocoaPods projects ([#8467](https://github.com/ionic-team/capacitor/issues/8467)) ([b2d7719](https://github.com/ionic-team/capacitor/commit/b2d771926a180e60deea31992d7d4abcd5ca3bc7))
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
### Bug Fixes
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index ca3b6f0ef1..d4b2fc5346 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
+
+**Note:** Version bump only for package @capacitor/android
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
### Bug Fixes
diff --git a/android/package.json b/android/package.json
index 5ecf01e071..07db8a26e4 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "8.3.2",
+ "version": "8.3.3",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index 1cc5720a18..452ba4cc62 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
+
+### Bug Fixes
+
+- **cli:** copy plugin files in CocoaPods projects ([#8467](https://github.com/ionic-team/capacitor/issues/8467)) ([b2d7719](https://github.com/ionic-team/capacitor/commit/b2d771926a180e60deea31992d7d4abcd5ca3bc7))
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
### Bug Fixes
diff --git a/cli/package.json b/cli/package.json
index abc3774df3..cd17f5d2ff 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "8.3.2",
+ "version": "8.3.3",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index 08c8815c37..26f5723fe9 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
+
+**Note:** Version bump only for package @capacitor/core
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
**Note:** Version bump only for package @capacitor/core
diff --git a/core/package.json b/core/package.json
index bc3bebc0f0..373249f982 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "8.3.2",
+ "version": "8.3.3",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 111b642acd..b9b7863b06 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
+
+**Note:** Version bump only for package @capacitor/ios
+
## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07)
**Note:** Version bump only for package @capacitor/ios
diff --git a/ios/package.json b/ios/package.json
index 0972ed9467..bd4c0a320b 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "8.3.2",
+ "version": "8.3.3",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/lerna.json b/lerna.json
index d0a1ebc027..d64bfcd44a 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "8.3.2",
+ "version": "8.3.3",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From 676b8643aed45432cf36abc41261e543119a83a8 Mon Sep 17 00:00:00 2001
From: "Github Workflow (on behalf of markemer)"
Date: Fri, 8 May 2026 17:21:39 +0000
Subject: [PATCH 26/69] Release 9.0.0-alpha.1
---
CHANGELOG.md | 4 ++++
android/CHANGELOG.md | 4 ++++
android/package.json | 4 ++--
cli/CHANGELOG.md | 4 ++++
cli/package.json | 2 +-
core/CHANGELOG.md | 4 ++++
core/package.json | 2 +-
ios/CHANGELOG.md | 4 ++++
ios/package.json | 4 ++--
lerna.json | 2 +-
10 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 802047e8f5..336a85b2a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
+
+**Note:** Version bump only for package capacitor
+
# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
### Bug Fixes
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index 4abb7c931b..a84db5cd3a 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
+
+**Note:** Version bump only for package @capacitor/android
+
# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
### Bug Fixes
diff --git a/android/package.json b/android/package.json
index 794b343d38..aec3fc4025 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "9.0.0-alpha.0",
+ "version": "9.0.0-alpha.1",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -26,7 +26,7 @@
"verify": "./gradlew clean lint build test -b capacitor/build.gradle"
},
"peerDependencies": {
- "@capacitor/core": "^9.0.0-alpha.0"
+ "@capacitor/core": "^9.0.0-alpha.1"
},
"publishConfig": {
"access": "public"
diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index b90561f8c2..66216f9348 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
+
+**Note:** Version bump only for package @capacitor/cli
+
# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
### Bug Fixes
diff --git a/cli/package.json b/cli/package.json
index 5341b2bb1d..239857cc9a 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "9.0.0-alpha.0",
+ "version": "9.0.0-alpha.1",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index 4448742a8f..b8468911ef 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
+
+**Note:** Version bump only for package @capacitor/core
+
# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
### Bug Fixes
diff --git a/core/package.json b/core/package.json
index e462f243da..dc1c0f967c 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "9.0.0-alpha.0",
+ "version": "9.0.0-alpha.1",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 20ea880360..083f685c6c 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
+
+**Note:** Version bump only for package @capacitor/ios
+
# [9.0.0-alpha.0](https://github.com/ionic-team/capacitor/compare/8.3.2...9.0.0-alpha.0) (2026-05-07)
### Bug Fixes
diff --git a/ios/package.json b/ios/package.json
index f3dca61381..243ef00cea 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "9.0.0-alpha.0",
+ "version": "9.0.0-alpha.1",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -25,7 +25,7 @@
"xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd .."
},
"peerDependencies": {
- "@capacitor/core": "^9.0.0-alpha.0"
+ "@capacitor/core": "^9.0.0-alpha.1"
},
"publishConfig": {
"access": "public"
diff --git a/lerna.json b/lerna.json
index fdc0f9716e..5189de7572 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "9.0.0-alpha.0",
+ "version": "9.0.0-alpha.1",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From b7e167bbab47c57a5f72ff9e76a726ab7d8a1c1f Mon Sep 17 00:00:00 2001
From: "Alex J."
Date: Tue, 12 May 2026 15:19:58 -0400
Subject: [PATCH 27/69] chore(cli): update telemetry endpoint (#8464)
---
cli/src/ipc.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/cli/src/ipc.ts b/cli/src/ipc.ts
index 41894e53a7..37c38110bb 100644
--- a/cli/src/ipc.ts
+++ b/cli/src/ipc.ts
@@ -49,9 +49,9 @@ export async function receive(msg: IPCMessage): Promise {
// This request is only made if telemetry is on.
const req = request(
{
- hostname: 'api.ionicjs.com',
+ hostname: 'metrics-capacitor.outsystems.com',
port: 443,
- path: '/events/metrics',
+ path: '/metrics',
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -60,7 +60,7 @@ export async function receive(msg: IPCMessage): Promise {
(response) => {
debug('Sent %O metric to events service (status: %O)', data.name, response.statusCode);
- if (response.statusCode !== 204) {
+ if (response.statusCode !== 202) {
response.on('data', (chunk) => {
debug('Bad response from events service. Request body: %O', chunk.toString());
});
From 6b44e781c4370c865da7b370aec85a01492d5728 Mon Sep 17 00:00:00 2001
From: "Alex J."
Date: Tue, 12 May 2026 15:19:58 -0400
Subject: [PATCH 28/69] chore(cli): update telemetry endpoint (#8464)
---
cli/src/ipc.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/cli/src/ipc.ts b/cli/src/ipc.ts
index 41894e53a7..37c38110bb 100644
--- a/cli/src/ipc.ts
+++ b/cli/src/ipc.ts
@@ -49,9 +49,9 @@ export async function receive(msg: IPCMessage): Promise {
// This request is only made if telemetry is on.
const req = request(
{
- hostname: 'api.ionicjs.com',
+ hostname: 'metrics-capacitor.outsystems.com',
port: 443,
- path: '/events/metrics',
+ path: '/metrics',
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -60,7 +60,7 @@ export async function receive(msg: IPCMessage): Promise {
(response) => {
debug('Sent %O metric to events service (status: %O)', data.name, response.statusCode);
- if (response.statusCode !== 204) {
+ if (response.statusCode !== 202) {
response.on('data', (chunk) => {
debug('Bad response from events service. Request body: %O', chunk.toString());
});
From de1e506e48204f3d9a51bc911fafc341fd81fc36 Mon Sep 17 00:00:00 2001
From: "Github Workflow (on behalf of alexgerardojacinto)"
Date: Tue, 12 May 2026 20:38:58 +0000
Subject: [PATCH 29/69] Release 8.3.4
---
CHANGELOG.md | 4 ++++
android/CHANGELOG.md | 4 ++++
android/package.json | 2 +-
cli/CHANGELOG.md | 4 ++++
cli/package.json | 2 +-
core/CHANGELOG.md | 4 ++++
core/package.json | 2 +-
ios/CHANGELOG.md | 4 ++++
ios/package.json | 2 +-
lerna.json | 2 +-
10 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4ad41ff2a..a7f2b77136 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
+
+**Note:** Version bump only for package capacitor
+
## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
### Bug Fixes
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index d4b2fc5346..74d538d9fe 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
+
+**Note:** Version bump only for package @capacitor/android
+
## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
**Note:** Version bump only for package @capacitor/android
diff --git a/android/package.json b/android/package.json
index 07db8a26e4..8ae93f3f2b 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "8.3.3",
+ "version": "8.3.4",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index 452ba4cc62..bba24688f6 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
+
+**Note:** Version bump only for package @capacitor/cli
+
## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
### Bug Fixes
diff --git a/cli/package.json b/cli/package.json
index cd17f5d2ff..9df346b143 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "8.3.3",
+ "version": "8.3.4",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index 26f5723fe9..017293794f 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
+
+**Note:** Version bump only for package @capacitor/core
+
## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
**Note:** Version bump only for package @capacitor/core
diff --git a/core/package.json b/core/package.json
index 373249f982..eef113a50a 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "8.3.3",
+ "version": "8.3.4",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index b9b7863b06..87ee73bb4f 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
+
+**Note:** Version bump only for package @capacitor/ios
+
## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08)
**Note:** Version bump only for package @capacitor/ios
diff --git a/ios/package.json b/ios/package.json
index bd4c0a320b..a7b8d31335 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "8.3.3",
+ "version": "8.3.4",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/lerna.json b/lerna.json
index d64bfcd44a..1e53d6bdca 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "8.3.3",
+ "version": "8.3.4",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From faf895efde7f200442c6396edadb51decb714dff Mon Sep 17 00:00:00 2001
From: "Github Workflow (on behalf of alexgerardojacinto)"
Date: Tue, 12 May 2026 21:10:26 +0000
Subject: [PATCH 30/69] Release 9.0.0-alpha.2
---
CHANGELOG.md | 4 ++++
android/CHANGELOG.md | 4 ++++
android/package.json | 4 ++--
cli/CHANGELOG.md | 4 ++++
cli/package.json | 2 +-
core/CHANGELOG.md | 4 ++++
core/package.json | 2 +-
ios/CHANGELOG.md | 4 ++++
ios/package.json | 4 ++--
lerna.json | 2 +-
10 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 336a85b2a3..814970df10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
+
+**Note:** Version bump only for package capacitor
+
# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
**Note:** Version bump only for package capacitor
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index a84db5cd3a..6bcf12438a 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
+
+**Note:** Version bump only for package @capacitor/android
+
# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
**Note:** Version bump only for package @capacitor/android
diff --git a/android/package.json b/android/package.json
index aec3fc4025..8022d692c8 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "9.0.0-alpha.1",
+ "version": "9.0.0-alpha.2",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -26,7 +26,7 @@
"verify": "./gradlew clean lint build test -b capacitor/build.gradle"
},
"peerDependencies": {
- "@capacitor/core": "^9.0.0-alpha.1"
+ "@capacitor/core": "^9.0.0-alpha.2"
},
"publishConfig": {
"access": "public"
diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index 66216f9348..8781458412 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
+
+**Note:** Version bump only for package @capacitor/cli
+
# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
**Note:** Version bump only for package @capacitor/cli
diff --git a/cli/package.json b/cli/package.json
index 239857cc9a..38c0c01b39 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "9.0.0-alpha.1",
+ "version": "9.0.0-alpha.2",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index b8468911ef..afde81bcfd 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
+
+**Note:** Version bump only for package @capacitor/core
+
# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
**Note:** Version bump only for package @capacitor/core
diff --git a/core/package.json b/core/package.json
index dc1c0f967c..8567c8be97 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "9.0.0-alpha.1",
+ "version": "9.0.0-alpha.2",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 083f685c6c..22acaa2c88 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
+
+**Note:** Version bump only for package @capacitor/ios
+
# [9.0.0-alpha.1](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...9.0.0-alpha.1) (2026-05-08)
**Note:** Version bump only for package @capacitor/ios
diff --git a/ios/package.json b/ios/package.json
index 243ef00cea..a57c13889e 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "9.0.0-alpha.1",
+ "version": "9.0.0-alpha.2",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -25,7 +25,7 @@
"xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd .."
},
"peerDependencies": {
- "@capacitor/core": "^9.0.0-alpha.1"
+ "@capacitor/core": "^9.0.0-alpha.2"
},
"publishConfig": {
"access": "public"
diff --git a/lerna.json b/lerna.json
index 5189de7572..cfcbca64cb 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "9.0.0-alpha.1",
+ "version": "9.0.0-alpha.2",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From 731a82e554d9c1270241897228e934c38759bbbb Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Thu, 21 May 2026 17:39:31 +0200
Subject: [PATCH 31/69] chore: format java code (#8475)
---
.../com/getcapacitor/BridgeWebChromeClient.java | 12 ++++++------
.../src/main/java/com/getcapacitor/FileUtils.java | 6 +++---
.../src/main/java/com/getcapacitor/Plugin.java | 4 ++--
.../cordova/MockCordovaWebViewImpl.java | 14 ++++++--------
.../com/getcapacitor/plugin/CapacitorHttp.java | 4 ++--
.../java/com/getcapacitor/ConfigBuildingTest.java | 6 +++---
6 files changed, 22 insertions(+), 24 deletions(-)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
index 6ca1c8a838..58371a9ff3 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
@@ -246,14 +246,14 @@ public boolean onJsPrompt(WebView view, String url, String message, String defau
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback);
Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: " + origin);
- final String[] geoPermissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };
+ final String[] geoPermissions = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
if (!PermissionHelper.hasPermissions(bridge.getContext(), geoPermissions)) {
permissionListener = (isGranted) -> {
if (isGranted) {
callback.invoke(origin, true, false);
} else {
- final String[] coarsePermission = { Manifest.permission.ACCESS_COARSE_LOCATION };
+ final String[] coarsePermission = {Manifest.permission.ACCESS_COARSE_LOCATION};
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
PermissionHelper.hasPermissions(bridge.getContext(), coarsePermission)
@@ -294,7 +294,7 @@ public boolean onShowFileChooser(
filePathCallback.onReceiveValue(null);
}
};
- final String[] camPermission = { Manifest.permission.CAMERA };
+ final String[] camPermission = {Manifest.permission.CAMERA};
permissionLauncher.launch(camPermission);
}
} else {
@@ -305,7 +305,7 @@ public boolean onShowFileChooser(
}
private boolean isMediaCaptureSupported() {
- String[] permissions = { Manifest.permission.CAMERA };
+ String[] permissions = {Manifest.permission.CAMERA};
return (
PermissionHelper.hasPermissions(bridge.getContext(), permissions) ||
!PermissionHelper.hasDefinedPermission(bridge.getContext(), Manifest.permission.CAMERA)
@@ -343,7 +343,7 @@ private boolean showImageCapturePicker(final ValueCallback filePathCallba
activityListener = (activityResult) -> {
Uri[] result = null;
if (activityResult.getResultCode() == Activity.RESULT_OK) {
- result = new Uri[] { imageFileUri };
+ result = new Uri[] {imageFileUri};
}
filePathCallback.onReceiveValue(result);
};
@@ -362,7 +362,7 @@ private boolean showVideoCapturePicker(final ValueCallback filePathCallba
activityListener = (activityResult) -> {
Uri[] result = null;
if (activityResult.getResultCode() == Activity.RESULT_OK) {
- result = new Uri[] { activityResult.getData().getData() };
+ result = new Uri[] {activityResult.getData().getData()};
}
filePathCallback.onReceiveValue(result);
};
diff --git a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java
index ce09e7c2f0..0fdf64feb3 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java
@@ -118,7 +118,7 @@ else if (isMediaDocument(uri)) {
}
final String selection = "_id=?";
- final String[] selectionArgs = new String[] { split[1] };
+ final String[] selectionArgs = new String[] {split[1]};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
@@ -195,7 +195,7 @@ private static String getDataColumn(Context context, Uri uri, String selection,
String path = null;
Cursor cursor = null;
final String column = "_data";
- final String[] projection = { column };
+ final String[] projection = {column};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
@@ -292,7 +292,7 @@ private static String getPathToNonPrimaryVolume(Context context, String tag) {
}
private static String sanitizeFilename(String displayName) {
- String[] badCharacters = new String[] { "..", "/" };
+ String[] badCharacters = new String[] {"..", "/"};
String[] segments = displayName.split("/");
String fileName = segments[segments.length - 1];
for (String suspString : badCharacters) {
diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java
index 19c8b8b3a6..e55d933ccb 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java
@@ -465,7 +465,7 @@ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String c
* @param callbackName the name of the callback to run when the permission request is complete
*/
protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginCall call, @NonNull String callbackName) {
- requestPermissionForAliases(new String[] { alias }, call, callbackName);
+ requestPermissionForAliases(new String[] {alias}, call, callbackName);
}
/**
@@ -583,7 +583,7 @@ public void pluginRequestAllPermissions() {
*/
@Deprecated
public void pluginRequestPermission(String permission, int requestCode) {
- ActivityCompat.requestPermissions(getActivity(), new String[] { permission }, requestCode);
+ ActivityCompat.requestPermissions(getActivity(), new String[] {permission}, requestCode);
}
/**
diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
index 1115429d7b..020c1457a1 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java
@@ -70,14 +70,12 @@ public CapacitorEvalBridgeMode(WebView webView, CordovaInterface cordova) {
@Override
public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) {
- cordova
- .getActivity()
- .runOnUiThread(() -> {
- String js = queue.popAndEncodeAsJs();
- if (js != null) {
- webView.evaluateJavascript(js, null);
- }
- });
+ cordova.getActivity().runOnUiThread(() -> {
+ String js = queue.popAndEncodeAsJs();
+ if (js != null) {
+ webView.evaluateJavascript(js, null);
+ }
+ });
}
}
diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java
index 46bc17417f..7d5ab337e3 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java
@@ -18,8 +18,8 @@
@CapacitorPlugin(
permissions = {
- @Permission(strings = { Manifest.permission.WRITE_EXTERNAL_STORAGE }, alias = "HttpWrite"),
- @Permission(strings = { Manifest.permission.READ_EXTERNAL_STORAGE }, alias = "HttpRead")
+ @Permission(strings = {Manifest.permission.WRITE_EXTERNAL_STORAGE}, alias = "HttpWrite"),
+ @Permission(strings = {Manifest.permission.READ_EXTERNAL_STORAGE}, alias = "HttpRead")
}
)
public class CapacitorHttp extends Plugin {
diff --git a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java
index 1af51039da..ae12edb02b 100644
--- a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java
+++ b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java
@@ -42,7 +42,7 @@ public void setup() {
config = new CapConfig.Builder(context)
.setAllowMixedContent(true)
- .setAllowNavigation(new String[] { "http://www.google.com" })
+ .setAllowNavigation(new String[] {"http://www.google.com"})
.setAndroidScheme("test")
.setCaptureInput(true)
.setLoggingEnabled(true)
@@ -64,7 +64,7 @@ public void setup() {
@Test
public void getCoreConfigValues() {
assertTrue(config.isMixedContentAllowed());
- assertArrayEquals(new String[] { "http://www.google.com" }, config.getAllowNavigation());
+ assertArrayEquals(new String[] {"http://www.google.com"}, config.getAllowNavigation());
assertEquals("test", config.getAndroidScheme());
assertTrue(config.isInputCaptured());
assertTrue(config.isLoggingEnabled());
@@ -97,7 +97,7 @@ public void getPluginInt() {
@Test
public void getPluginArray() {
- String[] comparison = new String[] { "5", "6", "7", "8" };
+ String[] comparison = new String[] {"5", "6", "7", "8"};
String[] testArray = config.getPluginConfiguration(TEST_PLUGIN_NAME).getArray("var5");
assertArrayEquals(comparison, testArray);
}
From bc745364129853769ca2fcfc3cc2a6c7624ee513 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Thu, 21 May 2026 18:04:37 +0200
Subject: [PATCH 32/69] chore: format java code (#8477)
---
.../java/com/getcapacitor/BridgeWebChromeClient.java | 12 ++++++------
.../src/main/java/com/getcapacitor/FileUtils.java | 6 +++---
.../src/main/java/com/getcapacitor/Plugin.java | 4 ++--
.../java/com/getcapacitor/plugin/CapacitorHttp.java | 4 ++--
.../java/com/getcapacitor/ConfigBuildingTest.java | 6 +++---
5 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
index 58371a9ff3..6ca1c8a838 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java
@@ -246,14 +246,14 @@ public boolean onJsPrompt(WebView view, String url, String message, String defau
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback);
Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: " + origin);
- final String[] geoPermissions = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
+ final String[] geoPermissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };
if (!PermissionHelper.hasPermissions(bridge.getContext(), geoPermissions)) {
permissionListener = (isGranted) -> {
if (isGranted) {
callback.invoke(origin, true, false);
} else {
- final String[] coarsePermission = {Manifest.permission.ACCESS_COARSE_LOCATION};
+ final String[] coarsePermission = { Manifest.permission.ACCESS_COARSE_LOCATION };
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
PermissionHelper.hasPermissions(bridge.getContext(), coarsePermission)
@@ -294,7 +294,7 @@ public boolean onShowFileChooser(
filePathCallback.onReceiveValue(null);
}
};
- final String[] camPermission = {Manifest.permission.CAMERA};
+ final String[] camPermission = { Manifest.permission.CAMERA };
permissionLauncher.launch(camPermission);
}
} else {
@@ -305,7 +305,7 @@ public boolean onShowFileChooser(
}
private boolean isMediaCaptureSupported() {
- String[] permissions = {Manifest.permission.CAMERA};
+ String[] permissions = { Manifest.permission.CAMERA };
return (
PermissionHelper.hasPermissions(bridge.getContext(), permissions) ||
!PermissionHelper.hasDefinedPermission(bridge.getContext(), Manifest.permission.CAMERA)
@@ -343,7 +343,7 @@ private boolean showImageCapturePicker(final ValueCallback filePathCallba
activityListener = (activityResult) -> {
Uri[] result = null;
if (activityResult.getResultCode() == Activity.RESULT_OK) {
- result = new Uri[] {imageFileUri};
+ result = new Uri[] { imageFileUri };
}
filePathCallback.onReceiveValue(result);
};
@@ -362,7 +362,7 @@ private boolean showVideoCapturePicker(final ValueCallback filePathCallba
activityListener = (activityResult) -> {
Uri[] result = null;
if (activityResult.getResultCode() == Activity.RESULT_OK) {
- result = new Uri[] {activityResult.getData().getData()};
+ result = new Uri[] { activityResult.getData().getData() };
}
filePathCallback.onReceiveValue(result);
};
diff --git a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java
index 0fdf64feb3..ce09e7c2f0 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java
@@ -118,7 +118,7 @@ else if (isMediaDocument(uri)) {
}
final String selection = "_id=?";
- final String[] selectionArgs = new String[] {split[1]};
+ final String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs);
}
@@ -195,7 +195,7 @@ private static String getDataColumn(Context context, Uri uri, String selection,
String path = null;
Cursor cursor = null;
final String column = "_data";
- final String[] projection = {column};
+ final String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
@@ -292,7 +292,7 @@ private static String getPathToNonPrimaryVolume(Context context, String tag) {
}
private static String sanitizeFilename(String displayName) {
- String[] badCharacters = new String[] {"..", "/"};
+ String[] badCharacters = new String[] { "..", "/" };
String[] segments = displayName.split("/");
String fileName = segments[segments.length - 1];
for (String suspString : badCharacters) {
diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java
index e55d933ccb..19c8b8b3a6 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java
@@ -465,7 +465,7 @@ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String c
* @param callbackName the name of the callback to run when the permission request is complete
*/
protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginCall call, @NonNull String callbackName) {
- requestPermissionForAliases(new String[] {alias}, call, callbackName);
+ requestPermissionForAliases(new String[] { alias }, call, callbackName);
}
/**
@@ -583,7 +583,7 @@ public void pluginRequestAllPermissions() {
*/
@Deprecated
public void pluginRequestPermission(String permission, int requestCode) {
- ActivityCompat.requestPermissions(getActivity(), new String[] {permission}, requestCode);
+ ActivityCompat.requestPermissions(getActivity(), new String[] { permission }, requestCode);
}
/**
diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java
index 7d5ab337e3..46bc17417f 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java
@@ -18,8 +18,8 @@
@CapacitorPlugin(
permissions = {
- @Permission(strings = {Manifest.permission.WRITE_EXTERNAL_STORAGE}, alias = "HttpWrite"),
- @Permission(strings = {Manifest.permission.READ_EXTERNAL_STORAGE}, alias = "HttpRead")
+ @Permission(strings = { Manifest.permission.WRITE_EXTERNAL_STORAGE }, alias = "HttpWrite"),
+ @Permission(strings = { Manifest.permission.READ_EXTERNAL_STORAGE }, alias = "HttpRead")
}
)
public class CapacitorHttp extends Plugin {
diff --git a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java
index ae12edb02b..1af51039da 100644
--- a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java
+++ b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java
@@ -42,7 +42,7 @@ public void setup() {
config = new CapConfig.Builder(context)
.setAllowMixedContent(true)
- .setAllowNavigation(new String[] {"http://www.google.com"})
+ .setAllowNavigation(new String[] { "http://www.google.com" })
.setAndroidScheme("test")
.setCaptureInput(true)
.setLoggingEnabled(true)
@@ -64,7 +64,7 @@ public void setup() {
@Test
public void getCoreConfigValues() {
assertTrue(config.isMixedContentAllowed());
- assertArrayEquals(new String[] {"http://www.google.com"}, config.getAllowNavigation());
+ assertArrayEquals(new String[] { "http://www.google.com" }, config.getAllowNavigation());
assertEquals("test", config.getAndroidScheme());
assertTrue(config.isInputCaptured());
assertTrue(config.isLoggingEnabled());
@@ -97,7 +97,7 @@ public void getPluginInt() {
@Test
public void getPluginArray() {
- String[] comparison = new String[] {"5", "6", "7", "8"};
+ String[] comparison = new String[] { "5", "6", "7", "8" };
String[] testArray = config.getPluginConfiguration(TEST_PLUGIN_NAME).getArray("var5");
assertArrayEquals(comparison, testArray);
}
From 93c72de40a2ec4c78b33659250cb08340083088e Mon Sep 17 00:00:00 2001
From: Robin Genz
Date: Thu, 21 May 2026 18:54:21 +0200
Subject: [PATCH 33/69] feat: add method getDouble to plugin config (#7638)
Co-authored-by: jcesarmobile
---
.../java/com/getcapacitor/PluginConfig.java | 11 ++++++++++
.../java/com/getcapacitor/util/JSONUtils.java | 20 +++++++++++++++++++
ios/Capacitor/Capacitor/PluginConfig.swift | 7 +++++++
3 files changed, 38 insertions(+)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java b/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java
index 0f00fc530f..1d21ae90e1 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java
@@ -65,6 +65,17 @@ public int getInt(String configKey, int defaultValue) {
return JSONUtils.getInt(config, configKey, defaultValue);
}
+ /**
+ * Get a double value for a plugin in the Capacitor config.
+ *
+ * @param configKey The key of the value to retrieve
+ * @param defaultValue A default value to return if the key does not exist in the config
+ * @return The value from the config, if key exists. Default value returned if not
+ */
+ public double getDouble(String configKey, double defaultValue) {
+ return JSONUtils.getDouble(config, configKey, defaultValue);
+ }
+
/**
* Get a string array value for a plugin in the Capacitor config.
*
diff --git a/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java b/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java
index 1d2fc20785..f24e5aa0b4 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java
@@ -75,6 +75,26 @@ public static int getInt(JSONObject jsonObject, String key, int defaultValue) {
return defaultValue;
}
+ /**
+ * Get a double value from the given JSON object.
+ *
+ * @param jsonObject A JSON object to search
+ * @param key A key to fetch from the JSON object
+ * @param defaultValue A default value to return if the key cannot be found
+ * @return The value at the given key in the JSON object, or the default value
+ */
+ public static double getDouble(JSONObject jsonObject, String key, double defaultValue) {
+ String k = getDeepestKey(key);
+ try {
+ JSONObject o = getDeepestObject(jsonObject, key);
+ return o.getDouble(k);
+ } catch (JSONException ignore) {
+ // value was not found
+ }
+
+ return defaultValue;
+ }
+
/**
* Get a JSON object value from the given JSON object.
*
diff --git a/ios/Capacitor/Capacitor/PluginConfig.swift b/ios/Capacitor/Capacitor/PluginConfig.swift
index f2461e102d..8d73bc1def 100644
--- a/ios/Capacitor/Capacitor/PluginConfig.swift
+++ b/ios/Capacitor/Capacitor/PluginConfig.swift
@@ -30,6 +30,13 @@ import Foundation
return defaultValue
}
+ @objc public func getDouble(_ configKey: String, _ defaultValue: Double) -> Double {
+ if let val = (self.config)[keyPath: KeyPath(configKey)] as? Double {
+ return val
+ }
+ return defaultValue
+ }
+
public func getArray(_ configKey: String, _ defaultValue: JSArray? = nil) -> JSArray? {
if let val = (self.config)[keyPath: KeyPath(configKey)] as? JSArray {
return val
From e456de083e19644f484bec5a5359cb67960ac8bc Mon Sep 17 00:00:00 2001
From: Joey Pender
Date: Fri, 22 May 2026 13:39:38 -0500
Subject: [PATCH 34/69] fix(SystemBars): make `safe-area-inset-x` available on
API <= 34 (#8424)
---
.../com/getcapacitor/plugin/SystemBars.java | 29 ++++++++++---------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
index b335b4b1cf..76e74207cc 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
@@ -164,13 +164,18 @@ private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
}
private void initSafeAreaCSSVariables() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
+ WindowInsetsCompat insets;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
View v = (View) this.getBridge().getWebView().getParent();
- WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v);
- if (insets != null) {
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
- }
+ insets = ViewCompat.getRootWindowInsets(v);
+ } else {
+ insets = WindowInsetsCompat.CONSUMED;
+ }
+
+ if (insets != null) {
+ Insets safeAreaInsets = calcSafeAreaInsets(insets);
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
}
}
@@ -186,10 +191,8 @@ private void initWindowInsetsListener() {
// We need to correct for a possible shown IME
v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
- }
+ Insets safeAreaInsets = calcSafeAreaInsets(insets);
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
return new WindowInsetsCompat.Builder(insets)
.setInsets(
@@ -221,10 +224,8 @@ private void initWindowInsetsListener() {
.setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
.build();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
- Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
- }
+ Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
return newInsets;
});
From 258867b7bf37b1837b99b02ec9638e5a6df08d97 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Tue, 26 May 2026 12:00:02 +0200
Subject: [PATCH 35/69] feat(cli): add experimental packageOptions (#8471)
---
cli/src/declarations.ts | 20 ++++++++++++++++++++
cli/src/ios/update.ts | 3 +++
cli/src/util/spm.ts | 21 ++++++++++++++++++---
3 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts
index 6c8645406d..bda7ff0c03 100644
--- a/cli/src/declarations.ts
+++ b/cli/src/declarations.ts
@@ -559,6 +559,13 @@ export interface CapacitorConfig {
* @since 8.3.0
*/
packageTraits?: { [pluginId: string]: string[] };
+ /**
+ * Define options to apply to the package.
+ * The key is the plugin ID (e.g. `@capacitor-community/device`)
+ *
+ * @since 8.4.0
+ */
+ packageOptions?: { [pluginId: string]: PackageOptions };
};
};
};
@@ -799,3 +806,16 @@ export interface PluginsConfig {
animation?: 'FADE' | 'NONE';
};
}
+
+export interface PackageOptions {
+ /**
+ * Create a symlink to the plugin folder instead of pointing to the plugin path.
+ * Useful when plugin names conflict.
+ */
+ symlink?: boolean;
+ /**
+ * Useful to avoid target name conflicts in dependencies
+ * [see](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/modulealiasing/)
+ */
+ moduleAliases?: { [target: string]: string };
+}
diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts
index 82ca85a88f..b060e59a8f 100644
--- a/cli/src/ios/update.ts
+++ b/cli/src/ios/update.ts
@@ -639,6 +639,9 @@ async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[])
async function removePluginsNativeFiles(config: Config) {
await remove(config.ios.cordovaPluginsDirAbs);
+ if ((await config.ios.packageManager) === 'SPM') {
+ await remove(join(config.ios.nativeProjectDirAbs, 'CapApp-SPM', 'symlinks'));
+ }
await extractTemplate(config.cli.assets.ios.cordovaPluginsTemplateArchiveAbs, config.ios.cordovaPluginsDirAbs);
}
diff --git a/cli/src/util/spm.ts b/cli/src/util/spm.ts
index 7d15787ce2..2ef26bcda0 100644
--- a/cli/src/util/spm.ts
+++ b/cli/src/util/spm.ts
@@ -1,4 +1,4 @@
-import { pathExists, existsSync, readFileSync, writeFileSync, remove, move, mkdtemp } from 'fs-extra';
+import { ensureSymlink, pathExists, existsSync, readFileSync, writeFileSync, remove, move, mkdtemp } from 'fs-extra';
import { tmpdir } from 'os';
import { join, relative, resolve } from 'path';
import type { PlistObject } from 'plist';
@@ -100,6 +100,7 @@ export async function generatePackageText(config: Config, plugins: Plugin[]): Pr
const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
const iosVersion = getMajoriOSVersion(config);
const packageTraits = config.app.extConfig.experimental?.ios?.spm?.packageTraits ?? {};
+ const packageOptions = config.app.extConfig.experimental?.ios?.spm?.packageOptions ?? {};
const swiftToolsVersion = config.app.extConfig.experimental?.ios?.spm?.swiftToolsVersion ?? '5.9';
let packageSwiftText = `// swift-tools-version: ${swiftToolsVersion}
@@ -132,7 +133,13 @@ let package = Package(
packageSwiftText += `,\n .package(name: "${plugin.name}", path: "../../capacitor-cordova-ios-plugins/sources/${plugin.name}")`;
}
} else {
- const relPath = relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
+ const options = packageOptions[plugin.id];
+ const symlink = options?.symlink;
+ const symlinkFolder = join('symlinks', plugin.name);
+ const relPath = symlink ? symlinkFolder : relative(config.ios.nativeXcodeProjDirAbs, plugin.rootPath);
+ if (symlink) {
+ await ensureSymlink(plugin.rootPath, resolve(config.ios.nativeProjectDirAbs, 'CapApp-SPM', symlinkFolder));
+ }
const traits = packageTraits[plugin.id];
const traitsSuffix = traits?.length
? `, traits: [${traits
@@ -156,7 +163,15 @@ let package = Package(
.product(name: "Cordova", package: "capacitor-swift-pm")`;
for (const plugin of plugins) {
- let pluginText = `,\n .product(name: "${plugin.ios?.name}", package: "${plugin.ios?.name}")`;
+ const aliases = Object.entries(packageOptions[plugin.id]?.moduleAliases ?? {});
+ const aliasText = aliases?.length
+ ? `, moduleAliases: [${aliases
+ .map(([target, replacement]) => {
+ return `"${target}": "${replacement}"`;
+ })
+ .join(', ')}]`
+ : '';
+ let pluginText = `,\n .product(name: "${plugin.ios?.name}", package: "${plugin.ios?.name}"${aliasText})`;
if (getPluginType(plugin, config.ios.name) === PluginType.Cordova) {
const platformTag = getPluginPlatform(plugin, config.ios.name);
if (platformTag.$?.package) {
From b4b297a52f8732659662d5e5aaeff81c0f7d9835 Mon Sep 17 00:00:00 2001
From: "Alex J."
Date: Wed, 27 May 2026 14:49:32 -0400
Subject: [PATCH 36/69] feat(cli): capture ios_package_manager in telemetry
(#8482)
---
cli/src/telemetry.ts | 25 +++++++++++++++-
cli/test/telemetry.spec.ts | 60 ++++++++++++++++++++++++++++++++++++++
2 files changed, 84 insertions(+), 1 deletion(-)
create mode 100644 cli/test/telemetry.spec.ts
diff --git a/cli/src/telemetry.ts b/cli/src/telemetry.ts
index 8e0d847ac3..593397502f 100644
--- a/cli/src/telemetry.ts
+++ b/cli/src/telemetry.ts
@@ -1,8 +1,9 @@
import { Command } from 'commander';
import Debug from 'debug';
+import { pathExists } from 'fs-extra';
import c from './colors';
-import type { Config } from './definitions';
+import type { Config, PackageManager } from './definitions';
import { send } from './ipc';
import { output } from './log';
import { readConfig, writeConfig } from './sysconfig';
@@ -26,6 +27,7 @@ export interface CommandMetricData {
error: string | null;
node_version: string;
os: string;
+ ios_package_manager: PackageManager | 'unknown';
}
export interface Metric {
@@ -80,9 +82,12 @@ export function telemetryAction(config: Config, action: CommanderAction): Comman
error: error ? (error.message ? error.message : String(error)) : null,
node_version: process.version,
os: config.cli.os,
+ ios_package_manager: await getIOSPackageManager(config),
...Object.fromEntries(versions),
};
+ debug('metric payload: %O', data);
+
if (isInteractive()) {
let sysconfig = await readConfig();
@@ -126,6 +131,24 @@ export async function sendMetric(
}
}
+/**
+ * Resolve the iOS dependency manager for telemetry. Returns 'unknown' for
+ * non-iOS projects or when detection throws, so callers never have to handle
+ * errors from this signal.
+ */
+export async function getIOSPackageManager(config: Config): Promise {
+ try {
+ if (!(await pathExists(config.ios.platformDirAbs))) {
+ return 'unknown';
+ }
+
+ return await config.ios.packageManager;
+ } catch (e) {
+ debug('Could not resolve iOS package manager for telemetry: %O', e);
+ return 'unknown';
+ }
+}
+
/**
* Get a unique anonymous identifier for this app.
*/
diff --git a/cli/test/telemetry.spec.ts b/cli/test/telemetry.spec.ts
new file mode 100644
index 0000000000..412048527c
--- /dev/null
+++ b/cli/test/telemetry.spec.ts
@@ -0,0 +1,60 @@
+import { pathExists } from 'fs-extra';
+
+import type { Config, IOSConfig } from '../src/definitions';
+import { getIOSPackageManager } from '../src/telemetry';
+
+jest.mock('fs-extra', () => ({
+ pathExists: jest.fn(),
+}));
+
+const mockedPathExists = pathExists as unknown as jest.Mock;
+
+function makeConfig(ios: Partial): Config {
+ return { ios } as unknown as Config;
+}
+
+describe('getIOSPackageManager', () => {
+ beforeEach(() => {
+ mockedPathExists.mockReset();
+ });
+
+ it('returns "unknown" when the iOS platform directory does not exist', async () => {
+ mockedPathExists.mockResolvedValueOnce(false);
+
+ const result = await getIOSPackageManager(
+ makeConfig({
+ platformDirAbs: '/tmp/no-such-dir/ios',
+ packageManager: Promise.resolve('SPM'),
+ }),
+ );
+
+ expect(result).toBe('unknown');
+ expect(mockedPathExists).toHaveBeenCalledWith('/tmp/no-such-dir/ios');
+ });
+
+ it('returns the resolved package manager when iOS is present', async () => {
+ mockedPathExists.mockResolvedValueOnce(true);
+
+ const result = await getIOSPackageManager(
+ makeConfig({
+ platformDirAbs: '/tmp/app/ios',
+ packageManager: Promise.resolve('SPM'),
+ }),
+ );
+
+ expect(result).toBe('SPM');
+ });
+
+ it('returns "unknown" when packageManager resolution throws', async () => {
+ mockedPathExists.mockResolvedValueOnce(true);
+
+ const result = await getIOSPackageManager(
+ makeConfig({
+ platformDirAbs: '/tmp/app/ios',
+ packageManager: Promise.reject(new Error('boom')),
+ }),
+ );
+
+ expect(result).toBe('unknown');
+ });
+});
From 1d031a4abec2c793079ba8897ad2e40c4cc6c7f9 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Thu, 28 May 2026 15:00:43 +0200
Subject: [PATCH 37/69] fix(cli): revert live reload config on failure (#8485)
---
cli/src/tasks/run.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/cli/src/tasks/run.ts b/cli/src/tasks/run.ts
index 2abdb9cd2c..439c292095 100644
--- a/cli/src/tasks/run.ts
+++ b/cli/src/tasks/run.ts
@@ -116,6 +116,9 @@ export async function runCommand(
await sleepForever();
}
} catch (e: any) {
+ if (options.liveReload) {
+ await CapLiveReloadHelper.revertCapConfigForLiveReload();
+ }
if (!isFatal(e)) {
fatal(e.stack ?? e);
}
From f1077ef9645b1ada2dd2635e3c3220612bdb4da6 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Fri, 29 May 2026 11:02:23 +0200
Subject: [PATCH 38/69] chore: fix typo in declarations.ts (#8488)
---
cli/src/declarations.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts
index bda7ff0c03..1103b65850 100644
--- a/cli/src/declarations.ts
+++ b/cli/src/declarations.ts
@@ -604,7 +604,7 @@ export interface CapacitorConfig {
* Configure the local scheme on Android.
*
* Custom schemes on Android are unable to change the URL path as of Webview 117. Changing this value from anything other than `http` or `https` can result in your
- * application unable to resolve routing. If you must change this for some reason, consider using a hash-based url strategy, but there are no guarentees that this
+ * application unable to resolve routing. If you must change this for some reason, consider using a hash-based url strategy, but there are no guarantees that this
* will continue to work long term as allowing non-standard schemes to modify query parameters and url fragments is only allowed for compatibility reasons.
* https://ionic.io/blog/capacitor-android-customscheme-issue-with-chrome-117
*
From e30773039c0ac249d13780be9d958104cc420cb5 Mon Sep 17 00:00:00 2001
From: Pedro Bilro
Date: Fri, 29 May 2026 13:31:18 +0100
Subject: [PATCH 39/69] feat(cli)!: merge live-reload options into `--url`
(#8468)
Co-authored-by: jcesarmobile
---
cli/src/index.ts | 15 ++----
cli/src/tasks/run.ts | 27 ++++++-----
cli/src/util/livereload.ts | 98 +-------------------------------------
3 files changed, 20 insertions(+), 120 deletions(-)
diff --git a/cli/src/index.ts b/cli/src/index.ts
index fb70e1039b..185df15bac 100644
--- a/cli/src/index.ts
+++ b/cli/src/index.ts
@@ -249,11 +249,8 @@ export function runProgram(config: Config): void {
)
.option('--no-sync', `do not run ${c.input('sync')}`)
.option('--forwardPorts ', 'Automatically run "adb reverse" for better live-reloading support')
- .option('-l, --live-reload', 'Set live-reload URL via CLI (uses defaults, overrides server.url config)')
- .option('--host ', 'Configure host for live-reload URL (used with --live-reload)')
- .option('--port ', 'Configure port for live-reload URL (used with --live-reload)')
+ .option('--url ', 'Load an external URL in the Web View, useful for live-reload (overrides server.url config)')
.option('--configuration ', 'Configuration name of the iOS Scheme')
- .option('--https', 'Use https:// instead of http:// for live-reload URL (used with --live-reload)')
.action(
wrapAction(
telemetryAction(
@@ -270,11 +267,8 @@ export function runProgram(config: Config): void {
targetNameSdkVersion,
sync,
forwardPorts,
- liveReload,
- host,
- port,
+ url,
configuration,
- https,
},
) => {
const { runCommand } = await import('./tasks/run');
@@ -288,11 +282,8 @@ export function runProgram(config: Config): void {
targetNameSdkVersion,
sync,
forwardPorts,
- liveReload,
- host,
- port,
+ url,
configuration,
- https,
});
},
),
diff --git a/cli/src/tasks/run.ts b/cli/src/tasks/run.ts
index 923a335d1a..124422e6e9 100644
--- a/cli/src/tasks/run.ts
+++ b/cli/src/tasks/run.ts
@@ -30,11 +30,8 @@ export interface RunCommandOptions {
targetNameSdkVersion?: string;
sync?: boolean;
forwardPorts?: string;
- liveReload?: boolean;
- host?: string;
- port?: string;
+ url?: string;
configuration?: string;
- https?: boolean;
}
export async function runCommand(
@@ -42,7 +39,17 @@ export async function runCommand(
selectedPlatformName: string,
options: RunCommandOptions,
): Promise {
- options.host = options.host ?? CapLiveReloadHelper.getIpAddress() ?? 'localhost';
+ if (options.url) {
+ let parsed: URL;
+ try {
+ parsed = new URL(options.url);
+ } catch {
+ fatal(`Invalid --url: "${options.url}" is not a valid URL`);
+ }
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
+ fatal(`Invalid --url: must use http:// or https:// (got "${parsed.protocol}//")`);
+ }
+ }
if (selectedPlatformName && !(await isValidPlatform(selectedPlatformName))) {
const platformDir = resolvePlatform(config, selectedPlatformName);
if (platformDir) {
@@ -91,14 +98,14 @@ export async function runCommand(
await sync(config, platformName, false, true);
}
const cordovaPlugins = await getCordovaPlugins(config, platformName);
- if (options.liveReload) {
+ if (options.url) {
await CapLiveReloadHelper.editCapConfigForLiveReload(config, platformName, options);
if (platformName === config.android.name) {
await await writeCordovaAndroidManifest(cordovaPlugins, config, platformName, true);
}
}
await run(config, platformName, options);
- if (options.liveReload) {
+ if (options.url) {
new Promise((resolve) => process.on('SIGINT', resolve))
.then(async () => {
await CapLiveReloadHelper.revertCapConfigForLiveReload();
@@ -107,13 +114,11 @@ export async function runCommand(
}
})
.then(() => process.exit());
- logger.info(
- `App running with live reload listing for: ${options.https ? 'https' : 'http'}://${options.host}${options.port ? `:${options.port}` : ''}. Press Ctrl+C to quit.`,
- );
+ logger.info(`App running with live reload listening for: ${options.url}. Press Ctrl+C to quit.`);
await sleepForever();
}
} catch (e: any) {
- if (options.liveReload) {
+ if (options.url) {
await CapLiveReloadHelper.revertCapConfigForLiveReload();
}
if (!isFatal(e)) {
diff --git a/cli/src/util/livereload.ts b/cli/src/util/livereload.ts
index c4e432b572..f447bec0a6 100644
--- a/cli/src/util/livereload.ts
+++ b/cli/src/util/livereload.ts
@@ -1,5 +1,4 @@
import { readJSONSync, writeJSONSync } from 'fs-extra';
-import { networkInterfaces } from 'os';
import { join } from 'path';
import type { Config } from '../definitions';
@@ -18,100 +17,6 @@ class CapLiveReload {
// nothing to do
}
- getIpAddress(name?: string, family?: any) {
- const interfaces: any = networkInterfaces() ?? {};
-
- const _normalizeFamily = (family?: any) => {
- if (family === 4) {
- return 'ipv4';
- }
- if (family === 6) {
- return 'ipv6';
- }
- return family ? family.toLowerCase() : 'ipv4';
- };
- const isLoopback = (addr: string) => {
- return (
- /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/.test(addr) ||
- /^fe80::1$/.test(addr) ||
- /^::1$/.test(addr) ||
- /^::$/.test(addr)
- );
- };
- const isPrivate = (addr: string) => {
- return (
- /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) ||
- /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) ||
- /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) ||
- /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) ||
- /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) ||
- /^f[cd][0-9a-f]{2}:/i.test(addr) ||
- /^fe80:/i.test(addr) ||
- /^::1$/.test(addr) ||
- /^::$/.test(addr)
- );
- };
- const isPublic = (addr: string) => {
- return !isPrivate(addr);
- };
- const loopback = (family?: any) => {
- //
- // Default to `ipv4`
- //
- family = _normalizeFamily(family);
-
- if (family !== 'ipv4' && family !== 'ipv6') {
- throw new Error('family must be ipv4 or ipv6');
- }
-
- return family === 'ipv4' ? '127.0.0.1' : 'fe80::1';
- };
-
- //
- // Default to `ipv4`
- //
- family = _normalizeFamily(family);
-
- //
- // If a specific network interface has been named,
- // return the address.
- //
- if (name && name !== 'private' && name !== 'public') {
- const res = interfaces[name].filter((details: any) => {
- const itemFamily = _normalizeFamily(details.family);
- return itemFamily === family;
- });
- if (res.length === 0) {
- return undefined;
- }
- return res[0].address;
- }
-
- const all = Object.keys(interfaces)
- .map((nic) => {
- //
- // Note: name will only be `public` or `private`
- // when this is called.
- //
- const addresses = interfaces[nic].filter((details: any) => {
- details.family = _normalizeFamily(details.family);
- if (details.family !== family || isLoopback(details.address)) {
- return false;
- }
- if (!name) {
- return true;
- }
-
- return name === 'public' ? isPrivate(details.address) : isPublic(details.address);
- });
-
- return addresses.length ? addresses[0].address : undefined;
- })
- .filter(Boolean);
-
- return !all.length ? loopback(family) : all[0];
- }
-
async editCapConfigForLiveReload(config: Config, platformName: string, options: RunCommandOptions): Promise {
const platformAbsPath =
platformName == config.ios.name
@@ -125,10 +30,9 @@ class CapLiveReload {
const configJson = readJSONSync(capConfigPath);
this.configJsonToRevertTo.json = JSON.stringify(configJson, null, 2);
this.configJsonToRevertTo.platformPath = capConfigPath;
- const url = `${options.https ? 'https' : 'http'}://${options.host}${options.port ? `:${options.port}` : ''}`;
configJson.server = {
...configJson.server,
- url,
+ url: options.url,
};
writeJSONSync(capConfigPath, configJson, { spaces: '\t' });
}
From d4ad7ffe39daf66e0cfc63af9028d5c05543bde7 Mon Sep 17 00:00:00 2001
From: Joey Pender
Date: Mon, 1 Jun 2026 10:31:48 -0500
Subject: [PATCH 40/69] fix(SystemBars): respect `insetsHandling` disable
(#8481)
---
.../com/getcapacitor/plugin/SystemBars.java | 54 ++++++++++++-------
1 file changed, 35 insertions(+), 19 deletions(-)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
index 76e74207cc..29b76e2f28 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
@@ -16,6 +16,7 @@
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.webkit.WebViewCompat;
+import com.getcapacitor.Logger;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
@@ -32,6 +33,7 @@ public class SystemBars extends Plugin {
static final String BAR_STATUS_BAR = "StatusBar";
static final String BAR_GESTURE_BAR = "NavigationBar";
+ // TODO: In Cap 9, add an additional option "full"
static final String INSETS_HANDLING_CSS = "css";
static final String INSETS_HANDLING_DISABLE = "disable";
@@ -53,7 +55,7 @@ function capacitorSystemBarsCheckMetaViewport() {
capacitorSystemBarsCheckMetaViewport();
""";
- private boolean insetHandlingEnabled = true;
+ private String insetsHandling = INSETS_HANDLING_CSS;
private boolean hasViewportCover = false;
private String currentStatusBarStyle = STYLE_DEFAULT;
@@ -94,9 +96,15 @@ private void initSystemBars() {
String style = getConfig().getString("style", STYLE_DEFAULT).toUpperCase(Locale.US);
boolean hidden = getConfig().getBoolean("hidden", false);
- String insetsHandling = getConfig().getString("insetsHandling", "css");
- if (insetsHandling.equals(INSETS_HANDLING_DISABLE)) {
- insetHandlingEnabled = false;
+ String configuredInsetsHandling = getConfig().getString("insetsHandling", INSETS_HANDLING_CSS);
+ if (INSETS_HANDLING_CSS.equals(configuredInsetsHandling) || INSETS_HANDLING_DISABLE.equals(configuredInsetsHandling)) {
+ insetsHandling = configuredInsetsHandling;
+ } else {
+ Logger.warn(
+ "SystemBars",
+ "Unknown insetsHandling value '" + configuredInsetsHandling + "'. Falling back to '" + INSETS_HANDLING_CSS + "'."
+ );
+ insetsHandling = INSETS_HANDLING_CSS;
}
initWindowInsetsListener();
@@ -146,13 +154,15 @@ public void setAnimation(final PluginCall call) {
@JavascriptInterface
public void onDOMReady() {
- getActivity().runOnUiThread(() -> {
- this.bridge.getWebView().evaluateJavascript(viewportMetaJSFunction, (res) -> {
- hasViewportCover = res.equals("true");
+ if (INSETS_HANDLING_CSS.equals(insetsHandling)) {
+ getActivity().runOnUiThread(() -> {
+ this.bridge.getWebView().evaluateJavascript(viewportMetaJSFunction, (res) -> {
+ hasViewportCover = res.equals("true");
- getBridge().getWebView().requestApplyInsets();
+ getBridge().getWebView().requestApplyInsets();
+ });
});
- });
+ }
}
private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
@@ -164,22 +174,28 @@ private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
}
private void initSafeAreaCSSVariables() {
- WindowInsetsCompat insets;
+ if (INSETS_HANDLING_CSS.equals(insetsHandling)) {
+ WindowInsetsCompat insets;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
- View v = (View) this.getBridge().getWebView().getParent();
- insets = ViewCompat.getRootWindowInsets(v);
- } else {
- insets = WindowInsetsCompat.CONSUMED;
- }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ View v = (View) this.getBridge().getWebView().getParent();
+ insets = ViewCompat.getRootWindowInsets(v);
+ } else {
+ insets = WindowInsetsCompat.CONSUMED;
+ }
- if (insets != null) {
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
+ if (insets != null) {
+ Insets safeAreaInsets = calcSafeAreaInsets(insets);
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
+ }
}
}
private void initWindowInsetsListener() {
+ if (INSETS_HANDLING_DISABLE.equals(insetsHandling)) {
+ return;
+ }
+
ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
From 4c6c3219afb5223211e857457e46283c37eb9424 Mon Sep 17 00:00:00 2001
From: Minh Vu
Date: Mon, 1 Jun 2026 21:04:08 +0200
Subject: [PATCH 41/69] fix(android): show only the requested system bar
(#8480)
Co-authored-by: Joey Pender
Co-authored-by: Pedro Bilro
Co-authored-by: Mark Anderson
---
.../com/getcapacitor/plugin/SystemBars.java | 14 ++--
.../getcapacitor/plugin/SystemBarsTest.java | 73 +++++++++++++++++++
2 files changed, 81 insertions(+), 6 deletions(-)
create mode 100644 android/capacitor/src/test/java/com/getcapacitor/plugin/SystemBarsTest.java
diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
index 29b76e2f28..fd71370945 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
@@ -304,19 +304,21 @@ private void setHidden(boolean hide, String bar) {
WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat.getInsetsController(window, window.getDecorView());
if (hide) {
- if (bar.isEmpty() || bar.equals(BAR_STATUS_BAR)) {
+ if (bar.isEmpty()) {
+ windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.systemBars());
+ } else if (bar.equals(BAR_STATUS_BAR)) {
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.statusBars());
- }
- if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
+ } else if (bar.equals(BAR_GESTURE_BAR)) {
windowInsetsControllerCompat.hide(WindowInsetsCompat.Type.navigationBars());
}
return;
}
- if (bar.isEmpty() || bar.equals(BAR_STATUS_BAR)) {
+ if (bar.isEmpty()) {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.systemBars());
- }
- if (bar.isEmpty() || bar.equals(BAR_GESTURE_BAR)) {
+ } else if (bar.equals(BAR_STATUS_BAR)) {
+ windowInsetsControllerCompat.show(WindowInsetsCompat.Type.statusBars());
+ } else if (bar.equals(BAR_GESTURE_BAR)) {
windowInsetsControllerCompat.show(WindowInsetsCompat.Type.navigationBars());
}
}
diff --git a/android/capacitor/src/test/java/com/getcapacitor/plugin/SystemBarsTest.java b/android/capacitor/src/test/java/com/getcapacitor/plugin/SystemBarsTest.java
new file mode 100644
index 0000000000..f72373a88e
--- /dev/null
+++ b/android/capacitor/src/test/java/com/getcapacitor/plugin/SystemBarsTest.java
@@ -0,0 +1,73 @@
+package com.getcapacitor.plugin;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.view.View;
+import android.view.Window;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.view.WindowCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.core.view.WindowInsetsControllerCompat;
+import com.getcapacitor.Bridge;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+
+public class SystemBarsTest {
+
+ @Test
+ public void showWithEmptyBarShowsSystemBars() throws Exception {
+ WindowInsetsControllerCompat controller = invokeSetHidden("");
+
+ verify(controller).show(WindowInsetsCompat.Type.systemBars());
+ verify(controller, never()).show(WindowInsetsCompat.Type.statusBars());
+ verify(controller, never()).show(WindowInsetsCompat.Type.navigationBars());
+ }
+
+ @Test
+ public void showWithStatusBarShowsOnlyStatusBars() throws Exception {
+ WindowInsetsControllerCompat controller = invokeSetHidden("StatusBar");
+
+ verify(controller).show(WindowInsetsCompat.Type.statusBars());
+ verify(controller, never()).show(WindowInsetsCompat.Type.systemBars());
+ verify(controller, never()).show(WindowInsetsCompat.Type.navigationBars());
+ }
+
+ @Test
+ public void showWithNavigationBarShowsOnlyNavigationBars() throws Exception {
+ WindowInsetsControllerCompat controller = invokeSetHidden("NavigationBar");
+
+ verify(controller).show(WindowInsetsCompat.Type.navigationBars());
+ verify(controller, never()).show(WindowInsetsCompat.Type.systemBars());
+ verify(controller, never()).show(WindowInsetsCompat.Type.statusBars());
+ }
+
+ private WindowInsetsControllerCompat invokeSetHidden(String bar) throws Exception {
+ SystemBars plugin = new SystemBars();
+ Bridge bridge = mock(Bridge.class);
+ AppCompatActivity activity = mock(AppCompatActivity.class);
+ Window window = mock(Window.class);
+ View decorView = mock(View.class);
+ WindowInsetsControllerCompat controller = mock(WindowInsetsControllerCompat.class);
+
+ when(bridge.getActivity()).thenReturn(activity);
+ when(activity.getWindow()).thenReturn(window);
+ when(window.getDecorView()).thenReturn(decorView);
+
+ plugin.setBridge(bridge);
+
+ try (MockedStatic windowCompat = mockStatic(WindowCompat.class)) {
+ windowCompat.when(() -> WindowCompat.getInsetsController(window, decorView)).thenReturn(controller);
+
+ Method setHidden = SystemBars.class.getDeclaredMethod("setHidden", boolean.class, String.class);
+ setHidden.setAccessible(true);
+ setHidden.invoke(plugin, false, bar);
+ }
+
+ return controller;
+ }
+}
From 36b729d10380687bf5eab2cfa8affcbd1655541b Mon Sep 17 00:00:00 2001
From: Mark Anderson
Date: Mon, 1 Jun 2026 15:11:51 -0400
Subject: [PATCH 42/69] chore: Decrease timeout for CI jobs from 60 to 30
minutes (#8476)
Co-authored-by: jcesarmobile
---
.github/workflows/ci.yml | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8664a402da..56fce53892 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ concurrency:
jobs:
setup:
runs-on: ubuntu-latest
- timeout-minutes: 60
+ timeout-minutes: 30
steps:
- name: Get Latest
uses: actions/setup-node@v6
@@ -32,7 +32,7 @@ jobs:
key: ${{ runner.OS }}-dependencies-cache-${{ hashFiles('**/package.json') }}
lint:
runs-on: macos-15
- timeout-minutes: 60
+ timeout-minutes: 30
steps:
- uses: actions/setup-node@v6
with:
@@ -48,7 +48,7 @@ jobs:
- run: npm run lint
test-cli:
runs-on: macos-15
- timeout-minutes: 60
+ timeout-minutes: 30
needs:
- setup
- lint
@@ -69,7 +69,7 @@ jobs:
working-directory: ./cli
test-core:
runs-on: ubuntu-latest
- timeout-minutes: 60
+ timeout-minutes: 30
needs:
- setup
- lint
@@ -90,7 +90,7 @@ jobs:
working-directory: ./core
test-ios:
runs-on: macos-15
- timeout-minutes: 60
+ timeout-minutes: 30
needs:
- setup
- lint
@@ -118,7 +118,7 @@ jobs:
run: sh ./scripts/native-podspec.sh lint
test-android:
runs-on: ubuntu-latest
- timeout-minutes: 60
+ timeout-minutes: 30
needs:
- setup
- lint
From c4f4e4beb562ca9f2d05f4da3573018c9d3cb986 Mon Sep 17 00:00:00 2001
From: OS-ruimoreiramendes
Date: Tue, 2 Jun 2026 16:53:35 +0100
Subject: [PATCH 43/69] feat(ios)!: bump minimum deployment target to 16
(#8491)
---
cli/src/config.ts | 2 +-
cli/src/tasks/migrate.ts | 2 +-
.../App/App.xcodeproj/project.pbxproj | 8 ++++----
ios-pods-template/App/Podfile | 2 +-
.../App/App.xcodeproj/project.pbxproj | 8 ++++----
ios-spm-template/App/CapApp-SPM/Package.swift | 2 +-
ios/Capacitor.podspec | 2 +-
.../Capacitor.xcodeproj/project.pbxproj | 16 ++++++++--------
.../Capacitor/CAPBridgeViewController.swift | 4 +---
ios/CapacitorCordova.podspec | 2 +-
.../CapacitorCordova.xcodeproj/project.pbxproj | 4 ++--
ios/scripts/pods_helpers.rb | 6 +++---
12 files changed, 28 insertions(+), 30 deletions(-)
diff --git a/cli/src/config.ts b/cli/src/config.ts
index 4e71fb01d1..2cb64e78d2 100644
--- a/cli/src/config.ts
+++ b/cli/src/config.ts
@@ -286,7 +286,7 @@ async function loadIOSConfig(rootDir: string, extConfig: ExternalConfig): Promis
};
return {
name,
- minVersion: '15.0',
+ minVersion: '16.0',
platformDir,
platformDirAbs,
scheme,
diff --git a/cli/src/tasks/migrate.ts b/cli/src/tasks/migrate.ts
index f1f792e431..47e14b4e4f 100644
--- a/cli/src/tasks/migrate.ts
+++ b/cli/src/tasks/migrate.ts
@@ -46,7 +46,7 @@ const plugins = [
const coreVersion = '^8.0.0';
const pluginVersion = '^8.0.0';
const gradleVersion = '8.14.3';
-const iOSVersion = '15';
+const iOSVersion = '16';
const kotlinVersion = '2.2.20';
let installFailed = false;
diff --git a/ios-pods-template/App/App.xcodeproj/project.pbxproj b/ios-pods-template/App/App.xcodeproj/project.pbxproj
index 092f574919..f1ec6501e8 100644
--- a/ios-pods-template/App/App.xcodeproj/project.pbxproj
+++ b/ios-pods-template/App/App.xcodeproj/project.pbxproj
@@ -283,7 +283,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -334,7 +334,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@@ -350,7 +350,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.0;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
@@ -370,7 +370,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.App;
diff --git a/ios-pods-template/App/Podfile b/ios-pods-template/App/Podfile
index 25801ecef7..b50444d463 100644
--- a/ios-pods-template/App/Podfile
+++ b/ios-pods-template/App/Podfile
@@ -1,6 +1,6 @@
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
-platform :ios, '15.0'
+platform :ios, '16.0'
use_frameworks!
# workaround to avoid Xcode caching of Pods that requires
diff --git a/ios-spm-template/App/App.xcodeproj/project.pbxproj b/ios-spm-template/App/App.xcodeproj/project.pbxproj
index 46f934efd5..06959b343b 100644
--- a/ios-spm-template/App/App.xcodeproj/project.pbxproj
+++ b/ios-spm-template/App/App.xcodeproj/project.pbxproj
@@ -230,7 +230,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -281,7 +281,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -298,7 +298,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -320,7 +320,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = App/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/ios-spm-template/App/CapApp-SPM/Package.swift b/ios-spm-template/App/CapApp-SPM/Package.swift
index 5cde8b6645..e3309a275a 100644
--- a/ios-spm-template/App/CapApp-SPM/Package.swift
+++ b/ios-spm-template/App/CapApp-SPM/Package.swift
@@ -4,7 +4,7 @@ import PackageDescription
// DO NOT MODIFY THIS FILE - managed by Capacitor CLI commands
let package = Package(
name: "CapApp-SPM",
- platforms: [.iOS(.v15)],
+ platforms: [.iOS(.v16)],
products: [
.library(
name: "CapApp-SPM",
diff --git a/ios/Capacitor.podspec b/ios/Capacitor.podspec
index 1b92962750..01ce20591e 100644
--- a/ios/Capacitor.podspec
+++ b/ios/Capacitor.podspec
@@ -12,7 +12,7 @@ Pod::Spec.new do |s|
s.summary = 'Capacitor for iOS'
s.license = 'MIT'
s.homepage = 'https://capacitorjs.com/'
- s.ios.deployment_target = '15.0'
+ s.ios.deployment_target = '16.0'
s.authors = { 'Ionic Team' => 'hi@ionicframework.com' }
s.source = { git: 'https://github.com/ionic-team/capacitor.git', tag: package['version'] }
s.source_files = "#{prefix}Capacitor/Capacitor/**/*.{swift,h,m}"
diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
index 9e5ac570fe..9da29479dc 100644
--- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
+++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
@@ -881,7 +881,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -939,7 +939,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@@ -966,7 +966,7 @@
);
INFOPLIST_FILE = Capacitor/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = Capacitor/Capacitor.modulemap;
PRODUCT_BUNDLE_IDENTIFIER = com.capacitorjs.ios.Capacitor;
@@ -996,7 +996,7 @@
);
INFOPLIST_FILE = Capacitor/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = Capacitor/Capacitor.modulemap;
PRODUCT_BUNDLE_IDENTIFIER = com.capacitorjs.ios.Capacitor;
@@ -1060,7 +1060,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = TestsHostApp/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
@@ -1083,7 +1083,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = TestsHostApp/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.capacitorjs.ios.TestsHostApp;
@@ -1106,7 +1106,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
@@ -1133,7 +1133,7 @@
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift
index 567d6c69a9..33f17be66c 100644
--- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift
+++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift
@@ -130,9 +130,7 @@ import WebKit
webViewConfiguration.allowsAirPlayForMediaPlayback = true
webViewConfiguration.mediaTypesRequiringUserActionForPlayback = []
webViewConfiguration.limitsNavigationsToAppBoundDomains = instanceConfiguration.limitsNavigationsToAppBoundDomains
- if #available(iOS 15.4, *) {
- webViewConfiguration.preferences.isElementFullscreenEnabled = true
- }
+ webViewConfiguration.preferences.isElementFullscreenEnabled = true
if let appendUserAgent = instanceConfiguration.appendedUserAgentString {
if let appName = webViewConfiguration.applicationNameForUserAgent {
webViewConfiguration.applicationNameForUserAgent = "\(appName) \(appendUserAgent)"
diff --git a/ios/CapacitorCordova.podspec b/ios/CapacitorCordova.podspec
index b4aa014f30..98f6bc3474 100644
--- a/ios/CapacitorCordova.podspec
+++ b/ios/CapacitorCordova.podspec
@@ -15,7 +15,7 @@ Pod::Spec.new do |s|
s.license = 'MIT'
s.authors = { 'Ionic Team' => 'hi@ionicframework.com' }
s.source = { git: 'https://github.com/ionic-team/capacitor', tag: s.version.to_s }
- s.platform = :ios, 15.0
+ s.platform = :ios, 16.0
s.source_files = "#{prefix}CapacitorCordova/CapacitorCordova/**/*.{h,m,swift}"
s.public_header_files = "#{prefix}CapacitorCordova/CapacitorCordova/Classes/Public/*.h",
"#{prefix}CapacitorCordova/CapacitorCordova/CapacitorCordova.h"
diff --git a/ios/CapacitorCordova/CapacitorCordova.xcodeproj/project.pbxproj b/ios/CapacitorCordova/CapacitorCordova.xcodeproj/project.pbxproj
index 3f9ef4c65c..e95b03f613 100644
--- a/ios/CapacitorCordova/CapacitorCordova.xcodeproj/project.pbxproj
+++ b/ios/CapacitorCordova/CapacitorCordova.xcodeproj/project.pbxproj
@@ -328,7 +328,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -383,7 +383,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
diff --git a/ios/scripts/pods_helpers.rb b/ios/scripts/pods_helpers.rb
index 7a240db461..42335c2072 100644
--- a/ios/scripts/pods_helpers.rb
+++ b/ios/scripts/pods_helpers.rb
@@ -1,11 +1,11 @@
def assertDeploymentTarget(installer)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
- # ensure IPHONEOS_DEPLOYMENT_TARGET is at least 15.0
+ # ensure IPHONEOS_DEPLOYMENT_TARGET is at least 16.0
deployment_target = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f
- should_upgrade = deployment_target < 15.0 && deployment_target != 0.0
+ should_upgrade = deployment_target < 16.0 && deployment_target != 0.0
if should_upgrade
- config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '16.0'
end
end
end
From 41fd9deb6df07ef83ae390d16824e8bcbefef517 Mon Sep 17 00:00:00 2001
From: "Github Workflow (on behalf of markemer)"
Date: Tue, 2 Jun 2026 15:56:03 +0000
Subject: [PATCH 44/69] Release 8.4.0
---
CHANGELOG.md | 15 +++++++++++++++
android/CHANGELOG.md | 12 ++++++++++++
android/package.json | 4 ++--
cli/CHANGELOG.md | 11 +++++++++++
cli/package.json | 2 +-
core/CHANGELOG.md | 4 ++++
core/package.json | 2 +-
ios/CHANGELOG.md | 6 ++++++
ios/package.json | 4 ++--
lerna.json | 2 +-
10 files changed, 55 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7f2b77136..cd4fe5f5ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02)
+
+### Bug Fixes
+
+- **android:** show only the requested system bar ([#8480](https://github.com/ionic-team/capacitor/issues/8480)) ([4c6c321](https://github.com/ionic-team/capacitor/commit/4c6c3219afb5223211e857457e46283c37eb9424))
+- **cli:** revert live reload config on failure ([#8485](https://github.com/ionic-team/capacitor/issues/8485)) ([1d031a4](https://github.com/ionic-team/capacitor/commit/1d031a4abec2c793079ba8897ad2e40c4cc6c7f9))
+- **SystemBars:** make `safe-area-inset-x` available on API <= 34 ([#8424](https://github.com/ionic-team/capacitor/issues/8424)) ([e456de0](https://github.com/ionic-team/capacitor/commit/e456de083e19644f484bec5a5359cb67960ac8bc))
+- **SystemBars:** respect `insetsHandling` disable ([#8481](https://github.com/ionic-team/capacitor/issues/8481)) ([d4ad7ff](https://github.com/ionic-team/capacitor/commit/d4ad7ffe39daf66e0cfc63af9028d5c05543bde7))
+
+### Features
+
+- add method getDouble to plugin config ([#7638](https://github.com/ionic-team/capacitor/issues/7638)) ([93c72de](https://github.com/ionic-team/capacitor/commit/93c72de40a2ec4c78b33659250cb08340083088e))
+- **cli:** add experimental packageOptions ([#8471](https://github.com/ionic-team/capacitor/issues/8471)) ([258867b](https://github.com/ionic-team/capacitor/commit/258867b7bf37b1837b99b02ec9638e5a6df08d97))
+- **cli:** capture ios_package_manager in telemetry ([#8482](https://github.com/ionic-team/capacitor/issues/8482)) ([b4b297a](https://github.com/ionic-team/capacitor/commit/b4b297a52f8732659662d5e5aaeff81c0f7d9835))
+
## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
**Note:** Version bump only for package capacitor
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index 74d538d9fe..df7ce545e4 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02)
+
+### Bug Fixes
+
+- **android:** show only the requested system bar ([#8480](https://github.com/ionic-team/capacitor/issues/8480)) ([4c6c321](https://github.com/ionic-team/capacitor/commit/4c6c3219afb5223211e857457e46283c37eb9424))
+- **SystemBars:** make `safe-area-inset-x` available on API <= 34 ([#8424](https://github.com/ionic-team/capacitor/issues/8424)) ([e456de0](https://github.com/ionic-team/capacitor/commit/e456de083e19644f484bec5a5359cb67960ac8bc))
+- **SystemBars:** respect `insetsHandling` disable ([#8481](https://github.com/ionic-team/capacitor/issues/8481)) ([d4ad7ff](https://github.com/ionic-team/capacitor/commit/d4ad7ffe39daf66e0cfc63af9028d5c05543bde7))
+
+### Features
+
+- add method getDouble to plugin config ([#7638](https://github.com/ionic-team/capacitor/issues/7638)) ([93c72de](https://github.com/ionic-team/capacitor/commit/93c72de40a2ec4c78b33659250cb08340083088e))
+
## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
**Note:** Version bump only for package @capacitor/android
diff --git a/android/package.json b/android/package.json
index 8ae93f3f2b..af3f4f556f 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "8.3.4",
+ "version": "8.4.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -23,7 +23,7 @@
"verify": "./gradlew clean lint build test -b capacitor/build.gradle"
},
"peerDependencies": {
- "@capacitor/core": "^8.3.0"
+ "@capacitor/core": "^8.4.0"
},
"publishConfig": {
"access": "public"
diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index bba24688f6..ee48a8d5f4 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02)
+
+### Bug Fixes
+
+- **cli:** revert live reload config on failure ([#8485](https://github.com/ionic-team/capacitor/issues/8485)) ([1d031a4](https://github.com/ionic-team/capacitor/commit/1d031a4abec2c793079ba8897ad2e40c4cc6c7f9))
+
+### Features
+
+- **cli:** add experimental packageOptions ([#8471](https://github.com/ionic-team/capacitor/issues/8471)) ([258867b](https://github.com/ionic-team/capacitor/commit/258867b7bf37b1837b99b02ec9638e5a6df08d97))
+- **cli:** capture ios_package_manager in telemetry ([#8482](https://github.com/ionic-team/capacitor/issues/8482)) ([b4b297a](https://github.com/ionic-team/capacitor/commit/b4b297a52f8732659662d5e5aaeff81c0f7d9835))
+
## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
**Note:** Version bump only for package @capacitor/cli
diff --git a/cli/package.json b/cli/package.json
index 9df346b143..1f987d33ad 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "8.3.4",
+ "version": "8.4.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index 017293794f..1de9522ff4 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02)
+
+**Note:** Version bump only for package @capacitor/core
+
## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
**Note:** Version bump only for package @capacitor/core
diff --git a/core/package.json b/core/package.json
index eef113a50a..b22aef174f 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "8.3.4",
+ "version": "8.4.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 87ee73bb4f..eac0a4a1ca 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [8.4.0](https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0) (2026-06-02)
+
+### Features
+
+- add method getDouble to plugin config ([#7638](https://github.com/ionic-team/capacitor/issues/7638)) ([93c72de](https://github.com/ionic-team/capacitor/commit/93c72de40a2ec4c78b33659250cb08340083088e))
+
## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12)
**Note:** Version bump only for package @capacitor/ios
diff --git a/ios/package.json b/ios/package.json
index a7b8d31335..a58c020903 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "8.3.4",
+ "version": "8.4.0",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -25,7 +25,7 @@
"xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd .."
},
"peerDependencies": {
- "@capacitor/core": "^8.3.0"
+ "@capacitor/core": "^8.4.0"
},
"publishConfig": {
"access": "public"
diff --git a/lerna.json b/lerna.json
index 1e53d6bdca..b2efb400d4 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "8.3.4",
+ "version": "8.4.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From 5bf135bc0741778db0c9b50477b971860f72878c Mon Sep 17 00:00:00 2001
From: "Github Workflow (on behalf of markemer)"
Date: Tue, 2 Jun 2026 16:26:21 +0000
Subject: [PATCH 45/69] Release 9.0.0-alpha.3
---
CHANGELOG.md | 23 +++++++++++++++++++++++
android/CHANGELOG.md | 16 ++++++++++++++++
android/package.json | 4 ++--
cli/CHANGELOG.md | 19 +++++++++++++++++++
cli/package.json | 2 +-
core/CHANGELOG.md | 8 ++++++++
core/package.json | 2 +-
ios/CHANGELOG.md | 10 ++++++++++
ios/package.json | 4 ++--
lerna.json | 2 +-
10 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0eb8f3a0e2..1b266d2040 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,29 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.2...9.0.0-alpha.3) (2026-06-02)
+
+### Bug Fixes
+
+- **android:** show only the requested system bar ([#8480](https://github.com/ionic-team/capacitor/issues/8480)) ([4c6c321](https://github.com/ionic-team/capacitor/commit/4c6c3219afb5223211e857457e46283c37eb9424))
+- **cli:** revert live reload config on failure ([#8485](https://github.com/ionic-team/capacitor/issues/8485)) ([1d031a4](https://github.com/ionic-team/capacitor/commit/1d031a4abec2c793079ba8897ad2e40c4cc6c7f9))
+- **SystemBars:** make `safe-area-inset-x` available on API <= 34 ([#8424](https://github.com/ionic-team/capacitor/issues/8424)) ([e456de0](https://github.com/ionic-team/capacitor/commit/e456de083e19644f484bec5a5359cb67960ac8bc))
+- **SystemBars:** respect `insetsHandling` disable ([#8481](https://github.com/ionic-team/capacitor/issues/8481)) ([d4ad7ff](https://github.com/ionic-team/capacitor/commit/d4ad7ffe39daf66e0cfc63af9028d5c05543bde7))
+
+### Features
+
+- add method getDouble to plugin config ([#7638](https://github.com/ionic-team/capacitor/issues/7638)) ([93c72de](https://github.com/ionic-team/capacitor/commit/93c72de40a2ec4c78b33659250cb08340083088e))
+- **cli:** add experimental packageOptions ([#8471](https://github.com/ionic-team/capacitor/issues/8471)) ([258867b](https://github.com/ionic-team/capacitor/commit/258867b7bf37b1837b99b02ec9638e5a6df08d97))
+- **cli:** capture ios_package_manager in telemetry ([#8482](https://github.com/ionic-team/capacitor/issues/8482)) ([b4b297a](https://github.com/ionic-team/capacitor/commit/b4b297a52f8732659662d5e5aaeff81c0f7d9835))
+
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...8.3.4) (2026-05-12)
+
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...8.3.3) (2026-05-08)
+
+### Bug Fixes
+
+- **cli:** copy plugin files in CocoaPods projects ([#8467](https://github.com/ionic-team/capacitor/issues/8467)) ([b2d7719](https://github.com/ionic-team/capacitor/commit/b2d771926a180e60deea31992d7d4abcd5ca3bc7))
+
# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
**Note:** Version bump only for package capacitor
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index 6b62e550ff..44df7be5e1 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.2...9.0.0-alpha.3) (2026-06-02)
+
+### Bug Fixes
+
+- **android:** show only the requested system bar ([#8480](https://github.com/ionic-team/capacitor/issues/8480)) ([4c6c321](https://github.com/ionic-team/capacitor/commit/4c6c3219afb5223211e857457e46283c37eb9424))
+- **SystemBars:** make `safe-area-inset-x` available on API <= 34 ([#8424](https://github.com/ionic-team/capacitor/issues/8424)) ([e456de0](https://github.com/ionic-team/capacitor/commit/e456de083e19644f484bec5a5359cb67960ac8bc))
+- **SystemBars:** respect `insetsHandling` disable ([#8481](https://github.com/ionic-team/capacitor/issues/8481)) ([d4ad7ff](https://github.com/ionic-team/capacitor/commit/d4ad7ffe39daf66e0cfc63af9028d5c05543bde7))
+
+### Features
+
+- add method getDouble to plugin config ([#7638](https://github.com/ionic-team/capacitor/issues/7638)) ([93c72de](https://github.com/ionic-team/capacitor/commit/93c72de40a2ec4c78b33659250cb08340083088e))
+
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...8.3.4) (2026-05-12)
+
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...8.3.3) (2026-05-08)
+
# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
**Note:** Version bump only for package @capacitor/android
diff --git a/android/package.json b/android/package.json
index 8022d692c8..575e8ec18d 100644
--- a/android/package.json
+++ b/android/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/android",
- "version": "9.0.0-alpha.2",
+ "version": "9.0.0-alpha.3",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -26,7 +26,7 @@
"verify": "./gradlew clean lint build test -b capacitor/build.gradle"
},
"peerDependencies": {
- "@capacitor/core": "^9.0.0-alpha.2"
+ "@capacitor/core": "^9.0.0-alpha.3"
},
"publishConfig": {
"access": "public"
diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index 1fd1c69356..1c4dd6279e 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.2...9.0.0-alpha.3) (2026-06-02)
+
+### Bug Fixes
+
+- **cli:** revert live reload config on failure ([#8485](https://github.com/ionic-team/capacitor/issues/8485)) ([1d031a4](https://github.com/ionic-team/capacitor/commit/1d031a4abec2c793079ba8897ad2e40c4cc6c7f9))
+
+### Features
+
+- **cli:** add experimental packageOptions ([#8471](https://github.com/ionic-team/capacitor/issues/8471)) ([258867b](https://github.com/ionic-team/capacitor/commit/258867b7bf37b1837b99b02ec9638e5a6df08d97))
+- **cli:** capture ios_package_manager in telemetry ([#8482](https://github.com/ionic-team/capacitor/issues/8482)) ([b4b297a](https://github.com/ionic-team/capacitor/commit/b4b297a52f8732659662d5e5aaeff81c0f7d9835))
+
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...8.3.4) (2026-05-12)
+
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...8.3.3) (2026-05-08)
+
+### Bug Fixes
+
+- **cli:** copy plugin files in CocoaPods projects ([#8467](https://github.com/ionic-team/capacitor/issues/8467)) ([b2d7719](https://github.com/ionic-team/capacitor/commit/b2d771926a180e60deea31992d7d4abcd5ca3bc7))
+
# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
**Note:** Version bump only for package @capacitor/cli
diff --git a/cli/package.json b/cli/package.json
index 38c0c01b39..8f7de6b14d 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/cli",
- "version": "9.0.0-alpha.2",
+ "version": "9.0.0-alpha.3",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md
index 3285ca234c..cbcba0d990 100644
--- a/core/CHANGELOG.md
+++ b/core/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.2...9.0.0-alpha.3) (2026-06-02)
+
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...8.3.4) (2026-05-12)
+
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...8.3.3) (2026-05-08)
+
+**Note:** Version bump only for package @capacitor/core
+
# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
**Note:** Version bump only for package @capacitor/core
diff --git a/core/package.json b/core/package.json
index 8567c8be97..2969b6130f 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/core",
- "version": "9.0.0-alpha.2",
+ "version": "9.0.0-alpha.3",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 0a6745fcf3..4a99bf4727 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [9.0.0-alpha.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.2...9.0.0-alpha.3) (2026-06-02)
+
+### Features
+
+- add method getDouble to plugin config ([#7638](https://github.com/ionic-team/capacitor/issues/7638)) ([93c72de](https://github.com/ionic-team/capacitor/commit/93c72de40a2ec4c78b33659250cb08340083088e))
+
+## [8.3.4](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...8.3.4) (2026-05-12)
+
+## [8.3.3](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.0...8.3.3) (2026-05-08)
+
# [9.0.0-alpha.2](https://github.com/ionic-team/capacitor/compare/9.0.0-alpha.1...9.0.0-alpha.2) (2026-05-12)
**Note:** Version bump only for package @capacitor/ios
diff --git a/ios/package.json b/ios/package.json
index a57c13889e..87c2b0b2e9 100644
--- a/ios/package.json
+++ b/ios/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor/ios",
- "version": "9.0.0-alpha.2",
+ "version": "9.0.0-alpha.3",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team (https://ionic.io)",
@@ -25,7 +25,7 @@
"xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd .."
},
"peerDependencies": {
- "@capacitor/core": "^9.0.0-alpha.2"
+ "@capacitor/core": "^9.0.0-alpha.3"
},
"publishConfig": {
"access": "public"
diff --git a/lerna.json b/lerna.json
index cfcbca64cb..eab51ece20 100644
--- a/lerna.json
+++ b/lerna.json
@@ -11,6 +11,6 @@
"tagVersionPrefix": ""
}
},
- "version": "9.0.0-alpha.2",
+ "version": "9.0.0-alpha.3",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
From 28bb2c687069dfdd6aa7abc866004a1c6388d103 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Tue, 2 Jun 2026 21:31:41 +0200
Subject: [PATCH 46/69] fix(cli): patch Capacitor SPM dependency version in
plugins (#8492)
Co-authored-by: Mark Anderson
---
cli/src/ios/update.ts | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts
index b060e59a8f..1f4bc87d03 100644
--- a/cli/src/ios/update.ts
+++ b/cli/src/ios/update.ts
@@ -1,5 +1,6 @@
import { copy, remove, pathExists, readFile, realpath, writeFile } from 'fs-extra';
import { basename, dirname, join, relative } from 'path';
+import { major } from 'semver';
import c from '../colors';
import { checkPlatformVersions, getCapacitorPackageVersion, runTask } from '../common';
@@ -58,6 +59,28 @@ async function updatePluginFiles(config: Config, plugins: Plugin[], deployment:
await generateCordovaPackageFiles(cordovaPlugins, config);
const validSPMPackages = await checkPluginsForPackageSwift(config, plugins);
+ await Promise.all(
+ validSPMPackages.map(async (plugin) => {
+ const iosPlatformVersion = await getCapacitorPackageVersion(config, config.ios.name);
+ const packageSwiftPath = join(plugin.rootPath, 'Package.swift');
+ let content = await readFile(packageSwiftPath, { encoding: 'utf-8' });
+ const regex = new RegExp(
+ 'url:\\s*"https://github.com/ionic-team/capacitor-swift-pm\\.git",\\s*from:\\s*"([^"]+)"',
+ );
+ const version = content.match(regex)?.[1];
+ const majorCapVersion = major(iosPlatformVersion);
+ if (version && major(version) != majorCapVersion) {
+ content = setAllStringIn(
+ content,
+ `url: "https://github.com/ionic-team/capacitor-swift-pm.git",`,
+ `)`,
+ ` from: "${majorCapVersion}.0.0"`,
+ );
+ await writeFile(packageSwiftPath, content);
+ logger.warn(`${plugin.id} is built for Capacitor ${major(version)}, it might cause issues`);
+ }
+ }),
+ );
await generatePackageFile(config, validSPMPackages.concat(cordovaPlugins));
} else {
From 16ca49dfcb26b761e35d5002b24710ba6e2150b2 Mon Sep 17 00:00:00 2001
From: OS-ruimoreiramendes
Date: Wed, 3 Jun 2026 12:40:10 +0100
Subject: [PATCH 47/69] chore(cli): bump default Kotlin version to 2.4.0
(#8494)
---
cli/src/android/update.ts | 2 +-
cli/src/tasks/migrate.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/cli/src/android/update.ts b/cli/src/android/update.ts
index 5053f639d5..45275bddc3 100644
--- a/cli/src/android/update.ts
+++ b/cli/src/android/update.ts
@@ -264,7 +264,7 @@ if (hasProperty('postBuildExtras')) {
export async function handleCordovaPluginsGradle(config: Config, cordovaPlugins: Plugin[]): Promise {
const pluginsGradlePath = join(config.android.cordovaPluginsDirAbs, 'build.gradle');
const kotlinNeeded = await kotlinNeededCheck(config, cordovaPlugins);
- const kotlinVersionString = config.app.extConfig.cordova?.preferences?.GradlePluginKotlinVersion ?? '2.2.20';
+ const kotlinVersionString = config.app.extConfig.cordova?.preferences?.GradlePluginKotlinVersion ?? '2.4.0';
const frameworksArray: any[] = [];
let prefsArray: any[] = [];
const applyArray: any[] = [];
diff --git a/cli/src/tasks/migrate.ts b/cli/src/tasks/migrate.ts
index 47e14b4e4f..addcd5bf4b 100644
--- a/cli/src/tasks/migrate.ts
+++ b/cli/src/tasks/migrate.ts
@@ -47,7 +47,7 @@ const coreVersion = '^8.0.0';
const pluginVersion = '^8.0.0';
const gradleVersion = '8.14.3';
const iOSVersion = '16';
-const kotlinVersion = '2.2.20';
+const kotlinVersion = '2.4.0';
let installFailed = false;
export async function migrateCommand(config: Config, noprompt: boolean, packagemanager: string): Promise {
From eaf8967594b490eb0393ee20db183219f5d5480d Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Wed, 3 Jun 2026 14:45:04 +0200
Subject: [PATCH 48/69] chore(android-template): bump compile/targetSdkVersion
to 37 (#8495)
---
android-template/variables.gradle | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/android-template/variables.gradle b/android-template/variables.gradle
index ee4ba41c46..445a4730d4 100644
--- a/android-template/variables.gradle
+++ b/android-template/variables.gradle
@@ -1,7 +1,7 @@
ext {
minSdkVersion = 24
- compileSdkVersion = 36
- targetSdkVersion = 36
+ compileSdkVersion = 37
+ targetSdkVersion = 37
androidxActivityVersion = '1.11.0'
androidxAppCompatVersion = '1.7.1'
androidxCoordinatorLayoutVersion = '1.3.0'
From 3d77cc20d94ee020f27ca224653121c791edf919 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Wed, 3 Jun 2026 14:45:18 +0200
Subject: [PATCH 49/69] chore(android)!: bump compile/targetSdkVersion to 37
(#8496)
---
android/capacitor-cordova/build.gradle | 4 ++--
android/capacitor/build.gradle | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/android/capacitor-cordova/build.gradle b/android/capacitor-cordova/build.gradle
index 717c6b1aa2..5987eb0e83 100644
--- a/android/capacitor-cordova/build.gradle
+++ b/android/capacitor-cordova/build.gradle
@@ -17,10 +17,10 @@ apply plugin: 'com.android.library'
android {
namespace = "com.getcapacitor.cordova"
- compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 37
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
- targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 37
versionCode 1
versionName "1.0"
consumerProguardFiles 'proguard-rules.pro'
diff --git a/android/capacitor/build.gradle b/android/capacitor/build.gradle
index 5841ded35d..7421ce57f8 100644
--- a/android/capacitor/build.gradle
+++ b/android/capacitor/build.gradle
@@ -40,10 +40,10 @@ if (System.getenv("CAP_PUBLISH") == "true") {
android {
namespace = "com.getcapacitor.android"
- compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 37
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
- targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 37
versionCode 1
versionName "1.0"
consumerProguardFiles 'proguard-rules.pro'
From 1c3a28d480c4acc1ae2e2aba0a6a902aabf540a7 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Wed, 3 Jun 2026 15:15:22 +0200
Subject: [PATCH 50/69] chore(cli)!: bump compile/targetSdkVersion to 37
(#8497)
---
capacitor-cordova-android-plugins/build.gradle | 4 ++--
cli/src/tasks/migrate.ts | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/capacitor-cordova-android-plugins/build.gradle b/capacitor-cordova-android-plugins/build.gradle
index 5edcbfd2c2..96d1707a95 100644
--- a/capacitor-cordova-android-plugins/build.gradle
+++ b/capacitor-cordova-android-plugins/build.gradle
@@ -17,10 +17,10 @@ apply plugin: 'com.android.library'
android {
namespace = "capacitor.cordova.android.plugins"
- compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 37
defaultConfig {
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
- targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 37
versionCode 1
versionName "1.0"
}
diff --git a/cli/src/tasks/migrate.ts b/cli/src/tasks/migrate.ts
index addcd5bf4b..a5fd06c7e6 100644
--- a/cli/src/tasks/migrate.ts
+++ b/cli/src/tasks/migrate.ts
@@ -90,7 +90,7 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem
const { migrateconfirm } = noprompt
? { migrateconfirm: 'y' }
- : await logPrompt(`Capacitor 8 sets a deployment target of iOS ${iOSVersion} and Android 16 (SDK 36). \n`, {
+ : await logPrompt(`Capacitor 8 sets a deployment target of iOS ${iOSVersion} and Android 17 (SDK 37). \n`, {
type: 'text',
name: 'migrateconfirm',
message: `Are you sure you want to migrate? (Y/n)`,
From fe6a83b4eb0b4ceacf3b566f3e262fc792001e58 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Thu, 4 Jun 2026 16:57:50 +0200
Subject: [PATCH 51/69] chore(android)!: bump minSdkVersion to 26 (#8500)
---
android/capacitor-cordova/build.gradle | 2 +-
android/capacitor/build.gradle | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/android/capacitor-cordova/build.gradle b/android/capacitor-cordova/build.gradle
index 5987eb0e83..2f32501240 100644
--- a/android/capacitor-cordova/build.gradle
+++ b/android/capacitor-cordova/build.gradle
@@ -19,7 +19,7 @@ android {
namespace = "com.getcapacitor.cordova"
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 37
defaultConfig {
- minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 26
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 37
versionCode 1
versionName "1.0"
diff --git a/android/capacitor/build.gradle b/android/capacitor/build.gradle
index 7421ce57f8..82277e6b44 100644
--- a/android/capacitor/build.gradle
+++ b/android/capacitor/build.gradle
@@ -42,7 +42,7 @@ android {
namespace = "com.getcapacitor.android"
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 37
defaultConfig {
- minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 26
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 37
versionCode 1
versionName "1.0"
From 753c97cac2c2b8ce862e5866b9feb03a47b62c27 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Thu, 4 Jun 2026 18:34:17 +0200
Subject: [PATCH 52/69] chore(cli)!: bump minSdkVersion to 26 (#8501)
---
capacitor-cordova-android-plugins/build.gradle | 2 +-
cli/src/config.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/capacitor-cordova-android-plugins/build.gradle b/capacitor-cordova-android-plugins/build.gradle
index 96d1707a95..e2495759b1 100644
--- a/capacitor-cordova-android-plugins/build.gradle
+++ b/capacitor-cordova-android-plugins/build.gradle
@@ -19,7 +19,7 @@ android {
namespace = "capacitor.cordova.android.plugins"
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 37
defaultConfig {
- minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 26
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 37
versionCode 1
versionName "1.0"
diff --git a/cli/src/config.ts b/cli/src/config.ts
index 2cb64e78d2..9c6c5183d8 100644
--- a/cli/src/config.ts
+++ b/cli/src/config.ts
@@ -236,7 +236,7 @@ async function loadAndroidConfig(
return {
name,
- minVersion: '24',
+ minVersion: '26',
studioPath,
platformDir,
platformDirAbs,
From c43cb8fb17aede606077fe46fc70990195d1cc53 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Fri, 5 Jun 2026 15:21:40 +0200
Subject: [PATCH 53/69] chore(android-template)!: bump minSdkVersion to 26
(#8502)
---
android-template/variables.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/android-template/variables.gradle b/android-template/variables.gradle
index 445a4730d4..36eff33927 100644
--- a/android-template/variables.gradle
+++ b/android-template/variables.gradle
@@ -1,5 +1,5 @@
ext {
- minSdkVersion = 24
+ minSdkVersion = 26
compileSdkVersion = 37
targetSdkVersion = 37
androidxActivityVersion = '1.11.0'
From 42caab564d3687c1aa29acc6386d6943883adcb9 Mon Sep 17 00:00:00 2001
From: jcesarmobile
Date: Wed, 10 Jun 2026 15:24:44 +0200
Subject: [PATCH 54/69] refactor(android): remove unneeded code (#8506)
---
.../main/java/com/getcapacitor/Bridge.java | 66 +++----------------
.../util/CapacitorHttpUrlConnection.java | 13 +---
2 files changed, 12 insertions(+), 67 deletions(-)
diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
index 0ca600aff6..00dd89d60e 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java
@@ -11,7 +11,6 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -51,7 +50,6 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONException;
@@ -294,63 +292,19 @@ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
@SuppressLint("WebViewApiAvailability")
public boolean isMinimumWebViewInstalled() {
- PackageManager pm = getContext().getPackageManager();
-
- // Check getCurrentWebViewPackage() directly if above Android 8
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- PackageInfo info = WebView.getCurrentWebViewPackage();
- Pattern pattern = Pattern.compile("(\\d+)");
- Matcher matcher = pattern.matcher(info.versionName);
- if (matcher.find()) {
- String majorVersionStr = matcher.group(0);
- int majorVersion = Integer.parseInt(majorVersionStr);
- if (info.packageName.equals("com.huawei.webview")) {
- return majorVersion >= config.getMinHuaweiWebViewVersion();
- }
- return majorVersion >= config.getMinWebViewVersion();
- } else {
- return false;
- }
- }
-
- // Otherwise manually check WebView versions
- try {
- PackageInfo info = InternalUtils.getPackageInfo(pm, "com.android.chrome");
- String majorVersionStr = info.versionName.split("\\.")[0];
- int majorVersion = Integer.parseInt(majorVersionStr);
- return majorVersion >= config.getMinWebViewVersion();
- } catch (Exception ex) {
- Logger.warn("Unable to get package info for 'com.google.android.webview'" + ex.toString());
- }
-
- try {
- PackageInfo info = InternalUtils.getPackageInfo(pm, "com.android.webview");
- String majorVersionStr = info.versionName.split("\\.")[0];
+ PackageInfo info = WebView.getCurrentWebViewPackage();
+ Pattern pattern = Pattern.compile("(\\d+)");
+ Matcher matcher = pattern.matcher(info.versionName);
+ if (matcher.find()) {
+ String majorVersionStr = matcher.group(0);
int majorVersion = Integer.parseInt(majorVersionStr);
+ if (info.packageName.equals("com.huawei.webview")) {
+ return majorVersion >= config.getMinHuaweiWebViewVersion();
+ }
return majorVersion >= config.getMinWebViewVersion();
- } catch (Exception ex) {
- Logger.warn("Unable to get package info for 'com.android.webview'" + ex.toString());
- }
-
- final int amazonFireMajorWebViewVersion = extractWebViewMajorVersion(pm, "com.amazon.webview.chromium");
- if (amazonFireMajorWebViewVersion >= config.getMinWebViewVersion()) {
- return true;
- }
-
- // Could not detect any webview, return false
- return false;
- }
-
- private int extractWebViewMajorVersion(final PackageManager pm, final String webViewPackageName) {
- try {
- final PackageInfo info = InternalUtils.getPackageInfo(pm, webViewPackageName);
- final String majorVersionStr = info.versionName.split("\\.")[0];
- final int majorVersion = Integer.parseInt(majorVersionStr);
- return majorVersion;
- } catch (Exception ex) {
- Logger.warn(String.format("Unable to get package info for '%s' with err '%s'", webViewPackageName, ex));
+ } else {
+ return false;
}
- return 0;
}
public boolean launchIntent(Uri url) {
diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java
index 6180d64200..7cb0f34690 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java
@@ -1,6 +1,5 @@
package com.getcapacitor.plugin.util;
-import android.os.Build;
import android.os.LocaleList;
import android.text.TextUtils;
import com.getcapacitor.Bridge;
@@ -211,9 +210,7 @@ public void setRequestBody(PluginCall call, JSValue body, String bodyType) throw
this.writeRequestBody(dataString != null ? dataString : "");
} else if (bodyType != null && bodyType.equals("file")) {
try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- os.write(Base64.getDecoder().decode(body.toString()));
- }
+ os.write(Base64.getDecoder().decode(body.toString()));
os.flush();
}
} else if (contentType.contains("application/x-www-form-urlencoded")) {
@@ -295,13 +292,7 @@ private void writeFormDataRequestBody(String boundary, JSArray entries) throws I
os.writeBytes("Content-Type: " + fileContentType + lineEnd);
os.writeBytes("Content-Transfer-Encoding: binary" + lineEnd);
os.writeBytes(lineEnd);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- os.write(Base64.getDecoder().decode(value));
- } else {
- os.write(android.util.Base64.decode(value, android.util.Base64.DEFAULT));
- }
-
+ os.write(Base64.getDecoder().decode(value));
os.writeBytes(lineEnd);
}
}
From 1a5543fbfcf4ba911270002d573275342f680bde Mon Sep 17 00:00:00 2001
From: Mark Anderson
Date: Wed, 10 Jun 2026 12:59:24 -0400
Subject: [PATCH 55/69] fix(ios): eliminate double comma in SPM generation
(#8505)
Co-authored-by: jcesarmobile
---
cli/src/util/spm.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cli/src/util/spm.ts b/cli/src/util/spm.ts
index d5491a20b0..3243c3efd3 100644
--- a/cli/src/util/spm.ts
+++ b/cli/src/util/spm.ts
@@ -162,10 +162,10 @@ let package = Package(
.target(
name: "CapApp-SPM",
dependencies: [
- .product(name: "Capacitor", package: "capacitor-swift-pm"),`;
+ .product(name: "Capacitor", package: "capacitor-swift-pm")`;
if (enableCordova) {
- packageSwiftText += ` .product(name: "Cordova", package: "capacitor-swift-pm")`;
+ packageSwiftText += `,\n .product(name: "Cordova", package: "capacitor-swift-pm")`;
}
for (const plugin of plugins) {
From 11f3c83f0883a1400e14c538f9040e13f7e0dbfc Mon Sep 17 00:00:00 2001
From: OS-ruimoreiramendes
Date: Wed, 10 Jun 2026 18:00:06 +0100
Subject: [PATCH 56/69] chore(android)!: bump AGP to 9.2.1 and Gradle to 9.5.1
(#8498)
---
android-template/build.gradle | 2 +-
.../gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 48462 bytes
.../gradle/wrapper/gradle-wrapper.properties | 4 ++-
android-template/gradlew | 7 ++--
android-template/gradlew.bat | 32 ++++++------------
android/capacitor-cordova/build.gradle | 2 +-
android/capacitor/build.gradle | 2 +-
android/gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 48462 bytes
.../gradle/wrapper/gradle-wrapper.properties | 4 ++-
android/gradlew | 7 ++--
android/gradlew.bat | 32 ++++++------------
android/package.json | 2 +-
.../build.gradle | 2 +-
cli/src/tasks/migrate.ts | 2 +-
scripts/publish-android.sh | 2 +-
15 files changed, 37 insertions(+), 63 deletions(-)
diff --git a/android-template/build.gradle b/android-template/build.gradle
index f8f0e43b6d..70bb56fc47 100644
--- a/android-template/build.gradle
+++ b/android-template/build.gradle
@@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.13.0'
+ classpath 'com.android.tools.build:gradle:9.2.1'
classpath 'com.google.gms:google-services:4.4.4'
// NOTE: Do not place your application dependencies here; they belong
diff --git a/android-template/gradle/wrapper/gradle-wrapper.jar b/android-template/gradle/wrapper/gradle-wrapper.jar
index 1b33c55baabb587c669f562ae36f953de2481846..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644
GIT binary patch
delta 40183
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