You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by dp...@apache.org on 2026/06/11 18:09:15 UTC
(cordova-ios) branch master updated: fix(wkwebview): fix ViewController being the ScriptMessageHandler (#1664)
This is an automated email from the ASF dual-hosted git repository.
dpogue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-ios.git
The following commit(s) were added to refs/heads/master by this push:
new 5669c2c9 fix(wkwebview): fix ViewController being the ScriptMessageHandler (#1664)
5669c2c9 is described below
commit 5669c2c9cb209f7ba6ecf70e1e2ea86b369fd459
Author: Darryl Pogue <da...@dpogue.ca>
AuthorDate: Thu Jun 11 11:09:09 2026 -0700
fix(wkwebview): fix ViewController being the ScriptMessageHandler (#1664)
We've had a mechanism for a while where if the ViewController is a
WKScriptMessageHandler then it will be registered as the handler for
Cordova actions rather than the default plugin implementation.
Except there was a bug where it would try to register both of them with
the same name and fail.
This checks if the ViewController is a candidate first, and only
registers the plugin as the script handler if it is not. Also add a unit
test case to ensure that a ViewController that implements the
WKScriptMessageHandler protocol correctly receives the script messages.
---
.../Plugins/CDVWebViewEngine/CDVWebViewEngine.m | 92 ++++++++++++++--------
tests/CordovaLibTests/CDVWebViewEngineTests.swift | 55 +++++++++++++
.../CordovaTests.xcodeproj/project.pbxproj | 4 +
3 files changed, 119 insertions(+), 32 deletions(-)
diff --git a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m
index 36cda781..250f2a9b 100644
--- a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m
+++ b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m
@@ -40,9 +40,6 @@
@interface CDVWebViewEngine ()
@property (nonatomic, strong, readwrite) UIView* engineWebView;
-@property (nonatomic, strong, readwrite) id <WKUIDelegate> uiDelegate;
-@property (nonatomic, weak) id <WKScriptMessageHandler> weakScriptMessageHandler;
-@property (nonatomic, strong) CDVURLSchemeHandler * schemeHandler;
@property (nonatomic, readwrite) NSString *CDV_ASSETS_URL;
@property (nonatomic, readwrite) Boolean cdvIsFileScheme;
@property (nullable, nonatomic, strong, readwrite) WKWebViewConfiguration *configuration;
@@ -191,18 +188,37 @@
self.CDV_ASSETS_URL = [NSString stringWithFormat:@"%@://%@", scheme, hostname];
}
- CDVWebViewUIDelegate* uiDelegate = [[CDVWebViewUIDelegate alloc] initWithViewController:self.viewController];
- uiDelegate.title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
- uiDelegate.mediaPermissionGrantType = [self parsePermissionGrantType:[settings cordovaSettingForKey:@"MediaPermissionGrantType"]];
- uiDelegate.allowNewWindows = [settings cordovaBoolSettingForKey:@"AllowNewWindows" defaultValue:NO];
- self.uiDelegate = uiDelegate;
- CDVWebViewWeakScriptMessageHandler *weakScriptMessageHandler = [[CDVWebViewWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self];
-
- WKUserContentController* userContentController = [[WKUserContentController alloc] init];
+ WKUserContentController *userContentController = [[WKUserContentController alloc] init];
+
+ // This is where the Cordova magic happens...
+ //
+ // We register a script message handler that is exposed to the webview as
+ // `window.webkit.messageHandlers.cordova` and allows `postMessage()` to be
+ // called, which is how the `cordova.exec` bridge works for WKWebView to
+ // pass commands to Cordova plugins.
+ //
+ // If the view controller implements the WKScriptMessageHandler protocol,
+ // we allow that to be registered as the handler for Cordova messages,
+ // otherwise we register ourselves as the default implementation. This
+ // gives library consumers a way to override the default bridge behaviour
+ // if they need to.
+ //
+ // There's some indirection here with CDVWebViewWeakScriptMessageHandler to
+ // avoid retain cycles where the web view holds on to the view controller
+ // as a script handler while the view controller holds on to the web view
+ // as a child view.
+ CDVWebViewWeakScriptMessageHandler *weakScriptMessageHandler;
+ if ([self.viewController conformsToProtocol:@protocol(WKScriptMessageHandler)]) {
+ weakScriptMessageHandler = [[CDVWebViewWeakScriptMessageHandler alloc] initWithScriptMessageHandler:(id<WKScriptMessageHandler>)self.viewController];
+ } else {
+ weakScriptMessageHandler = [[CDVWebViewWeakScriptMessageHandler alloc] initWithScriptMessageHandler:self];
+ }
[userContentController addScriptMessageHandler:weakScriptMessageHandler name:CDV_BRIDGE_NAME];
- if(self.CDV_ASSETS_URL) {
+ // If we have an asset URL, inject some JavaScript into the webview to
+ // make it available on the window object as a global variable
+ if (self.CDV_ASSETS_URL) {
NSString *scriptCode = [NSString stringWithFormat:@"window.CDV_ASSETS_URL = '%@';", self.CDV_ASSETS_URL];
WKUserScript *wkScript = [[WKUserScript alloc] initWithSource:scriptCode injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
@@ -211,18 +227,17 @@
}
}
- WKWebViewConfiguration* configuration = [self createConfigurationFromSettings:settings];
+ WKWebViewConfiguration *configuration = [self createConfigurationFromSettings:settings];
configuration.userContentController = userContentController;
- // Do not configure the scheme handler if the scheme is default (file)
- if(!self.cdvIsFileScheme) {
- self.schemeHandler = [[CDVURLSchemeHandler alloc] initWithViewController:self.viewController];
- [configuration setURLSchemeHandler:self.schemeHandler forURLScheme:scheme];
+ // Set up the scheme handler for the custom scheme (if we are not serving from a file:/// URL)
+ if (!self.cdvIsFileScheme) {
+ CDVURLSchemeHandler *schemeHandler = [[CDVURLSchemeHandler alloc] initWithViewController:self.viewController];
+ [configuration setURLSchemeHandler:schemeHandler forURLScheme:scheme];
}
// re-create WKWebView, since we need to update configuration
- WKWebView* wkWebView = [[WKWebView alloc] initWithFrame:self.engineWebView.frame configuration:configuration];
- wkWebView.UIDelegate = self.uiDelegate;
+ WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:self.engineWebView.frame configuration:configuration];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400
// With the introduction of iOS 16.4 the webview is no longer inspectable by default.
@@ -238,31 +253,44 @@
}
#endif
- /*
- * This is where the "OverrideUserAgent" is handled. This will replace the entire UserAgent
- * with the user defined custom UserAgent.
- */
+ // This is where the "OverrideUserAgent" is handled. This will replace the entire UserAgent
+ // with the user defined custom UserAgent.
if ([settings cordovaSettingForKey:@"OverrideUserAgent"] != nil) {
wkWebView.customUserAgent = [settings cordovaSettingForKey:@"OverrideUserAgent"];
}
- [wkWebView addObserver:self forKeyPath:@"themeColor" options:NSKeyValueObservingOptionInitial context:nil];
+ // Hook up the web view's UI delegate
+ //
+ // The UI delegate is responsible for determining how the app implements
+ // things like web view alerts, prompts, permission requests, and new
+ // windows.
+ //
+ // If the view controller implements the WKUIDelegate protocol, we allow
+ // that to be registered as the UI delegate, otherwise we create a
+ // CDVWebViewUIDelegate instance. This gives library consumers a way to
+ // override the UI delegate and customize behaviour if needed.
+ if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) {
+ wkWebView.UIDelegate = (id<WKUIDelegate>)self.viewController;
+ } else {
+ CDVWebViewUIDelegate *uiDelegate = [[CDVWebViewUIDelegate alloc] initWithViewController:self.viewController];
- self.engineWebView = wkWebView;
+ uiDelegate.title = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
+ uiDelegate.mediaPermissionGrantType = [self parsePermissionGrantType:[settings cordovaSettingForKey:@"MediaPermissionGrantType"]];
+ uiDelegate.allowNewWindows = [settings cordovaBoolSettingForKey:@"AllowNewWindows" defaultValue:NO];
- if ([self.viewController conformsToProtocol:@protocol(WKUIDelegate)]) {
- wkWebView.UIDelegate = (id <WKUIDelegate>)self.viewController;
+ wkWebView.UIDelegate = uiDelegate;
}
if ([self.viewController conformsToProtocol:@protocol(WKNavigationDelegate)]) {
- wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self.viewController;
+ wkWebView.navigationDelegate = (id<WKNavigationDelegate>)self.viewController;
} else {
- wkWebView.navigationDelegate = (id <WKNavigationDelegate>)self;
+ wkWebView.navigationDelegate = self;
}
- if ([self.viewController conformsToProtocol:@protocol(WKScriptMessageHandler)]) {
- [wkWebView.configuration.userContentController addScriptMessageHandler:(id < WKScriptMessageHandler >)self.viewController name:CDV_BRIDGE_NAME];
- }
+ // Add an observer to react to themeColor changes for status bar colouring
+ [wkWebView addObserver:self forKeyPath:@"themeColor" options:NSKeyValueObservingOptionInitial context:nil];
+
+ self.engineWebView = wkWebView;
[self updateSettings:settings];
diff --git a/tests/CordovaLibTests/CDVWebViewEngineTests.swift b/tests/CordovaLibTests/CDVWebViewEngineTests.swift
new file mode 100644
index 00000000..929acdaf
--- /dev/null
+++ b/tests/CordovaLibTests/CDVWebViewEngineTests.swift
@@ -0,0 +1,55 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+import XCTest
+import WebKit
+import Cordova
+@testable import CordovaApp
+
+class ScriptHandlingViewController: ViewController, WKScriptMessageHandler {
+ var expectation: XCTestExpectation? = nil
+
+ func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
+ if let expectation = self.expectation {
+ expectation.fulfill()
+ }
+
+ if let webView = self.webViewEngine.engineWebView as? WKWebView,
+ let scriptMessageHandler = self.webViewEngine as? WKScriptMessageHandler {
+ scriptMessageHandler.userContentController(webView.configuration.userContentController, didReceive:message)
+ }
+ }
+}
+
+class CDVWebViewEngineTests: XCTestCase {
+ @MainActor func testViewControllerScriptMessageHandler() {
+ let vc = ScriptHandlingViewController()
+ vc.webContentFolderName = "www"
+ vc.startPage = "index.html"
+ vc.expectation = expectation(description: "Script Handler expectation")
+
+ let appDelegate = UIApplication.shared.delegate as! AppDelegate
+ appDelegate.testWindow?.rootViewController = vc
+ appDelegate.testWindow?.makeKeyAndVisible()
+
+ waitForExpectations(timeout: 10.0, handler: nil)
+
+ appDelegate.testWindow?.rootViewController = UIViewController()
+ }
+}
diff --git a/tests/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj b/tests/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj
index 04755659..6a096353 100644
--- a/tests/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj
+++ b/tests/CordovaLibTests/CordovaTests.xcodeproj/project.pbxproj
@@ -28,6 +28,7 @@
900A9F542CF0677F00FECE7A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 900A9F532CF0677F00FECE7A /* ViewController.swift */; };
900A9F5A2CF0696300FECE7A /* SwiftInitPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 900A9F582CF0695D00FECE7A /* SwiftInitPlugin.swift */; };
902530FC29A6DC53004AF1CF /* CDVPluginInitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 902530FA29A6DC53004AF1CF /* CDVPluginInitTests.m */; };
+ 9021AA402FD8E41C00A950DD /* CDVWebViewEngineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9021AA3F2FD8E41500A950DD /* CDVWebViewEngineTests.swift */; };
905C8BA82CF0756E007EECEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905C8BA72CF0756E007EECEF /* AppDelegate.swift */; };
9085EE302CF0617800603B73 /* Cordova.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0FA7CAC1E4BB6B30077B045 /* Cordova.framework */; };
90A775DD2C6F257500FC5BB0 /* CDVSettingsDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A775DC2C6F256D00FC5BB0 /* CDVSettingsDictionaryTests.m */; };
@@ -90,6 +91,7 @@
900A9F532CF0677F00FECE7A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
900A9F582CF0695D00FECE7A /* SwiftInitPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftInitPlugin.swift; sourceTree = "<group>"; };
902530FA29A6DC53004AF1CF /* CDVPluginInitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVPluginInitTests.m; sourceTree = "<group>"; };
+ 9021AA3F2FD8E41500A950DD /* CDVWebViewEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CDVWebViewEngineTests.swift; sourceTree = "<group>"; };
905C8BA72CF0756E007EECEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
9085EE2F2CF05B5900603B73 /* CordovaTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CordovaTests.xctestplan; sourceTree = "<group>"; };
90A775DC2C6F256D00FC5BB0 /* CDVSettingsDictionaryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CDVSettingsDictionaryTests.m; sourceTree = "<group>"; };
@@ -190,6 +192,7 @@
isa = PBXGroup;
children = (
90D08FEC2F4E690700A9838F /* CDVSettingsDictionarySwiftTests.swift */,
+ 9021AA3F2FD8E41500A950DD /* CDVWebViewEngineTests.swift */,
90D08FEA2F4E63CA00A9838F /* CDVURLSchemeHandlerTest.m */,
90A775DC2C6F256D00FC5BB0 /* CDVSettingsDictionaryTests.m */,
902530FA29A6DC53004AF1CF /* CDVPluginInitTests.m */,
@@ -353,6 +356,7 @@
C0FA7CBB1E4BBBBE0077B045 /* CDVViewControllerTest.m in Sources */,
90D08FEB2F4E63CA00A9838F /* CDVURLSchemeHandlerTest.m in Sources */,
C0FA7CBC1E4BBBBE0077B045 /* CDVInvokedUrlCommandTests.m in Sources */,
+ 9021AA402FD8E41C00A950DD /* CDVWebViewEngineTests.swift in Sources */,
C0FA7CBF1E4BBBBE0077B045 /* CDVCommandDelegateTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org