pub use objc2_app_kit::{
NSAutoresizingMaskOptions, NSTrackingAreaOptions, NSWindowCollectionBehavior, NSWindowStyleMask,
};
#[doc(hidden)]
pub fn resolve_tracking_area_options(
mut options: objc2_app_kit::NSTrackingAreaOptions,
auto_resize: bool,
) -> objc2_app_kit::NSTrackingAreaOptions {
if auto_resize {
options |= objc2_app_kit::NSTrackingAreaOptions::InVisibleRect;
}
options
}
#[doc(hidden)]
pub fn install_text_input_client_window_level(root: &objc2_app_kit::NSView) {
use std::ffi::CStr;
use objc2::runtime::AnyProtocol;
let protocol_name = CStr::from_bytes_with_nul(b"NSTextInputClient\0")
.expect("NSTextInputClient protocol name must be NUL-terminated");
let Some(text_input_client) = AnyProtocol::get(protocol_name) else {
return;
};
install_text_input_client_window_level_in_subtree(root, text_input_client);
}
fn install_text_input_client_window_level_in_subtree(
root: &objc2_app_kit::NSView,
text_input_client: &objc2::runtime::AnyProtocol,
) {
use objc2_foundation::NSObjectProtocol;
if root.conformsToProtocol(text_input_client) {
let _ = install_window_level_method(stable_text_input_client_class(root.class()));
}
let subviews = root.subviews();
for subview in subviews.iter() {
install_text_input_client_window_level_in_subtree(&subview, text_input_client);
}
}
fn stable_text_input_client_class(
runtime_class: &objc2::runtime::AnyClass,
) -> &objc2::runtime::AnyClass {
let mut candidate = runtime_class;
while let Some(superclass) = candidate.superclass() {
if superclass.name().to_bytes() == b"WKWebView" {
return candidate;
}
candidate = superclass;
}
runtime_class
}
fn install_window_level_method(class: &objc2::runtime::AnyClass) -> bool {
use objc2::runtime::{Imp, Sel};
let selector = objc2::sel!(windowLevel);
if class.instance_method(selector).is_some() {
return false;
}
unsafe extern "C-unwind" fn window_level(
view: &objc2_app_kit::NSView,
_selector: Sel,
) -> objc2::ffi::NSInteger {
view.window().map_or(0, |window| window.level())
}
let implementation: unsafe extern "C-unwind" fn(
&objc2_app_kit::NSView,
Sel,
) -> objc2::ffi::NSInteger = window_level;
let implementation: Imp = unsafe { std::mem::transmute(implementation) };
let type_encoding = b"q@:\0";
unsafe {
objc2::ffi::class_addMethod(
class as *const _ as *mut _,
selector,
implementation,
type_encoding.as_ptr().cast(),
)
.as_bool()
}
}
#[macro_export]
macro_rules! panel {
(
$class_name:ident {
$(config: {
$($method:ident: $value:expr),* $(,)?
})?
$(with: {
$(tracking_area: {
options: $tracking_options:expr,
auto_resize: $auto_resize:expr $(,)?
})?
})?
}
) => {
$crate::pastey::paste! {
struct [<$class_name Ivars>];
$crate::objc2::define_class!(
#[unsafe(super = $crate::objc2_app_kit::NSPanel)]
#[name = stringify!($class_name)]
#[ivars = [<$class_name Ivars>]]
struct [<Raw $class_name>];
unsafe impl NSObjectProtocol for [<Raw $class_name>] {}
impl [<Raw $class_name>] {
$($(
#[doc = concat!(" Returns whether panels of this class ", stringify!([<$method:lower_camel>]))]
#[unsafe(method([<$method:lower_camel>]))]
fn [<__ $method:snake>]() -> bool {
$value
}
#[doc = concat!(" Returns whether this specific panel instance ", stringify!([<$method:lower_camel>]))]
#[unsafe(method([<$method:lower_camel>]))]
fn [<__ $method:snake _instance>](&self) -> bool {
$value
}
)*)?
#[unsafe(method(mouseEntered:))]
fn __mouse_entered(&self, event: &$crate::objc2_app_kit::NSEvent) {
unsafe {
let delegate: Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>> = $crate::objc2::msg_send![self, delegate];
if let Some(ref d) = delegate {
let selector = $crate::objc2::sel!(mouseEntered:);
let responds: bool = $crate::objc2::msg_send![&**d, respondsToSelector: selector];
if responds {
let _: () = $crate::objc2::msg_send![&**d, mouseEntered: event];
}
}
}
}
#[unsafe(method(mouseExited:))]
fn __mouse_exited(&self, event: &$crate::objc2_app_kit::NSEvent) {
unsafe {
let delegate: Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>> = $crate::objc2::msg_send![self, delegate];
if let Some(ref d) = delegate {
let selector = $crate::objc2::sel!(mouseExited:);
let responds: bool = $crate::objc2::msg_send![&**d, respondsToSelector: selector];
if responds {
let _: () = $crate::objc2::msg_send![&**d, mouseExited: event];
}
}
}
}
#[unsafe(method(mouseMoved:))]
fn __mouse_moved(&self, event: &$crate::objc2_app_kit::NSEvent) {
unsafe {
let delegate: Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>> = $crate::objc2::msg_send![self, delegate];
if let Some(ref d) = delegate {
let selector = $crate::objc2::sel!(mouseMoved:);
let responds: bool = $crate::objc2::msg_send![&**d, respondsToSelector: selector];
if responds {
let _: () = $crate::objc2::msg_send![&**d, mouseMoved: event];
}
}
}
}
#[unsafe(method(cursorUpdate:))]
fn __cursor_update(&self, event: &$crate::objc2_app_kit::NSEvent) {
unsafe {
let delegate: Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>> = $crate::objc2::msg_send![self, delegate];
if let Some(ref d) = delegate {
let selector = $crate::objc2::sel!(cursorUpdate:);
let responds: bool = $crate::objc2::msg_send![&**d, respondsToSelector: selector];
if responds {
let _: () = $crate::objc2::msg_send![&**d, cursorUpdate: event];
}
}
}
}
}
);
#[doc = " A public wrapper for `Raw" $class_name "` "]
pub struct $class_name<R: tauri::Runtime = tauri::Wry> {
panel: $crate::objc2::rc::Retained<[<Raw $class_name>]>,
label: String,
original_class: *const $crate::objc2::runtime::AnyClass,
original_delegate: std::cell::OnceCell<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>>,
app_handle: tauri::AppHandle<R>,
event_handler: std::cell::RefCell<Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>>>,
}
unsafe impl<R: tauri::Runtime> Send for $class_name<R> {}
unsafe impl<R: tauri::Runtime> Sync for $class_name<R> {}
impl<R: tauri::Runtime> $class_name<R> where $class_name<R>: $crate::Panel<R> {
fn with_label(panel: $crate::objc2::rc::Retained<[<Raw $class_name>]>, label: String, original_class: *const $crate::objc2::runtime::AnyClass, app_handle: tauri::AppHandle<R>) -> Self {
Self {
panel,
label,
original_class,
original_delegate: std::cell::OnceCell::new(),
app_handle,
event_handler: std::cell::RefCell::new(None),
}
}
pub fn from_window(window: &tauri::WebviewWindow<R>) -> tauri::Result<Self> {
let label = window.label().to_string();
<Self as $crate::FromWindow<R>>::from_window(window.clone(), label)
}
}
impl<R: tauri::Runtime> $crate::Panel<R> for $class_name<R> {
fn show(&self) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, orderFrontRegardless];
}
}
fn hide(&self) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, orderOut: $crate::objc2::ffi::nil];
}
}
fn to_window(&self) -> Option<tauri::WebviewWindow<R>> {
use tauri::Manager;
use $crate::ManagerExt;
unsafe extern "C" {
fn object_setClass(
obj: *mut $crate::objc2_foundation::NSObject,
cls: *const $crate::objc2::runtime::AnyClass,
) -> *const $crate::objc2::runtime::AnyClass;
}
if let Some(_) = self.app_handle.remove_webview_panel(self.label.as_str()) {
self.set_event_handler(None);
self.set_released_when_closed(false);
unsafe {
let target_class = if !self.original_class.is_null() {
self.original_class
} else {
$crate::objc2_app_kit::NSWindow::class()
};
object_setClass(
&*self.panel as *const [<Raw $class_name>] as *mut $crate::objc2_foundation::NSObject,
target_class,
);
}
self.app_handle.get_webview_window(&self.label)
} else {
None
}
}
fn as_panel(&self) -> &$crate::objc2_app_kit::NSPanel {
unsafe { &*(&*self.panel as *const [<Raw $class_name>] as *const $crate::objc2_app_kit::NSPanel) }
}
fn label(&self) -> &str {
&self.label
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn set_event_handler(
&self,
handler: Option<&$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>,
) {
unsafe {
match handler {
Some(h) => {
if self.event_handler.borrow().is_none() && self.original_delegate.get().is_none() {
if let Some(current_delegate) = unsafe { self.panel.delegate() } {
let _ = self.original_delegate.set(current_delegate);
}
}
let retained_handler = h.retain();
*self.event_handler.borrow_mut() = Some(retained_handler);
let _: () = $crate::objc2::msg_send![&*self.panel, setDelegate: h];
}
None => {
if self.original_delegate.get().is_none() {
return;
}
*self.event_handler.borrow_mut() = None;
if let Some(orig_delegate) = self.original_delegate.get() {
let _: () = $crate::objc2::msg_send![&*self.panel, setDelegate: &**orig_delegate];
}
}
}
}
}
fn is_visible(&self) -> bool {
unsafe {
$crate::objc2::msg_send![&*self.panel, isVisible]
}
}
fn is_floating_panel(&self) -> bool {
unsafe {
$crate::objc2::msg_send![&*self.panel, isFloatingPanel]
}
}
fn becomes_key_only_if_needed(&self) -> bool {
unsafe {
$crate::objc2::msg_send![&*self.panel, becomesKeyOnlyIfNeeded]
}
}
fn can_become_key_window(&self) -> bool {
unsafe {
$crate::objc2::msg_send![&*self.panel, canBecomeKeyWindow]
}
}
fn can_become_main_window(&self) -> bool {
unsafe {
$crate::objc2::msg_send![&*self.panel, canBecomeMainWindow]
}
}
fn hides_on_deactivate(&self) -> bool {
unsafe {
$crate::objc2::msg_send![&*self.panel, hidesOnDeactivate]
}
}
fn make_key_window(&self) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, makeKeyWindow];
}
}
fn make_main_window(&self) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, makeMainWindow];
}
}
fn resign_key_window(&self) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, resignKeyWindow];
}
}
fn make_key_and_order_front(&self) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, makeKeyAndOrderFront: $crate::objc2::ffi::nil];
}
}
fn order_front_regardless(&self) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, orderFrontRegardless];
}
}
fn show_and_make_key(&self) {
unsafe {
let content_view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> =
$crate::objc2::msg_send![&*self.panel, contentView];
let _: bool = $crate::objc2::msg_send![&*self.panel, makeFirstResponder: &*content_view];
let _: () = $crate::objc2::msg_send![&*self.panel, orderFrontRegardless];
let _: () = $crate::objc2::msg_send![&*self.panel, makeKeyWindow];
}
}
fn set_level(&self, level: i64) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setLevel: level];
}
}
fn set_floating_panel(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setFloatingPanel: value];
}
}
fn set_becomes_key_only_if_needed(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setBecomesKeyOnlyIfNeeded: value];
}
}
fn set_hides_on_deactivate(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setHidesOnDeactivate: value];
}
}
fn set_works_when_modal(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setWorksWhenModal: value];
}
}
fn set_alpha_value(&self, value: f64) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setAlphaValue: value];
}
}
fn set_released_when_closed(&self, released: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setReleasedWhenClosed: released];
}
}
fn set_content_size(&self, width: f64, height: f64) {
unsafe {
let size = $crate::objc2_foundation::NSSize::new(width, height);
let _: () = $crate::objc2::msg_send![&*self.panel, setContentSize: size];
}
}
fn set_has_shadow(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setHasShadow: value];
}
}
fn set_opaque(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setOpaque: value];
}
}
fn set_accepts_mouse_moved_events(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setAcceptsMouseMovedEvents: value];
}
}
fn set_ignores_mouse_events(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setIgnoresMouseEvents: value];
}
}
fn set_movable_by_window_background(&self, value: bool) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setMovableByWindowBackground: value];
}
}
fn set_collection_behavior(&self, behavior: $crate::objc2_app_kit::NSWindowCollectionBehavior) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setCollectionBehavior: behavior];
}
}
fn content_view(&self) -> $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> {
unsafe {
$crate::objc2::msg_send![&*self.panel, contentView]
}
}
fn resign_main_window(&self) {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, resignMainWindow];
}
}
fn set_style_mask(
&self,
style_mask: $crate::objc2_app_kit::NSWindowStyleMask,
) -> Result<(), $crate::StyleMaskError> {
$crate::catch_style_mask_exception(|| {
unsafe {
let _: () = $crate::objc2::msg_send![&*self.panel, setStyleMask: style_mask];
}
})
}
fn make_first_responder(&self, responder: Option<&$crate::objc2_app_kit::NSResponder>) -> bool {
unsafe {
let result: bool = match responder {
Some(resp) => $crate::objc2::msg_send![&*self.panel, makeFirstResponder: resp],
None => $crate::objc2::msg_send![&*self.panel, makeFirstResponder: $crate::objc2::ffi::nil],
};
result
}
}
fn set_corner_radius(&self, radius: f64) {
unsafe {
let content_view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> = $crate::objc2::msg_send![&*self.panel, contentView];
let _: () = $crate::objc2::msg_send![&*content_view, setWantsLayer: true];
let content_layer: $crate::objc2::rc::Retained<$crate::objc2_foundation::NSObject> = $crate::objc2::msg_send![&*content_view, layer];
let _: () = $crate::objc2::msg_send![&*content_layer, setCornerRadius: radius];
}
}
fn set_transparent(&self, transparent: bool) {
unsafe {
if transparent {
let clear_color: $crate::objc2::rc::Retained<$crate::objc2_foundation::NSObject> = $crate::objc2::msg_send![$crate::objc2::class!(NSColor), clearColor];
let _: () = $crate::objc2::msg_send![&*self.panel, setBackgroundColor: &*clear_color];
let _: () = $crate::objc2::msg_send![&*self.panel, setOpaque: false];
} else {
let default_color: $crate::objc2::rc::Retained<$crate::objc2_foundation::NSObject> = $crate::objc2::msg_send![$crate::objc2::class!(NSColor), windowBackgroundColor];
let _: () = $crate::objc2::msg_send![&*self.panel, setBackgroundColor: &*default_color];
let _: () = $crate::objc2::msg_send![&*self.panel, setOpaque: true];
}
}
}
}
impl<R: tauri::Runtime> $crate::FromWindow<R> for $class_name<R> {
fn from_window(window: tauri::WebviewWindow<R>, label: String) -> tauri::Result<Self> {
let ns_window = window.ns_window().map_err(|e| {
tauri::Error::Io(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Failed to get NSWindow: {:?}", e),
))
})?;
unsafe {
unsafe extern "C" {
fn object_setClass(
obj: *mut $crate::objc2_foundation::NSObject,
cls: *const $crate::objc2::runtime::AnyClass,
) -> *const $crate::objc2::runtime::AnyClass;
fn object_getClass(
obj: *mut $crate::objc2_foundation::NSObject,
) -> *const $crate::objc2::runtime::AnyClass;
}
let original_class = object_getClass(ns_window as *mut $crate::objc2_foundation::NSObject);
object_setClass(
ns_window as *mut $crate::objc2_foundation::NSObject,
[<Raw $class_name>]::class(),
);
let panel_ptr = ns_window as *mut [<Raw $class_name>];
let panel = $crate::objc2::rc::Retained::retain(panel_ptr).ok_or_else(|| {
tauri::Error::Io(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to retain panel",
))
})?;
$($(
Self::apply_instance_property(&panel, stringify!($method), $value);
)*)?
$($(
Self::add_tracking_area(&panel, $tracking_options, $auto_resize);
)?)?
let content_view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> =
$crate::objc2::msg_send![&panel, contentView];
$crate::panel::install_text_input_client_window_level(&content_view);
let subviews: $crate::objc2::rc::Retained<$crate::objc2_foundation::NSArray<$crate::objc2_app_kit::NSView>> =
$crate::objc2::msg_send![&content_view, subviews];
let count: usize = $crate::objc2::msg_send![&subviews, count];
let resize_mask = $crate::objc2_app_kit::NSAutoresizingMaskOptions::ViewWidthSizable
| $crate::objc2_app_kit::NSAutoresizingMaskOptions::ViewHeightSizable;
for i in 0..count {
let view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> =
$crate::objc2::msg_send![&subviews, objectAtIndex: i];
let _: () = $crate::objc2::msg_send![&view, setAutoresizingMask: resize_mask];
}
Ok($class_name::with_label(
panel,
label,
original_class,
tauri::Manager::app_handle(&window).clone(),
))
}
}
}
impl<R: tauri::Runtime> $class_name<R> where $class_name<R>: $crate::Panel<R> {
#[allow(unused)]
fn apply_instance_property(panel: &$crate::objc2_app_kit::NSPanel, method: &str, value: bool) {
unsafe {
match method {
"hides_on_deactivate" | "hidesOnDeactivate" => {
let _: () = $crate::objc2::msg_send![panel, setHidesOnDeactivate: value];
},
"becomes_key_only_if_needed" | "becomesKeyOnlyIfNeeded" => {
let _: () = $crate::objc2::msg_send![panel, setBecomesKeyOnlyIfNeeded: value];
},
"works_when_modal" | "worksWhenModal" => {
let _: () = $crate::objc2::msg_send![panel, setWorksWhenModal: value];
},
"is_floating_panel" | "isFloatingPanel" => {
let _: () = $crate::objc2::msg_send![panel, setFloatingPanel: value];
},
_ => {
}
}
}
}
#[allow(unused)]
fn add_tracking_area(panel: &$crate::objc2_app_kit::NSPanel, options: impl Into<$crate::objc2_app_kit::NSTrackingAreaOptions>, auto_resize: bool) {
unsafe {
let content_view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> =
$crate::objc2::msg_send![panel, contentView];
let bounds: $crate::objc2_foundation::NSRect =
$crate::objc2::msg_send![&content_view, bounds];
let options = $crate::panel::resolve_tracking_area_options(
options.into(),
auto_resize,
);
let tracking_area: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSTrackingArea> = {
let alloc: *mut $crate::objc2_app_kit::NSTrackingArea = $crate::objc2::msg_send![
$crate::objc2_app_kit::NSTrackingArea::class(),
alloc
];
let area: *mut $crate::objc2_app_kit::NSTrackingArea = $crate::objc2::msg_send![
alloc,
initWithRect: bounds,
options: options,
owner: &*content_view,
userInfo: $crate::objc2::ffi::nil
];
$crate::objc2::rc::Retained::from_raw(area).unwrap()
};
let _: () = $crate::objc2::msg_send![&content_view, addTrackingArea: &*tracking_area];
}
}
}
}
};
}
#[cfg(test)]
mod tests {
use std::ffi::CStr;
use std::sync::OnceLock;
use objc2::{runtime::ClassBuilder, ClassType};
use objc2_app_kit::{NSTrackingAreaOptions, NSView};
use super::{
install_window_level_method, resolve_tracking_area_options, stable_text_input_client_class,
};
#[test]
fn auto_resize_tracks_the_visible_view_rect() {
let base = NSTrackingAreaOptions::ActiveAlways
| NSTrackingAreaOptions::MouseEnteredAndExited
| NSTrackingAreaOptions::MouseMoved;
let options = resolve_tracking_area_options(base, true);
assert!(options.contains(NSTrackingAreaOptions::InVisibleRect));
assert!(options.contains(base));
}
#[test]
fn fixed_tracking_area_preserves_the_requested_options() {
let base = NSTrackingAreaOptions::ActiveAlways
| NSTrackingAreaOptions::MouseEnteredAndExited
| NSTrackingAreaOptions::MouseMoved;
assert_eq!(resolve_tracking_area_options(base, false), base);
}
#[test]
fn window_level_method_is_installed_once() {
static CLASS: OnceLock<&'static objc2::runtime::AnyClass> = OnceLock::new();
let class = CLASS.get_or_init(|| {
let name = CStr::from_bytes_with_nul(b"TauriNSPanelTextInputClientTest\0")
.expect("test class name must be NUL-terminated");
ClassBuilder::new(name, NSView::class())
.expect("test class should only be registered once")
.register()
});
assert!(install_window_level_method(class));
assert!(class.instance_method(objc2::sel!(windowLevel)).is_some());
assert!(!install_window_level_method(class));
}
#[test]
fn window_level_method_targets_the_stable_webview_subclass() {
static CLASSES: OnceLock<(
&'static objc2::runtime::AnyClass,
&'static objc2::runtime::AnyClass,
)> = OnceLock::new();
let (webview, notifying_webview) = CLASSES.get_or_init(|| {
let wk_webview_name = CStr::from_bytes_with_nul(b"WKWebView\0").unwrap();
let wk_webview = objc2::runtime::AnyClass::get(wk_webview_name)
.expect("WebKit should be loaded by Tauri");
let webview_name =
CStr::from_bytes_with_nul(b"TauriNSPanelStableWebViewTest\0").unwrap();
let webview = ClassBuilder::new(webview_name, wk_webview)
.expect("stable test webview class should only be registered once")
.register();
let notifying_name =
CStr::from_bytes_with_nul(b"TauriNSPanelNotifyingWebViewTest\0").unwrap();
let notifying_webview = ClassBuilder::new(notifying_name, webview)
.expect("notifying test webview class should only be registered once")
.register();
(webview, notifying_webview)
});
assert_eq!(stable_text_input_client_class(notifying_webview), *webview);
}
}