1use std::cell::Cell;
6use std::cmp::Ordering;
7
8use base::IpcSend;
9use base::id::HistoryStateId;
10use constellation_traits::{
11 ScriptToConstellationMessage, StructuredSerializedData, TraversalDirection,
12};
13use dom_struct::dom_struct;
14use js::jsapi::Heap;
15use js::jsval::{JSVal, NullValue, UndefinedValue};
16use js::rust::{HandleValue, MutableHandleValue};
17use net_traits::CoreResourceMsg;
18use profile_traits::ipc;
19use profile_traits::ipc::channel;
20use servo_url::ServoUrl;
21
22use crate::dom::bindings::codegen::Bindings::HistoryBinding::HistoryMethods;
23use crate::dom::bindings::codegen::Bindings::LocationBinding::Location_Binding::LocationMethods;
24use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
25use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
26use crate::dom::bindings::inheritance::Castable;
27use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
28use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot};
29use crate::dom::bindings::str::{DOMString, USVString};
30use crate::dom::bindings::structuredclone;
31use crate::dom::event::Event;
32use crate::dom::eventtarget::EventTarget;
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::hashchangeevent::HashChangeEvent;
35use crate::dom::popstateevent::PopStateEvent;
36use crate::dom::window::Window;
37use crate::script_runtime::{CanGc, JSContext};
38
39enum PushOrReplace {
40 Push,
41 Replace,
42}
43
44#[dom_struct]
46pub(crate) struct History {
47 reflector_: Reflector,
48 window: Dom<Window>,
49 #[ignore_malloc_size_of = "mozjs"]
50 state: Heap<JSVal>,
51 #[no_trace]
52 state_id: Cell<Option<HistoryStateId>>,
53}
54
55impl History {
56 pub(crate) fn new_inherited(window: &Window) -> History {
57 History {
58 reflector_: Reflector::new(),
59 window: Dom::from_ref(window),
60 state: Heap::default(),
61 state_id: Cell::new(None),
62 }
63 }
64
65 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<History> {
66 let dom_root = reflect_dom_object(Box::new(History::new_inherited(window)), window, can_gc);
67 dom_root.state.set(NullValue());
68 dom_root
69 }
70}
71
72impl History {
73 fn traverse_history(&self, direction: TraversalDirection) -> ErrorResult {
74 if !self.window.Document().is_fully_active() {
75 return Err(Error::Security);
76 }
77 let msg = ScriptToConstellationMessage::TraverseHistory(direction);
78 let _ = self
79 .window
80 .as_global_scope()
81 .script_to_constellation_chan()
82 .send(msg);
83 Ok(())
84 }
85
86 pub(crate) fn activate_state(
89 &self,
90 state_id: Option<HistoryStateId>,
91 url: ServoUrl,
92 can_gc: CanGc,
93 ) {
94 let document = self.window.Document();
96 let old_url = document.url().clone();
97 document.set_url(url.clone());
98
99 let hash_changed = old_url.fragment() != url.fragment();
101
102 if let Some(fragment) = url.fragment() {
104 document.check_and_scroll_fragment(fragment);
105 }
106
107 let state_changed = state_id != self.state_id.get();
109 self.state_id.set(state_id);
110 let serialized_data = match state_id {
111 Some(state_id) => {
112 let (tx, rx) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
113 let _ = self
114 .window
115 .as_global_scope()
116 .resource_threads()
117 .send(CoreResourceMsg::GetHistoryState(state_id, tx));
118 rx.recv().unwrap()
119 },
120 None => None,
121 };
122
123 match serialized_data {
124 Some(data) => {
125 let data = StructuredSerializedData {
126 serialized: data,
127 ..Default::default()
128 };
129 rooted!(in(*GlobalScope::get_cx()) let mut state = UndefinedValue());
130 if structuredclone::read(self.window.as_global_scope(), data, state.handle_mut())
131 .is_err()
132 {
133 warn!("Error reading structuredclone data");
134 }
135 self.state.set(state.get());
136 },
137 None => {
138 self.state.set(NullValue());
139 },
140 }
141
142 if state_changed {
145 PopStateEvent::dispatch_jsval(
146 self.window.upcast::<EventTarget>(),
147 &self.window,
148 self.state.as_handle_value(),
149 can_gc,
150 );
151 }
152
153 if hash_changed {
155 let event = HashChangeEvent::new(
156 &self.window,
157 atom!("hashchange"),
158 false,
159 false,
160 old_url.into_string(),
161 url.into_string(),
162 can_gc,
163 );
164 event
165 .upcast::<Event>()
166 .fire(self.window.upcast::<EventTarget>(), can_gc);
167 }
168 }
169
170 pub(crate) fn remove_states(&self, states: Vec<HistoryStateId>) {
171 let _ = self
172 .window
173 .as_global_scope()
174 .resource_threads()
175 .send(CoreResourceMsg::RemoveHistoryStates(states));
176 }
177
178 fn push_or_replace_state(
181 &self,
182 cx: JSContext,
183 data: HandleValue,
184 _title: DOMString,
185 url: Option<USVString>,
186 push_or_replace: PushOrReplace,
187 ) -> ErrorResult {
188 let document = self.window.Document();
190
191 if !document.is_fully_active() {
193 return Err(Error::Security);
194 }
195
196 let serialized_data = structuredclone::write(cx, data, None)?;
201
202 let new_url: ServoUrl = match url {
204 Some(urlstring) => {
206 let document_url = document.url();
207
208 let Ok(url) = ServoUrl::parse_with_base(Some(&document_url), &urlstring.0) else {
211 return Err(Error::Security);
213 };
214
215 if !Self::can_have_url_rewritten(&document_url, &url) {
218 return Err(Error::Security);
219 }
220
221 url
222 },
223 None => document.url(),
224 };
225
226 let state_id = match push_or_replace {
228 PushOrReplace::Push => {
229 let state_id = HistoryStateId::new();
230 self.state_id.set(Some(state_id));
231 let msg = ScriptToConstellationMessage::PushHistoryState(state_id, new_url.clone());
232 let _ = self
233 .window
234 .as_global_scope()
235 .script_to_constellation_chan()
236 .send(msg);
237 state_id
238 },
239 PushOrReplace::Replace => {
240 let state_id = match self.state_id.get() {
241 Some(state_id) => state_id,
242 None => {
243 let state_id = HistoryStateId::new();
244 self.state_id.set(Some(state_id));
245 state_id
246 },
247 };
248 let msg =
249 ScriptToConstellationMessage::ReplaceHistoryState(state_id, new_url.clone());
250 let _ = self
251 .window
252 .as_global_scope()
253 .script_to_constellation_chan()
254 .send(msg);
255 state_id
256 },
257 };
258
259 let _ = self.window.as_global_scope().resource_threads().send(
260 CoreResourceMsg::SetHistoryState(state_id, serialized_data.serialized.clone()),
261 );
262
263 document.set_url(new_url);
268
269 rooted!(in(*cx) let mut state = UndefinedValue());
271 if structuredclone::read(
272 self.window.as_global_scope(),
273 serialized_data,
274 state.handle_mut(),
275 )
276 .is_err()
277 {
278 warn!("Error reading structuredclone data");
279 }
280
281 self.state.set(state.get());
283
284 Ok(())
288 }
289
290 fn can_have_url_rewritten(document_url: &ServoUrl, target_url: &ServoUrl) -> bool {
293 if target_url.scheme() != document_url.scheme() ||
296 target_url.username() != document_url.username() ||
297 target_url.password() != document_url.password() ||
298 target_url.host() != document_url.host() ||
299 target_url.port() != document_url.port()
300 {
301 return false;
302 }
303
304 if target_url.scheme() == "http" || target_url.scheme() == "https" {
306 return true;
307 }
308
309 if target_url.scheme() == "file" {
311 return target_url.path() == document_url.path();
314 }
315
316 if target_url.path() != document_url.path() || target_url.query() != document_url.query() {
319 return false;
320 }
321
322 true
324 }
325}
326
327impl HistoryMethods<crate::DomTypeHolder> for History {
328 fn GetState(&self, _cx: JSContext, mut retval: MutableHandleValue) -> Fallible<()> {
330 if !self.window.Document().is_fully_active() {
331 return Err(Error::Security);
332 }
333 retval.set(self.state.get());
334 Ok(())
335 }
336
337 fn GetLength(&self) -> Fallible<u32> {
339 if !self.window.Document().is_fully_active() {
340 return Err(Error::Security);
341 }
342 let (sender, recv) = channel(self.global().time_profiler_chan().clone())
343 .expect("Failed to create channel to send jsh length.");
344 let msg = ScriptToConstellationMessage::JointSessionHistoryLength(sender);
345 let _ = self
346 .window
347 .as_global_scope()
348 .script_to_constellation_chan()
349 .send(msg);
350 Ok(recv.recv().unwrap())
351 }
352
353 fn Go(&self, delta: i32, can_gc: CanGc) -> ErrorResult {
355 let direction = match delta.cmp(&0) {
356 Ordering::Greater => TraversalDirection::Forward(delta as usize),
357 Ordering::Less => TraversalDirection::Back(-delta as usize),
358 Ordering::Equal => return self.window.Location().Reload(can_gc),
359 };
360
361 self.traverse_history(direction)
362 }
363
364 fn Back(&self) -> ErrorResult {
366 self.traverse_history(TraversalDirection::Back(1))
367 }
368
369 fn Forward(&self) -> ErrorResult {
371 self.traverse_history(TraversalDirection::Forward(1))
372 }
373
374 fn PushState(
376 &self,
377 cx: JSContext,
378 data: HandleValue,
379 title: DOMString,
380 url: Option<USVString>,
381 ) -> ErrorResult {
382 self.push_or_replace_state(cx, data, title, url, PushOrReplace::Push)
383 }
384
385 fn ReplaceState(
387 &self,
388 cx: JSContext,
389 data: HandleValue,
390 title: DOMString,
391 url: Option<USVString>,
392 ) -> ErrorResult {
393 self.push_or_replace_state(cx, data, title, url, PushOrReplace::Replace)
394 }
395}