Skip to main content

inset_services/
restoration.rs

1//! Flutter counterpart: `services/restoration.dart`.
2
3use std::fmt::{self, Debug};
4use std::rc::Rc;
5
6use indexmap::{IndexMap, IndexSet};
7pub use inset_embedder::{RestorationData, RestorationMap, RestorationUpdate};
8use inset_foundation::{App, ChangeNotifier, ChangeNotifierData, Handle};
9use inset_scheduler::{FrameCallback, SchedulerBinding};
10
11/// Manages the restoration data in the framework and synchronizes it with the host.
12///
13/// Restoration data can be serialized out and - at a later point in time - be
14/// used to restore the application to the previous state described by the
15/// serialized data. Mobile operating systems use the concept of state
16/// restoration to provide the illusion that apps continue to run in the
17/// background forever: after an app has been backgrounded, the user can always
18/// return to it and find it in the same state. In practice, the operating
19/// system may, however, terminate the app to free resources for other apps
20/// running in the foreground. Before that happens, the app gets a chance to
21/// serialize out its restoration data. When the user navigates back to the
22/// backgrounded app, it is restarted and the serialized restoration data is
23/// provided to it again. Ideally, the app will use that data to restore itself
24/// to the same state it was in when the user backgrounded the app.
25///
26/// Restoration data is organized in a tree of [`RestorationBucket`]s which is
27/// rooted in the [`root_bucket`](Self::root_bucket). All information that the
28/// application needs to restore its current state must be stored in a bucket in
29/// this hierarchy. To store data in the hierarchy, entities must claim ownership
30/// of a child bucket from a parent bucket (which may be the root bucket provided
31/// by this [`RestorationManager`]). The owner of a bucket may store arbitrary
32/// [`RestorationData`] in the bucket. The values are stored in the bucket under a
33/// given restoration ID as key. A restoration ID is a string that must be unique
34/// within a given bucket. To access the stored value again during state
35/// restoration, the same restoration ID must be provided again. The owner of the
36/// bucket may also make the bucket available to other entities so that they can
37/// claim child buckets from it for their own restoration needs. Within a bucket,
38/// child buckets are also identified by unique restoration IDs. The restoration
39/// ID must be provided when claiming a child bucket.
40///
41/// When restoration data is provided to the [`RestorationManager`] (e.g. after
42/// the application relaunched when foregrounded again), the bucket hierarchy
43/// with all the data stored in it is restored. Entities can retrieve the data
44/// again by using the same restoration IDs that they originally used to store
45/// the data.
46///
47/// In addition to providing restoration data when the app is launched,
48/// restoration data may also be provided to a running app to restore it to a
49/// previous state (e.g. when the user hits the back/forward button in the web
50/// browser). When this happens, the [`RestorationManager`] notifies its listeners
51/// (added via [`add_listener`]) that a new root bucket is available. In response
52/// to the notification, listeners must stop using the old bucket and restore
53/// their state from the information in the new root bucket.
54///
55/// Some platforms restrict the size of the restoration data. Therefore, the
56/// data stored in the buckets should be as small as possible while still
57/// allowing the app to restore its current state from it. Data that can be
58/// retrieved from other services (e.g. a database or a web server) should not
59/// be included in the restoration data. Instead, a small identifier (e.g. a
60/// UUID, database record number, or resource locator) should be stored that can
61/// be used to retrieve the data again from its original source during state
62/// restoration.
63///
64/// The [`RestorationManager`] sends a copy of the bucket hierarchy over to the
65/// host at the end of a frame in which the data in the hierarchy or its shape
66/// has changed. The host caches the data until the operating system needs it.
67/// The application is responsible for keeping the data in the bucket always
68/// up-to-date to reflect its current state.
69///
70/// ## Discussion
71///
72/// Due to the threading model of the platforms Flutter runs on, restoration data
73/// must be stored in the buckets proactively as described above. When the
74/// operating system asks for the restoration data, it will do so on the platform
75/// thread expecting a synchronous response. To avoid the risk of deadlocks, the
76/// platform thread cannot block and call into the thread running the application
77/// to retrieve the restoration data. For this reason, the [`RestorationManager`]
78/// always sends the latest copy of the restoration data over to the host
79/// whenever it changes. That way, the restoration data is always ready to go on
80/// the platform thread when the operating system needs it.
81///
82/// Dart's `ServicesBinding.restorationManager` is [`instance`](Self::instance),
83/// the App's singleton.
84///
85/// See also:
86///
87///  * [`RestorationBucket`], which make up the restoration data hierarchy.
88///
89/// [`add_listener`]: inset_foundation::ListenableObject::add_listener
90#[derive(Default)]
91pub struct RestorationManager {
92    change_notifier: ChangeNotifierData,
93    // May be `None` to indicate that restoration is turned off.
94    root_bucket: Option<Handle<RestorationBucket>>,
95    // Dart's `Completer` for the outstanding request to the engine; the host answers
96    // within the call, so only the fact that a request is in flight remains.
97    pending_root_bucket: bool,
98    root_bucket_is_valid: bool,
99    is_replacing: bool,
100    debug_doing_update: bool,
101    serialization_scheduled: bool,
102    buckets_needing_serialization: IndexSet<Handle<RestorationBucket>>,
103}
104
105impl ChangeNotifier for RestorationManager {
106    fn change_notifier_data(&self) -> &ChangeNotifierData {
107        &self.change_notifier
108    }
109
110    fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
111        &mut self.change_notifier
112    }
113}
114
115impl RestorationManager {
116    /// The application's restoration manager.
117    ///
118    /// Dart's `ServicesBinding.instance.restorationManager`.
119    pub fn instance(app: &mut App) -> Handle<RestorationManager> {
120        app.singleton::<RestorationManager>()
121    }
122
123    /// The root of the [`RestorationBucket`] hierarchy containing the restoration
124    /// data.
125    ///
126    /// Child buckets can be claimed from this bucket via
127    /// [`RestorationBucket::claim_child`]. If the [`RestorationManager`] has been
128    /// asked to restore the application to a previous state, these buckets will
129    /// contain the previously stored data. Otherwise the root bucket (and all
130    /// children claimed from it) will be empty.
131    ///
132    /// The [`RestorationManager`] informs its listeners when the value returned by
133    /// this getter changes. This happens when new restoration data has been
134    /// provided to the [`RestorationManager`] to restore the application to a
135    /// different state. In response to the notification, listeners must stop using
136    /// the old root bucket and obtain the new one via this getter (it will have
137    /// been updated to return the new bucket just before the listeners are
138    /// notified).
139    ///
140    /// The restoration data describing the current bucket hierarchy is retrieved
141    /// from the host the first time the root bucket is accessed via this getter.
142    ///
143    /// Returns `None` if state restoration is currently turned off.
144    pub fn root_bucket(self: Handle<Self>, app: &mut App) -> Option<Handle<RestorationBucket>> {
145        if app.get(self).root_bucket_is_valid {
146            return app.get(self).root_bucket;
147        }
148        if !app.get(self).pending_root_bucket {
149            app.get_mut(self).pending_root_bucket = true;
150            self.get_root_bucket_from_engine(app);
151        }
152        app.get(self).root_bucket
153    }
154
155    /// Returns true for the frame after the root bucket has been replaced with a
156    /// new non-null bucket.
157    ///
158    /// When true, entities should forget their current state and restore
159    /// their state according to the information in the new root bucket.
160    ///
161    /// The [`RestorationManager`] informs its listeners when this flag changes from
162    /// false to true.
163    pub fn is_replacing(self: Handle<Self>, app: &App) -> bool {
164        app.get(self).is_replacing
165    }
166
167    fn get_root_bucket_from_engine(self: Handle<Self>, app: &mut App) {
168        let config = app
169            .platform()
170            .restoration()
171            .and_then(|restoration| restoration.get());
172        if !app.get(self).pending_root_bucket {
173            // The restoration data was obtained via other means (e.g. by calling
174            // `handle_restoration_update_from_engine` while the request to the host was
175            // outstanding). Ignore the host's response.
176            return;
177        }
178        debug_assert!(app.get(self).root_bucket.is_none());
179        self.parse_and_handle_restoration_update_from_engine(app, config);
180    }
181
182    fn parse_and_handle_restoration_update_from_engine(
183        self: Handle<Self>,
184        app: &mut App,
185        update: Option<RestorationUpdate>,
186    ) {
187        let (enabled, data) = match update {
188            Some(update) => (update.enabled, update.data),
189            None => (false, None),
190        };
191        self.handle_restoration_update_from_engine(app, enabled, data);
192    }
193
194    /// Parses the restoration information obtained from the host.
195    ///
196    /// The `enabled` parameter indicates whether the host wants to receive
197    /// restoration data. When `enabled` is false, state restoration is turned
198    /// off and the root bucket is set to `None`. When `enabled` is true, the
199    /// provided restoration `data` becomes the new root bucket. If `data` is
200    /// `None`, an empty root bucket is instantiated.
201    ///
202    /// A host may call this method at any time to inject restoration data
203    /// (obtained e.g. from [`send_to_engine`](Self::send_to_engine)) into the
204    /// [`RestorationManager`], which is what Flutter's `push` message on
205    /// `SystemChannels.restoration` does. When the method is called before the
206    /// root bucket is accessed, [`root_bucket`](Self::root_bucket) answers without
207    /// asking the host.
208    pub fn handle_restoration_update_from_engine(
209        self: Handle<Self>,
210        app: &mut App,
211        enabled: bool,
212        data: Option<RestorationMap>,
213    ) {
214        debug_assert!(enabled || data.is_none());
215
216        let is_replacing = app.get(self).root_bucket_is_valid && enabled;
217        app.get_mut(self).is_replacing = is_replacing;
218        if is_replacing {
219            SchedulerBinding::add_post_frame_callback(
220                app,
221                FrameCallback::new(move |app, _| app.get_mut(self).is_replacing = false),
222                None,
223            );
224        }
225
226        let old_root = app.get(self).root_bucket;
227        let new_root = enabled.then(|| RestorationBucket::root(app, self, data));
228        let this = app.get_mut(self);
229        this.root_bucket = new_root;
230        this.root_bucket_is_valid = true;
231        this.pending_root_bucket = false;
232
233        if new_root != old_root {
234            self.notify_listeners(app);
235            if let Some(old_root) = old_root {
236                old_root.dispose(app);
237            }
238        }
239    }
240
241    /// Sends the provided restoration data to the host.
242    ///
243    /// The `encoded_data` describes the entire bucket hierarchy that makes up the
244    /// current restoration data.
245    pub fn send_to_engine(self: Handle<Self>, app: &mut App, encoded_data: RestorationMap) {
246        if let Some(restoration) = app.platform().restoration() {
247            restoration.put(encoded_data);
248        }
249    }
250
251    /// Called by a [`RestorationBucket`] to request serialization for that bucket.
252    ///
253    /// This method is called by a bucket in the hierarchy whenever the data
254    /// in it or the shape of the hierarchy has changed.
255    ///
256    /// Calling this is a no-op when the bucket is already scheduled for
257    /// serialization.
258    pub fn schedule_serialization_for(
259        self: Handle<Self>,
260        app: &mut App,
261        bucket: Handle<RestorationBucket>,
262    ) {
263        debug_assert!(app.get(bucket).manager == Some(self));
264        debug_assert!(!app.get(self).debug_doing_update);
265        app.get_mut(self)
266            .buckets_needing_serialization
267            .insert(bucket);
268        if !app.get(self).serialization_scheduled {
269            app.get_mut(self).serialization_scheduled = true;
270            SchedulerBinding::add_post_frame_callback(
271                app,
272                FrameCallback::new(move |app, _| self.do_serialization(app)),
273                None,
274            );
275        }
276    }
277
278    /// Called by a [`RestorationBucket`] to unschedule a request for serialization.
279    ///
280    /// This method is called by a bucket in the hierarchy whenever it no longer
281    /// needs to be serialized (e.g. because the bucket got disposed).
282    ///
283    /// It is safe to call this even when the bucket wasn't scheduled for
284    /// serialization before.
285    pub fn unschedule_serialization_for(
286        self: Handle<Self>,
287        app: &mut App,
288        bucket: Handle<RestorationBucket>,
289    ) {
290        debug_assert!(app.get(bucket).manager == Some(self));
291        debug_assert!(!app.get(self).debug_doing_update);
292        app.get_mut(self)
293            .buckets_needing_serialization
294            .shift_remove(&bucket);
295    }
296
297    fn do_serialization(self: Handle<Self>, app: &mut App) {
298        if !app.get(self).serialization_scheduled {
299            return;
300        }
301        if cfg!(debug_assertions) {
302            app.get_mut(self).debug_doing_update = true;
303        }
304        app.get_mut(self).serialization_scheduled = false;
305
306        let buckets = std::mem::take(&mut app.get_mut(self).buckets_needing_serialization);
307        for bucket in buckets {
308            bucket.finalize(app);
309        }
310        let root = app
311            .get(self)
312            .root_bucket
313            .expect("a bucket that scheduled serialization is in the root bucket's hierarchy");
314        let data = root.with_raw_data(app, |data| data.clone());
315        self.send_to_engine(app, data);
316
317        if cfg!(debug_assertions) {
318            app.get_mut(self).debug_doing_update = false;
319        }
320    }
321
322    /// Called to manually flush the restoration data to the host.
323    ///
324    /// A change in restoration data is usually accompanied by scheduling a frame
325    /// (because the restoration data is modified inside a `State::set_state` call,
326    /// because it is usually something that affects the interface). Restoration
327    /// data is automatically flushed to the host at the end of a frame. As a
328    /// result, it is uncommon to need to call this method directly. However, if
329    /// restoration data is changed without triggering a frame, this method must
330    /// be called to ensure that the updated restoration data is sent to the
331    /// host in a timely manner. An example of such a use case is the scrollable,
332    /// where the final scroll offset after a scroll activity finishes is
333    /// determined between frames without scheduling a new frame.
334    ///
335    /// Calling this method is a no-op if a frame is already scheduled. In that
336    /// case, the restoration data will be flushed to the host at the end of
337    /// that frame. If this method is called and no frame is scheduled, the
338    /// current restoration data is directly sent to the host.
339    pub fn flush_data(self: Handle<Self>, app: &mut App) {
340        debug_assert!(!app.get(self).debug_doing_update);
341        if SchedulerBinding::has_scheduled_frame(app) {
342            return;
343        }
344        self.do_serialization(app);
345        debug_assert!(!app.get(self).serialization_scheduled);
346    }
347
348    /// Discards any resources used by the manager, and the root bucket with them.
349    pub fn dispose(self: Handle<Self>, app: &mut App) {
350        if let Some(root_bucket) = app.get(self).root_bucket {
351            root_bucket.dispose(app);
352        }
353        app.get_mut(self).change_notifier.dispose();
354    }
355}
356
357const CHILDREN_MAP_KEY: &str = "c";
358const VALUES_MAP_KEY: &str = "v";
359
360/// A restoration ID as the raw data maps key it.
361fn data_key(restoration_id: &str) -> RestorationData {
362    RestorationData::String(restoration_id.to_string())
363}
364
365/// Dart's `_rawData.putIfAbsent(key, () => <Object?, Object?>{})`.
366fn sub_map<'a>(data: &'a mut RestorationMap, key: &str) -> &'a mut RestorationMap {
367    data.entry(data_key(key))
368        .or_insert_with(|| RestorationData::Map(RestorationMap::new()))
369        .as_map_mut()
370        .expect("the values and children entries of raw data are maps")
371}
372
373/// Where a bucket's raw map lives.
374///
375/// Dart's child bucket holds the very map its parent stores under its restoration ID in
376/// `_rawChildren`; a Rust value has one owner, so a bucket whose parent stores it reads
377/// and writes its map through that parent.
378enum RawData {
379    Own(RestorationMap),
380    InParent,
381}
382
383/// A [`RestorationBucket`] holds pieces of the restoration data that a part of
384/// the application needs to restore its state.
385///
386/// For a general overview of how state restoration works, see the
387/// [`RestorationManager`].
388///
389/// [`RestorationBucket`]s are organized in a tree that is rooted in
390/// [`RestorationManager::root_bucket`] and managed by a [`RestorationManager`].
391/// The tree is serializable and must contain all the data an application needs
392/// to restore its current state at a later point in time.
393///
394/// A [`RestorationBucket`] stores restoration data as key-value pairs. The key is
395/// a string representing a restoration ID that identifies a piece of data
396/// uniquely within a bucket. The value is any [`RestorationData`]. Furthermore, a
397/// [`RestorationBucket`] may have child buckets, which are identified within their
398/// parent via a unique restoration ID as well.
399///
400/// During state restoration, the data previously stored in the
401/// [`RestorationBucket`] hierarchy will be made available again to the
402/// application to restore it to the state it had when the data was collected.
403/// State restoration to a previous state may happen when the app is launched
404/// (e.g. after it has been terminated gracefully while running in the
405/// background) or after the app has already been running for a while.
406///
407/// ## Lifecycle
408///
409/// A [`RestorationBucket`] is rarely instantiated directly via its constructors.
410/// Instead, when an entity wants to store data in or retrieve data from a
411/// restoration bucket, it typically obtains a child bucket from a parent by
412/// calling [`claim_child`](Self::claim_child). If no parent is available,
413/// [`RestorationManager::root_bucket`] may be used as a parent. When claiming a
414/// child, the claimer must provide the restoration ID of the child it would
415/// like to own. A child bucket with a given restoration ID can at most have
416/// one owner. If another owner tries to claim a bucket with the same ID from
417/// the same parent, an error is raised (see discussion in
418/// [`claim_child`](Self::claim_child)). The restoration IDs that a given owner
419/// uses to claim a child (and to store data in that child, see below) must be
420/// stable across app launches to ensure that after the app restarts the owner can
421/// retrieve the same data again that it stored during a previous run.
422///
423/// Per convention, the owner of the bucket has exclusive access to the values
424/// stored in the bucket. It can read, add, modify, and remove values via the
425/// [`read`](Self::read), [`write`](Self::write), and [`remove`](Self::remove)
426/// methods. In general, the owner should store all the data in the bucket that it
427/// needs to restore its current state. If its current state changes, the data in
428/// the bucket must be updated. At the same time, the data in the bucket should be
429/// kept to a minimum. For example, for data that can be retrieved from other
430/// sources (like a database or web service) only enough information (e.g. an ID
431/// or resource locator) to re-obtain that data should be stored in the bucket. In
432/// addition to managing the data in a bucket, an owner may also make the bucket
433/// available to other entities so they can claim child buckets from it via
434/// [`claim_child`](Self::claim_child) for their own restoration needs.
435///
436/// The bucket returned by [`claim_child`](Self::claim_child) may either contain
437/// state information that the owner had previously (e.g. during a previous run of
438/// the application) stored in it or it may be empty. If the bucket contains data,
439/// the owner is expected to restore its state with the information previously
440/// stored in the bucket. If the bucket is empty, it may initialize itself to
441/// default values.
442///
443/// When the data stored in a bucket is no longer needed to restore the
444/// application to its current state (e.g. because the owner of the bucket is no
445/// longer shown on screen), the bucket must be [`dispose`](Self::dispose)d. This
446/// will remove all information stored in the bucket from the app's restoration
447/// data and that information will not be available again when the application is
448/// restored to this state in the future.
449pub struct RestorationBucket {
450    manager: Option<Handle<RestorationManager>>,
451    parent: Option<Handle<RestorationBucket>>,
452    raw_data: RawData,
453    restoration_id: String,
454    // The restoration IDs and associated buckets of children that have been
455    // claimed via `claim_child`.
456    claimed_children: IndexMap<String, Handle<RestorationBucket>>,
457    // Newly created child buckets whose restoration ID is still in use, see
458    // comment in `claim_child` for details.
459    children_to_add: IndexMap<String, Vec<Handle<RestorationBucket>>>,
460    needs_serialization: bool,
461    debug_owner: Option<Rc<dyn Debug>>,
462}
463
464impl Debug for RestorationBucket {
465    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466        write!(
467            f,
468            "RestorationBucket(restorationId: {}, owner: {})",
469            self.restoration_id,
470            owner_description(self)
471        )
472    }
473}
474
475/// Dart's `'$debugOwner'`.
476fn owner_description(bucket: &RestorationBucket) -> String {
477    match &bucket.debug_owner {
478        Some(owner) => format!("{owner:?}"),
479        None => "None".to_string(),
480    }
481}
482
483impl RestorationBucket {
484    /// Creates an empty [`RestorationBucket`] to be provided to
485    /// [`adopt_child`](Self::adopt_child) to add it to the bucket hierarchy.
486    ///
487    /// Instantiating a bucket directly is rare, most buckets are created by
488    /// claiming a child from a parent via [`claim_child`](Self::claim_child). If no
489    /// parent bucket is available, [`RestorationManager::root_bucket`] may be used
490    /// as a parent.
491    pub fn empty(
492        app: &mut App,
493        restoration_id: &str,
494        debug_owner: Option<Rc<dyn Debug>>,
495    ) -> Handle<RestorationBucket> {
496        let bucket = RestorationBucket::new(restoration_id, RestorationMap::new(), debug_owner);
497        app.create(bucket)
498    }
499
500    /// Creates the root [`RestorationBucket`] for the provided restoration
501    /// `manager`.
502    ///
503    /// The `raw_data` must either be `None` (in which case an empty bucket will be
504    /// instantiated) or it must be a nested map describing the entire bucket
505    /// hierarchy in the following format:
506    ///
507    /// ```text
508    /// {
509    ///  'v': {  // key-value pairs
510    ///     // * key is a string representation of a restoration ID
511    ///     // * value is any RestorationData
512    ///    '<restoration-id>': <RestorationData>,
513    ///   },
514    ///  'c': {  // child buckets
515    ///    '<restoration-id>': <nested map representing a child bucket>
516    ///   }
517    /// }
518    /// ```
519    ///
520    /// Instantiating a bucket directly is rare, most buckets are created by
521    /// claiming a child from a parent via [`claim_child`](Self::claim_child).
522    pub fn root(
523        app: &mut App,
524        manager: Handle<RestorationManager>,
525        raw_data: Option<RestorationMap>,
526    ) -> Handle<RestorationBucket> {
527        let debug_owner: Option<Rc<dyn Debug>> =
528            cfg!(debug_assertions).then(|| Rc::new(manager) as Rc<dyn Debug>);
529        let mut bucket = RestorationBucket::new("root", raw_data.unwrap_or_default(), debug_owner);
530        bucket.manager = Some(manager);
531        app.create(bucket)
532    }
533
534    /// Creates a child bucket initialized with the data that the provided
535    /// `parent` has stored under the provided `restoration_id`.
536    ///
537    /// This constructor cannot be used if the `parent` does not have any child
538    /// data stored under the given ID. In that case, create an empty bucket (via
539    /// [`empty`](Self::empty)) and have the parent adopt it via
540    /// [`adopt_child`](Self::adopt_child).
541    ///
542    /// Instantiating a bucket directly is rare, most buckets are created by
543    /// claiming a child from a parent via [`claim_child`](Self::claim_child).
544    pub fn child(
545        app: &mut App,
546        restoration_id: &str,
547        parent: Handle<RestorationBucket>,
548        debug_owner: Option<Rc<dyn Debug>>,
549    ) -> Handle<RestorationBucket> {
550        debug_assert!(parent.with_raw_children(app, |children| {
551            children.contains_key(&data_key(restoration_id))
552        }));
553        let mut bucket = RestorationBucket::new(restoration_id, RestorationMap::new(), debug_owner);
554        bucket.raw_data = RawData::InParent;
555        bucket.manager = app.get(parent).manager;
556        bucket.parent = Some(parent);
557        app.create(bucket)
558    }
559
560    fn new(
561        restoration_id: &str,
562        raw_data: RestorationMap,
563        debug_owner: Option<Rc<dyn Debug>>,
564    ) -> RestorationBucket {
565        RestorationBucket {
566            manager: None,
567            parent: None,
568            raw_data: RawData::Own(raw_data),
569            restoration_id: restoration_id.to_string(),
570            claimed_children: IndexMap::new(),
571            children_to_add: IndexMap::new(),
572            needs_serialization: false,
573            debug_owner: cfg!(debug_assertions).then_some(debug_owner).flatten(),
574        }
575    }
576
577    /// The owner of the bucket that was provided when the bucket was claimed via
578    /// [`claim_child`](Self::claim_child).
579    ///
580    /// The value is used in error messages. Accessing the value is only valid
581    /// in debug mode, otherwise it will return `None`.
582    pub fn debug_owner(self: Handle<Self>, app: &App) -> Option<Rc<dyn Debug>> {
583        debug_assert!(self.debug_assert_not_disposed(app));
584        app.get(self).debug_owner.clone()
585    }
586
587    /// Returns true when entities processing this bucket should restore their
588    /// state from the information in the bucket (e.g. via [`read`](Self::read) and
589    /// [`claim_child`](Self::claim_child)) instead of copying their current state
590    /// information into the bucket (e.g. via [`write`](Self::write) and
591    /// [`adopt_child`](Self::adopt_child)).
592    ///
593    /// This flag is true for the frame after the [`RestorationManager`] has been
594    /// instructed to restore the application from newly provided restoration
595    /// data.
596    pub fn is_replacing(self: Handle<Self>, app: &App) -> bool {
597        app.get(self)
598            .manager
599            .is_some_and(|manager| manager.is_replacing(app))
600    }
601
602    /// The restoration ID under which the bucket is currently stored in the
603    /// parent of this bucket (or wants to be stored if it is currently
604    /// parent-less).
605    pub fn restoration_id(self: Handle<Self>, app: &App) -> &str {
606        debug_assert!(self.debug_assert_not_disposed(app));
607        &app.get(self).restoration_id
608    }
609
610    /// The bucket's own raw map, wherever it currently lives.
611    fn with_raw_data<R>(
612        self: Handle<Self>,
613        app: &mut App,
614        action: impl FnOnce(&mut RestorationMap) -> R,
615    ) -> R {
616        let mut ancestry = Vec::new();
617        let mut owner = self;
618        while matches!(app.get(owner).raw_data, RawData::InParent) {
619            ancestry.push(data_key(&app.get(owner).restoration_id));
620            owner = app
621                .get(owner)
622                .parent
623                .expect("a bucket whose map lives in its parent has one");
624        }
625        let mut map = match &mut app.get_mut(owner).raw_data {
626            RawData::Own(map) => map,
627            RawData::InParent => unreachable!("the walk ends at the bucket that owns the map"),
628        };
629        for restoration_id in ancestry.iter().rev() {
630            map = map
631                .get_mut(&data_key(CHILDREN_MAP_KEY))
632                .and_then(RestorationData::as_map_mut)
633                .and_then(|children| children.get_mut(restoration_id))
634                .and_then(RestorationData::as_map_mut)
635                .expect("a stored bucket's map is under its parent's children");
636        }
637        action(map)
638    }
639
640    /// Maps a restoration ID to the raw map representation of a child bucket.
641    fn with_raw_children<R>(
642        self: Handle<Self>,
643        app: &mut App,
644        action: impl FnOnce(&mut RestorationMap) -> R,
645    ) -> R {
646        self.with_raw_data(app, |data| action(sub_map(data, CHILDREN_MAP_KEY)))
647    }
648
649    /// Maps a restoration ID to a value that is stored in this bucket.
650    fn with_raw_values<R>(
651        self: Handle<Self>,
652        app: &mut App,
653        action: impl FnOnce(&mut RestorationMap) -> R,
654    ) -> R {
655        self.with_raw_data(app, |data| action(sub_map(data, VALUES_MAP_KEY)))
656    }
657
658    // Get and store values.
659
660    /// Returns the value that is currently stored in the bucket under the provided
661    /// `restoration_id`.
662    ///
663    /// Returns `None` if nothing is stored under that ID.
664    ///
665    /// See also:
666    ///
667    ///  * [`write`](Self::write), which stores a value in the bucket.
668    ///  * [`remove`](Self::remove), which removes a value from the bucket.
669    ///  * [`contains`](Self::contains), which checks whether any value is stored
670    ///    under a given restoration ID.
671    pub fn read(
672        self: Handle<Self>,
673        app: &mut App,
674        restoration_id: &str,
675    ) -> Option<RestorationData> {
676        debug_assert!(self.debug_assert_not_disposed(app));
677        self.with_raw_values(app, |values| values.get(&data_key(restoration_id)).cloned())
678    }
679
680    /// Stores the provided `value` under the provided `restoration_id` in the
681    /// bucket.
682    ///
683    /// Any value that has previously been stored under that ID is overwritten
684    /// with the new value.
685    ///
686    /// [`RestorationData::Null`] is stored in the bucket as-is. To remove a value,
687    /// use [`remove`](Self::remove).
688    ///
689    /// See also:
690    ///
691    ///  * [`read`](Self::read), which retrieves a stored value from the bucket.
692    ///  * [`remove`](Self::remove), which removes a value from the bucket.
693    ///  * [`contains`](Self::contains), which checks whether any value is stored
694    ///    under a given restoration ID.
695    pub fn write(
696        self: Handle<Self>,
697        app: &mut App,
698        restoration_id: &str,
699        value: impl Into<RestorationData>,
700    ) {
701        debug_assert!(self.debug_assert_not_disposed(app));
702        let value = value.into();
703        let key = data_key(restoration_id);
704        let written = self.with_raw_values(app, |values| {
705            if values.get(&key) != Some(&value) || !values.contains_key(&key) {
706                values.insert(key, value);
707                return true;
708            }
709            false
710        });
711        if written {
712            self.mark_needs_serialization(app);
713        }
714    }
715
716    /// Deletes the value currently stored under the provided `restoration_id` from
717    /// the bucket.
718    ///
719    /// The value removed from the bucket is returned. If no value was stored under
720    /// that ID, `None` is returned.
721    ///
722    /// See also:
723    ///
724    ///  * [`read`](Self::read), which retrieves a stored value from the bucket.
725    ///  * [`write`](Self::write), which stores a value in the bucket.
726    ///  * [`contains`](Self::contains), which checks whether any value is stored
727    ///    under a given restoration ID.
728    pub fn remove(
729        self: Handle<Self>,
730        app: &mut App,
731        restoration_id: &str,
732    ) -> Option<RestorationData> {
733        debug_assert!(self.debug_assert_not_disposed(app));
734        let key = data_key(restoration_id);
735        let (result, needs_update) = self.with_raw_data(app, |data| {
736            let values = sub_map(data, VALUES_MAP_KEY);
737            let needs_update = values.contains_key(&key);
738            let result = values.shift_remove(&key);
739            let emptied = values.is_empty();
740            if emptied {
741                data.shift_remove(&data_key(VALUES_MAP_KEY));
742            }
743            (result, needs_update)
744        });
745        if needs_update {
746            self.mark_needs_serialization(app);
747        }
748        result
749    }
750
751    /// Checks whether a value is stored in the bucket under the provided
752    /// `restoration_id`.
753    ///
754    /// See also:
755    ///
756    ///  * [`read`](Self::read), which retrieves a stored value from the bucket.
757    ///  * [`write`](Self::write), which stores a value in the bucket.
758    ///  * [`remove`](Self::remove), which removes a value from the bucket.
759    pub fn contains(self: Handle<Self>, app: &mut App, restoration_id: &str) -> bool {
760        debug_assert!(self.debug_assert_not_disposed(app));
761        self.with_raw_values(app, |values| values.contains_key(&data_key(restoration_id)))
762    }
763
764    // Child management.
765
766    /// Claims ownership of the child with the provided `restoration_id` from this
767    /// bucket.
768    ///
769    /// If the application is getting restored to a previous state, the bucket
770    /// will contain all the data that was previously stored in the bucket.
771    /// Otherwise, an empty bucket is returned.
772    ///
773    /// The claimer of the bucket is expected to use the data stored in the bucket
774    /// to restore itself to its previous state described by the data in the
775    /// bucket. If the bucket is empty, it should initialize itself to default
776    /// values. Whenever the information that the claimer needs to restore its
777    /// state changes, the data in the bucket should be updated to reflect that.
778    ///
779    /// A child bucket with a given `restoration_id` can only have one owner. If
780    /// another owner claims a child bucket with the same `restoration_id` the end
781    /// of the current frame panics in debug mode unless the previous owner has
782    /// either deleted its bucket by calling [`dispose`](Self::dispose) or has moved
783    /// it to a new parent via [`adopt_child`](Self::adopt_child).
784    ///
785    /// When the returned bucket is no longer needed, it must be
786    /// [`dispose`](Self::dispose)d to delete the information stored in it from the
787    /// app's restoration data.
788    pub fn claim_child(
789        self: Handle<Self>,
790        app: &mut App,
791        restoration_id: &str,
792        debug_owner: Option<Rc<dyn Debug>>,
793    ) -> Handle<RestorationBucket> {
794        debug_assert!(self.debug_assert_not_disposed(app));
795        // There are three cases to consider:
796        // 1. Claiming an ID that has already been claimed.
797        // 2. Claiming an ID that doesn't yet exist in the raw children.
798        // 3. Claiming an ID that does exist in the raw children and hasn't been
799        //    claimed yet.
800        // If an ID has already been claimed (case 1) the current owner may give up
801        // that ID later this frame and it can be re-used. In anticipation of the
802        // previous owner's surrender of the id, we return an empty bucket for this
803        // new claim and check in `debug_assert_integrity` that at the end of the
804        // frame the old owner actually did surrender the id.
805        // Case 2 also requires the creation of a new empty bucket.
806        // In case 3 we create a new bucket wrapping the existing raw child data.
807
808        // Case 1+2: Adopt and return an empty bucket.
809        let claimed = app.get(self).claimed_children.contains_key(restoration_id);
810        let stored = self.with_raw_children(app, |children| {
811            children.contains_key(&data_key(restoration_id))
812        });
813        if claimed || !stored {
814            let child = RestorationBucket::empty(app, restoration_id, debug_owner);
815            self.adopt_child(app, child);
816            return child;
817        }
818
819        // Case 3: Return a bucket wrapping the existing data.
820        let child = RestorationBucket::child(app, restoration_id, self, debug_owner);
821        app.get_mut(self)
822            .claimed_children
823            .insert(restoration_id.to_string(), child);
824        child
825    }
826
827    /// Adopts the provided `child` bucket.
828    ///
829    /// The `child` will be dropped from its old parent, if it had one.
830    ///
831    /// The `child` is stored under its
832    /// [`restoration_id`](Self::restoration_id) in this bucket. If this bucket
833    /// already contains a child bucket under the same ID, the owner of that
834    /// existing bucket must give it up (e.g. by moving the child bucket to a
835    /// different parent or by disposing it) before the end of the current frame.
836    /// Otherwise the illegal use of duplicated restoration IDs panics in debug
837    /// mode.
838    ///
839    /// No-op if the provided bucket is already a child of this bucket.
840    pub fn adopt_child(self: Handle<Self>, app: &mut App, child: Handle<RestorationBucket>) {
841        debug_assert!(self.debug_assert_not_disposed(app));
842        if app.get(child).parent != Some(self) {
843            if let Some(old_parent) = app.get(child).parent {
844                old_parent.remove_child_data(app, child);
845            }
846            app.get_mut(child).parent = Some(self);
847            self.add_child_data(app, child);
848            if app.get(child).manager != app.get(self).manager {
849                self.recursively_update_manager(app, child);
850            }
851        }
852        debug_assert!(app.get(child).parent == Some(self));
853        debug_assert!(app.get(child).manager == app.get(self).manager);
854    }
855
856    fn drop_child(self: Handle<Self>, app: &mut App, child: Handle<RestorationBucket>) {
857        debug_assert!(app.get(child).parent == Some(self));
858        self.remove_child_data(app, child);
859        app.get_mut(child).parent = None;
860        if app.get(child).manager.is_some() {
861            child.update_manager(app, None);
862            child.visit_children(app, |app, grandchild| {
863                self.recursively_update_manager(app, grandchild);
864            });
865        }
866    }
867
868    fn mark_needs_serialization(self: Handle<Self>, app: &mut App) {
869        if !app.get(self).needs_serialization {
870            app.get_mut(self).needs_serialization = true;
871            if let Some(manager) = app.get(self).manager {
872                manager.schedule_serialization_for(app, self);
873            }
874        }
875    }
876
877    /// Called by the [`RestorationManager`] just before the data of the bucket
878    /// is serialized and sent to the host.
879    pub fn finalize(self: Handle<Self>, app: &mut App) {
880        debug_assert!(self.debug_assert_not_disposed(app));
881        debug_assert!(app.get(self).needs_serialization);
882        app.get_mut(self).needs_serialization = false;
883        debug_assert!(self.debug_assert_integrity(app));
884    }
885
886    fn recursively_update_manager(self: Handle<Self>, app: &mut App, bucket: Handle<Self>) {
887        bucket.update_manager(app, app.get(self).manager);
888        bucket.visit_children(app, |app, child| {
889            self.recursively_update_manager(app, child)
890        });
891    }
892
893    fn update_manager(
894        self: Handle<Self>,
895        app: &mut App,
896        new_manager: Option<Handle<RestorationManager>>,
897    ) {
898        if app.get(self).manager == new_manager {
899            return;
900        }
901        if app.get(self).needs_serialization
902            && let Some(manager) = app.get(self).manager
903        {
904            manager.unschedule_serialization_for(app, self);
905        }
906        app.get_mut(self).manager = new_manager;
907        if app.get(self).needs_serialization && app.get(self).manager.is_some() {
908            app.get_mut(self).needs_serialization = false;
909            self.mark_needs_serialization(app);
910        }
911    }
912
913    fn debug_assert_integrity(self: Handle<Self>, app: &App) -> bool {
914        if !cfg!(debug_assertions) {
915            return true;
916        }
917        let this = app.get(self);
918        if this.children_to_add.is_empty() {
919            return true;
920        }
921        let mut error = vec![
922            "Multiple owners claimed child RestorationBuckets with the same IDs.".to_string(),
923            format!("The following IDs were claimed multiple times from the parent {this:?}:"),
924        ];
925        for (restoration_id, buckets) in &this.children_to_add {
926            debug_assert!(!buckets.is_empty());
927            error.push(format!(" * \"{restoration_id}\" was claimed by:"));
928            for bucket in buckets {
929                error.push(format!("   * {}", owner_description(app.get(*bucket))));
930            }
931            let owner = this
932                .claimed_children
933                .get(restoration_id)
934                .expect("a pending child implies a current owner of the same ID");
935            error.push(format!(
936                "   * {} (current owner)",
937                owner_description(app.get(*owner))
938            ));
939        }
940        panic!("{}", error.join("\n"));
941    }
942
943    fn remove_child_data(self: Handle<Self>, app: &mut App, child: Handle<RestorationBucket>) {
944        debug_assert!(app.get(child).parent == Some(self));
945        let child_id = app.get(child).restoration_id.clone();
946        if app.get_mut(self).claimed_children.shift_remove(&child_id) == Some(child) {
947            child.reclaim_raw_data(app);
948            let pending_child = app
949                .get_mut(self)
950                .children_to_add
951                .get_mut(&child_id)
952                .and_then(Vec::pop);
953            if let Some(to_add) = pending_child {
954                self.finalize_add_child_data(app, to_add);
955                if app.get(self).children_to_add[&child_id].is_empty() {
956                    app.get_mut(self).children_to_add.shift_remove(&child_id);
957                }
958            }
959            let no_children = self.with_raw_children(app, |children| children.is_empty());
960            if no_children {
961                self.with_raw_data(app, |data| data.shift_remove(&data_key(CHILDREN_MAP_KEY)));
962            }
963            self.mark_needs_serialization(app);
964            return;
965        }
966        let Some(pending) = app.get_mut(self).children_to_add.get_mut(&child_id) else {
967            return;
968        };
969        if let Some(index) = pending.iter().position(|bucket| *bucket == child) {
970            pending.remove(index);
971        }
972        if pending.is_empty() {
973            app.get_mut(self).children_to_add.shift_remove(&child_id);
974        }
975    }
976
977    fn add_child_data(self: Handle<Self>, app: &mut App, child: Handle<RestorationBucket>) {
978        debug_assert!(app.get(child).parent == Some(self));
979        let child_id = app.get(child).restoration_id.clone();
980        if app.get(self).claimed_children.contains_key(&child_id) {
981            // Delay addition until the end of the frame in the hopes that the current
982            // owner of the child with the same ID will have given up that child by
983            // then.
984            app.get_mut(self)
985                .children_to_add
986                .entry(child_id)
987                .or_default()
988                .push(child);
989            self.mark_needs_serialization(app);
990            return;
991        }
992        self.finalize_add_child_data(app, child);
993        self.mark_needs_serialization(app);
994    }
995
996    fn finalize_add_child_data(
997        self: Handle<Self>,
998        app: &mut App,
999        child: Handle<RestorationBucket>,
1000    ) {
1001        let child_id = app.get(child).restoration_id.clone();
1002        debug_assert!(!app.get(self).claimed_children.contains_key(&child_id));
1003        debug_assert!(
1004            self.with_raw_children(app, |children| !children.contains_key(&data_key(&child_id)))
1005        );
1006        app.get_mut(self)
1007            .claimed_children
1008            .insert(child_id.clone(), child);
1009        let data = match std::mem::replace(&mut app.get_mut(child).raw_data, RawData::InParent) {
1010            RawData::Own(data) => data,
1011            RawData::InParent => unreachable!("a bucket being stored owns its map"),
1012        };
1013        self.with_raw_children(app, |children| {
1014            children.insert(data_key(&child_id), RestorationData::Map(data));
1015        });
1016    }
1017
1018    /// Dart's `parent._rawChildren.remove(id)`: the map the parent stored comes back to
1019    /// the bucket it belongs to.
1020    fn reclaim_raw_data(self: Handle<Self>, app: &mut App) {
1021        if matches!(app.get(self).raw_data, RawData::Own(_)) {
1022            return;
1023        }
1024        let parent = app
1025            .get(self)
1026            .parent
1027            .expect("a bucket whose map lives in its parent has one");
1028        let key = data_key(&app.get(self).restoration_id);
1029        let data = parent
1030            .with_raw_children(app, |children| children.shift_remove(&key))
1031            .and_then(RestorationData::into_map)
1032            .expect("a stored bucket's map is under its parent's children");
1033        app.get_mut(self).raw_data = RawData::Own(data);
1034    }
1035
1036    fn visit_children(
1037        self: Handle<Self>,
1038        app: &mut App,
1039        visitor: impl Fn(&mut App, Handle<RestorationBucket>),
1040    ) {
1041        let this = app.get(self);
1042        let children: Vec<Handle<RestorationBucket>> = this
1043            .claimed_children
1044            .values()
1045            .copied()
1046            .chain(this.children_to_add.values().flatten().copied())
1047            .collect();
1048        for child in children {
1049            visitor(app, child);
1050        }
1051    }
1052
1053    // Bucket management.
1054
1055    /// Changes the restoration ID under which the bucket is (or will be) stored
1056    /// in its parent to `new_restoration_id`.
1057    ///
1058    /// No-op if the bucket is already stored under the provided ID.
1059    ///
1060    /// If another owner has already claimed a bucket with the provided
1061    /// `new_restoration_id` the end of the current frame panics in debug mode
1062    /// unless the other owner has deleted its bucket by calling
1063    /// [`dispose`](Self::dispose), [`rename`](Self::rename)d it using another ID, or
1064    /// has moved it to a new parent via [`adopt_child`](Self::adopt_child).
1065    pub fn rename(self: Handle<Self>, app: &mut App, new_restoration_id: &str) {
1066        debug_assert!(self.debug_assert_not_disposed(app));
1067        if new_restoration_id == app.get(self).restoration_id {
1068            return;
1069        }
1070        if let Some(parent) = app.get(self).parent {
1071            parent.remove_child_data(app, self);
1072        }
1073        app.get_mut(self).restoration_id = new_restoration_id.to_string();
1074        if let Some(parent) = app.get(self).parent {
1075            parent.add_child_data(app, self);
1076        }
1077    }
1078
1079    /// Deletes the bucket and all the data stored in it from the bucket
1080    /// hierarchy.
1081    ///
1082    /// After [`dispose`](Self::dispose) has been called, the data stored in this
1083    /// bucket and its children are no longer part of the app's restoration data.
1084    /// The data originally stored in the bucket will not be available again when
1085    /// the application is restored to this state in the future. It is up to the
1086    /// owners of the children to either move them (via
1087    /// [`adopt_child`](Self::adopt_child)) to a new parent that is still part of
1088    /// the bucket hierarchy or to [`dispose`](Self::dispose) of them as well.
1089    ///
1090    /// This method must only be called by the object's owner.
1091    pub fn dispose(self: Handle<Self>, app: &mut App) {
1092        debug_assert!(self.debug_assert_not_disposed(app));
1093        self.visit_children(app, |app, child| self.drop_child(app, child));
1094        app.get_mut(self).claimed_children.clear();
1095        app.get_mut(self).children_to_add.clear();
1096        if let Some(parent) = app.get(self).parent {
1097            parent.remove_child_data(app, self);
1098        }
1099        app.get_mut(self).parent = None;
1100        self.update_manager(app, None);
1101        app.destroy(self);
1102    }
1103
1104    fn debug_assert_not_disposed(self: Handle<Self>, app: &App) -> bool {
1105        assert!(
1106            app.contains(self),
1107            "A RestorationBucket was used after being disposed.\n\
1108             Once you have called dispose() on a RestorationBucket, it can no longer be used."
1109        );
1110        true
1111    }
1112}
1113
1114#[cfg(test)]
1115mod tests {
1116    use inset_foundation::AppCell;
1117    use std::cell::{Cell, RefCell};
1118    use std::panic::{AssertUnwindSafe, catch_unwind};
1119    use std::time::Duration;
1120
1121    use inset_embedder::Restoration;
1122    use inset_foundation::{ListenableObject, Listener};
1123    use inset_scheduler::FrameTarget;
1124    use inset_test::{IMPLICIT_VIEW_ID, TestPlatform};
1125
1126    use super::*;
1127
1128    /// A host that answers `Restoration::get` with what it was handed and records every
1129    /// `Restoration::put`.
1130    #[derive(Default)]
1131    struct RecordingRestoration {
1132        stored: RefCell<Option<RestorationUpdate>>,
1133        puts: RefCell<Vec<RestorationMap>>,
1134        gets: Cell<usize>,
1135    }
1136
1137    impl Restoration for RecordingRestoration {
1138        fn get(&self) -> Option<RestorationUpdate> {
1139            self.gets.set(self.gets.get() + 1);
1140            self.stored.borrow().clone()
1141        }
1142
1143        fn put(&self, data: RestorationMap) {
1144            self.puts.borrow_mut().push(data);
1145        }
1146    }
1147
1148    struct Fixture {
1149        cell: Rc<AppCell>,
1150        restoration: Rc<RecordingRestoration>,
1151        manager: Handle<RestorationManager>,
1152    }
1153
1154    impl Fixture {
1155        fn new() -> Fixture {
1156            let restoration = Rc::new(RecordingRestoration::default());
1157            // A view, for a test that schedules a frame.
1158            let platform = TestPlatform::new()
1159                .with_restoration(restoration.clone())
1160                .with_views(&[IMPLICIT_VIEW_ID]);
1161            let cell = AppCell::with_platform(Rc::new(platform));
1162            let mut app = cell.borrow_mut();
1163            let manager = RestorationManager::instance(&mut app);
1164            drop(app);
1165            Fixture {
1166                cell,
1167                restoration,
1168                manager,
1169            }
1170        }
1171
1172        /// A manager whose root bucket holds `data`, as after a restart.
1173        fn restored(data: RestorationMap) -> (Fixture, Handle<RestorationBucket>) {
1174            let mut fixture = Fixture::new();
1175            fixture.push_from_host(true, Some(data));
1176            let root = fixture
1177                .manager
1178                .root_bucket(&mut fixture.cell.borrow_mut())
1179                .expect("restoration is enabled");
1180            (fixture, root)
1181        }
1182
1183        /// Flutter's `_pushDataFromEngine`.
1184        fn push_from_host(&mut self, enabled: bool, data: Option<RestorationMap>) {
1185            self.manager.handle_restoration_update_from_engine(
1186                &mut self.cell.borrow_mut(),
1187                enabled,
1188                data,
1189            );
1190        }
1191
1192        /// Flutter's `MockRestorationManager.doSerialization`: what reached the host.
1193        fn serialize(&mut self) -> Vec<RestorationMap> {
1194            self.manager.flush_data(&mut self.cell.borrow_mut());
1195            self.restoration.puts.borrow_mut().drain(..).collect()
1196        }
1197
1198        /// The data the host would keep, or `None` when nothing was scheduled.
1199        fn serialized(&mut self) -> Option<RestorationMap> {
1200            let mut puts = self.serialize();
1201            assert!(puts.len() <= 1, "one serialization sends one message");
1202            puts.pop()
1203        }
1204
1205        fn pump(&mut self) {
1206            let views = [IMPLICIT_VIEW_ID];
1207            SchedulerBinding::handle_begin_frame(
1208                &mut self.cell.borrow_mut(),
1209                &views,
1210                Some(Duration::ZERO),
1211            );
1212            self.cell.borrow_mut().drain_microtasks();
1213            SchedulerBinding::handle_draw_frame(&mut self.cell.borrow_mut(), &views);
1214            self.cell.borrow_mut().drain_microtasks();
1215        }
1216    }
1217
1218    fn map<const N: usize>(entries: [(&str, RestorationData); N]) -> RestorationMap {
1219        entries
1220            .into_iter()
1221            .map(|(key, value)| (RestorationData::from(key), value))
1222            .collect()
1223    }
1224
1225    /// Flutter's `_createRawDataSet`.
1226    fn raw_data_set() -> RestorationMap {
1227        map([
1228            (
1229                "v",
1230                map([("value1", 10i64.into()), ("value2", "Hello".into())]).into(),
1231            ),
1232            (
1233                "c",
1234                map([(
1235                    "child1",
1236                    map([("v", map([("foo", 22i64.into())]).into())]).into(),
1237                )])
1238                .into(),
1239            ),
1240        ])
1241    }
1242
1243    /// Flutter's `_createEncodedRestorationData2`.
1244    fn other_raw_data_set() -> RestorationMap {
1245        map([
1246            ("v", map([("foo", 33i64.into())]).into()),
1247            (
1248                "c",
1249                map([(
1250                    "childFoo",
1251                    map([("v", map([("bar", "Hello".into())]).into())]).into(),
1252                )])
1253                .into(),
1254            ),
1255        ])
1256    }
1257
1258    /// The value stored down `path`, e.g. `["c", "child1", "v", "foo"]`.
1259    fn at<'a>(data: &'a RestorationMap, path: &[&str]) -> Option<&'a RestorationData> {
1260        let (last, parents) = path.split_last()?;
1261        let mut map = data;
1262        for key in parents {
1263            map = map.get(&RestorationData::from(*key))?.as_map()?;
1264        }
1265        map.get(&RestorationData::from(*last))
1266    }
1267
1268    fn owner(name: &'static str) -> Option<Rc<dyn Debug>> {
1269        Some(Rc::new(name))
1270    }
1271
1272    fn panic_message(error: &Box<dyn std::any::Any + Send>) -> String {
1273        error
1274            .downcast_ref::<String>()
1275            .cloned()
1276            .or_else(|| error.downcast_ref::<&str>().map(|text| text.to_string()))
1277            .unwrap_or_default()
1278    }
1279
1280    #[test]
1281    fn the_root_bucket_comes_from_the_host_and_is_asked_for_once() {
1282        let fixture = Fixture::new();
1283        *fixture.restoration.stored.borrow_mut() = Some(RestorationUpdate {
1284            enabled: true,
1285            data: Some(raw_data_set()),
1286        });
1287
1288        let root = fixture
1289            .manager
1290            .root_bucket(&mut fixture.cell.borrow_mut())
1291            .expect("the host enabled restoration");
1292        assert_eq!(fixture.restoration.gets.get(), 1);
1293        assert_eq!(root.restoration_id(&fixture.cell.borrow()), "root");
1294        assert_eq!(
1295            root.read(&mut fixture.cell.borrow_mut(), "value1"),
1296            Some(10i64.into())
1297        );
1298        assert_eq!(
1299            root.read(&mut fixture.cell.borrow_mut(), "value2"),
1300            Some("Hello".into())
1301        );
1302
1303        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", None);
1304        assert_eq!(
1305            child.read(&mut fixture.cell.borrow_mut(), "foo"),
1306            Some(22i64.into())
1307        );
1308
1309        assert_eq!(
1310            fixture.manager.root_bucket(&mut fixture.cell.borrow_mut()),
1311            Some(root)
1312        );
1313        assert_eq!(
1314            fixture.restoration.gets.get(),
1315            1,
1316            "the host is asked once, then the answer is kept"
1317        );
1318    }
1319
1320    #[test]
1321    fn data_received_before_the_first_access_is_used_without_asking_the_host() {
1322        let mut fixture = Fixture::new();
1323        fixture.push_from_host(true, Some(raw_data_set()));
1324
1325        let root = fixture.manager.root_bucket(&mut fixture.cell.borrow_mut());
1326        assert!(root.is_some());
1327        assert_eq!(fixture.restoration.gets.get(), 0);
1328    }
1329
1330    #[test]
1331    fn new_data_replaces_the_root_bucket_and_notifies_listeners() {
1332        let (mut fixture, root) = Fixture::restored(raw_data_set());
1333        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", None);
1334        assert_eq!(
1335            child.read(&mut fixture.cell.borrow_mut(), "foo"),
1336            Some(22i64.into())
1337        );
1338
1339        let notifications = Rc::new(Cell::new(0));
1340        let counter = Rc::clone(&notifications);
1341        fixture.manager.add_listener(
1342            &mut fixture.cell.borrow_mut(),
1343            Listener::new(move |_app| counter.set(counter.get() + 1)),
1344        );
1345
1346        fixture.push_from_host(true, Some(other_raw_data_set()));
1347        assert_eq!(notifications.get(), 1);
1348
1349        let new_root = fixture
1350            .manager
1351            .root_bucket(&mut fixture.cell.borrow_mut())
1352            .expect("restoration is still enabled");
1353        assert_ne!(new_root, root);
1354        assert!(
1355            !fixture.cell.borrow().contains(root),
1356            "the old root was disposed"
1357        );
1358
1359        child.dispose(&mut fixture.cell.borrow_mut());
1360
1361        assert_eq!(
1362            new_root.read(&mut fixture.cell.borrow_mut(), "foo"),
1363            Some(33i64.into())
1364        );
1365        assert_eq!(
1366            new_root.read(&mut fixture.cell.borrow_mut(), "value1"),
1367            None
1368        );
1369        let new_child = new_root.claim_child(&mut fixture.cell.borrow_mut(), "childFoo", None);
1370        assert_eq!(
1371            new_child.read(&mut fixture.cell.borrow_mut(), "bar"),
1372            Some("Hello".into())
1373        );
1374    }
1375
1376    #[test]
1377    fn there_is_no_root_bucket_while_restoration_is_disabled() {
1378        let mut fixture = Fixture::new();
1379        *fixture.restoration.stored.borrow_mut() = Some(RestorationUpdate {
1380            enabled: false,
1381            data: None,
1382        });
1383        let notifications = Rc::new(Cell::new(0));
1384        let counter = Rc::clone(&notifications);
1385        fixture.manager.add_listener(
1386            &mut fixture.cell.borrow_mut(),
1387            Listener::new(move |_app| counter.set(counter.get() + 1)),
1388        );
1389
1390        assert_eq!(
1391            fixture.manager.root_bucket(&mut fixture.cell.borrow_mut()),
1392            None
1393        );
1394        assert_eq!(notifications.get(), 0);
1395
1396        fixture.push_from_host(true, Some(raw_data_set()));
1397        assert_eq!(notifications.get(), 1);
1398        assert!(
1399            fixture
1400                .manager
1401                .root_bucket(&mut fixture.cell.borrow_mut())
1402                .is_some()
1403        );
1404
1405        fixture.push_from_host(false, None);
1406        assert_eq!(notifications.get(), 2);
1407        assert_eq!(
1408            fixture.manager.root_bucket(&mut fixture.cell.borrow_mut()),
1409            None
1410        );
1411    }
1412
1413    #[test]
1414    fn a_host_that_stores_nothing_leaves_restoration_off() {
1415        let fixture = Fixture::new();
1416        assert_eq!(
1417            fixture.manager.root_bucket(&mut fixture.cell.borrow_mut()),
1418            None
1419        );
1420        assert_eq!(fixture.restoration.gets.get(), 1);
1421    }
1422
1423    #[test]
1424    fn is_replacing_is_true_until_the_end_of_the_next_frame() {
1425        let (mut fixture, root) = Fixture::restored(raw_data_set());
1426        assert!(!fixture.manager.is_replacing(&fixture.cell.borrow()));
1427        assert!(!root.is_replacing(&fixture.cell.borrow()));
1428
1429        fixture.push_from_host(true, None);
1430        let new_root = fixture
1431            .manager
1432            .root_bucket(&mut fixture.cell.borrow_mut())
1433            .expect("restoration is enabled");
1434        assert_ne!(new_root, root);
1435        assert!(fixture.manager.is_replacing(&fixture.cell.borrow()));
1436        assert!(new_root.is_replacing(&fixture.cell.borrow()));
1437
1438        fixture.pump();
1439        assert!(!fixture.manager.is_replacing(&fixture.cell.borrow()));
1440        assert!(!new_root.is_replacing(&fixture.cell.borrow()));
1441
1442        fixture.push_from_host(false, None);
1443        assert!(!fixture.manager.is_replacing(&fixture.cell.borrow()));
1444    }
1445
1446    #[test]
1447    fn scheduled_serialization_reaches_the_host_at_the_end_of_the_frame() {
1448        let (mut fixture, root) = Fixture::restored(raw_data_set());
1449        root.write(&mut fixture.cell.borrow_mut(), "value1", 22i64);
1450        assert!(fixture.restoration.puts.borrow().is_empty());
1451
1452        fixture.pump();
1453
1454        let puts = fixture
1455            .restoration
1456            .puts
1457            .borrow_mut()
1458            .drain(..)
1459            .collect::<Vec<_>>();
1460        assert_eq!(puts.len(), 1);
1461        assert_eq!(at(&puts[0], &["v", "value1"]), Some(&22i64.into()));
1462    }
1463
1464    #[test]
1465    fn flush_data_waits_for_a_scheduled_frame_and_otherwise_sends_at_once() {
1466        let (mut fixture, root) = Fixture::restored(raw_data_set());
1467        SchedulerBinding::schedule_frame(&mut fixture.cell.borrow_mut(), FrameTarget::EveryView);
1468        root.write(&mut fixture.cell.borrow_mut(), "foo", 1i64);
1469
1470        fixture.manager.flush_data(&mut fixture.cell.borrow_mut());
1471        assert!(
1472            fixture.restoration.puts.borrow().is_empty(),
1473            "the scheduled frame will flush it"
1474        );
1475
1476        fixture.pump();
1477        assert_eq!(fixture.restoration.puts.borrow_mut().drain(..).count(), 1);
1478
1479        root.write(&mut fixture.cell.borrow_mut(), "foo", 2i64);
1480        let data = fixture
1481            .serialized()
1482            .expect("a write schedules serialization");
1483        assert_eq!(at(&data, &["v", "foo"]), Some(&2i64.into()));
1484    }
1485
1486    #[test]
1487    fn nothing_is_sent_when_no_bucket_asked_for_serialization() {
1488        let (mut fixture, _root) = Fixture::restored(raw_data_set());
1489        assert!(fixture.serialized().is_none());
1490    }
1491
1492    #[test]
1493    fn root_bucket_values() {
1494        let (mut fixture, root) = Fixture::restored(raw_data_set());
1495        assert_eq!(
1496            root.debug_owner(&fixture.cell.borrow()).is_some(),
1497            cfg!(debug_assertions)
1498        );
1499        assert_eq!(
1500            root.read(&mut fixture.cell.borrow_mut(), "value1"),
1501            Some(10i64.into())
1502        );
1503        assert_eq!(
1504            root.read(&mut fixture.cell.borrow_mut(), "value2"),
1505            Some("Hello".into())
1506        );
1507        assert_eq!(root.read(&mut fixture.cell.borrow_mut(), "value3"), None);
1508        assert!(fixture.serialized().is_none());
1509
1510        root.write(&mut fixture.cell.borrow_mut(), "value1", 22i64);
1511        assert_eq!(
1512            root.read(&mut fixture.cell.borrow_mut(), "value1"),
1513            Some(22i64.into())
1514        );
1515        let data = fixture
1516            .serialized()
1517            .expect("a write schedules serialization");
1518        assert_eq!(at(&data, &["v", "value1"]), Some(&22i64.into()));
1519
1520        root.write(&mut fixture.cell.borrow_mut(), "value3", true);
1521        assert_eq!(
1522            root.read(&mut fixture.cell.borrow_mut(), "value3"),
1523            Some(true.into())
1524        );
1525        let data = fixture
1526            .serialized()
1527            .expect("a write schedules serialization");
1528        assert_eq!(at(&data, &["v", "value3"]), Some(&true.into()));
1529
1530        assert_eq!(
1531            root.remove(&mut fixture.cell.borrow_mut(), "value1"),
1532            Some(22i64.into())
1533        );
1534        assert_eq!(root.read(&mut fixture.cell.borrow_mut(), "value1"), None);
1535        let data = fixture
1536            .serialized()
1537            .expect("a removal schedules serialization");
1538        assert_eq!(at(&data, &["v", "value1"]), None);
1539
1540        assert_eq!(root.remove(&mut fixture.cell.borrow_mut(), "value4"), None);
1541        assert!(
1542            fixture.serialized().is_none(),
1543            "removing what is not there changes nothing"
1544        );
1545
1546        root.write(&mut fixture.cell.borrow_mut(), "value4", None::<i64>);
1547        assert_eq!(
1548            root.read(&mut fixture.cell.borrow_mut(), "value4"),
1549            Some(RestorationData::Null)
1550        );
1551        assert!(root.contains(&mut fixture.cell.borrow_mut(), "value4"));
1552        let data = fixture
1553            .serialized()
1554            .expect("a write schedules serialization");
1555        assert_eq!(at(&data, &["v", "value4"]), Some(&RestorationData::Null));
1556    }
1557
1558    #[test]
1559    fn child_bucket_values() {
1560        let (mut fixture, root) = Fixture::restored(raw_data_set());
1561        let child = RestorationBucket::child(
1562            &mut fixture.cell.borrow_mut(),
1563            "child1",
1564            root,
1565            owner("owner"),
1566        );
1567
1568        assert_eq!(child.restoration_id(&fixture.cell.borrow()), "child1");
1569        assert_eq!(
1570            child.read(&mut fixture.cell.borrow_mut(), "foo"),
1571            Some(22i64.into())
1572        );
1573        assert_eq!(child.read(&mut fixture.cell.borrow_mut(), "bar"), None);
1574        assert!(fixture.serialized().is_none());
1575
1576        child.write(&mut fixture.cell.borrow_mut(), "foo", 44i64);
1577        assert_eq!(
1578            child.read(&mut fixture.cell.borrow_mut(), "foo"),
1579            Some(44i64.into())
1580        );
1581        let data = fixture
1582            .serialized()
1583            .expect("a write schedules serialization");
1584        assert_eq!(at(&data, &["c", "child1", "v", "foo"]), Some(&44i64.into()));
1585
1586        assert_eq!(
1587            child.remove(&mut fixture.cell.borrow_mut(), "foo"),
1588            Some(44i64.into())
1589        );
1590        let data = fixture
1591            .serialized()
1592            .expect("a removal schedules serialization");
1593        assert_eq!(at(&data, &["c", "child1", "v", "foo"]), None);
1594    }
1595
1596    #[test]
1597    fn claiming_a_child_with_existing_data_keeps_that_data() {
1598        let (mut fixture, root) = Fixture::restored(raw_data_set());
1599        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner"));
1600        assert!(
1601            fixture.serialized().is_none(),
1602            "claiming stored data changes nothing"
1603        );
1604        assert_eq!(child.restoration_id(&fixture.cell.borrow()), "child1");
1605        assert_eq!(
1606            child.read(&mut fixture.cell.borrow_mut(), "foo"),
1607            Some(22i64.into())
1608        );
1609
1610        child.write(&mut fixture.cell.borrow_mut(), "bar", 44i64);
1611        let data = fixture
1612            .serialized()
1613            .expect("a write schedules serialization");
1614        assert_eq!(at(&data, &["c", "child1", "v", "bar"]), Some(&44i64.into()));
1615    }
1616
1617    #[test]
1618    fn claiming_a_child_with_no_existing_data_gives_an_empty_bucket() {
1619        let (mut fixture, root) = Fixture::restored(raw_data_set());
1620        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child2", owner("owner"));
1621        assert_eq!(child.restoration_id(&fixture.cell.borrow()), "child2");
1622
1623        child.write(&mut fixture.cell.borrow_mut(), "foo", 55i64);
1624        assert_eq!(
1625            child.read(&mut fixture.cell.borrow_mut(), "foo"),
1626            Some(55i64.into())
1627        );
1628        let data = fixture
1629            .serialized()
1630            .expect("a new child schedules serialization");
1631        assert_eq!(at(&data, &["c", "child2", "v", "foo"]), Some(&55i64.into()));
1632    }
1633
1634    #[cfg(debug_assertions)]
1635    #[test]
1636    fn claiming_a_claimed_child_reports_the_duplicate_ids_at_finalization() {
1637        let (mut fixture, root) = Fixture::restored(raw_data_set());
1638        let _first = root.claim_child(
1639            &mut fixture.cell.borrow_mut(),
1640            "child1",
1641            owner("FirstClaim"),
1642        );
1643        let second = root.claim_child(
1644            &mut fixture.cell.borrow_mut(),
1645            "child1",
1646            owner("SecondClaim"),
1647        );
1648        assert_eq!(second.read(&mut fixture.cell.borrow_mut(), "foo"), None);
1649
1650        let error = catch_unwind(AssertUnwindSafe(|| fixture.serialized())).unwrap_err();
1651        let message = panic_message(&error);
1652        assert!(
1653            message.contains("Multiple owners claimed child RestorationBuckets with the same IDs."),
1654            "{message}"
1655        );
1656        assert!(
1657            message.contains(" * \"child1\" was claimed by:"),
1658            "{message}"
1659        );
1660        assert!(message.contains("\"SecondClaim\""), "{message}");
1661        assert!(
1662            message.contains("\"FirstClaim\" (current owner)"),
1663            "{message}"
1664        );
1665    }
1666
1667    #[test]
1668    fn claiming_a_claimed_child_is_fine_once_the_first_owner_gives_it_up() {
1669        let (mut fixture, root) = Fixture::restored(raw_data_set());
1670        let first = root.claim_child(
1671            &mut fixture.cell.borrow_mut(),
1672            "child1",
1673            owner("FirstClaim"),
1674        );
1675        let second = root.claim_child(
1676            &mut fixture.cell.borrow_mut(),
1677            "child1",
1678            owner("SecondClaim"),
1679        );
1680        second.write(&mut fixture.cell.borrow_mut(), "bar", 55i64);
1681
1682        first.dispose(&mut fixture.cell.borrow_mut());
1683
1684        let data = fixture
1685            .serialized()
1686            .expect("the shuffle schedules serialization");
1687        assert_eq!(at(&data, &["c", "child1", "v", "foo"]), None);
1688        assert_eq!(at(&data, &["c", "child1", "v", "bar"]), Some(&55i64.into()));
1689    }
1690
1691    #[test]
1692    fn unclaiming_and_claiming_the_same_id_gives_a_fresh_bucket() {
1693        let (fixture, root) = Fixture::restored(raw_data_set());
1694        let first = root.claim_child(
1695            &mut fixture.cell.borrow_mut(),
1696            "child1",
1697            owner("FirstClaim"),
1698        );
1699        assert_eq!(
1700            first.read(&mut fixture.cell.borrow_mut(), "foo"),
1701            Some(22i64.into())
1702        );
1703        first.dispose(&mut fixture.cell.borrow_mut());
1704
1705        let second = root.claim_child(
1706            &mut fixture.cell.borrow_mut(),
1707            "child1",
1708            owner("SecondClaim"),
1709        );
1710        assert_eq!(second.read(&mut fixture.cell.borrow_mut(), "foo"), None);
1711    }
1712
1713    #[test]
1714    fn the_raw_data_drops_the_values_and_children_maps_when_they_empty_out() {
1715        let (mut fixture, root) = Fixture::restored(raw_data_set());
1716        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner"));
1717        child.dispose(&mut fixture.cell.borrow_mut());
1718        let data = fixture
1719            .serialized()
1720            .expect("dropping a child schedules serialization");
1721        assert_eq!(at(&data, &["c"]), None);
1722
1723        assert_eq!(
1724            root.remove(&mut fixture.cell.borrow_mut(), "value1"),
1725            Some(10i64.into())
1726        );
1727        assert_eq!(
1728            root.remove(&mut fixture.cell.borrow_mut(), "value2"),
1729            Some("Hello".into())
1730        );
1731        let data = fixture
1732            .serialized()
1733            .expect("a removal schedules serialization");
1734        assert_eq!(at(&data, &["v"]), None);
1735    }
1736
1737    #[test]
1738    fn dispose_deletes_the_data_of_the_whole_subtree() {
1739        let (mut fixture, root) = Fixture::restored(raw_data_set());
1740        let child1 = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1741        child1.claim_child(
1742            &mut fixture.cell.borrow_mut(),
1743            "child1OfChild1",
1744            owner("owner1.1"),
1745        );
1746        let child2 = root.claim_child(&mut fixture.cell.borrow_mut(), "child2", owner("owner2"));
1747        child2.write(&mut fixture.cell.borrow_mut(), "foo", 1i64);
1748
1749        let data = fixture
1750            .serialized()
1751            .expect("new children schedule serialization");
1752        assert!(at(&data, &["c", "child1"]).is_some());
1753        assert!(at(&data, &["c", "child2"]).is_some());
1754
1755        child1.dispose(&mut fixture.cell.borrow_mut());
1756        let data = fixture
1757            .serialized()
1758            .expect("dropping a child schedules serialization");
1759        assert_eq!(at(&data, &["c", "child1"]), None);
1760
1761        child2.dispose(&mut fixture.cell.borrow_mut());
1762        let data = fixture
1763            .serialized()
1764            .expect("dropping a child schedules serialization");
1765        assert_eq!(at(&data, &["c"]), None);
1766    }
1767
1768    #[test]
1769    fn rename_to_the_same_id_changes_nothing() {
1770        let (mut fixture, root) = Fixture::restored(raw_data_set());
1771        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1772
1773        child.rename(&mut fixture.cell.borrow_mut(), "child1");
1774        assert_eq!(child.restoration_id(&fixture.cell.borrow()), "child1");
1775        assert!(fixture.serialized().is_none());
1776    }
1777
1778    #[test]
1779    fn rename_to_an_unused_id_moves_the_data() {
1780        let (mut fixture, root) = Fixture::restored(raw_data_set());
1781        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1782
1783        child.rename(&mut fixture.cell.borrow_mut(), "new-name");
1784        assert_eq!(child.restoration_id(&fixture.cell.borrow()), "new-name");
1785
1786        let data = fixture
1787            .serialized()
1788            .expect("a rename schedules serialization");
1789        assert_eq!(at(&data, &["c", "child1"]), None);
1790        assert_eq!(
1791            at(&data, &["c", "new-name", "v", "foo"]),
1792            Some(&22i64.into())
1793        );
1794    }
1795
1796    #[cfg(debug_assertions)]
1797    #[test]
1798    fn rename_onto_a_used_id_reports_the_duplicate_unless_it_is_given_up() {
1799        let (mut fixture, root) = Fixture::restored(raw_data_set());
1800        let _child1 = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1801        let child2 = root.claim_child(&mut fixture.cell.borrow_mut(), "child2", owner("owner2"));
1802        child2.rename(&mut fixture.cell.borrow_mut(), "child1");
1803
1804        let error = catch_unwind(AssertUnwindSafe(|| fixture.serialized())).unwrap_err();
1805        assert!(
1806            panic_message(&error)
1807                .contains("Multiple owners claimed child RestorationBuckets with the same IDs."),
1808        );
1809    }
1810
1811    #[test]
1812    fn rename_onto_a_given_up_id_takes_it_over() {
1813        let (mut fixture, root) = Fixture::restored(raw_data_set());
1814        let child1 = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1815        let child2 = root.claim_child(&mut fixture.cell.borrow_mut(), "child2", owner("owner2"));
1816        child2.write(&mut fixture.cell.borrow_mut(), "bar", 7i64);
1817        fixture.serialize();
1818
1819        child2.rename(&mut fixture.cell.borrow_mut(), "child1");
1820        child1.dispose(&mut fixture.cell.borrow_mut());
1821
1822        let data = fixture
1823            .serialized()
1824            .expect("the shuffle schedules serialization");
1825        assert_eq!(at(&data, &["c", "child1", "v", "bar"]), Some(&7i64.into()));
1826        assert_eq!(at(&data, &["c", "child2"]), None);
1827    }
1828
1829    #[test]
1830    fn renaming_a_child_that_is_still_waiting_for_its_id() {
1831        let (mut fixture, root) = Fixture::restored(raw_data_set());
1832        let child1 = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1833        let child2 = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner2"));
1834
1835        child2.rename(&mut fixture.cell.borrow_mut(), "foo");
1836
1837        let data = fixture
1838            .serialized()
1839            .expect("the shuffle schedules serialization");
1840        assert_eq!(child1.restoration_id(&fixture.cell.borrow()), "child1");
1841        assert_eq!(child2.restoration_id(&fixture.cell.borrow()), "foo");
1842        assert_eq!(at(&data, &["c", "child1", "v", "foo"]), Some(&22i64.into()));
1843        assert_eq!(
1844            at(&data, &["c", "foo"]),
1845            Some(&RestorationMap::new().into())
1846        );
1847    }
1848
1849    #[test]
1850    fn adopting_a_child_of_this_bucket_changes_nothing() {
1851        let (mut fixture, root) = Fixture::restored(raw_data_set());
1852        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1853
1854        root.adopt_child(&mut fixture.cell.borrow_mut(), child);
1855        assert!(fixture.serialized().is_none());
1856    }
1857
1858    #[test]
1859    fn adopting_a_fresh_child_puts_it_in_the_hierarchy() {
1860        let (mut fixture, root) = Fixture::restored(raw_data_set());
1861        let child = RestorationBucket::empty(
1862            &mut fixture.cell.borrow_mut(),
1863            "fresh-child",
1864            owner("owner1"),
1865        );
1866        assert!(!child.is_replacing(&fixture.cell.borrow()));
1867
1868        root.adopt_child(&mut fixture.cell.borrow_mut(), child);
1869        child.write(&mut fixture.cell.borrow_mut(), "value", 22i64);
1870
1871        let data = fixture
1872            .serialized()
1873            .expect("an adoption schedules serialization");
1874        assert_eq!(
1875            at(&data, &["c", "fresh-child", "v", "value"]),
1876            Some(&22i64.into())
1877        );
1878    }
1879
1880    #[test]
1881    fn adopting_a_child_that_already_had_a_parent_moves_its_data() {
1882        let (mut fixture, root) = Fixture::restored(raw_data_set());
1883        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1884        let child_of_child = child.claim_child(
1885            &mut fixture.cell.borrow_mut(),
1886            "childOfChild",
1887            owner("owner2"),
1888        );
1889        child_of_child.write(&mut fixture.cell.borrow_mut(), "foo", "bar");
1890        let data = fixture
1891            .serialized()
1892            .expect("a write schedules serialization");
1893        assert_eq!(
1894            at(&data, &["c", "child1", "c", "childOfChild", "v", "foo"]),
1895            Some(&"bar".into())
1896        );
1897
1898        root.adopt_child(&mut fixture.cell.borrow_mut(), child_of_child);
1899
1900        let data = fixture
1901            .serialized()
1902            .expect("an adoption schedules serialization");
1903        assert_eq!(at(&data, &["c", "child1", "c"]), None);
1904        assert_eq!(
1905            at(&data, &["c", "childOfChild", "v", "foo"]),
1906            Some(&"bar".into())
1907        );
1908    }
1909
1910    #[cfg(debug_assertions)]
1911    #[test]
1912    fn adopting_onto_a_used_id_reports_the_duplicate_unless_it_is_given_up() {
1913        let (mut fixture, root) = Fixture::restored(raw_data_set());
1914        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1915        let child_of_child =
1916            child.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner2"));
1917        child_of_child.write(&mut fixture.cell.borrow_mut(), "foo", "bar");
1918
1919        root.adopt_child(&mut fixture.cell.borrow_mut(), child_of_child);
1920
1921        let error = catch_unwind(AssertUnwindSafe(|| fixture.serialized())).unwrap_err();
1922        assert!(
1923            panic_message(&error)
1924                .contains("Multiple owners claimed child RestorationBuckets with the same IDs."),
1925        );
1926    }
1927
1928    #[test]
1929    fn adopting_onto_a_given_up_id_takes_it_over() {
1930        let (mut fixture, root) = Fixture::restored(raw_data_set());
1931        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1932        let child_of_child =
1933            child.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner2"));
1934        child_of_child.write(&mut fixture.cell.borrow_mut(), "foo", "bar");
1935        fixture.serialize();
1936
1937        root.adopt_child(&mut fixture.cell.borrow_mut(), child_of_child);
1938        child.dispose(&mut fixture.cell.borrow_mut());
1939
1940        let data = fixture
1941            .serialized()
1942            .expect("the shuffle schedules serialization");
1943        assert_eq!(at(&data, &["c", "child1", "v", "foo"]), Some(&"bar".into()));
1944    }
1945
1946    #[test]
1947    fn a_child_of_a_parentless_bucket_joins_the_manager_when_it_is_adopted() {
1948        let (mut fixture, root) = Fixture::restored(raw_data_set());
1949        let detached =
1950            RestorationBucket::empty(&mut fixture.cell.borrow_mut(), "detached", owner("owner1"));
1951        let child = detached.claim_child(&mut fixture.cell.borrow_mut(), "leaf", owner("owner2"));
1952        child.write(&mut fixture.cell.borrow_mut(), "foo", 3i64);
1953        assert!(
1954            fixture.serialized().is_none(),
1955            "a bucket outside the hierarchy has no manager to schedule with"
1956        );
1957
1958        root.adopt_child(&mut fixture.cell.borrow_mut(), detached);
1959
1960        let data = fixture
1961            .serialized()
1962            .expect("an adoption schedules serialization");
1963        assert_eq!(
1964            at(&data, &["c", "detached", "c", "leaf", "v", "foo"]),
1965            Some(&3i64.into())
1966        );
1967    }
1968
1969    #[cfg(debug_assertions)]
1970    #[test]
1971    fn a_bucket_cannot_be_used_after_it_is_disposed() {
1972        let cell = AppCell::new();
1973        let mut app = cell.borrow_mut();
1974        let bucket = RestorationBucket::empty(&mut app, "foo", None);
1975        bucket.dispose(&mut app);
1976
1977        let error = catch_unwind(AssertUnwindSafe(|| bucket.read(&mut app, "foo"))).unwrap_err();
1978        assert!(
1979            panic_message(&error).contains("A RestorationBucket was used after being disposed."),
1980        );
1981    }
1982
1983    #[test]
1984    fn disposing_the_manager_disposes_the_root_bucket() {
1985        let (fixture, root) = Fixture::restored(raw_data_set());
1986        fixture.manager.dispose(&mut fixture.cell.borrow_mut());
1987        assert!(!fixture.cell.borrow().contains(root));
1988    }
1989
1990    #[test]
1991    fn a_bucket_reports_the_owner_it_was_claimed_with() {
1992        let (fixture, root) = Fixture::restored(raw_data_set());
1993        let child = root.claim_child(&mut fixture.cell.borrow_mut(), "child1", owner("owner1"));
1994        let described = format!("{:?}", fixture.cell.borrow().get(child));
1995        if cfg!(debug_assertions) {
1996            assert_eq!(
1997                described,
1998                "RestorationBucket(restorationId: child1, owner: \"owner1\")"
1999            );
2000        } else {
2001            assert_eq!(
2002                described,
2003                "RestorationBucket(restorationId: child1, owner: None)"
2004            );
2005        }
2006    }
2007}