Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ios): Plugin Registration and Plugin Instance Support #6072

Merged
merged 7 commits into from
Nov 22, 2022
4 changes: 4 additions & 0 deletions ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
A38C3D7B2848BE6F004B3680 /* CapacitorCookieManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A38C3D7A2848BE6F004B3680 /* CapacitorCookieManager.swift */; };
A71289E627F380A500DADDF3 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71289E527F380A500DADDF3 /* Router.swift */; };
A71289EB27F380FD00DADDF3 /* RouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A71289EA27F380FD00DADDF3 /* RouterTests.swift */; };
A7F7EDCD291EC75C0015B73B /* CAPPlugin+LoadInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F7EDCC291EC75C0015B73B /* CAPPlugin+LoadInstance.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -225,6 +226,7 @@
A38C3D7A2848BE6F004B3680 /* CapacitorCookieManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorCookieManager.swift; sourceTree = "<group>"; };
A71289E527F380A500DADDF3 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
A71289EA27F380FD00DADDF3 /* RouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterTests.swift; sourceTree = "<group>"; };
A7F7EDCC291EC75C0015B73B /* CAPPlugin+LoadInstance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CAPPlugin+LoadInstance.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -312,6 +314,7 @@
62959B0F2524DA7700A3D7F1 /* Capacitor.h */,
62959B132524DA7700A3D7F1 /* CAPPlugin.h */,
62959B012524DA7700A3D7F1 /* CAPPlugin.m */,
A7F7EDCC291EC75C0015B73B /* CAPPlugin+LoadInstance.swift */,
62959AE52524DA7700A3D7F1 /* CAPBridgedPlugin.h */,
62959B092524DA7700A3D7F1 /* CAPPluginMethod.h */,
62959AE82524DA7700A3D7F1 /* CAPPluginMethod.m */,
Expand Down Expand Up @@ -645,6 +648,7 @@
6214934725509C3F006C36F9 /* CAPInstanceConfiguration.swift in Sources */,
623D6914254C7030002D01D1 /* CAPInstanceDescriptor.swift in Sources */,
621ECCE3254206A600D3D615 /* CAPApplicationDelegateProxy.swift in Sources */,
A7F7EDCD291EC75C0015B73B /* CAPPlugin+LoadInstance.swift in Sources */,
62959B262524DA7800A3D7F1 /* WebView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
5 changes: 5 additions & 0 deletions ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import WebKit
var isSimEnvironment: Bool { get }
var isDevEnvironment: Bool { get }
var userInterfaceStyle: UIUserInterfaceStyle { get }
var autoRegisterPlugins: Bool { get set }
var statusBarVisible: Bool { get set }
var statusBarStyle: UIStatusBarStyle { get set }
var statusBarAnimation: UIStatusBarAnimation { get set }
Expand Down Expand Up @@ -72,6 +73,10 @@ import WebKit
func portablePath(fromLocalURL localURL: URL?) -> URL?
func setServerBasePath(_ path: String)

// MARK: - Plugins
func registerPluginType(_ pluginType: CAPPlugin.Type)
func registerPluginInstance(_ pluginInstance: CAPPlugin)

// MARK: - View Presentation
func showAlertWith(title: String, message: String, buttonTitle: String)
func presentVC(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?)
Expand Down
23 changes: 23 additions & 0 deletions ios/Capacitor/Capacitor/CAPPlugin+LoadInstance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// CAPPlugin+LoadInstance.swift
// Capacitor
//
// Created by Steven Sherry on 11/9/22.
// Copyright © 2022 Drifty Co. All rights reserved.
//

import Foundation

open class CAPInstancePlugin: CAPPlugin {}

extension CAPPlugin {
func load(as bridgedType: CAPBridgedPlugin.Type, on bridge: CAPBridgeProtocol) {
self.bridge = bridge
webView = bridge.webView
pluginId = bridgedType.pluginId()
pluginName = bridgedType.jsName()
shouldStringifyDatesInCalls = true
retainedEventArguments = [:]
load()
}
}
8 changes: 6 additions & 2 deletions ios/Capacitor/Capacitor/CAPWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ open class CAPWebView: UIView {
delegate: self,
cordovaConfiguration: configDescriptor.cordovaConfiguration,
assetHandler: assetHandler,
delegationHandler: delegationHandler
delegationHandler: delegationHandler,
autoRegisterPlugins: autoRegisterPlugins
)

public final var bridge: CAPBridgeProtocol {
Expand All @@ -31,15 +32,18 @@ open class CAPWebView: UIView {
}()

private lazy var delegationHandler = WebViewDelegationHandler()
private var autoRegisterPlugins: Bool

open var router: Router { _Router() }

public required init?(coder: NSCoder) {
autoRegisterPlugins = true
super.init(coder: coder)
setup()
}

public init() {
public init(autoRegisterPlugins: Bool = true) {
self.autoRegisterPlugins = autoRegisterPlugins
super.init(frame: .zero)
setup()
}
Expand Down
83 changes: 63 additions & 20 deletions ios/Capacitor/Capacitor/CapacitorBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol {
return bridgeDelegate?.bridgedWebView
}

public var autoRegisterPlugins: Bool

public var notificationRouter: NotificationRouter

public var isSimEnvironment: Bool {
Expand Down Expand Up @@ -188,15 +190,15 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol {

// MARK: - Initialization

init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: CDVConfigParser, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler) {
init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: CDVConfigParser, 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
super.init()

self.webViewDelegationHandler.bridge = self
Expand Down Expand Up @@ -273,32 +275,73 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol {
Register all plugins that have been declared
*/
func registerPlugins() {
let classCount = objc_getClassList(nil, 0)
let classes = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classCount))

let releasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classes)
let numClasses: Int32 = objc_getClassList(releasingClasses, classCount)

for classIndex in 0..<Int(numClasses) {
if let aClass: AnyClass = classes[classIndex] {
if class_getSuperclass(aClass) == CDVPlugin.self {
injectCordovaFiles = true
}
if class_conformsToProtocol(aClass, CAPBridgedPlugin.self),
let pluginType = aClass as? CAPPlugin.Type,
let bridgeType = aClass as? CAPBridgedPlugin.Type {
let pluginClassName = NSStringFromClass(aClass)
registerPlugin(pluginClassName, bridgeType.jsName(), pluginType)
if autoRegisterPlugins {
let classCount = objc_getClassList(nil, 0)
let classes = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classCount))

let releasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classes)
let numClasses: Int32 = objc_getClassList(releasingClasses, classCount)

for classIndex in 0..<Int(numClasses) {
if let aClass: AnyClass = classes[classIndex] {
if class_getSuperclass(aClass) == CDVPlugin.self {
injectCordovaFiles = true
}
if class_conformsToProtocol(aClass, CAPBridgedPlugin.self),
let pluginType = aClass as? CAPPlugin.Type,
let bridgeType = aClass as? CAPBridgedPlugin.Type {
if aClass is CAPInstancePlugin.Type { continue }
registerPlugin(bridgeType.jsName(), pluginType)
}
}
}
classes.deallocate()
} else {
// register core plugins only
[CAPHttpPlugin.self, CAPConsolePlugin.self, CAPWebViewPlugin.self, CAPCookiesPlugin.self]
.forEach { registerPluginType($0) }
}
}

public func registerPluginType(_ pluginType: CAPPlugin.Type) {
if autoRegisterPlugins { return }
if pluginType is CAPInstancePlugin.Type {
Swift.fatalError("""

⚡️ ❌ Cannot register class \(pluginType): CAPInstancePlugin through registerPluginType(_:).
⚡️ ❌ Use `registerPluginInstance(_:)` to register subclasses of CAPInstancePlugin.
""")
}
guard let bridgedType = pluginType as? CAPBridgedPlugin.Type else { return }
registerPlugin(bridgedType.jsName(), pluginType)
}

public func registerPluginInstance(_ pluginInstance: CAPPlugin) {
guard
let pluginInstance = pluginInstance as? (CAPPlugin & CAPBridgedPlugin),
let pluginClass = pluginInstance.classForCoder as? (CAPPlugin & CAPBridgedPlugin).Type
else { return }

let jsName = pluginClass.jsName()!

knownPlugins[jsName] = pluginClass
if plugins[jsName] != nil {
CAPLog.print("⚡️ Overriding existing registered plugin \(pluginClass)")
}
classes.deallocate()
plugins[jsName] = pluginInstance
pluginInstance.load(as: pluginClass, on: self)

JSExport.exportJS(
userContentController: webViewDelegationHandler.contentController,
pluginClassName: jsName,
pluginType: pluginClass
)
}

/**
Register a single plugin.
*/
func registerPlugin(_ pluginClassName: String, _ jsName: String, _ pluginType: CAPPlugin.Type) {
func registerPlugin(_ jsName: String, _ pluginType: CAPPlugin.Type) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed pluginClassName because it was no longer used. It being internal I removed it to avoid having to do NSStringFromClass to pass in a value that wasn't used.

// let bridgeType = pluginType as! CAPBridgedPlugin.Type
knownPlugins[jsName] = pluginType
JSExport.exportJS(userContentController: webViewDelegationHandler.contentController, pluginClassName: jsName, pluginType: pluginType)
Expand Down