Skip to main content

script/dom/window/
location.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use dom_struct::dom_struct;
6use js::context::JSContext;
7use net_traits::request::Referrer;
8use script_bindings::reflector::{Reflector, reflect_dom_object};
9use servo_constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior};
10use servo_url::ServoUrl;
11
12use crate::dom::bindings::codegen::Bindings::LocationBinding::LocationMethods;
13use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
14use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
15use crate::dom::bindings::inheritance::Castable;
16use crate::dom::bindings::root::{Dom, DomRoot};
17use crate::dom::bindings::str::USVString;
18use crate::dom::document::Document;
19use crate::dom::domstringlist::DOMStringList;
20use crate::dom::globalscope::GlobalScope;
21use crate::dom::urlhelper::UrlHelper;
22use crate::dom::window::Window;
23use crate::navigation::navigate;
24
25#[derive(PartialEq)]
26pub(crate) enum NavigationType {
27    /// The "[`Location`-object navigate][1]" steps.
28    ///
29    /// [1]: https://html.spec.whatwg.org/multipage/#location-object-navigate
30    Normal,
31
32    /// The last step of [`reload()`][1] (`reload_triggered == true`)
33    ///
34    /// [1]: https://html.spec.whatwg.org/multipage/#dom-location-reload
35    ReloadByScript,
36
37    /// User-requested navigation (the unlabeled paragraph after
38    /// [`reload()`][1]).
39    ///
40    /// [1]: https://html.spec.whatwg.org/multipage/#dom-location-reload
41    ReloadByConstellation,
42}
43
44#[dom_struct]
45pub(crate) struct Location {
46    reflector_: Reflector,
47    window: Dom<Window>,
48    /// <https://html.spec.whatwg.org/multipage/#concept-location-empty-domstringlist>
49    empty_dom_string_list: Dom<DOMStringList>,
50}
51
52impl Location {
53    fn new_inherited(window: &Window, empty_dom_string_list: &DOMStringList) -> Location {
54        Location {
55            reflector_: Reflector::new(),
56            window: Dom::from_ref(window),
57            empty_dom_string_list: Dom::from_ref(empty_dom_string_list),
58        }
59    }
60
61    pub(crate) fn new(cx: &mut JSContext, window: &Window) -> DomRoot<Location> {
62        let empty_dom_string_list = DOMStringList::new(cx, window.upcast(), vec![]);
63        reflect_dom_object(
64            cx,
65            Box::new(Location::new_inherited(window, &empty_dom_string_list)),
66            window,
67        )
68    }
69
70    /// <https://html.spec.whatwg.org/multipage/#location-object-navigate>
71    fn navigate_a_location(
72        &self,
73        cx: &mut JSContext,
74        url: ServoUrl,
75        history_handling: NavigationHistoryBehavior,
76    ) {
77        // Step 1. Let navigable be location's relevant global object's navigable.
78        let navigable = &self.window;
79        let navigable_document = navigable.Document();
80        // Step 2. Let sourceDocument be the incumbent global object's associated Document.
81        let incumbent_global = GlobalScope::incumbent().expect("no incumbent global object");
82        let mut load_data = incumbent_global
83            .as_window()
84            .load_data_for_document(url, incumbent_global.pipeline_id());
85        load_data.about_base_url = navigable_document.about_base_url();
86        // Step 3. If location's relevant Document is not yet completely loaded,
87        // and the incumbent global object does not have transient activation, then set historyHandling to "replace".
88        //
89        // TODO: check for transient activation
90        let history_handling = if !navigable_document.completely_loaded() {
91            NavigationHistoryBehavior::Replace
92        } else {
93            history_handling
94        };
95        // Step 4. Navigate navigable to url using sourceDocument, with exceptionsEnabled set to true and historyHandling set to historyHandling.
96        navigate(cx, navigable, history_handling, false, load_data);
97    }
98
99    /// Navigate the relevant `Document`'s browsing context.
100    ///
101    /// This is ostensibly an implementation of
102    /// <https://html.spec.whatwg.org/multipage/#navigate>, but the specification has
103    /// greatly deviated from our code.
104    fn navigate(
105        &self,
106        cx: &mut JSContext,
107        url: ServoUrl,
108        history_handling: NavigationHistoryBehavior,
109        navigation_type: NavigationType,
110    ) {
111        fn incumbent_window() -> DomRoot<Window> {
112            let incumbent_global = GlobalScope::incumbent().expect("no incumbent global object");
113            DomRoot::downcast(incumbent_global).expect("global object is not a Window")
114        }
115
116        // The active document of the source browsing context used for
117        // navigation determines the request's referrer and referrer policy.
118        let source_window = match navigation_type {
119            NavigationType::ReloadByScript | NavigationType::ReloadByConstellation => {
120                // > Navigate the browsing context [...] the source browsing context
121                // > set to the browsing context being navigated.
122                DomRoot::from_ref(&*self.window)
123            },
124            NavigationType::Normal => {
125                // > 2. Let `sourceBrowsingContext` be the incumbent global object's
126                // >    browsing context.
127                incumbent_window()
128            },
129        };
130        let source_document = source_window.Document();
131
132        let referrer = Referrer::ReferrerUrl(source_document.url());
133        let referrer_policy = source_document.get_referrer_policy();
134
135        // <https://html.spec.whatwg.org/multipage/#navigate>
136        // > Let `incumbentNavigationOrigin` be the origin of the incumbent
137        // > settings object, or if no script was involved, the origin of the
138        // > node document of the element that initiated the navigation.
139        let navigation_origin_window = match navigation_type {
140            NavigationType::Normal | NavigationType::ReloadByScript => incumbent_window(),
141            NavigationType::ReloadByConstellation => DomRoot::from_ref(&*self.window),
142        };
143        let (load_origin, creator_pipeline_id) = (
144            navigation_origin_window.origin().snapshot(),
145            Some(navigation_origin_window.pipeline_id()),
146        );
147
148        // Is `historyHandling` `reload`?
149        let reload_triggered = match navigation_type {
150            NavigationType::ReloadByScript | NavigationType::ReloadByConstellation => true,
151            NavigationType::Normal => false,
152        };
153
154        // Initiate navigation
155        // TODO: rethrow exceptions, set exceptions enabled flag.
156        let load_data = LoadData::new(
157            LoadOrigin::Script(load_origin),
158            url,
159            source_document.about_base_url(),
160            creator_pipeline_id,
161            referrer,
162            referrer_policy,
163            None, // Top navigation doesn't inherit secure context
164            Some(source_document.insecure_requests_policy()),
165            source_document.has_trustworthy_ancestor_origin(),
166            source_document.creation_sandboxing_flag_set_considering_parent_iframe(),
167        );
168        navigate(
169            cx,
170            &self.window,
171            history_handling,
172            reload_triggered,
173            load_data,
174        );
175    }
176
177    /// Get if this `Location`'s [relevant `Document`][1] is non-null.
178    ///
179    /// [1]: https://html.spec.whatwg.org/multipage/#relevant-document
180    fn has_document(&self) -> bool {
181        // <https://html.spec.whatwg.org/multipage/#relevant-document>
182        //
183        // > A `Location` object has an associated relevant `Document`, which is
184        // > this `Location` object's relevant global object's browsing
185        // > context's active document, if this `Location` object's relevant
186        // > global object's browsing context is non-null, and null otherwise.
187        self.window.Document().browsing_context().is_some()
188    }
189
190    /// Get this `Location` object's [relevant `Document`][1], or
191    /// `Err(Error::Security(..))` if it's non-null and its origin is not same
192    /// origin-domain with the entry setting object's origin.
193    ///
194    /// In the specification's terms:
195    ///
196    ///  1. If this `Location` object's relevant `Document` is null, then return
197    ///     null.
198    ///
199    ///  2. If this `Location` object's relevant `Document`'s origin is not same
200    ///     origin-domain with the entry settings object's origin, then throw a
201    ///     "`SecurityError`" `DOMException`.
202    ///
203    ///  3. Return this `Location` object's relevant `Document`.
204    ///
205    /// [1]: https://html.spec.whatwg.org/multipage/#relevant-document
206    fn document_if_same_origin(&self) -> Fallible<Option<DomRoot<Document>>> {
207        // <https://html.spec.whatwg.org/multipage/#relevant-document>
208        //
209        // > A `Location` object has an associated relevant `Document`, which is
210        // > this `Location` object's relevant global object's browsing
211        // > context's active document, if this `Location` object's relevant
212        // > global object's browsing context is non-null, and null otherwise.
213        if let Some(window_proxy) = self.window.Document().browsing_context() {
214            // `Location`'s many other operations:
215            //
216            // > If this `Location` object's relevant `Document` is non-null and
217            // > its origin is not same origin-domain with the entry settings
218            // > object's origin, then throw a "SecurityError" `DOMException`.
219            //
220            // FIXME: We should still return the active document if it's same
221            //        origin but not fully active. `WindowProxy::document`
222            //        currently returns `None` in this case.
223            if let Some(document) = window_proxy.document().filter(|document| {
224                self.entry_settings_object()
225                    .origin()
226                    .same_origin_domain(&document.origin())
227            }) {
228                Ok(Some(document))
229            } else {
230                Err(Error::Security("Location's relevant Document is not same origin-domain with the entry settings object's origin".to_string().into()))
231            }
232        } else {
233            // The browsing context is null
234            Ok(None)
235        }
236    }
237
238    /// Get this `Location` object's [relevant url][1] or
239    /// `Err(Error::Security(..))` if the [relevant `Document`][2] if it's non-null
240    /// and its origin is not same origin-domain with the entry setting object's
241    /// origin.
242    ///
243    /// [1]: https://html.spec.whatwg.org/multipage/#concept-location-url
244    /// [2]: https://html.spec.whatwg.org/multipage/#relevant-document
245    fn get_url_if_same_origin(&self) -> Fallible<ServoUrl> {
246        Ok(if let Some(document) = self.document_if_same_origin()? {
247            document.url()
248        } else {
249            ServoUrl::parse("about:blank").unwrap()
250        })
251    }
252
253    fn entry_settings_object(&self) -> DomRoot<GlobalScope> {
254        GlobalScope::entry()
255    }
256
257    /// The common algorithm for `Location`'s setters and `Location::Assign`.
258    #[inline]
259    fn setter_common(
260        &self,
261        cx: &mut JSContext,
262        f: impl FnOnce(ServoUrl) -> Fallible<Option<ServoUrl>>,
263    ) -> ErrorResult {
264        // Step 1: If this Location object's relevant Document is null, then return.
265        // Step 2: If this Location object's relevant Document's origin is not
266        // same origin-domain with the entry settings object's origin, then
267        // throw a "SecurityError" DOMException.
268        if let Some(document) = self.document_if_same_origin()? {
269            // Step 3: Let copyURL be a copy of this Location object's url.
270            // Step 4: Assign the result of running f(copyURL) to copyURL.
271            if let Some(copy_url) = f(document.url())? {
272                // Step 5: Terminate these steps if copyURL is null.
273                // Step 6: Location-object navigate to copyURL.
274                self.navigate(
275                    cx,
276                    copy_url,
277                    NavigationHistoryBehavior::Push,
278                    NavigationType::Normal,
279                );
280            }
281        }
282        Ok(())
283    }
284
285    /// Perform a user-requested reload (the unlabeled paragraph after
286    /// [`reload()`][1]).
287    ///
288    /// [1]: https://html.spec.whatwg.org/multipage/#dom-location-reload
289    pub(crate) fn reload_without_origin_check(&self, cx: &mut JSContext) {
290        // > When a user requests that the active document of a browsing context
291        // > be reloaded through a user interface element, the user agent should
292        // > navigate the browsing context to the same resource as that
293        // > `Document`, with `historyHandling` set to "reload".
294        let url = self.window.get_url();
295        self.navigate(
296            cx,
297            url,
298            NavigationHistoryBehavior::Replace,
299            NavigationType::ReloadByConstellation,
300        );
301    }
302}
303
304impl LocationMethods<crate::DomTypeHolder> for Location {
305    /// <https://html.spec.whatwg.org/multipage/#dom-location-assign>
306    fn Assign(&self, cx: &mut JSContext, url: USVString) -> ErrorResult {
307        self.setter_common(cx, |_copy_url| {
308            // Step 3: Let urlRecord be the result of encoding-parsing a URL given url, relative to the entry settings object. If that failed,
309            // throw a "SyntaxError" DOMException.
310            let url = match self.entry_settings_object().encoding_parse_a_url(&url.0) {
311                Ok(url) => url,
312                Err(e) => return Err(Error::Syntax(Some(format!("Couldn't parse URL: {}", e)))),
313            };
314
315            Ok(Some(url))
316        })
317    }
318
319    /// <https://html.spec.whatwg.org/multipage/#dom-location-reload>
320    fn Reload(&self, cx: &mut JSContext) -> ErrorResult {
321        let url = self.get_url_if_same_origin()?;
322        self.navigate(
323            cx,
324            url,
325            NavigationHistoryBehavior::Replace,
326            NavigationType::ReloadByScript,
327        );
328        Ok(())
329    }
330
331    /// <https://html.spec.whatwg.org/multipage/#dom-location-replace>
332    fn Replace(&self, cx: &mut JSContext, url: USVString) -> ErrorResult {
333        // Step 1: If this Location object's relevant Document is null, then return.
334        if self.has_document() {
335            // Step 2. Let urlRecord be the result of encoding-parsing a URL given url, relative to the entry settings object.
336            let url = match self.entry_settings_object().encoding_parse_a_url(&url.0) {
337                Ok(url) => url,
338                // Step 3. If urlRecord is failure, then throw a "SyntaxError" DOMException.
339                Err(e) => return Err(Error::Syntax(Some(format!("Couldn't parse URL: {}", e)))),
340            };
341            // Step 4. Location-object navigate this to urlRecord given "replace".
342            self.navigate_a_location(cx, url, NavigationHistoryBehavior::Replace);
343        }
344        Ok(())
345    }
346
347    /// <https://html.spec.whatwg.org/multipage/#dom-location-hash>
348    fn GetHash(&self) -> Fallible<USVString> {
349        Ok(UrlHelper::Hash(&self.get_url_if_same_origin()?))
350    }
351
352    /// <https://html.spec.whatwg.org/multipage/#dom-location-hash>
353    fn SetHash(&self, cx: &mut JSContext, value: USVString) -> ErrorResult {
354        // Step 1. If this's relevant Document is null, then return.
355        if self.has_document() {
356            // Step 2. If this's relevant Document's origin is not same origin-domain
357            // with the entry settings object's origin, then throw a "SecurityError" DOMException.
358            // Step 3. Let copyURL be a copy of this's url.
359            let mut copy_url = self.get_url_if_same_origin()?;
360            // Step 4. Let thisURLFragment be copyURL's fragment if it is non-null; otherwise the empty string.
361            let this_url_fragment = copy_url.fragment().map(str::to_owned).unwrap_or_default();
362            // Step 6. Set copyURL's fragment to the empty string.
363            // Step 7. Basic URL parse input, with copyURL as url and fragment state as state override.
364            let input = &value.0;
365            // Note that if the hash is the empty string, we shouldn't then set the fragment to `None`.
366            // That's because the empty string is a valid hash target and should then scroll to the
367            // top of the document. Therefore, we don't use `UrlHelpers::SetHash` here, which would
368            // set it to `None`.
369            copy_url.set_fragment(match input {
370                // Step 5. Let input be the given value with a single leading "#" removed, if any.
371                _ if input.starts_with('#') => Some(&input[1..]),
372                _ => Some(input),
373            });
374            // Step 8. If copyURL's fragment is thisURLFragment, then return.
375            if copy_url.fragment() != Some(&this_url_fragment) {
376                // Step 9. Location-object navigate this to copyURL.
377                self.navigate_a_location(cx, copy_url, NavigationHistoryBehavior::Auto);
378            }
379        }
380        Ok(())
381    }
382
383    /// <https://html.spec.whatwg.org/multipage/#dom-location-host>
384    fn GetHost(&self) -> Fallible<USVString> {
385        Ok(UrlHelper::Host(&self.get_url_if_same_origin()?))
386    }
387
388    /// <https://html.spec.whatwg.org/multipage/#dom-location-host>
389    fn SetHost(&self, cx: &mut JSContext, value: USVString) -> ErrorResult {
390        self.setter_common(cx, |mut copy_url| {
391            // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps.
392            if copy_url.cannot_be_a_base() {
393                return Ok(None);
394            }
395
396            // Step 5: Basic URL parse the given value, with copyURL as url and host state
397            // as state override.
398            let _ = copy_url.as_mut_url().set_host(Some(&value.0));
399
400            Ok(Some(copy_url))
401        })
402    }
403
404    /// <https://html.spec.whatwg.org/multipage/#dom-location-origin>
405    fn GetOrigin(&self) -> Fallible<USVString> {
406        Ok(UrlHelper::Origin(&self.get_url_if_same_origin()?))
407    }
408
409    /// <https://html.spec.whatwg.org/multipage/#dom-location-hostname>
410    fn GetHostname(&self) -> Fallible<USVString> {
411        Ok(UrlHelper::Hostname(&self.get_url_if_same_origin()?))
412    }
413
414    /// <https://html.spec.whatwg.org/multipage/#dom-location-hostname>
415    fn SetHostname(&self, cx: &mut JSContext, value: USVString) -> ErrorResult {
416        self.setter_common(cx, |mut copy_url| {
417            // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps.
418            if copy_url.cannot_be_a_base() {
419                return Ok(None);
420            }
421
422            // Step 5: Basic URL parse the given value, with copyURL as url and hostname
423            // state as state override.
424            let _ = copy_url.as_mut_url().set_host(Some(&value.0));
425
426            Ok(Some(copy_url))
427        })
428    }
429
430    /// <https://html.spec.whatwg.org/multipage/#dom-location-href>
431    fn GetHref(&self) -> Fallible<USVString> {
432        Ok(UrlHelper::Href(&self.get_url_if_same_origin()?))
433    }
434
435    /// <https://html.spec.whatwg.org/multipage/#dom-location-href>
436    fn SetHref(&self, cx: &mut JSContext, value: USVString) -> ErrorResult {
437        // Step 1. If this's relevant Document is null, then return.
438        if self.has_document() {
439            // Note: no call to self.check_same_origin_domain()
440            // Step 2: Let url be the result of encoding-parsing a URL given the given value, relative to the entry settings object.
441            let url = match self.entry_settings_object().encoding_parse_a_url(&value.0) {
442                Ok(url) => url,
443                // Step 3: If url is failure, then throw a "SyntaxError" DOMException.
444                Err(e) => return Err(Error::Syntax(Some(format!("Couldn't parse URL: {}", e)))),
445            };
446            // Step 4: Location-object navigate this to url.
447            self.navigate_a_location(cx, url, NavigationHistoryBehavior::Auto);
448        }
449        Ok(())
450    }
451
452    /// <https://html.spec.whatwg.org/multipage/#dom-location-pathname>
453    fn GetPathname(&self) -> Fallible<USVString> {
454        Ok(UrlHelper::Pathname(&self.get_url_if_same_origin()?))
455    }
456
457    /// <https://html.spec.whatwg.org/multipage/#dom-location-pathname>
458    fn SetPathname(&self, cx: &mut JSContext, value: USVString) -> ErrorResult {
459        self.setter_common(cx, |mut copy_url| {
460            // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps.
461            if copy_url.cannot_be_a_base() {
462                return Ok(None);
463            }
464
465            // Step 5: Set copyURL's path to the empty list.
466            // Step 6: Basic URL parse the given value, with copyURL as url and path
467            // start state as state override.
468            copy_url.as_mut_url().set_path(&value.0);
469
470            Ok(Some(copy_url))
471        })
472    }
473
474    /// <https://html.spec.whatwg.org/multipage/#dom-location-port>
475    fn GetPort(&self) -> Fallible<USVString> {
476        Ok(UrlHelper::Port(&self.get_url_if_same_origin()?))
477    }
478
479    /// <https://html.spec.whatwg.org/multipage/#dom-location-port>
480    fn SetPort(&self, cx: &mut JSContext, value: USVString) -> ErrorResult {
481        self.setter_common(cx, |mut copy_url| {
482            // Step 4: If copyURL cannot have a username/password/port, then return.
483            // https://url.spec.whatwg.org/#cannot-have-a-username-password-port
484            if copy_url.host().is_none() ||
485                copy_url.cannot_be_a_base() ||
486                copy_url.scheme() == "file"
487            {
488                return Ok(None);
489            }
490
491            // Step 5: If the given value is the empty string, then set copyURL's
492            // port to null.
493            // Step 6: Otherwise, basic URL parse the given value, with copyURL as url
494            // and port state as state override.
495            let _ = url::quirks::set_port(copy_url.as_mut_url(), &value.0);
496
497            Ok(Some(copy_url))
498        })
499    }
500
501    /// <https://html.spec.whatwg.org/multipage/#dom-location-protocol>
502    fn GetProtocol(&self) -> Fallible<USVString> {
503        Ok(UrlHelper::Protocol(&self.get_url_if_same_origin()?))
504    }
505
506    /// <https://html.spec.whatwg.org/multipage/#dom-location-protocol>
507    fn SetProtocol(&self, cx: &mut JSContext, value: USVString) -> ErrorResult {
508        self.setter_common(cx, |mut copy_url| {
509            // Step 4: Let possibleFailure be the result of basic URL parsing the given
510            // value, followed by ":", with copyURL as url and scheme start state as
511            // state override.
512            let scheme = match value.0.find(':') {
513                Some(position) => &value.0[..position],
514                None => &value.0,
515            };
516
517            if copy_url.as_mut_url().set_scheme(scheme).is_err() {
518                // Step 5: If possibleFailure is failure, then throw a "SyntaxError" DOMException.
519                return Err(Error::Syntax(
520                    "Couldn't parse URL scheme".to_string().into(),
521                ));
522            }
523
524            // Step 6: If copyURL's scheme is not an HTTP(S) scheme, then terminate these steps.
525            if !copy_url.scheme().eq_ignore_ascii_case("http") &&
526                !copy_url.scheme().eq_ignore_ascii_case("https")
527            {
528                return Ok(None);
529            }
530
531            Ok(Some(copy_url))
532        })
533    }
534
535    /// <https://html.spec.whatwg.org/multipage/#dom-location-search>
536    fn GetSearch(&self) -> Fallible<USVString> {
537        Ok(UrlHelper::Search(&self.get_url_if_same_origin()?))
538    }
539
540    /// <https://html.spec.whatwg.org/multipage/#dom-location-search>
541    fn SetSearch(&self, cx: &mut JSContext, value: USVString) -> ErrorResult {
542        self.setter_common(cx, |mut copy_url| {
543            // Step 4: If the given value is the empty string, set copyURL's query to null.
544            // Step 5: Otherwise, run these substeps:
545            //   1. Let input be the given value with a single leading "?" removed, if any.
546            //   2. Set copyURL's query to the empty string.
547            //   3. Basic URL parse input, with copyURL as url and query state as state
548            //      override, and the relevant Document's document's character encoding as
549            //      encoding override.
550            copy_url.as_mut_url().set_query(match value.0.as_str() {
551                "" => None,
552                _ if value.0.starts_with('?') => Some(&value.0[1..]),
553                _ => Some(&value.0),
554            });
555
556            Ok(Some(copy_url))
557        })
558    }
559
560    /// <https://html.spec.whatwg.org/multipage/#dom-location-ancestororigins>
561    fn GetAncestorOrigins(&self) -> Fallible<DomRoot<DOMStringList>> {
562        // Step 1. If this's relevant Document is null, then return this's empty DOMStringList.
563        if !self.has_document() {
564            return Ok(self.empty_dom_string_list.as_rooted());
565        }
566        // Step 2. If this's relevant Document's origin is not same origin-domain
567        // with the entry settings object's origin, then throw a "SecurityError" DOMException.
568        let document = self.window.Document();
569        if !document
570            .origin()
571            .same_origin_domain(&self.entry_settings_object().origin())
572        {
573            return Err(Error::Security("Location's relevant Document is not \
574                                                            same origin-domain with the entry settings object's \
575                                                            origin".to_string().into()));
576        }
577        // Step 3. Assert: this's relevant Document's ancestor origins list is not null.
578        // Step 4. Otherwise, return this's relevant Document's ancestor origins list.
579        Ok(document
580            .ancestor_origins_list()
581            .expect("Must always have ancestor origins initialized"))
582    }
583}