blob: 9c2bf51faf2143f24a9c431fa24004100d5bb987 [file] [log] [blame]
Brian Silverman4e662aa2022-05-11 23:10:19 -07001// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use autocxx::subclass::prelude::*;
10use autocxx::{c_int, PinMut};
11use std::cell::{Ref, RefCell, RefMut};
12use std::ops::Deref;
13use std::pin::Pin;
14use std::rc::Rc;
15
16use crate::ffi;
17
18/// A memory-safe handle to a C++ RenderFrameHost.
19///
20/// This is a toy, hypothetical, example.
21///
22/// Creation: in this sample, the only option is to use [`RenderFrameHostHandle::from_id`]
23/// which corresponds to the equivalent method in C++ `RenderFrameHost`. Unlike
24/// the C++ version, you must pass a WebContents so that Rust wrappers can listen for
25/// destruction events.
26///
27/// The returned handle is memory safe and can be used to access the methods
28/// of [`ffi::content::RenderFrameHost`]. To use such a method, you have three options:
29/// * If you believe there is no chance that the `RenderFrameHost` has been
30/// destroyed, and if the method is const, you can just go ahead and call methods
31/// on this object. As it implements [`std::ops::Deref`], that will just work -
32/// but your code will panic if the `RenderFrameHost` was already destroyed.
33/// * If the method is non-const, you'll have to call `.pin_mut().method()` instead
34// but otherwise this is functionally identical.
35/// * If you believe that there is a chance that the `RenderFrameHost` was already
36/// destroyed, use [`RenderFrameHostHandle::try_borrow`] or
37/// [`RenderFrameHostHandle::try_borrow_mut`]. This will return
38/// a guard object which guarantees the existence of the `RenderFrameHost`
39/// during its lifetime.
40///
41/// # Performance characteristics
42///
43/// The existence of this object registers an observer with the `WebContents`
44/// and deregisters it on destruction. That is, of course, an overhead, but
45/// that's necessary to keep track of liveness. (A more efficient
46/// implementation would use a single observer for multiple such handles - but
47/// this is a toy implementation).
48///
49/// In addition, each time you extract the value from this
50/// `RenderFrameHostHandle`, a liveness check is performed. This involves
51/// not just a null check but also some reference count manipulation.
52/// If you're going to access the `RenderFrameHost` multiple times, it's
53/// advised that you call [`RenderFrameHostHandle::try_borrow`] or
54/// [`RenderFrameHostHandle::try_borrow_mut`] and then use
55/// the result multiple times. The liveness check for the `RenderFrameHost`
56/// will be performed only once at runtime.
57///
58/// # Destruction of RenderFrameHosts while borrowed
59///
60/// If you have called [`RenderFrameHostHandle::try_borrow`] (or its mutable
61/// equivalent) and still have an outstanding borrow, any code path - via C++
62/// - which results it the destruction of the `RenderFrameHost` will result in
63/// a runtime panic.
64pub struct RenderFrameHostHandle<'wc> {
65 obs: Rc<RefCell<RenderFrameHostForWebContents>>,
66 web_contents: Pin<&'wc mut ffi::content::WebContents>,
67}
68
69impl<'wc> RenderFrameHostHandle<'wc> {
70 /// Create a memory-safe handle to a RenderFrameHost using its
71 /// process ID and frame ID.
72 pub fn from_id(
73 render_process_id: c_int,
74 render_frame_id: c_int,
75 mut web_contents: Pin<&'wc mut ffi::content::WebContents>,
76 ) -> Self {
77 // Instantiate our WebContentsObserver subclass.
78 let obs = RenderFrameHostForWebContents::new_rust_owned(RenderFrameHostForWebContents {
79 rfh: ffi::content::RenderFrameHost::FromId(render_process_id, render_frame_id),
80 cpp_peer: Default::default(),
81 });
82
83 // And now register it.
84 // This nasty line will go away when autocxx is a bit more sophisticated.
85 let superclass_ptr = cast_to_superclass(obs.as_ref().borrow_mut().peer_mut());
86
87 // But this will remain unsafe. cxx policy is that any raw pointer
88 // passed into a C++ function requires an unsafe {} block and that
89 // is sensible. We may of course provide an ergonomic Rust wrapper
90 // around WebContents which provides safe Rust equivalents
91 // (using references or similar rather than pointers) in which case
92 // this unsafe block would go away.
93 unsafe { web_contents.as_mut().AddObserver(superclass_ptr) };
94
95 Self { obs, web_contents }
96 }
97
98 /// Tries to return a mutable reference to the RenderFrameHost.
99 /// Because this requires `self` to be `&mut`, and that lifetime is
100 /// applied to the returned `RenderFrameHost`, the compiler will prevent
101 /// multiple such references existing in Rust at the same time.
102 /// This will return `None` if the RenderFrameHost were already destroyed.
103 pub fn try_borrow_mut<'a>(
104 &'a mut self,
105 ) -> Option<impl PinMut<ffi::content::RenderFrameHost> + 'a> {
106 let ref_mut = self.obs.as_ref().borrow_mut();
107 if ref_mut.rfh.is_null() {
108 None
109 } else {
110 Some(RenderFrameHostRefMut(ref_mut))
111 }
112 }
113
114 /// Tries to return a reference to the RenderFrameHost.
115 /// The compiler will prevent calls to this if anyone has an outstanding
116 /// mutable reference from [`RenderFrameHostHandle::try_borrow_mut`].
117 /// This will return `None` if the RenderFrameHost were already destroyed.
118 #[allow(dead_code)]
119 pub fn try_borrow<'a>(&'a self) -> Option<impl AsRef<ffi::content::RenderFrameHost> + 'a> {
120 let ref_non_mut = self.obs.as_ref().borrow();
121 if ref_non_mut.rfh.is_null() {
122 None
123 } else {
124 Some(RenderFrameHostRef(ref_non_mut))
125 }
126 }
127}
128
129impl<'wc> Drop for RenderFrameHostHandle<'wc> {
130 fn drop(&mut self) {
131 // Unregister our observer.
132 let superclass_ptr = cast_to_superclass(self.obs.as_ref().borrow_mut().peer_mut());
133 unsafe { self.web_contents.as_mut().RemoveObserver(superclass_ptr) };
134 }
135}
136
137impl<'wc> AsRef<ffi::content::RenderFrameHost> for RenderFrameHostHandle<'wc> {
138 fn as_ref(&self) -> &ffi::content::RenderFrameHost {
139 let ref_non_mut = self.obs.as_ref().borrow();
140 // Safety: the .rfh field is guaranteed to be a RenderFrameHost
141 // and we are observing its lifetime so it will be reset to null
142 // if destroyed.
143 unsafe { ref_non_mut.rfh.as_ref() }.expect("This RenderFrameHost was already destroyed")
144 }
145}
146
147impl<'wc> PinMut<ffi::content::RenderFrameHost> for RenderFrameHostHandle<'wc> {
148 fn pin_mut(&mut self) -> Pin<&mut ffi::content::RenderFrameHost> {
149 let ref_mut = self.obs.as_ref().borrow_mut();
150 // Safety: the .rfh field is guaranteed to be a RenderFrameHost
151 // and we are observing its lifetime so it will be reset to null
152 // if destroyed.
153 unsafe { ref_mut.rfh.as_mut().map(|p| Pin::new_unchecked(p)) }
154 .expect("This RenderFrameHost was already destroyed")
155 }
156}
157
158impl<'wc> Deref for RenderFrameHostHandle<'wc> {
159 type Target = ffi::content::RenderFrameHost;
160 fn deref(&self) -> &Self::Target {
161 self.as_ref()
162 }
163}
164
165#[doc(hidden)]
166struct RenderFrameHostRefMut<'a>(RefMut<'a, RenderFrameHostForWebContents>);
167
168#[doc(hidden)]
169struct RenderFrameHostRef<'a>(Ref<'a, RenderFrameHostForWebContents>);
170
171impl<'a> AsRef<ffi::content::RenderFrameHost> for RenderFrameHostRef<'a> {
172 fn as_ref(&self) -> &ffi::content::RenderFrameHost {
173 // Safety:
174 // Creation precondition is that self.0.rfh is not null
175 // and it can't be destroyed whilst this borrow exists.
176 unsafe { self.0.rfh.as_ref().unwrap() }
177 }
178}
179
180impl<'a> PinMut<ffi::content::RenderFrameHost> for RenderFrameHostRefMut<'a> {
181 fn pin_mut(&mut self) -> Pin<&mut ffi::content::RenderFrameHost> {
182 // Safety:
183 // Creation precondition is that self.0.rfh is not null
184 // and it can't be destroyed whilst this borrow exists.
185 unsafe { Pin::new_unchecked(self.0.rfh.as_mut().unwrap()) }
186 }
187}
188
189impl<'a> AsRef<ffi::content::RenderFrameHost> for RenderFrameHostRefMut<'a> {
190 fn as_ref(&self) -> &ffi::content::RenderFrameHost {
191 // Safety:
192 // Creation precondition is that self.0.rfh is not null
193 // and it can't be destroyed whilst this borrow exists.
194 unsafe { self.0.rfh.as_ref().unwrap() }
195 }
196}
197
198impl<'a> Deref for RenderFrameHostRef<'a> {
199 type Target = ffi::content::RenderFrameHost;
200 fn deref(&self) -> &Self::Target {
201 self.as_ref()
202 }
203}
204
205impl<'a> Deref for RenderFrameHostRefMut<'a> {
206 type Target = ffi::content::RenderFrameHost;
207 fn deref(&self) -> &Self::Target {
208 self.as_ref()
209 }
210}
211
212#[is_subclass(superclass("content::WebContentsObserver"))]
213#[doc(hidden)]
214pub struct RenderFrameHostForWebContents {
215 rfh: *mut ffi::content::RenderFrameHost,
216}
217
218impl ffi::content::WebContentsObserver_methods for RenderFrameHostForWebContents {
219 unsafe fn RenderFrameDeleted(&mut self, destroyed_rfh: *mut ffi::content::RenderFrameHost) {
220 if self.rfh == destroyed_rfh {
221 self.rfh = std::ptr::null_mut()
222 }
223 }
224}
225
226fn cast_to_superclass(
227 obs: Pin<&mut ffi::RenderFrameHostForWebContentsCpp>,
228) -> *mut ffi::content::WebContentsObserver {
229 // This horrid code will all go away once we implement
230 // https://github.com/google/autocxx/issues/592; safe wrappers will
231 // be automatically generated to allow upcasting to superclasses.
232 // NB this code is probably actually _wrong_ too meanwhile; we need to cast
233 // on the C++ side.
234 let subclass_obs_ptr =
235 unsafe { Pin::into_inner_unchecked(obs) } as *mut ffi::RenderFrameHostForWebContentsCpp;
236 unsafe {
237 std::mem::transmute::<
238 *mut ffi::RenderFrameHostForWebContentsCpp,
239 *mut ffi::content::WebContentsObserver,
240 >(subclass_obs_ptr)
241 }
242}