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}