aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/history.rs
diff options
context:
space:
mode:
authoryvt <i@yvt.jp>2021-07-10 17:24:27 +0900
committeryvt <i@yvt.jp>2021-07-10 17:55:42 +0900
commit01a7de50ab1843d85295f9dccad7f4c099e7208c (patch)
treeee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/dom/history.rs
parentff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff)
parent94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff)
downloadservo-01a7de50ab1843d85295f9dccad7f4c099e7208c.tar.gz
servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.zip
Merge remote-tracking branch 'upstream/master' into feat-cow-infra
`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html` was reverted to the upstream version.
Diffstat (limited to 'components/script/dom/history.rs')
-rw-r--r--components/script/dom/history.rs346
1 files changed, 307 insertions, 39 deletions
diff --git a/components/script/dom/history.rs b/components/script/dom/history.rs
index 537e9cbbb22..9023a397c66 100644
--- a/components/script/dom/history.rs
+++ b/components/script/dom/history.rs
@@ -1,66 +1,313 @@
/* 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 http://mozilla.org/MPL/2.0/. */
-
-use dom::bindings::codegen::Bindings::HistoryBinding;
-use dom::bindings::codegen::Bindings::HistoryBinding::HistoryMethods;
-use dom::bindings::codegen::Bindings::LocationBinding::LocationBinding::LocationMethods;
-use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
-use dom::bindings::error::Fallible;
-use dom::bindings::inheritance::Castable;
-use dom::bindings::js::{JS, Root};
-use dom::bindings::reflector::{Reflector, reflect_dom_object};
-use dom::globalscope::GlobalScope;
-use dom::window::Window;
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::codegen::Bindings::HistoryBinding::HistoryMethods;
+use crate::dom::bindings::codegen::Bindings::LocationBinding::LocationBinding::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::{reflect_dom_object, DomObject, Reflector};
+use crate::dom::bindings::root::{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::globalscope::GlobalScope;
+use crate::dom::hashchangeevent::HashChangeEvent;
+use crate::dom::popstateevent::PopStateEvent;
+use crate::dom::window::Window;
+use crate::script_runtime::JSContext;
use dom_struct::dom_struct;
-use ipc_channel::ipc;
-use msg::constellation_msg::TraversalDirection;
-use script_traits::ScriptMsg as ConstellationMsg;
+use js::jsapi::Heap;
+use js::jsval::{JSVal, NullValue, UndefinedValue};
+use js::rust::HandleValue;
+use msg::constellation_msg::{HistoryStateId, TraversalDirection};
+use net_traits::{CoreResourceMsg, IpcSend};
+use profile_traits::ipc;
+use profile_traits::ipc::channel;
+use script_traits::{ScriptMsg, StructuredSerializedData};
+use servo_url::ServoUrl;
+use std::cell::Cell;
+
+enum PushOrReplace {
+ Push,
+ Replace,
+}
// https://html.spec.whatwg.org/multipage/#the-history-interface
#[dom_struct]
pub struct History {
reflector_: Reflector,
- window: JS<Window>,
+ window: Dom<Window>,
+ #[ignore_malloc_size_of = "mozjs"]
+ state: Heap<JSVal>,
+ state_id: Cell<Option<HistoryStateId>>,
}
impl History {
pub fn new_inherited(window: &Window) -> History {
+ let state = Heap::default();
+ state.set(NullValue());
History {
reflector_: Reflector::new(),
- window: JS::from_ref(&window),
+ window: Dom::from_ref(&window),
+ state: state,
+ state_id: Cell::new(None),
}
}
- pub fn new(window: &Window) -> Root<History> {
- reflect_dom_object(box History::new_inherited(window),
- window,
- HistoryBinding::Wrap)
+ pub fn new(window: &Window) -> DomRoot<History> {
+ reflect_dom_object(Box::new(History::new_inherited(window)), window)
}
}
impl History {
- fn traverse_history(&self, direction: TraversalDirection) {
+ fn traverse_history(&self, direction: TraversalDirection) -> ErrorResult {
+ if !self.window.Document().is_fully_active() {
+ return Err(Error::Security);
+ }
+ let msg = ScriptMsg::TraverseHistory(direction);
+ let _ = self
+ .window
+ .upcast::<GlobalScope>()
+ .script_to_constellation_chan()
+ .send(msg);
+ Ok(())
+ }
+
+ // https://html.spec.whatwg.org/multipage/#history-traversal
+ // Steps 5-16
+ #[allow(unsafe_code)]
+ pub fn activate_state(&self, state_id: Option<HistoryStateId>, url: ServoUrl) {
+ // Steps 5
+ let document = self.window.Document();
+ let old_url = document.url().clone();
+ document.set_url(url.clone());
+
+ // Step 6
+ let hash_changed = old_url.fragment() != url.fragment();
+
+ // Step 8
+ if let Some(fragment) = url.fragment() {
+ document.check_and_scroll_fragment(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) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
+ let _ = self
+ .window
+ .upcast::<GlobalScope>()
+ .resource_threads()
+ .send(CoreResourceMsg::GetHistoryState(state_id, tx));
+ rx.recv().unwrap()
+ },
+ None => None,
+ };
+
+ match serialized_data {
+ Some(data) => {
+ let data = StructuredSerializedData {
+ serialized: data,
+ ports: None,
+ blobs: None,
+ };
+ let global_scope = self.window.upcast::<GlobalScope>();
+ rooted!(in(*global_scope.get_cx()) let mut state = UndefinedValue());
+ if let Err(_) = structuredclone::read(&global_scope, data, state.handle_mut()) {
+ 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(
+ self.window.upcast::<EventTarget>(),
+ &*self.window,
+ unsafe { HandleValue::from_raw(self.state.handle()) },
+ );
+ }
+
+ // Step 16.3
+ if hash_changed {
+ let event = HashChangeEvent::new(
+ &self.window,
+ atom!("hashchange"),
+ false,
+ false,
+ old_url.into_string(),
+ url.into_string(),
+ );
+ event
+ .upcast::<Event>()
+ .fire(self.window.upcast::<EventTarget>());
+ }
+ }
+
+ pub fn remove_states(&self, states: Vec<HistoryStateId>) {
+ let _ = self
+ .window
+ .upcast::<GlobalScope>()
+ .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: 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);
+ }
+
+ // TODO: Step 3 Optionally abort these steps
+ // https://github.com/servo/servo/issues/19159
+
+ // TODO: Step 4
+
+ // Step 5
+ let serialized_data = structuredclone::write(cx, data, None)?;
+
+ let new_url: ServoUrl = match url {
+ // Step 6
+ Some(urlstring) => {
+ let document_url = document.url();
+
+ // Step 6.1
+ let new_url = match ServoUrl::parse_with_base(Some(&document_url), &urlstring.0) {
+ // Step 6.3
+ Ok(parsed_url) => parsed_url,
+ // Step 6.2
+ Err(_) => return Err(Error::Security),
+ };
+
+ // Step 6.4
+ if new_url.scheme() != document_url.scheme() ||
+ new_url.host() != document_url.host() ||
+ new_url.port() != document_url.port() ||
+ new_url.username() != document_url.username() ||
+ new_url.password() != document_url.password()
+ {
+ return Err(Error::Security);
+ }
+
+ // Step 6.5
+ if new_url.origin() != document_url.origin() {
+ return Err(Error::Security);
+ }
+
+ new_url
+ },
+ // Step 7
+ 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 = ScriptMsg::PushHistoryState(state_id, new_url.clone());
+ let _ = self
+ .window
+ .upcast::<GlobalScope>()
+ .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 = ScriptMsg::ReplaceHistoryState(state_id, new_url.clone());
+ let _ = self
+ .window
+ .upcast::<GlobalScope>()
+ .script_to_constellation_chan()
+ .send(msg);
+ state_id
+ },
+ };
+
+ let _ = self.window.upcast::<GlobalScope>().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
let global_scope = self.window.upcast::<GlobalScope>();
- let pipeline = global_scope.pipeline_id();
- let msg = ConstellationMsg::TraverseHistory(Some(pipeline), direction);
- let _ = global_scope.constellation_chan().send(msg);
+ rooted!(in(*cx) let mut state = UndefinedValue());
+ if let Err(_) = structuredclone::read(&global_scope, serialized_data, state.handle_mut()) {
+ 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(())
}
}
impl HistoryMethods for History {
+ // https://html.spec.whatwg.org/multipage/#dom-history-state
+ fn GetState(&self, _cx: JSContext) -> Fallible<JSVal> {
+ if !self.window.Document().is_fully_active() {
+ return Err(Error::Security);
+ }
+ Ok(self.state.get())
+ }
+
// https://html.spec.whatwg.org/multipage/#dom-history-length
- fn Length(&self) -> u32 {
- let global_scope = self.window.upcast::<GlobalScope>();
- let pipeline = global_scope.pipeline_id();
- let (sender, recv) = ipc::channel().expect("Failed to create channel to send jsh length.");
- let msg = ConstellationMsg::JointSessionHistoryLength(pipeline, sender);
- let _ = global_scope.constellation_chan().send(msg);
- recv.recv().unwrap()
+ fn GetLength(&self) -> Fallible<u32> {
+ if !self.window.Document().is_fully_active() {
+ return Err(Error::Security);
+ }
+ let (sender, recv) = channel(self.global().time_profiler_chan().clone())
+ .expect("Failed to create channel to send jsh length.");
+ let msg = ScriptMsg::JointSessionHistoryLength(sender);
+ let _ = self
+ .window
+ .upcast::<GlobalScope>()
+ .script_to_constellation_chan()
+ .send(msg);
+ Ok(recv.recv().unwrap())
}
// https://html.spec.whatwg.org/multipage/#dom-history-go
- fn Go(&self, delta: i32) -> Fallible<()> {
+ fn Go(&self, delta: i32) -> ErrorResult {
let direction = if delta > 0 {
TraversalDirection::Forward(delta as usize)
} else if delta < 0 {
@@ -69,17 +316,38 @@ impl HistoryMethods for History {
return self.window.Location().Reload();
};
- self.traverse_history(direction);
- Ok(())
+ self.traverse_history(direction)
}
// https://html.spec.whatwg.org/multipage/#dom-history-back
- fn Back(&self) {
- self.traverse_history(TraversalDirection::Back(1));
+ fn Back(&self) -> ErrorResult {
+ self.traverse_history(TraversalDirection::Back(1))
}
// https://html.spec.whatwg.org/multipage/#dom-history-forward
- fn Forward(&self) {
- self.traverse_history(TraversalDirection::Forward(1));
+ fn Forward(&self) -> ErrorResult {
+ self.traverse_history(TraversalDirection::Forward(1))
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-history-pushstate
+ fn PushState(
+ &self,
+ cx: 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: JSContext,
+ data: HandleValue,
+ title: DOMString,
+ url: Option<USVString>,
+ ) -> ErrorResult {
+ self.push_or_replace_state(cx, data, title, url, PushOrReplace::Replace)
}
}