From b187b1dc49c3d7d653020049533b78565328013d Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Wed, 12 Oct 2022 14:26:29 -0400 Subject: [PATCH 1/9] Refactor foundation/class to support finding classes across multiple bundles (issue #63) --- src/foundation/class.rs | 130 +++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 70 deletions(-) diff --git a/src/foundation/class.rs b/src/foundation/class.rs index 6dec898..f877f9f 100644 --- a/src/foundation/class.rs +++ b/src/foundation/class.rs @@ -11,98 +11,82 @@ lazy_static! { static ref CLASSES: ClassMap = ClassMap::new(); } +/// Represents an entry in a `ClassMap`. We store an optional superclass_name for debugging +/// purposes; it's an `Option` to make the logic of loading a class type where we don't need to +/// care about the superclass type simpler. +#[derive(Debug)] +struct ClassEntry { + pub superclass_name: Option<&'static str>, + pub ptr: usize +} + +/// Represents a key in a `ClassMap`. +type ClassKey = (&'static str, Option<&'static str>); + /// A ClassMap is a general cache for our Objective-C class lookup and registration. Rather than /// constantly calling into the runtime, we store pointers to Class types here after first lookup -/// and/or creation. The general store format is (roughly speaking) as follows: -/// -/// ```ignore -/// { -/// "subclass_type": { -/// "superclass_type": *const Class as usize -/// } -/// } -/// ``` -/// -/// The reasoning behind the double map is that it allows for lookup without allocating a `String` -/// on each hit; allocations are only required when creating a Class to inject, purely for naming -/// and debugging reasons. +/// and/or creation. /// /// There may be a way to do this without using HashMaps and avoiding the heap, but working and /// usable beats ideal for now. Open to suggestions. #[derive(Debug)] -pub(crate) struct ClassMap(RwLock>>); +pub(crate) struct ClassMap(RwLock>); impl ClassMap { /// Returns a new ClassMap. pub fn new() -> Self { - ClassMap(RwLock::new({ - let mut map = HashMap::new(); - - // Top-level classes, like `NSView`, we cache here. The reasoning is that if a subclass - // is being created, we can avoid querying the runtime for the superclass - i.e, many - // subclasses will have `NSView` as their superclass. - map.insert("_supers", HashMap::new()); - - map - })) + ClassMap(RwLock::new(HashMap::new())) } - /// Attempts to load a previously registered subclass. - pub fn load_subclass(&self, subclass_name: &'static str, superclass_name: &'static str) -> Option<*const Class> { - let reader = self.0.read().unwrap(); - - if let Some(inner) = (*reader).get(subclass_name) { - if let Some(class) = inner.get(superclass_name) { - return Some(*class as *const Class); - } - } - - None - } - - /// Store a newly created subclass type. - pub fn store_subclass(&self, subclass_name: &'static str, superclass_name: &'static str, class: *const Class) { - let mut writer = self.0.write().unwrap(); - - if let Some(map) = (*writer).get_mut(subclass_name) { - map.insert(superclass_name, class as usize); - } else { - let mut map = HashMap::new(); - map.insert(superclass_name, class as usize); - (*writer).insert(subclass_name, map); - } - } - - /// Attempts to load a Superclass. This first checks for the cached pointer; if not present, it - /// will load the superclass from the Objective-C runtime and cache it for future lookup. This - /// assumes that the class is one that should *already* and *always* exist in the runtime, and - /// by design will panic if it can't load the correct superclass, as that would lead to very - /// invalid behavior. - pub fn load_superclass(&self, name: &'static str) -> Option<*const Class> { + /// Attempts to load a previously registered class. + /// + /// This checks our internal map first, and then calls out to the Objective-C runtime to ensure + /// we're not missing anything. + pub fn load(&self, class_name: &'static str, superclass_name: Option<&'static str>) -> Option<*const Class> { { let reader = self.0.read().unwrap(); - if let Some(superclass) = (*reader)["_supers"].get(name) { - return Some(*superclass as *const Class); + if let Some(entry) = (*reader).get(&(class_name, superclass_name)) { + let ptr = &entry.ptr; + return Some(*ptr as *const Class); } } - let objc_superclass_name = CString::new(name).unwrap(); - let superclass = unsafe { objc_getClass(objc_superclass_name.as_ptr() as *const _) }; + // If we don't have an entry for the class_name in our internal map, we should still check + // if we can load it from the Objective-C runtime directly. The reason we need to do this + // is that there's a use-case where someone might have multiple bundles attempting to + // use or register the same subclass; Rust doesn't see the same pointers unlike the Objective-C + // runtime, and we can wind up in a situation where we're attempting to register a Class + // that already exists but we can't see. + let objc_class_name = CString::new(class_name).unwrap(); + let class = unsafe { objc_getClass(objc_class_name.as_ptr() as *const _) }; - // This should not happen, for our use-cases, but it's conceivable that this could actually + // This should not happen for our use-cases, but it's conceivable that this could actually // be expected, so just return None and let the caller panic if so desired. - if superclass.is_null() { + if class.is_null() { return None; } + // If we got here, then this class exists in the Objective-C runtime but is not known to + // us. For consistency's sake, we'll add this to our store and return that. { let mut writer = self.0.write().unwrap(); - if let Some(supers) = (*writer).get_mut("_supers") { - supers.insert(name, superclass as usize); - } + writer.insert((class_name, superclass_name), ClassEntry { + superclass_name, + ptr: class as usize + }); } - Some(superclass) + Some(class) + } + + /// Store a newly created subclass type. + pub fn store(&self, class_name: &'static str, superclass_name: Option<&'static str>, class: *const Class) { + let mut writer = self.0.write().unwrap(); + + writer.insert((class_name, superclass_name), ClassEntry { + superclass_name, + ptr: class as usize + }); } } @@ -120,15 +104,21 @@ impl ClassMap { /// /// There's definitely room to optimize here, but it works for now. #[inline(always)] -pub fn load_or_register_class(superclass_name: &'static str, subclass_name: &'static str, config: F) -> *const Class +pub fn load_or_register_class( + superclass_name: &'static str, + subclass_name: &'static str, + config: F +) -> *const Class where F: Fn(&mut ClassDecl) + 'static { - if let Some(subclass) = CLASSES.load_subclass(subclass_name, superclass_name) { + if let Some(subclass) = CLASSES.load(subclass_name, Some(superclass_name)) { return subclass; } - if let Some(superclass) = CLASSES.load_superclass(superclass_name) { + // If we can't find the class anywhere, then we'll attempt to load the superclass and register + // our new class type. + if let Some(superclass) = CLASSES.load(superclass_name, None) { let objc_subclass_name = format!("{}_{}", subclass_name, superclass_name); match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) { @@ -136,7 +126,7 @@ where config(&mut decl); let class = decl.register(); - CLASSES.store_subclass(subclass_name, superclass_name, class); + CLASSES.store(subclass_name, Some(superclass_name), class); return class; }, From 2525cbfee4d38a1bcfa06b98ac723c8426088d45 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Wed, 12 Oct 2022 14:32:23 -0400 Subject: [PATCH 2/9] Update webview implementation to use load_or_register_class (Issue #63) --- src/webview/class.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/webview/class.rs b/src/webview/class.rs index 920002c..e6da759 100644 --- a/src/webview/class.rs +++ b/src/webview/class.rs @@ -12,7 +12,7 @@ use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NO, YES}; +use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NO, YES, load_or_register_class}; use crate::webview::actions::{NavigationAction, NavigationResponse}; use crate::webview::{mimetype::MimeType, WebViewDelegate, WEBVIEW_DELEGATE_PTR}; //, OpenPanelParameters}; //use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy}; @@ -186,13 +186,7 @@ pub fn register_webview_class() -> *const Class { /// both a subclass of `NSViewController` and a delegate of the held `WKWebView` (for the various /// varieties of delegates needed there). pub fn register_webview_delegate_class() -> *const Class { - static mut VIEW_CLASS: *const Class = 0 as *const Class; - static INIT: Once = Once::new(); - - INIT.call_once(|| unsafe { - let superclass = class!(NSObject); - let mut decl = ClassDecl::new("RSTWebViewDelegate", superclass).unwrap(); - + load_or_register_class("NSObject", "RSTWebViewDelegate", |decl| unsafe { decl.add_ivar::(WEBVIEW_DELEGATE_PTR); // WKNavigationDelegate @@ -239,9 +233,5 @@ pub fn register_webview_delegate_class() -> *const Class { sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download:: as extern "C" fn(&Object, _, id, id, usize) ); - - VIEW_CLASS = decl.register(); - }); - - unsafe { VIEW_CLASS } + }) } From 4d1e0ddb9dce4dbc4187dd1f23d794ebfa51c718 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Wed, 12 Oct 2022 15:42:16 -0400 Subject: [PATCH 3/9] Support NAME on WebViewDelegate trait impls --- examples/browser/main.rs | 4 +++- src/webview/class.rs | 4 ++-- src/webview/mod.rs | 3 ++- src/webview/traits.rs | 11 +++++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/browser/main.rs b/examples/browser/main.rs index e6433fc..8033360 100644 --- a/examples/browser/main.rs +++ b/examples/browser/main.rs @@ -91,7 +91,9 @@ impl Dispatcher for BasicApp { #[derive(Default)] pub struct WebViewInstance; -impl WebViewDelegate for WebViewInstance {} +impl WebViewDelegate for WebViewInstance { + const NAME: &'static str = "BrowserWebViewDelegate"; +} struct AppWindow { toolbar: Toolbar, diff --git a/src/webview/class.rs b/src/webview/class.rs index e6da759..507ebf4 100644 --- a/src/webview/class.rs +++ b/src/webview/class.rs @@ -185,8 +185,8 @@ pub fn register_webview_class() -> *const Class { /// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as /// both a subclass of `NSViewController` and a delegate of the held `WKWebView` (for the various /// varieties of delegates needed there). -pub fn register_webview_delegate_class() -> *const Class { - load_or_register_class("NSObject", "RSTWebViewDelegate", |decl| unsafe { +pub fn register_webview_delegate_class(instance: &T) -> *const Class { + load_or_register_class("NSObject", instance.subclass_name(), |decl| unsafe { decl.add_ivar::(WEBVIEW_DELEGATE_PTR); // WKNavigationDelegate diff --git a/src/webview/mod.rs b/src/webview/mod.rs index 9c9f1ad..1161068 100644 --- a/src/webview/mod.rs +++ b/src/webview/mod.rs @@ -230,10 +230,11 @@ where /// Initializes a new WebView with a given `WebViewDelegate`. This enables you to respond to events /// and customize the view as a module, similar to class-based systems. pub fn with(config: WebViewConfig, delegate: T) -> WebView { + let delegate_class = register_webview_delegate_class(&delegate); let mut delegate = Box::new(delegate); let objc_delegate = unsafe { - let objc_delegate: id = msg_send![register_webview_delegate_class::(), new]; + let objc_delegate: id = msg_send![delegate_class, new]; let ptr: *const T = &*delegate; (&mut *objc_delegate).set_ivar(WEBVIEW_DELEGATE_PTR, ptr as usize); ShareId::from_ptr(objc_delegate) diff --git a/src/webview/traits.rs b/src/webview/traits.rs index 06bc2ea..fc9d221 100644 --- a/src/webview/traits.rs +++ b/src/webview/traits.rs @@ -8,6 +8,17 @@ use crate::webview::WebView; /// You can implement this on structs to handle callbacks from the underlying `WKWebView`. pub trait WebViewDelegate { + /// Used to cache subclass creations on the Objective-C side. + /// You can just set this to be the name of your delegate type. This + /// value *must* be unique per-type. + const NAME: &'static str; + + /// You should rarely (read: probably never) need to implement this yourself. + /// It simply acts as a getter for the associated `NAME` const on this trait. + fn subclass_name(&self) -> &'static str { + Self::NAME + } + /// Called when the View is ready to work with. You're passed a `ViewHandle` - this is safe to /// store and use repeatedly, but it's not thread safe - any UI calls must be made from the /// main thread! From 215eee4ae24f39a462faa7138f96eae9fb4762cc Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 13 Oct 2022 17:15:52 -0400 Subject: [PATCH 4/9] Experiment with bundle identifier being attached to generated subclass names (Issue #63) --- src/foundation/class.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/foundation/class.rs b/src/foundation/class.rs index f877f9f..aad1902 100644 --- a/src/foundation/class.rs +++ b/src/foundation/class.rs @@ -4,13 +4,37 @@ use std::sync::{Arc, RwLock}; use lazy_static::lazy_static; +use objc::{class, msg_send, sel, sel_impl}; use objc::declare::ClassDecl; -use objc::runtime::{objc_getClass, Class}; +use objc::runtime::{objc_getClass, Class, Object}; lazy_static! { static ref CLASSES: ClassMap = ClassMap::new(); } +/// A temporary method for testing; this will get cleaned up if it's worth bringing in permanently. +/// +/// (and probably not repeatedly queried...) +/// +/// This accounts for code not running in a standard bundle, and returns `None` if the bundle +/// identifier is nil. +fn get_bundle_id() -> Option { + let identifier: *mut Object = unsafe { + let bundle: *mut Object = msg_send![class!(NSBundle), mainBundle]; + msg_send![bundle, bundleIdentifier] + }; + + if identifier == crate::foundation::nil { + return None; + } + + let identifier = crate::foundation::NSString::retain(identifier).to_string() + .replace(".", "_") + .replace("-", "_"); + + Some(identifier) +} + /// Represents an entry in a `ClassMap`. We store an optional superclass_name for debugging /// purposes; it's an `Option` to make the logic of loading a class type where we don't need to /// care about the superclass type simpler. @@ -119,7 +143,10 @@ where // If we can't find the class anywhere, then we'll attempt to load the superclass and register // our new class type. if let Some(superclass) = CLASSES.load(superclass_name, None) { - let objc_subclass_name = format!("{}_{}", subclass_name, superclass_name); + let objc_subclass_name = match get_bundle_id() { + Some(bundle_id) => format!("{}_{}_{}", subclass_name, superclass_name, bundle_id), + None => format!("{}_{}", subclass_name, superclass_name) + }; match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) { Some(mut decl) => { From 65578f06fe53178e5c7b948d5b2a600f179cce6e Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 13 Oct 2022 17:25:20 -0400 Subject: [PATCH 5/9] Swap WebView class registration to use class fn --- src/webview/class.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/webview/class.rs b/src/webview/class.rs index 507ebf4..aedc5d4 100644 --- a/src/webview/class.rs +++ b/src/webview/class.rs @@ -170,16 +170,7 @@ extern "C" fn handle_download(this: &Object, _: Sel, downloa /// both a subclass of `NSViewController` and a delegate of the held `WKWebView` (for the various /// varieties of delegates needed there). pub fn register_webview_class() -> *const Class { - static mut VIEW_CLASS: *const Class = 0 as *const Class; - static INIT: Once = Once::new(); - - INIT.call_once(|| unsafe { - let superclass = class!(WKWebView); - let decl = ClassDecl::new("RSTWebView", superclass).unwrap(); - VIEW_CLASS = decl.register(); - }); - - unsafe { VIEW_CLASS } + load_or_register_class("WKWebView", "CacaoWebView", |decl| unsafe {}) } /// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as From dd0786bdab5ef88f072443739457a5a0b76a3017 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 14 Oct 2022 15:34:41 -0400 Subject: [PATCH 6/9] Generate a random u64 to append to subclass names (Issue #63) --- src/foundation/class.rs | 62 ++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/foundation/class.rs b/src/foundation/class.rs index aad1902..bf38cbb 100644 --- a/src/foundation/class.rs +++ b/src/foundation/class.rs @@ -1,38 +1,34 @@ +use std::cell::Cell; use std::collections::HashMap; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; use std::ffi::CString; +use std::thread; +use std::time::Instant; use std::sync::{Arc, RwLock}; use lazy_static::lazy_static; - -use objc::{class, msg_send, sel, sel_impl}; use objc::declare::ClassDecl; -use objc::runtime::{objc_getClass, Class, Object}; +use objc::runtime::{objc_getClass, Class}; lazy_static! { static ref CLASSES: ClassMap = ClassMap::new(); } -/// A temporary method for testing; this will get cleaned up if it's worth bringing in permanently. -/// -/// (and probably not repeatedly queried...) -/// -/// This accounts for code not running in a standard bundle, and returns `None` if the bundle -/// identifier is nil. -fn get_bundle_id() -> Option { - let identifier: *mut Object = unsafe { - let bundle: *mut Object = msg_send![class!(NSBundle), mainBundle]; - msg_send![bundle, bundleIdentifier] - }; - - if identifier == crate::foundation::nil { - return None; - } - - let identifier = crate::foundation::NSString::retain(identifier).to_string() - .replace(".", "_") - .replace("-", "_"); - - Some(identifier) +thread_local! { + /// A very simple RNG seed that we use in constructing unique subclass names. + /// + /// Why are we doing this? Mainly because I just don't want to bring in another + /// crate for something that can be done like this; we don't need cryptographically + /// secure generation or anything fancy, as we're just after a unique dangling bit + /// for class names. + static RNG_SEED: Cell = Cell::new({ + let mut hasher = DefaultHasher::new(); + Instant::now().hash(&mut hasher); + thread::current().id().hash(&mut hasher); + let hash = hasher.finish(); + (hash << 1) | 1 + }); } /// Represents an entry in a `ClassMap`. We store an optional superclass_name for debugging @@ -143,10 +139,20 @@ where // If we can't find the class anywhere, then we'll attempt to load the superclass and register // our new class type. if let Some(superclass) = CLASSES.load(superclass_name, None) { - let objc_subclass_name = match get_bundle_id() { - Some(bundle_id) => format!("{}_{}_{}", subclass_name, superclass_name, bundle_id), - None => format!("{}_{}", subclass_name, superclass_name) - }; + // When we're generating a new Subclass name, we need to append a random-ish component + // due to some oddities that can come up in certain scenarios (e.g, various bundler + // situations appear to have odd rules about subclass name usage/registration, this simply + // guarantees that we almost always have a unique name to register with the ObjC runtime). + // + // For more context, see: https://github.com/ryanmcgrath/cacao/issues/63 + let objc_subclass_name = format!("{}_{}_{}", subclass_name, superclass_name, RNG_SEED.with(|rng| { + rng.set(rng.get().wrapping_add(0xa0761d6478bd642f)); + let s = rng.get(); + let t = u128::from(s) * (u128::from(s ^ 0xe7037ed1a0b428db)); + ((t >> 64) as u64) ^ (t as u64) + })); + + println!("{}", objc_subclass_name); match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) { Some(mut decl) => { From f8e836e3eb2579bb0b49a0733ee1b7563e51fde6 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 14 Oct 2022 15:37:36 -0400 Subject: [PATCH 7/9] Remove println --- src/foundation/class.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/foundation/class.rs b/src/foundation/class.rs index bf38cbb..ba8201a 100644 --- a/src/foundation/class.rs +++ b/src/foundation/class.rs @@ -152,8 +152,6 @@ where ((t >> 64) as u64) ^ (t as u64) })); - println!("{}", objc_subclass_name); - match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) { Some(mut decl) => { config(&mut decl); From e54a28a1106c60ef1236af816132d4e11d6216b3 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 4 Nov 2022 15:54:15 -0700 Subject: [PATCH 8/9] Update webview example --- examples/webview_custom_protocol.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/webview_custom_protocol.rs b/examples/webview_custom_protocol.rs index 7b326a8..626d8de 100644 --- a/examples/webview_custom_protocol.rs +++ b/examples/webview_custom_protocol.rs @@ -23,6 +23,8 @@ impl AppDelegate for BasicApp { pub struct WebViewInstance; impl WebViewDelegate for WebViewInstance { + const NAME: &'static str = "CustomWebViewDelegate"; + fn on_custom_protocol_request(&self, path: &str) -> Option> { let requested_asset_path = path.replace("cacao://", ""); From 2e40bb6ae37b699d17bf0cf24ee2e21fc91157f0 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 4 Nov 2022 15:57:53 -0700 Subject: [PATCH 9/9] Fmting pass --- src/foundation/class.rs | 31 ++++++++++++++++--------------- src/webview/class.rs | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/foundation/class.rs b/src/foundation/class.rs index ba8201a..8c242f6 100644 --- a/src/foundation/class.rs +++ b/src/foundation/class.rs @@ -1,11 +1,11 @@ use std::cell::Cell; -use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; +use std::collections::HashMap; use std::ffi::CString; +use std::hash::{Hash, Hasher}; +use std::sync::{Arc, RwLock}; use std::thread; use std::time::Instant; -use std::sync::{Arc, RwLock}; use lazy_static::lazy_static; use objc::declare::ClassDecl; @@ -32,7 +32,7 @@ thread_local! { } /// Represents an entry in a `ClassMap`. We store an optional superclass_name for debugging -/// purposes; it's an `Option` to make the logic of loading a class type where we don't need to +/// purposes; it's an `Option` to make the logic of loading a class type where we don't need to /// care about the superclass type simpler. #[derive(Debug)] struct ClassEntry { @@ -124,11 +124,7 @@ impl ClassMap { /// /// There's definitely room to optimize here, but it works for now. #[inline(always)] -pub fn load_or_register_class( - superclass_name: &'static str, - subclass_name: &'static str, - config: F -) -> *const Class +pub fn load_or_register_class(superclass_name: &'static str, subclass_name: &'static str, config: F) -> *const Class where F: Fn(&mut ClassDecl) + 'static { @@ -145,12 +141,17 @@ where // guarantees that we almost always have a unique name to register with the ObjC runtime). // // For more context, see: https://github.com/ryanmcgrath/cacao/issues/63 - let objc_subclass_name = format!("{}_{}_{}", subclass_name, superclass_name, RNG_SEED.with(|rng| { - rng.set(rng.get().wrapping_add(0xa0761d6478bd642f)); - let s = rng.get(); - let t = u128::from(s) * (u128::from(s ^ 0xe7037ed1a0b428db)); - ((t >> 64) as u64) ^ (t as u64) - })); + let objc_subclass_name = format!( + "{}_{}_{}", + subclass_name, + superclass_name, + RNG_SEED.with(|rng| { + rng.set(rng.get().wrapping_add(0xa0761d6478bd642f)); + let s = rng.get(); + let t = u128::from(s) * (u128::from(s ^ 0xe7037ed1a0b428db)); + ((t >> 64) as u64) ^ (t as u64) + }) + ); match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) { Some(mut decl) => { diff --git a/src/webview/class.rs b/src/webview/class.rs index aedc5d4..44b368e 100644 --- a/src/webview/class.rs +++ b/src/webview/class.rs @@ -12,7 +12,7 @@ use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NO, YES, load_or_register_class}; +use crate::foundation::{id, load_or_register_class, nil, NSArray, NSInteger, NSString, NO, YES}; use crate::webview::actions::{NavigationAction, NavigationResponse}; use crate::webview::{mimetype::MimeType, WebViewDelegate, WEBVIEW_DELEGATE_PTR}; //, OpenPanelParameters}; //use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy};