summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/client/app/src/data.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/client/app/src/data.rs')
-rw-r--r--toolkit/crashreporter/client/app/src/data.rs400
1 files changed, 400 insertions, 0 deletions
diff --git a/toolkit/crashreporter/client/app/src/data.rs b/toolkit/crashreporter/client/app/src/data.rs
new file mode 100644
index 0000000000..474da8966a
--- /dev/null
+++ b/toolkit/crashreporter/client/app/src/data.rs
@@ -0,0 +1,400 @@
+/* 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/. */
+
+//! Data binding types are used to implement dynamic behaviors in UIs. [`Event`] is the primitive
+//! type underlying most others. [Properties](Property) are what should usually be used in UI
+//! models, since they have `From` impls allowing different binding behaviors to be set.
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+/// An event which can have multiple subscribers.
+///
+/// The type parameter is the payload of the event.
+pub struct Event<T> {
+ subscribers: Rc<RefCell<Vec<Box<dyn Fn(&T)>>>>,
+}
+
+impl<T> Clone for Event<T> {
+ fn clone(&self) -> Self {
+ Event {
+ subscribers: self.subscribers.clone(),
+ }
+ }
+}
+
+impl<T> Default for Event<T> {
+ fn default() -> Self {
+ Event {
+ subscribers: Default::default(),
+ }
+ }
+}
+
+impl<T> std::fmt::Debug for Event<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(
+ f,
+ "{} {{ {} subscribers }}",
+ std::any::type_name::<Self>(),
+ self.subscribers.borrow().len()
+ )
+ }
+}
+
+impl<T> Event<T> {
+ /// Add a callback for when the event is fired.
+ pub fn subscribe<F>(&self, f: F)
+ where
+ F: Fn(&T) + 'static,
+ {
+ self.subscribers.borrow_mut().push(Box::new(f));
+ }
+
+ /// Fire the event with the given payload.
+ pub fn fire(&self, payload: &T) {
+ for f in self.subscribers.borrow().iter() {
+ f(payload);
+ }
+ }
+}
+
+/// A synchronized runtime value.
+///
+/// Consumers can subscribe to change events on the value. Change events are fired when
+/// `borrow_mut()` references are dropped.
+#[derive(Default)]
+pub struct Synchronized<T> {
+ inner: Rc<SynchronizedInner<T>>,
+}
+
+impl<T> Clone for Synchronized<T> {
+ fn clone(&self) -> Self {
+ Synchronized {
+ inner: self.inner.clone(),
+ }
+ }
+}
+
+impl<T: std::fmt::Debug> std::fmt::Debug for Synchronized<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_struct(std::any::type_name::<Self>())
+ .field("current", &*self.inner.current.borrow())
+ .field("change", &self.inner.change)
+ .finish()
+ }
+}
+
+#[derive(Default)]
+struct SynchronizedInner<T> {
+ current: RefCell<T>,
+ change: Event<T>,
+}
+
+impl<T> Synchronized<T> {
+ /// Create a new value with the given inner data.
+ pub fn new(initial: T) -> Self {
+ Synchronized {
+ inner: Rc::new(SynchronizedInner {
+ current: RefCell::new(initial),
+ change: Default::default(),
+ }),
+ }
+ }
+
+ /// Borrow a value's data.
+ pub fn borrow(&self) -> std::cell::Ref<T> {
+ self.inner.current.borrow()
+ }
+
+ /// Mutably borrow a value's data.
+ ///
+ /// When the mutable reference is dropped, a change event is fired.
+ pub fn borrow_mut(&self) -> ValueRefMut<'_, T> {
+ ValueRefMut {
+ value: std::mem::ManuallyDrop::new(self.inner.current.borrow_mut()),
+ inner: &self.inner,
+ }
+ }
+
+ /// Subscribe to change events in the value.
+ pub fn on_change<F: Fn(&T) + 'static>(&self, f: F) {
+ self.inner.change.subscribe(f);
+ }
+
+ /// Update another synchronized value when this one changes.
+ pub fn update_on_change<U: 'static, F: Fn(&T) -> U + 'static>(
+ &self,
+ other: &Synchronized<U>,
+ f: F,
+ ) {
+ let other = other.clone();
+ self.on_change(move |val| {
+ *other.borrow_mut() = f(val);
+ });
+ }
+
+ /// Create a new synchronized value which will update when this one changes.
+ pub fn mapped<U: 'static, F: Fn(&T) -> U + 'static>(&self, f: F) -> Synchronized<U> {
+ let s = Synchronized::new(f(&*self.borrow()));
+ self.update_on_change(&s, f);
+ s
+ }
+
+ pub fn join<A: 'static, B: 'static, F: Fn(&A, &B) -> T + Clone + 'static>(
+ a: &Synchronized<A>,
+ b: &Synchronized<B>,
+ f: F,
+ ) -> Self
+ where
+ T: 'static,
+ {
+ let s = Synchronized::new(f(&*a.borrow(), &*b.borrow()));
+ let update = cc! { (a,b,s) move || {
+ *s.borrow_mut() = f(&*a.borrow(), &*b.borrow());
+ }};
+ a.on_change(cc! { (update) move |_| update()});
+ b.on_change(move |_| update());
+ s
+ }
+}
+
+/// A runtime value that can be fetched on-demand (read-only).
+///
+/// Consumers call [`read`] or [`get`] to retrieve the value, while producers call [`register`] to
+/// set the function which is called to retrieve the value. This is of most use for things like
+/// editable text strings, where it would be unnecessarily expensive to e.g. update a
+/// `Synchronized` property as the text string is changed (debouncing could be used, but if change
+/// notification isn't needed then it's still unnecessary).
+pub struct OnDemand<T> {
+ get: Rc<RefCell<Option<Box<dyn Fn(&mut T) + 'static>>>>,
+}
+
+impl<T> Default for OnDemand<T> {
+ fn default() -> Self {
+ OnDemand {
+ get: Default::default(),
+ }
+ }
+}
+
+impl<T> Clone for OnDemand<T> {
+ fn clone(&self) -> Self {
+ OnDemand {
+ get: self.get.clone(),
+ }
+ }
+}
+
+impl<T> std::fmt::Debug for OnDemand<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(
+ f,
+ "{} {{ {} }}",
+ std::any::type_name::<Self>(),
+ if self.get.borrow().is_none() {
+ "not registered"
+ } else {
+ "registered"
+ }
+ )
+ }
+}
+
+impl<T> OnDemand<T> {
+ /// Reads the current value.
+ pub fn read(&self, value: &mut T) {
+ match &*self.get.borrow() {
+ None => {
+ // The test UI doesn't always register OnDemand getters (only on a per-test basis),
+ // so don't panic otherwise the tests will fail unnecessarily.
+ #[cfg(not(test))]
+ panic!("OnDemand not registered by renderer")
+ }
+ Some(f) => f(value),
+ }
+ }
+
+ /// Get a copy of the current value.
+ pub fn get(&self) -> T
+ where
+ T: Default,
+ {
+ let mut r = T::default();
+ self.read(&mut r);
+ r
+ }
+
+ /// Register the function to use when getting the value.
+ pub fn register(&self, f: impl Fn(&mut T) + 'static) {
+ *self.get.borrow_mut() = Some(Box::new(f));
+ }
+}
+
+/// A UI element property.
+///
+/// Properties support static and dynamic value bindings.
+/// * `T` can be converted to static bindings.
+/// * `Synchronized<T>` can be converted to dynamic bindings which will be updated
+/// bidirectionally.
+/// * `OnDemand<T>` can be converted to dynamic bindings which can be queried on an as-needed
+/// basis.
+#[derive(Clone, Debug)]
+pub enum Property<T> {
+ Static(T),
+ Binding(Synchronized<T>),
+ ReadOnly(OnDemand<T>),
+}
+
+#[cfg(test)]
+impl<T: Clone + Default + 'static> Property<T> {
+ pub fn set(&self, value: T) {
+ match self {
+ Property::Static(_) => panic!("cannot set static property"),
+ Property::Binding(s) => *s.borrow_mut() = value,
+ Property::ReadOnly(o) => o.register(move |v| *v = value.clone()),
+ }
+ }
+
+ pub fn get(&self) -> T {
+ match self {
+ Property::Static(v) => v.clone(),
+ Property::Binding(s) => s.borrow().clone(),
+ Property::ReadOnly(o) => o.get(),
+ }
+ }
+}
+
+impl<T: Default> Default for Property<T> {
+ fn default() -> Self {
+ Property::Static(Default::default())
+ }
+}
+
+impl<T> From<T> for Property<T> {
+ fn from(value: T) -> Self {
+ Property::Static(value)
+ }
+}
+
+impl<T> From<&Synchronized<T>> for Property<T> {
+ fn from(value: &Synchronized<T>) -> Self {
+ Property::Binding(value.clone())
+ }
+}
+
+impl<T> From<&OnDemand<T>> for Property<T> {
+ fn from(value: &OnDemand<T>) -> Self {
+ Property::ReadOnly(value.clone())
+ }
+}
+
+/// A mutable Value reference.
+///
+/// When dropped, the Value's change event will fire (_after_ demoting the RefMut to a Ref).
+pub struct ValueRefMut<'a, T> {
+ value: std::mem::ManuallyDrop<std::cell::RefMut<'a, T>>,
+ inner: &'a SynchronizedInner<T>,
+}
+
+impl<T> std::ops::Deref for ValueRefMut<'_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &*self.value
+ }
+}
+
+impl<T> std::ops::DerefMut for ValueRefMut<'_, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut *self.value
+ }
+}
+
+impl<T> Drop for ValueRefMut<'_, T> {
+ fn drop(&mut self) {
+ unsafe { std::mem::ManuallyDrop::drop(&mut self.value) };
+ self.inner.change.fire(&*self.inner.current.borrow());
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
+
+ #[derive(Default, Clone)]
+ struct Trace {
+ count: Rc<AtomicUsize>,
+ }
+
+ impl Trace {
+ fn inc(&self) {
+ self.count.fetch_add(1, Relaxed);
+ }
+
+ fn set(&self, v: usize) {
+ self.count.store(v, Relaxed);
+ }
+
+ fn count(&self) -> usize {
+ self.count.load(Relaxed)
+ }
+ }
+
+ #[test]
+ fn event() {
+ let t1 = Trace::default();
+ let t2 = Trace::default();
+ let evt = Event::default();
+ evt.subscribe(cc! { (t1) move |x| {
+ assert!(x == &42);
+ t1.inc()
+ }});
+ evt.fire(&42);
+ assert_eq!(t1.count(), 1);
+ evt.subscribe(cc! { (t2) move |_| t2.inc() });
+ evt.fire(&42);
+ assert_eq!(t1.count(), 2);
+ assert_eq!(t2.count(), 1);
+ }
+
+ #[test]
+ fn synchronized() {
+ let t1 = Trace::default();
+ let s = Synchronized::<usize>::default();
+ assert_eq!(*s.borrow(), 0);
+
+ s.on_change(cc! { (t1) move |v| t1.set(*v) });
+ {
+ let mut s_ref = s.borrow_mut();
+ *s_ref = 41;
+ // Changes should only occur when the ref is dropped
+ assert_eq!(t1.count(), 0);
+ *s_ref = 42;
+ }
+ assert_eq!(t1.count(), 42);
+ assert_eq!(*s.borrow(), 42);
+ }
+
+ #[test]
+ fn ondemand() {
+ let t1 = Trace::default();
+ let d = OnDemand::<usize>::default();
+ d.register(|v| *v = 42);
+ {
+ let mut v = 0;
+ d.read(&mut v);
+ assert_eq!(v, 42);
+ }
+ d.register(|v| *v = 10);
+ assert_eq!(d.get(), 10);
+
+ t1.inc();
+ d.register(cc! { (t1) move |v| *v = t1.count() });
+ assert_eq!(d.get(), 1);
+ t1.set(42);
+ assert_eq!(d.get(), 42);
+ }
+}