summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/client/app/src/ui/macos/objc.rs
blob: d4f3f1c4195595653a9126eb286f19378483b7c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Objective-C bindings and helpers.

// Forward all exports from the `objc` crate.
pub use objc::*;

/// An objc class instance which contains rust data `T`.
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Objc<T> {
    pub instance: cocoa::id,
    _phantom: std::marker::PhantomData<*mut T>,
}

impl<T> Objc<T> {
    pub fn new(instance: cocoa::id) -> Self {
        Objc {
            instance,
            _phantom: std::marker::PhantomData,
        }
    }

    pub fn data(&self) -> &T {
        let data = *unsafe { (*self.instance).get_ivar::<usize>("rust_self") } as *mut T;
        unsafe { &*data }
    }

    pub fn data_mut(&mut self) -> &mut T {
        let data = *unsafe { (*self.instance).get_ivar::<usize>("rust_self") } as *mut T;
        unsafe { &mut *data }
    }
}

impl<T> std::ops::Deref for Objc<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        self.data()
    }
}

impl<T> std::ops::DerefMut for Objc<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.data_mut()
    }
}

unsafe impl<T> Encode for Objc<T> {
    fn encode() -> Encoding {
        cocoa::id::encode()
    }
}

/// Wrapper to provide `Encode` for bindgen-generated types (bindgen should do this in the future).
#[repr(transparent)]
pub struct Ptr<T>(pub T);

unsafe impl<T> Encode for Ptr<T> {
    fn encode() -> Encoding {
        cocoa::id::encode()
    }
}

/// A strong objective-c reference to `T`.
#[repr(transparent)]
pub struct StrongRef<T> {
    ptr: rc::StrongPtr,
    _phantom: std::marker::PhantomData<T>,
}

impl<T> Clone for StrongRef<T> {
    fn clone(&self) -> Self {
        StrongRef {
            ptr: self.ptr.clone(),
            _phantom: self._phantom,
        }
    }
}

impl<T> std::ops::Deref for StrongRef<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        let obj: &cocoa::id = &*self.ptr;
        unsafe { std::mem::transmute(obj) }
    }
}

impl<T> StrongRef<T> {
    /// Assume the given pointer-wrapper is an already-retained strong reference.
    ///
    /// # Safety
    /// The type _must_ be the same size as cocoa::id and contain only a cocoa::id.
    pub unsafe fn new(v: T) -> Self {
        std::mem::transmute_copy(&v)
    }

    /// Retain the given pointer-wrapper.
    ///
    /// # Safety
    /// The type _must_ be the same size as cocoa::id and contain only a cocoa::id.
    #[allow(dead_code)]
    pub unsafe fn retain(v: T) -> Self {
        let obj: cocoa::id = std::mem::transmute_copy(&v);
        StrongRef {
            ptr: rc::StrongPtr::retain(obj),
            _phantom: std::marker::PhantomData,
        }
    }

    pub fn autorelease(self) -> T {
        let obj = self.ptr.autorelease();
        unsafe { std::mem::transmute_copy(&obj) }
    }

    pub fn weak(&self) -> WeakRef<T> {
        WeakRef {
            ptr: self.ptr.weak(),
            _phantom: std::marker::PhantomData,
        }
    }

    /// Unwrap the StrongRef value without affecting reference counts.
    ///
    /// This is the opposite of `new`.
    #[allow(dead_code)]
    pub fn unwrap(self: Self) -> T {
        let v = unsafe { std::mem::transmute_copy(&self) };
        std::mem::forget(self);
        v
    }

    /// Cast to a base class.
    ///
    /// Bindgen pointer-wrappers have trival `From<Derived> for Base` implementations.
    pub fn cast<U: From<T>>(self) -> StrongRef<U> {
        StrongRef {
            ptr: self.ptr,
            _phantom: std::marker::PhantomData,
        }
    }
}

/// A weak objective-c reference to `T`.
#[derive(Clone)]
#[repr(transparent)]
pub struct WeakRef<T> {
    ptr: rc::WeakPtr,
    _phantom: std::marker::PhantomData<T>,
}

impl<T> WeakRef<T> {
    pub fn lock(&self) -> Option<StrongRef<T>> {
        let ptr = self.ptr.load();
        if ptr.is_null() {
            None
        } else {
            Some(StrongRef {
                ptr,
                _phantom: std::marker::PhantomData,
            })
        }
    }
}

/// A macro for creating an objc class.
///
/// Classes _must_ be registered before use (`Objc<T>::register()`).
///
/// Example:
/// ```
/// struct Foo(u8);
///
/// objc_class! {
///     impl Foo: NSObject {
///         #[sel(mySelector:)]
///         fn my_selector(&mut self, arg: u8) -> u8 {
///             self.0 + arg
///         }
///     }
/// }
///
/// fn make_foo() -> StrongRef<Objc<Foo>> {
///     Foo(42).into_object()
/// }
/// ```
///
/// Call `T::into_object()` to create the objective-c class instance.
macro_rules! objc_class {
    ( impl $name:ident : $base:ident $(<$($protocol:ident),+>)? {
        $(
            #[sel($($sel:tt)+)]
            fn $mname:ident (&mut $self:ident $(, $argname:ident : $argtype:ty )*) $(-> $rettype:ty)? $body:block
        )*
    }) => {
        impl Objc<$name> {
            pub fn register() {
                let mut decl = declare::ClassDecl::new(concat!("CR", stringify!($name)), class!($base)).expect(concat!("failed to declare ", stringify!($name), " class"));
                $($(decl.add_protocol(runtime::Protocol::get(stringify!($protocol)).expect(concat!("failed to find ",stringify!($protocol)," protocol")));)+)?
                decl.add_ivar::<usize>("rust_self");
                $({
                    extern fn method_impl(obj: &mut runtime::Object, _: runtime::Sel $(, $argname: $argtype )*) $(-> $rettype)? {
                        Objc::<$name>::new(obj).$mname($($argname),*)
                    }
                    unsafe {
                        decl.add_method(sel!($($sel)+), method_impl as extern fn(&mut runtime::Object, runtime::Sel $(, $argname: $argtype )*) $(-> $rettype)?);
                    }
                })*
                {
                    extern fn dealloc_impl(obj: &runtime::Object, _: runtime::Sel) {
                        drop(unsafe { Box::from_raw(*obj.get_ivar::<usize>("rust_self") as *mut $name) });
                        unsafe {
                            let _: () = msg_send![super(obj, class!(NSObject)), dealloc];
                        }
                    }
                    unsafe {
                        decl.add_method(sel!(dealloc), dealloc_impl as extern fn(&runtime::Object, runtime::Sel));
                    }
                }
                decl.register();
            }

            pub fn class() -> &'static runtime::Class {
                runtime::Class::get(concat!("CR", stringify!($name))).expect("class not registered")
            }

            $(fn $mname (&mut $self $(, $argname : $argtype )*) $(-> $rettype)? $body)*
        }

        impl $name {
            pub fn into_object(self) -> StrongRef<Objc<$name>> {
                let obj: *mut runtime::Object = unsafe { msg_send![Objc::<Self>::class(), alloc] };
                unsafe { (*obj).set_ivar("rust_self", Box::into_raw(Box::new(self)) as usize) };
                let obj: *mut runtime::Object = unsafe { msg_send![obj, init] };
                unsafe { StrongRef::new(Objc::new(obj)) }
            }
        }
    }
}

pub(crate) use objc_class;