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