/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/window/location.rs
549 строк
24 KB
Josh Matthews
script: Use active document's origin as Window's origin. (#46013)
26 июн 2026, 20:33
Не верифицирован
26 июн 2026, 20:33
84d1ee5
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use dom_struct::dom_struct; use js::context::JSContext; use net_traits::request::Referrer; use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx}; use servo_constellation_traits::{LoadData, LoadOrigin, NavigationHistoryBehavior}; use servo_url::ServoUrl; use crate::dom::bindings::codegen::Bindings::LocationBinding::LocationMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::USVString; use crate::dom::document::Document; use crate::dom::globalscope::GlobalScope; use crate::dom::urlhelper::UrlHelper; use crate::dom::window::Window; use crate::navigation::navigate; #[derive(PartialEq)] pub(crate) enum NavigationType { /// The "[`Location`-object navigate][1]" steps. /// /// [1]: https://html.spec.whatwg.org/multipage/#location-object-navigate Normal, /// The last step of [`reload()`][1] (`reload_triggered == true`) /// /// [1]: https://html.spec.whatwg.org/multipage/#dom-location-reload ReloadByScript, /// User-requested navigation (the unlabeled paragraph after /// [`reload()`][1]). /// /// [1]: https://html.spec.whatwg.org/multipage/#dom-location-reload ReloadByConstellation, } #[dom_struct] pub(crate) struct Location { reflector_: Reflector, window: Dom<Window>, } impl Location { fn new_inherited(window: &Window) -> Location { Location { reflector_: Reflector::new(), window: Dom::from_ref(window), } } pub(crate) fn new(cx: &mut JSContext, window: &Window) -> DomRoot<Location> { reflect_dom_object_with_cx(Box::new(Location::new_inherited(window)), window, cx) } /// <https://html.spec.whatwg.org/multipage/#location-object-navigate> fn navigate_a_location( &self, cx: &mut JSContext, url: ServoUrl, history_handling: NavigationHistoryBehavior, ) { // Step 1. Let navigable be location's relevant global object's navigable. let navigable = &self.window; let navigable_document = navigable.Document(); // Step 2. Let sourceDocument be the incumbent global object's associated Document. let incumbent_global = GlobalScope::incumbent().expect("no incumbent global object"); let mut load_data = incumbent_global .as_window() .load_data_for_document(url, incumbent_global.pipeline_id()); load_data.about_base_url = navigable_document.about_base_url(); // Step 3. If location's relevant Document is not yet completely loaded, // and the incumbent global object does not have transient activation, then set historyHandling to "replace". // // TODO: check for transient activation let history_handling = if !navigable_document.completely_loaded() { NavigationHistoryBehavior::Replace } else { history_handling }; // Step 4. Navigate navigable to url using sourceDocument, with exceptionsEnabled set to true and historyHandling set to historyHandling. navigate(cx, navigable, history_handling, false, load_data); } /// Navigate the relevant `Document`'s browsing context. /// /// This is ostensibly an implementation of /// <https://html.spec.whatwg.org/multipage/#navigate>, but the specification has /// greatly deviated from our code. fn navigate( &self, cx: &mut JSContext, url: ServoUrl, history_handling: NavigationHistoryBehavior, navigation_type: NavigationType, ) { fn incumbent_window() -> DomRoot<Window> { let incumbent_global = GlobalScope::incumbent().expect("no incumbent global object"); DomRoot::downcast(incumbent_global).expect("global object is not a Window") } // The active document of the source browsing context used for // navigation determines the request's referrer and referrer policy. let source_window = match navigation_type { NavigationType::ReloadByScript | NavigationType::ReloadByConstellation => { // > Navigate the browsing context [...] the source browsing context // > set to the browsing context being navigated. DomRoot::from_ref(&*self.window) }, NavigationType::Normal => { // > 2. Let `sourceBrowsingContext` be the incumbent global object's // > browsing context. incumbent_window() }, }; let source_document = source_window.Document(); let referrer = Referrer::ReferrerUrl(source_document.url()); let referrer_policy = source_document.get_referrer_policy(); // <https://html.spec.whatwg.org/multipage/#navigate> // > Let `incumbentNavigationOrigin` be the origin of the incumbent // > settings object, or if no script was involved, the origin of the // > node document of the element that initiated the navigation. let navigation_origin_window = match navigation_type { NavigationType::Normal | NavigationType::ReloadByScript => incumbent_window(), NavigationType::ReloadByConstellation => DomRoot::from_ref(&*self.window), }; let (load_origin, creator_pipeline_id) = ( navigation_origin_window.origin().snapshot(), Some(navigation_origin_window.pipeline_id()), ); // Is `historyHandling` `reload`? let reload_triggered = match navigation_type { NavigationType::ReloadByScript | NavigationType::ReloadByConstellation => true, NavigationType::Normal => false, }; // Initiate navigation // TODO: rethrow exceptions, set exceptions enabled flag. let load_data = LoadData::new( LoadOrigin::Script(load_origin), url, source_document.about_base_url(), creator_pipeline_id, referrer, referrer_policy, None, // Top navigation doesn't inherit secure context Some(source_document.insecure_requests_policy()), source_document.has_trustworthy_ancestor_origin(), source_document.creation_sandboxing_flag_set_considering_parent_iframe(), ); navigate( cx, &self.window, history_handling, reload_triggered, load_data, ); } /// Get if this `Location`'s [relevant `Document`][1] is non-null. /// /// [1]: https://html.spec.whatwg.org/multipage/#relevant-document fn has_document(&self) -> bool { // <https://html.spec.whatwg.org/multipage/#relevant-document> // // > A `Location` object has an associated relevant `Document`, which is // > this `Location` object's relevant global object's browsing // > context's active document, if this `Location` object's relevant // > global object's browsing context is non-null, and null otherwise. self.window.Document().browsing_context().is_some() } /// Get this `Location` object's [relevant `Document`][1], or /// `Err(Error::Security(..))` if it's non-null and its origin is not same /// origin-domain with the entry setting object's origin. /// /// In the specification's terms: /// /// 1. If this `Location` object's relevant `Document` is null, then return /// null. /// /// 2. If this `Location` object's relevant `Document`'s origin is not same /// origin-domain with the entry settings object's origin, then throw a /// "`SecurityError`" `DOMException`. /// /// 3. Return this `Location` object's relevant `Document`. /// /// [1]: https://html.spec.whatwg.org/multipage/#relevant-document fn document_if_same_origin(&self) -> Fallible<Option<DomRoot<Document>>> { // <https://html.spec.whatwg.org/multipage/#relevant-document> // // > A `Location` object has an associated relevant `Document`, which is // > this `Location` object's relevant global object's browsing // > context's active document, if this `Location` object's relevant // > global object's browsing context is non-null, and null otherwise. if let Some(window_proxy) = self.window.Document().browsing_context() { // `Location`'s many other operations: // // > If this `Location` object's relevant `Document` is non-null and // > its origin is not same origin-domain with the entry settings // > object's origin, then throw a "SecurityError" `DOMException`. // // FIXME: We should still return the active document if it's same // origin but not fully active. `WindowProxy::document` // currently returns `None` in this case. if let Some(document) = window_proxy.document().filter(|document| { self.entry_settings_object() .origin() .same_origin_domain(&document.origin()) }) { Ok(Some(document)) } else { Err(Error::Security("Location's relevant Document is not same origin-domain with the entry settings object's origin".to_string().into())) } } else { // The browsing context is null Ok(None) } } /// Get this `Location` object's [relevant url][1] or /// `Err(Error::Security(..))` if the [relevant `Document`][2] if it's non-null /// and its origin is not same origin-domain with the entry setting object's /// origin. /// /// [1]: https://html.spec.whatwg.org/multipage/#concept-location-url /// [2]: https://html.spec.whatwg.org/multipage/#relevant-document fn get_url_if_same_origin(&self) -> Fallible<ServoUrl> { Ok(if let Some(document) = self.document_if_same_origin()? { document.url() } else { ServoUrl::parse("about:blank").unwrap() }) } fn entry_settings_object(&self) -> DomRoot<GlobalScope> { GlobalScope::entry() } /// The common algorithm for `Location`'s setters and `Location::Assign`. #[inline] fn setter_common( &self, cx: &mut JSContext, f: impl FnOnce(ServoUrl) -> Fallible<Option<ServoUrl>>, ) -> ErrorResult { // Step 1: If this Location object's relevant Document is null, then return. // Step 2: If this Location object's relevant Document's origin is not // same origin-domain with the entry settings object's origin, then // throw a "SecurityError" DOMException. if let Some(document) = self.document_if_same_origin()? { // Step 3: Let copyURL be a copy of this Location object's url. // Step 4: Assign the result of running f(copyURL) to copyURL. if let Some(copy_url) = f(document.url())? { // Step 5: Terminate these steps if copyURL is null. // Step 6: Location-object navigate to copyURL. self.navigate( cx, copy_url, NavigationHistoryBehavior::Push, NavigationType::Normal, ); } } Ok(()) } /// Perform a user-requested reload (the unlabeled paragraph after /// [`reload()`][1]). /// /// [1]: https://html.spec.whatwg.org/multipage/#dom-location-reload pub(crate) fn reload_without_origin_check(&self, cx: &mut JSContext) { // > When a user requests that the active document of a browsing context // > be reloaded through a user interface element, the user agent should // > navigate the browsing context to the same resource as that // > `Document`, with `historyHandling` set to "reload". let url = self.window.get_url(); self.navigate( cx, url, NavigationHistoryBehavior::Replace, NavigationType::ReloadByConstellation, ); } } impl LocationMethods<crate::DomTypeHolder> for Location { /// <https://html.spec.whatwg.org/multipage/#dom-location-assign> fn Assign(&self, cx: &mut JSContext, url: USVString) -> ErrorResult { self.setter_common(cx, |_copy_url| { // Step 3: Let urlRecord be the result of encoding-parsing a URL given url, relative to the entry settings object. If that failed, // throw a "SyntaxError" DOMException. let url = match self.entry_settings_object().encoding_parse_a_url(&url.0) { Ok(url) => url, Err(e) => return Err(Error::Syntax(Some(format!("Couldn't parse URL: {}", e)))), }; Ok(Some(url)) }) } /// <https://html.spec.whatwg.org/multipage/#dom-location-reload> fn Reload(&self, cx: &mut JSContext) -> ErrorResult { let url = self.get_url_if_same_origin()?; self.navigate( cx, url, NavigationHistoryBehavior::Replace, NavigationType::ReloadByScript, ); Ok(()) } /// <https://html.spec.whatwg.org/multipage/#dom-location-replace> fn Replace(&self, cx: &mut JSContext, url: USVString) -> ErrorResult { // Step 1: If this Location object's relevant Document is null, then return. if self.has_document() { // Step 2. Let urlRecord be the result of encoding-parsing a URL given url, relative to the entry settings object. let url = match self.entry_settings_object().encoding_parse_a_url(&url.0) { Ok(url) => url, // Step 3. If urlRecord is failure, then throw a "SyntaxError" DOMException. Err(e) => return Err(Error::Syntax(Some(format!("Couldn't parse URL: {}", e)))), }; // Step 4. Location-object navigate this to urlRecord given "replace". self.navigate_a_location(cx, url, NavigationHistoryBehavior::Replace); } Ok(()) } /// <https://html.spec.whatwg.org/multipage/#dom-location-hash> fn GetHash(&self) -> Fallible<USVString> { Ok(UrlHelper::Hash(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-hash> fn SetHash(&self, cx: &mut JSContext, value: USVString) -> ErrorResult { // Step 1. If this's relevant Document is null, then return. if self.has_document() { // Step 2. If this's relevant Document's origin is not same origin-domain // with the entry settings object's origin, then throw a "SecurityError" DOMException. // Step 3. Let copyURL be a copy of this's url. let mut copy_url = self.get_url_if_same_origin()?; // Step 4. Let thisURLFragment be copyURL's fragment if it is non-null; otherwise the empty string. let this_url_fragment = copy_url.fragment().map(str::to_owned).unwrap_or_default(); // Step 6. Set copyURL's fragment to the empty string. // Step 7. Basic URL parse input, with copyURL as url and fragment state as state override. let input = &value.0; // Note that if the hash is the empty string, we shouldn't then set the fragment to `None`. // That's because the empty string is a valid hash target and should then scroll to the // top of the document. Therefore, we don't use `UrlHelpers::SetHash` here, which would // set it to `None`. copy_url.set_fragment(match input { // Step 5. Let input be the given value with a single leading "#" removed, if any. _ if input.starts_with('#') => Some(&input[1..]), _ => Some(input), }); // Step 8. If copyURL's fragment is thisURLFragment, then return. if copy_url.fragment() != Some(&this_url_fragment) { // Step 9. Location-object navigate this to copyURL. self.navigate_a_location(cx, copy_url, NavigationHistoryBehavior::Auto); } } Ok(()) } /// <https://html.spec.whatwg.org/multipage/#dom-location-host> fn GetHost(&self) -> Fallible<USVString> { Ok(UrlHelper::Host(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-host> fn SetHost(&self, cx: &mut JSContext, value: USVString) -> ErrorResult { self.setter_common(cx, |mut copy_url| { // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps. if copy_url.cannot_be_a_base() { return Ok(None); } // Step 5: Basic URL parse the given value, with copyURL as url and host state // as state override. let _ = copy_url.as_mut_url().set_host(Some(&value.0)); Ok(Some(copy_url)) }) } /// <https://html.spec.whatwg.org/multipage/#dom-location-origin> fn GetOrigin(&self) -> Fallible<USVString> { Ok(UrlHelper::Origin(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-hostname> fn GetHostname(&self) -> Fallible<USVString> { Ok(UrlHelper::Hostname(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-hostname> fn SetHostname(&self, cx: &mut JSContext, value: USVString) -> ErrorResult { self.setter_common(cx, |mut copy_url| { // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps. if copy_url.cannot_be_a_base() { return Ok(None); } // Step 5: Basic URL parse the given value, with copyURL as url and hostname // state as state override. let _ = copy_url.as_mut_url().set_host(Some(&value.0)); Ok(Some(copy_url)) }) } /// <https://html.spec.whatwg.org/multipage/#dom-location-href> fn GetHref(&self) -> Fallible<USVString> { Ok(UrlHelper::Href(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-href> fn SetHref(&self, cx: &mut JSContext, value: USVString) -> ErrorResult { // Step 1. If this's relevant Document is null, then return. if self.has_document() { // Note: no call to self.check_same_origin_domain() // Step 2: Let url be the result of encoding-parsing a URL given the given value, relative to the entry settings object. let url = match self.entry_settings_object().encoding_parse_a_url(&value.0) { Ok(url) => url, // Step 3: If url is failure, then throw a "SyntaxError" DOMException. Err(e) => return Err(Error::Syntax(Some(format!("Couldn't parse URL: {}", e)))), }; // Step 4: Location-object navigate this to url. self.navigate_a_location(cx, url, NavigationHistoryBehavior::Auto); } Ok(()) } /// <https://html.spec.whatwg.org/multipage/#dom-location-pathname> fn GetPathname(&self) -> Fallible<USVString> { Ok(UrlHelper::Pathname(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-pathname> fn SetPathname(&self, cx: &mut JSContext, value: USVString) -> ErrorResult { self.setter_common(cx, |mut copy_url| { // Step 4: If copyURL's cannot-be-a-base-URL flag is set, terminate these steps. if copy_url.cannot_be_a_base() { return Ok(None); } // Step 5: Set copyURL's path to the empty list. // Step 6: Basic URL parse the given value, with copyURL as url and path // start state as state override. copy_url.as_mut_url().set_path(&value.0); Ok(Some(copy_url)) }) } /// <https://html.spec.whatwg.org/multipage/#dom-location-port> fn GetPort(&self) -> Fallible<USVString> { Ok(UrlHelper::Port(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-port> fn SetPort(&self, cx: &mut JSContext, value: USVString) -> ErrorResult { self.setter_common(cx, |mut copy_url| { // Step 4: If copyURL cannot have a username/password/port, then return. // https://url.spec.whatwg.org/#cannot-have-a-username-password-port if copy_url.host().is_none() || copy_url.cannot_be_a_base() || copy_url.scheme() == "file" { return Ok(None); } // Step 5: If the given value is the empty string, then set copyURL's // port to null. // Step 6: Otherwise, basic URL parse the given value, with copyURL as url // and port state as state override. let _ = url::quirks::set_port(copy_url.as_mut_url(), &value.0); Ok(Some(copy_url)) }) } /// <https://html.spec.whatwg.org/multipage/#dom-location-protocol> fn GetProtocol(&self) -> Fallible<USVString> { Ok(UrlHelper::Protocol(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-protocol> fn SetProtocol(&self, cx: &mut JSContext, value: USVString) -> ErrorResult { self.setter_common(cx, |mut copy_url| { // Step 4: Let possibleFailure be the result of basic URL parsing the given // value, followed by ":", with copyURL as url and scheme start state as // state override. let scheme = match value.0.find(':') { Some(position) => &value.0[..position], None => &value.0, }; if copy_url.as_mut_url().set_scheme(scheme).is_err() { // Step 5: If possibleFailure is failure, then throw a "SyntaxError" DOMException. return Err(Error::Syntax( "Couldn't parse URL scheme".to_string().into(), )); } // Step 6: If copyURL's scheme is not an HTTP(S) scheme, then terminate these steps. if !copy_url.scheme().eq_ignore_ascii_case("http") && !copy_url.scheme().eq_ignore_ascii_case("https") { return Ok(None); } Ok(Some(copy_url)) }) } /// <https://html.spec.whatwg.org/multipage/#dom-location-search> fn GetSearch(&self) -> Fallible<USVString> { Ok(UrlHelper::Search(&self.get_url_if_same_origin()?)) } /// <https://html.spec.whatwg.org/multipage/#dom-location-search> fn SetSearch(&self, cx: &mut JSContext, value: USVString) -> ErrorResult { self.setter_common(cx, |mut copy_url| { // Step 4: If the given value is the empty string, set copyURL's query to null. // Step 5: Otherwise, run these substeps: // 1. Let input be the given value with a single leading "?" removed, if any. // 2. Set copyURL's query to the empty string. // 3. Basic URL parse input, with copyURL as url and query state as state // override, and the relevant Document's document's character encoding as // encoding override. copy_url.as_mut_url().set_query(match value.0.as_str() { "" => None, _ if value.0.starts_with('?') => Some(&value.0[1..]), _ => Some(&value.0), }); Ok(Some(copy_url)) }) } }