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