/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/window/history.rs
409 строк
14 KB
Tim van der Lippe
script: Move window interfaces into window/ (#45843)
20 июн 2026, 18:16
Не верифицирован
20 июн 2026, 18:16
e01601d
Код
Авторство
О чём код?
/* 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 std::cell::Cell; use std::cmp::Ordering; use dom_struct::dom_struct; use js::context::JSContext; use js::jsapi::Heap; use js::jsval::{JSVal, NullValue, UndefinedValue}; use js::rust::{HandleValue, MutableHandleValue}; use net_traits::CoreResourceMsg; use profile_traits::generic_channel; use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx}; use servo_base::generic_channel::GenericSend; use servo_base::id::HistoryStateId; use servo_constellation_traits::{ ScriptToConstellationMessage, StructuredSerializedData, TraversalDirection, }; use servo_url::ServoUrl; use crate::dom::bindings::codegen::Bindings::HistoryBinding::HistoryMethods; use crate::dom::bindings::codegen::Bindings::LocationBinding::Location_Binding::LocationMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::structuredclone; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::hashchangeevent::HashChangeEvent; use crate::dom::popstateevent::PopStateEvent; use crate::dom::window::Window; enum PushOrReplace { Push, Replace, } /// <https://html.spec.whatwg.org/multipage/#the-history-interface> #[dom_struct] pub(crate) struct History { reflector_: Reflector, window: Dom<Window>, #[ignore_malloc_size_of = "mozjs"] state: Heap<JSVal>, #[no_trace] state_id: Cell<Option<HistoryStateId>>, } impl History { pub(crate) fn new_inherited(window: &Window) -> History { History { reflector_: Reflector::new(), window: Dom::from_ref(window), state: Heap::default(), state_id: Cell::new(None), } } pub(crate) fn new(cx: &mut JSContext, window: &Window) -> DomRoot<History> { let dom_root = reflect_dom_object_with_cx(Box::new(History::new_inherited(window)), window, cx); dom_root.state.set(NullValue()); dom_root } } impl History { fn traverse_history(&self, direction: TraversalDirection) -> ErrorResult { if !self.window.Document().is_fully_active() { return Err(Error::Security(None)); } let msg = ScriptToConstellationMessage::TraverseHistory(direction); let _ = self .window .as_global_scope() .script_to_constellation_chan() .send(msg); Ok(()) } /// <https://html.spec.whatwg.org/multipage/#history-traversal> /// Steps 5-16 pub(crate) fn activate_state( &self, cx: &mut JSContext, state_id: Option<HistoryStateId>, url: ServoUrl, ) { // Steps 5 let document = self.window.Document(); let old_url = document.url(); document.set_url(url.clone()); // Step 6 let hash_changed = old_url.fragment() != url.fragment(); // Step 8 if let Some(fragment) = url.fragment() { document.scroll_to_the_fragment(cx, fragment); } // Step 11 let state_changed = state_id != self.state_id.get(); self.state_id.set(state_id); let serialized_data = match state_id { Some(state_id) => { let (tx, rx) = generic_channel::channel(self.global().time_profiler_chan().clone()).unwrap(); let _ = self .window .as_global_scope() .resource_threads() .send(CoreResourceMsg::GetHistoryState(state_id, tx)); rx.recv().unwrap() }, None => None, }; match serialized_data { Some(data) => { let data = StructuredSerializedData { serialized: data, ..Default::default() }; rooted!(&in(cx) let mut state = UndefinedValue()); if structuredclone::read( cx, self.window.as_global_scope(), data, state.handle_mut(), ) .is_err() { warn!("Error reading structuredclone data"); } self.state.set(state.get()); }, None => { self.state.set(NullValue()); }, } // TODO: Queue events on DOM Manipulation task source if non-blocking flag is set. // Step 16.1 if state_changed { PopStateEvent::dispatch_jsval( cx, self.window.upcast::<EventTarget>(), &self.window, self.state.as_handle_value(), ); } // Step 16.3 if hash_changed { let event = HashChangeEvent::new( cx, &self.window, atom!("hashchange"), false, false, old_url.into_string(), url.into_string(), ); event .upcast::<Event>() .fire(cx, self.window.upcast::<EventTarget>()); } } pub(crate) fn remove_states(&self, states: Vec<HistoryStateId>) { let _ = self .window .as_global_scope() .resource_threads() .send(CoreResourceMsg::RemoveHistoryStates(states)); } /// <https://html.spec.whatwg.org/multipage/#dom-history-pushstate> /// <https://html.spec.whatwg.org/multipage/#dom-history-replacestate> fn push_or_replace_state( &self, cx: &mut JSContext, data: HandleValue, _title: DOMString, url: Option<USVString>, push_or_replace: PushOrReplace, ) -> ErrorResult { // Step 1 let document = self.window.Document(); // Step 2 if !document.is_fully_active() { return Err(Error::Security(None)); } // TODO: Step 3 Optionally abort these steps // https://github.com/servo/servo/issues/19159 // Step 4. Let serializedData be StructuredSerializeForStorage(data). Rethrow any exceptions. let serialized_data = structuredclone::write(cx, data, None)?; // Step 5. Let newURL be document's URL. let new_url: ServoUrl = match url { // Step 6. If url is not null or the empty string, then: Some(urlstring) => { let document_url = document.url(); // Step 6.1 Set newURL to the result of encoding-parsing a URL given url, // relative to the relevant settings object of history. let Ok(url) = ServoUrl::parse_with_base(Some(&document_url), &urlstring.0) else { // Step 6.2 If newURL is failure, then throw a "SecurityError" DOMException. return Err(Error::Security(None)); }; // Step 6.3 If document cannot have its URL rewritten to newURL, // then throw a "SecurityError" DOMException. if !Self::can_have_url_rewritten(&document_url, &url) { return Err(Error::Security(None)); } url }, None => document.url(), }; // Step 8 let state_id = match push_or_replace { PushOrReplace::Push => { let state_id = HistoryStateId::new(); self.state_id.set(Some(state_id)); let msg = ScriptToConstellationMessage::PushHistoryState(state_id, new_url.clone()); let _ = self .window .as_global_scope() .script_to_constellation_chan() .send(msg); state_id }, PushOrReplace::Replace => { let state_id = match self.state_id.get() { Some(state_id) => state_id, None => { let state_id = HistoryStateId::new(); self.state_id.set(Some(state_id)); state_id }, }; let msg = ScriptToConstellationMessage::ReplaceHistoryState(state_id, new_url.clone()); let _ = self .window .as_global_scope() .script_to_constellation_chan() .send(msg); state_id }, }; let _ = self.window.as_global_scope().resource_threads().send( CoreResourceMsg::SetHistoryState(state_id, serialized_data.serialized.clone()), ); // TODO: Step 9 Update current entry to represent a GET request // https://github.com/servo/servo/issues/19156 // Step 10 document.set_url(new_url); // Step 11 rooted!(&in(cx) let mut state = UndefinedValue()); if structuredclone::read( cx, self.window.as_global_scope(), serialized_data, state.handle_mut(), ) .is_err() { warn!("Error reading structuredclone data"); } // Step 12 self.state.set(state.get()); // TODO: Step 13 Update Document's latest entry to current entry // https://github.com/servo/servo/issues/19158 Ok(()) } /// <https://html.spec.whatwg.org/multipage/#can-have-its-url-rewritten> /// Step 2-6 fn can_have_url_rewritten(document_url: &ServoUrl, target_url: &ServoUrl) -> bool { // Step 2. If targetURL and documentURL differ in their scheme, username, // password, host, or port components, then return false. if target_url.scheme() != document_url.scheme() || target_url.username() != document_url.username() || target_url.password() != document_url.password() || target_url.host() != document_url.host() || target_url.port() != document_url.port() { return false; } // Step 3. If targetURL's scheme is an HTTP(S) scheme, then return true. if target_url.scheme() == "http" || target_url.scheme() == "https" { return true; } // Step 4. If targetURL's scheme is "file", then: if target_url.scheme() == "file" { // Step 4.1 If targetURL and documentURL differ in their path component, then return false. // Step 4.2 Return true. return target_url.path() == document_url.path(); } // Step 5. If targetURL and documentURL differ in their path component // or query components, then return false. if target_url.path() != document_url.path() || target_url.query() != document_url.query() { return false; } // Step 6. Return true. true } } impl HistoryMethods<crate::DomTypeHolder> for History { /// <https://html.spec.whatwg.org/multipage/#dom-history-state> fn GetState(&self, _cx: &mut JSContext, mut retval: MutableHandleValue) -> Fallible<()> { if !self.window.Document().is_fully_active() { return Err(Error::Security(None)); } retval.set(self.state.get()); Ok(()) } /// <https://html.spec.whatwg.org/multipage/#dom-history-length> fn GetLength(&self) -> Fallible<u32> { if !self.window.Document().is_fully_active() { return Err(Error::Security(None)); } let Some((sender, recv)) = generic_channel::channel(self.global().time_profiler_chan().clone()) else { return Err(Error::InvalidState(None)); }; let msg = ScriptToConstellationMessage::JointSessionHistoryLength(sender); self.window .as_global_scope() .script_to_constellation_chan() .send(msg) .map_err(|_| Error::InvalidState(None))?; recv.recv().map_err(|_| Error::InvalidState(None)) } /// <https://html.spec.whatwg.org/multipage/#dom-history-go> fn Go(&self, cx: &mut JSContext, delta: i32) -> ErrorResult { let direction = match delta.cmp(&0) { Ordering::Greater => TraversalDirection::Forward(delta as usize), Ordering::Less => TraversalDirection::Back(-delta as usize), Ordering::Equal => return self.window.Location(cx).Reload(cx), }; self.traverse_history(direction) } /// <https://html.spec.whatwg.org/multipage/#dom-history-back> fn Back(&self) -> ErrorResult { self.traverse_history(TraversalDirection::Back(1)) } /// <https://html.spec.whatwg.org/multipage/#dom-history-forward> fn Forward(&self) -> ErrorResult { self.traverse_history(TraversalDirection::Forward(1)) } /// <https://html.spec.whatwg.org/multipage/#dom-history-pushstate> fn PushState( &self, cx: &mut JSContext, data: HandleValue, title: DOMString, url: Option<USVString>, ) -> ErrorResult { self.push_or_replace_state(cx, data, title, url, PushOrReplace::Push) } /// <https://html.spec.whatwg.org/multipage/#dom-history-replacestate> fn ReplaceState( &self, cx: &mut JSContext, data: HandleValue, title: DOMString, url: Option<USVString>, ) -> ErrorResult { self.push_or_replace_state(cx, data, title, url, PushOrReplace::Replace) } }