From 326933cff4267951b4036f8f853969bc94316acd Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Wed, 15 Feb 2017 09:47:55 -0600 Subject: [PATCH 1/7] Link frame entries together that share pipelines Various frame-related cleanup --- components/constellation/constellation.rs | 163 ++++++++++------------ components/constellation/frame.rs | 132 ++++++++++++------ 2 files changed, 168 insertions(+), 127 deletions(-) diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 16475c1a7fa5..1f8da322e5dd 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -75,7 +75,7 @@ use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg}; use euclid::scale_factor::ScaleFactor; use euclid::size::{Size2D, TypedSize2D}; use event_loop::EventLoop; -use frame::{Frame, FrameChange, FrameState, FrameTreeIterator, FullFrameTreeIterator}; +use frame::{Frame, FrameChange, FrameState, FrameTreeIterator, FullFrameTreeIterator, ReplaceOrUpdate}; use gfx::font_cache_thread::FontCacheThread; use gfx_traits::Epoch; use ipc_channel::SerializeError; @@ -110,7 +110,6 @@ use servo_remutex::ReentrantMutex; use servo_url::ServoUrl; use std::borrow::ToOwned; use std::collections::{HashMap, VecDeque}; -use std::iter::once; use std::marker::PhantomData; use std::process; use std::rc::{Rc, Weak}; @@ -614,7 +613,7 @@ impl Constellation .map(|pipeline| pipeline.visible); let prev_visibility = self.frames.get(&frame_id) - .and_then(|frame| self.pipelines.get(&frame.pipeline_id)) + .and_then(|frame| self.pipelines.get(&frame.pipeline_id())) .map(|pipeline| pipeline.visible) .or(parent_visibility); @@ -714,7 +713,7 @@ impl Constellation /// frame in the frame tree, sorted reverse chronologically. fn joint_session_past<'a>(&self, frame_id_root: FrameId) -> impl Iterator { let mut past: Vec<(Instant, FrameState)> = self.full_frame_tree_iter(frame_id_root) - .flat_map(|frame| frame.prev.iter().rev().scan(frame.instant, |prev_instant, entry| { + .flat_map(|frame| frame.prev.iter().rev().scan(frame.current.instant, |prev_instant, entry| { let instant = *prev_instant; *prev_instant = entry.instant; Some((instant, entry.clone())) @@ -1249,7 +1248,7 @@ impl Constellation // Notify the browser chrome that the pipeline has failed self.trigger_mozbrowsererror(top_level_frame_id, reason, backtrace); - let pipeline_id = self.frames.get(&top_level_frame_id).map(|frame| frame.pipeline_id); + let pipeline_id = self.frames.get(&top_level_frame_id).map(|frame| frame.pipeline_id()); let pipeline_url = pipeline_id.and_then(|id| self.pipelines.get(&id).map(|pipeline| pipeline.url.clone())); let parent_info = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.parent_info)); let window_size = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.size)); @@ -1409,7 +1408,8 @@ impl Constellation }; let replace = if load_info.info.replace { - self.frames.get(&load_info.info.frame_id).map(|frame| frame.current()) + self.frames.get(&load_info.info.frame_id).map(|frame| frame.current.clone()) + .map(|entry| ReplaceOrUpdate::Replace(entry)) } else { None }; @@ -1467,7 +1467,8 @@ impl Constellation }; let replace = if replace { - self.frames.get(&frame_id).map(|frame| frame.current()) + self.frames.get(&frame_id).map(|frame| frame.current.clone()) + .map(|entry| ReplaceOrUpdate::Replace(entry)) } else { None }; @@ -1534,7 +1535,7 @@ impl Constellation match self.frames.get(&self.root_frame_id) { None => warn!("Alert sent after root frame closure."), - Some(root_frame) => match self.pipelines.get(&root_frame.pipeline_id) { + Some(root_frame) => match self.pipelines.get(&root_frame.pipeline_id()) { None => warn!("Alert sent after root pipeline closure."), Some(root_pipeline) => root_pipeline.trigger_mozbrowser_event(Some(top_level_frame_id), event), } @@ -1611,7 +1612,8 @@ impl Constellation let root_frame_id = self.root_frame_id; let sandbox = IFrameSandboxState::IFrameUnsandboxed; let replace = if replace { - self.frames.get(&frame_id).map(|frame| frame.current()) + self.frames.get(&frame_id).map(|frame| frame.current.clone()) + .map(|entry| ReplaceOrUpdate::Replace(entry)) } else { None }; @@ -1709,7 +1711,7 @@ impl Constellation // frame's current pipeline. If neither exist, fall back to sending to // the compositor below. let root_pipeline_id = self.frames.get(&self.root_frame_id) - .map(|root_frame| root_frame.pipeline_id); + .map(|root_frame| root_frame.pipeline_id()); let pipeline_id = self.focus_pipeline_id.or(root_pipeline_id); match pipeline_id { @@ -1734,7 +1736,7 @@ impl Constellation fn handle_reload_msg(&mut self) { // Send Reload constellation msg to root script channel. let root_pipeline_id = self.frames.get(&self.root_frame_id) - .map(|root_frame| root_frame.pipeline_id); + .map(|root_frame| root_frame.pipeline_id()); if let Some(pipeline_id) = root_pipeline_id { let msg = ConstellationControlMsg::Reload(pipeline_id); @@ -1779,7 +1781,7 @@ impl Constellation resp_chan: IpcSender>) { let frame_id = frame_id.unwrap_or(self.root_frame_id); let current_pipeline_id = self.frames.get(&frame_id) - .map(|frame| frame.pipeline_id); + .map(|frame| frame.pipeline_id()); let pipeline_id_loaded = self.pending_frames.iter().rev() .find(|x| x.old_pipeline_id == current_pipeline_id) .map(|x| x.new_pipeline_id) @@ -1830,9 +1832,7 @@ impl Constellation fn handle_remove_iframe_msg(&mut self, frame_id: FrameId) -> Vec { let result = self.full_frame_tree_iter(frame_id) - .flat_map(|frame| frame.next.iter().chain(frame.prev.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(frame.pipeline_id))) + .flat_map(|frame| frame.entry_iter().filter_map(|entry| entry.pipeline_id())) .collect(); self.close_frame(frame_id, ExitPipelineMode::Normal); result @@ -1845,9 +1845,7 @@ impl Constellation }; let child_pipeline_ids: Vec = self.full_frame_tree_iter(frame_id) - .flat_map(|frame| frame.prev.iter().chain(frame.next.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(frame.pipeline_id))) + .flat_map(|frame| frame.entry_iter().filter_map(|entry| entry.pipeline_id())) .collect(); for id in child_pipeline_ids { @@ -1948,7 +1946,7 @@ impl Constellation }, WebDriverCommandMsg::TakeScreenshot(pipeline_id, reply) => { let current_pipeline_id = self.frames.get(&self.root_frame_id) - .map(|root_frame| root_frame.pipeline_id); + .map(|root_frame| root_frame.pipeline_id()); if Some(pipeline_id) == current_pipeline_id { self.compositor_proxy.send(ToCompositorMsg::CreatePng(reply)); } else { @@ -1964,33 +1962,34 @@ impl Constellation fn traverse_to_entry(&mut self, entry: FrameState) { // Step 1. let frame_id = entry.frame_id; - let pipeline_id = match entry.pipeline_id { + let pipeline_id = match entry.pipeline_id() { Some(pipeline_id) => pipeline_id, None => { // If there is no pipeline, then the document for this // entry has been discarded, so we navigate to the entry // URL instead. When the document has activated, it will - // traverse to the entry, but with the new pipeline id. + // traverse to the entry, but the new pipeline will exist now. debug!("Reloading document {} for frame {}.", entry.url, frame_id); // TODO: referrer? let load_data = LoadData::new(entry.url.clone(), None, None); // TODO: save the sandbox state so it can be restored here. let sandbox = IFrameSandboxState::IFrameUnsandboxed; - let new_pipeline_id = PipelineId::new(); let (old_pipeline_id, parent_info, window_size, is_private) = match self.frames.get(&frame_id) { - Some(frame) => match self.pipelines.get(&frame.pipeline_id) { - Some(pipeline) => (frame.pipeline_id, pipeline.parent_info, pipeline.size, pipeline.is_private), - None => (frame.pipeline_id, None, None, false), + Some(frame) => match self.pipelines.get(&frame.pipeline_id()) { + Some(pipeline) => (frame.pipeline_id(), pipeline.parent_info, + pipeline.size, pipeline.is_private), + None => (frame.pipeline_id(), None, None, false), }, None => return warn!("no frame to traverse"), }; + let new_pipeline_id = PipelineId::new(); self.new_pipeline(new_pipeline_id, frame_id, parent_info, window_size, load_data, sandbox, is_private); self.pending_frames.push(FrameChange { frame_id: frame_id, old_pipeline_id: Some(old_pipeline_id), new_pipeline_id: new_pipeline_id, url: entry.url.clone(), - replace: Some(entry), + replace: Some(ReplaceOrUpdate::Update(entry)), }); return; } @@ -2003,29 +2002,8 @@ impl Constellation let old_pipeline_id = match self.frames.get_mut(&frame_id) { Some(frame) => { - let old_pipeline_id = frame.pipeline_id; - let mut curr_entry = frame.current(); - - if entry.instant > frame.instant { - // We are traversing to the future. - while let Some(next) = frame.next.pop() { - frame.prev.push(curr_entry); - curr_entry = next; - if entry.instant <= curr_entry.instant { break; } - } - } else if entry.instant < frame.instant { - // We are traversing to the past. - while let Some(prev) = frame.prev.pop() { - frame.next.push(curr_entry); - curr_entry = prev; - if entry.instant >= curr_entry.instant { break; } - } - } - - debug_assert_eq!(entry.instant, curr_entry.instant); - - frame.update_current(pipeline_id, &entry); - + let old_pipeline_id = frame.pipeline_id(); + frame.traverse_to_entry(entry.clone()); old_pipeline_id }, None => return warn!("no frame to traverse"), @@ -2112,20 +2090,29 @@ impl Constellation } } - let (evicted_id, new_frame, navigated, location_changed) = if let Some(mut entry) = frame_change.replace { + let (evicted_id, new_frame, navigated, location_changed) = if let Some(replace) = frame_change.replace { debug!("Replacing pipeline in existing frame."); - let evicted_id = entry.pipeline_id; - entry.replace_pipeline(frame_change.new_pipeline_id, frame_change.url.clone()); + let (evicted_id, entry) = match replace { + ReplaceOrUpdate::Replace(mut entry) => { + let evicted_id = entry.pipeline_id(); + entry.replace_pipeline(frame_change.new_pipeline_id, frame_change.url.clone()); + (evicted_id, entry) + }, + ReplaceOrUpdate::Update(entry) => { + entry.update_pipeline(frame_change.new_pipeline_id); + (None, entry) + }, + }; self.traverse_to_entry(entry); (evicted_id, false, None, false) } else if let Some(frame) = self.frames.get_mut(&frame_change.frame_id) { debug!("Adding pipeline to existing frame."); - let old_pipeline_id = frame.pipeline_id; + let old_pipeline_id = frame.pipeline_id(); frame.load(frame_change.new_pipeline_id, frame_change.url.clone()); let evicted_id = frame.prev.len() .checked_sub(PREFS.get("session-history.max-length").as_u64().unwrap_or(20) as usize) - .and_then(|index| frame.prev.get_mut(index)) - .and_then(|entry| entry.pipeline_id.take()); + .and_then(|index| frame.prev.get(index)) + .and_then(|entry| frame.discard_entry(entry)); (evicted_id, false, Some(old_pipeline_id), true) } else { debug!("Adding pipeline to new frame."); @@ -2192,7 +2179,7 @@ impl Constellation if let Some(frame) = self.frames.get(&self.root_frame_id) { // Send Resize (or ResizeInactive) messages to each // pipeline in the frame tree. - let pipeline_id = frame.pipeline_id; + let pipeline_id = frame.pipeline_id(); let pipeline = match self.pipelines.get(&pipeline_id) { None => return warn!("Pipeline {:?} resized after closing.", pipeline_id), Some(pipeline) => pipeline, @@ -2202,10 +2189,13 @@ impl Constellation new_size, size_type )); - let pipelines = frame.prev.iter().chain(frame.next.iter()) - .filter_map(|entry| entry.pipeline_id) + + // TODO(cbrewster): Once we allow entries to share pipeline ids, we need to dedup. + let inactive_pipelines = frame.entry_iter() + .filter_map(|entry| entry.pipeline_id()) + .filter(|id| id != &pipeline_id) .filter_map(|pipeline_id| self.pipelines.get(&pipeline_id)); - for pipeline in pipelines { + for pipeline in inactive_pipelines { let _ = pipeline.event_loop.send(ConstellationControlMsg::ResizeInactive( pipeline.id, new_size @@ -2274,7 +2264,7 @@ impl Constellation // are met, then the output image should not change and a reftest // screenshot can safely be written. for frame in self.current_frame_tree_iter(self.root_frame_id) { - let pipeline_id = frame.pipeline_id; + let pipeline_id = frame.pipeline_id(); debug!("Checking readiness of frame {}, pipeline {}.", frame.id, pipeline_id); let pipeline = match self.pipelines.get(&pipeline_id) { @@ -2302,7 +2292,7 @@ impl Constellation } // See if this pipeline has reached idle script state yet. - match self.document_states.get(&frame.pipeline_id) { + match self.document_states.get(&frame.pipeline_id()) { Some(&DocumentState::Idle) => {} Some(&DocumentState::Pending) | None => { return ReadyToSave::DocumentLoading; @@ -2322,7 +2312,7 @@ impl Constellation } // Get the epoch that the compositor has drawn for this pipeline. - let compositor_epoch = pipeline_states.get(&frame.pipeline_id); + let compositor_epoch = pipeline_states.get(&frame.pipeline_id()); match compositor_epoch { Some(compositor_epoch) => { // Synchronously query the layout thread to see if the current @@ -2359,7 +2349,7 @@ impl Constellation loop { if let Some(ancestor) = self.pipelines.get(&ancestor_id) { if let Some(frame) = self.frames.get(&ancestor.frame_id) { - if frame.pipeline_id == ancestor_id { + if frame.pipeline_id() == ancestor_id { if let Some((parent_id, FrameType::IFrame)) = ancestor.parent_info { ancestor_id = parent_id; continue; @@ -2389,7 +2379,7 @@ impl Constellation }; for child_id in &pipeline.children { if let Some(child) = self.frames.get(child_id) { - self.set_activity(child.pipeline_id, child_activity); + self.set_activity(child.pipeline_id(), child_activity); } } } @@ -2410,7 +2400,7 @@ impl Constellation None => continue, }; for entry in evicted { - if let Some(pipeline_id) = entry.pipeline_id { + if let Some(pipeline_id) = entry.pipeline_id() { self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); } } @@ -2421,7 +2411,7 @@ impl Constellation fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) { debug!("Closing frame {}.", frame_id); let parent_info = self.frames.get(&frame_id) - .and_then(|frame| self.pipelines.get(&frame.pipeline_id)) + .and_then(|frame| self.pipelines.get(&frame.pipeline_id())) .and_then(|pipeline| pipeline.parent_info); self.close_frame_children(frame_id, DiscardBrowsingContext::Yes, exit_mode); @@ -2454,9 +2444,9 @@ impl Constellation .collect(); if let Some(frame) = self.frames.get(&frame_id) { - pipelines_to_close.extend(frame.next.iter().filter_map(|state| state.pipeline_id)); - pipelines_to_close.push(frame.pipeline_id); - pipelines_to_close.extend(frame.prev.iter().filter_map(|state| state.pipeline_id)); + pipelines_to_close.extend(frame.next.iter().filter_map(|state| state.pipeline_id())); + pipelines_to_close.push(frame.pipeline_id()); + pipelines_to_close.extend(frame.prev.iter().filter_map(|state| state.pipeline_id())); } for pipeline_id in pipelines_to_close { @@ -2476,8 +2466,19 @@ impl Constellation let frames_to_close = { let mut frames_to_close = vec!(); - if let Some(pipeline) = self.pipelines.get(&pipeline_id) { - frames_to_close.extend_from_slice(&pipeline.children); + match self.pipelines.get(&pipeline_id) { + Some(pipeline) => { + frames_to_close.extend_from_slice(&pipeline.children); + // Inform script, compositor that this pipeline has exited. + // Note, we don't remove the pipeline now, we wait for the message to come back from + // the pipeline. + match exit_mode { + ExitPipelineMode::Normal => pipeline.exit(dbc), + ExitPipelineMode::Force => pipeline.force_exit(dbc), + } + debug!("Closed pipeline {:?}.", pipeline_id); + }, + None => return warn!("Closing pipeline {:?} twice.", pipeline_id), } frames_to_close @@ -2488,13 +2489,6 @@ impl Constellation self.close_frame(*child_frame, exit_mode); } - // Note, we don't remove the pipeline now, we wait for the message to come back from - // the pipeline. - let pipeline = match self.pipelines.get(&pipeline_id) { - Some(pipeline) => pipeline, - None => return warn!("Closing pipeline {:?} twice.", pipeline_id), - }; - // Remove this pipeline from pending frames if it hasn't loaded yet. let pending_index = self.pending_frames.iter().position(|frame_change| { frame_change.new_pipeline_id == pipeline_id @@ -2502,13 +2496,6 @@ impl Constellation if let Some(pending_index) = pending_index { self.pending_frames.remove(pending_index); } - - // Inform script, compositor that this pipeline has exited. - match exit_mode { - ExitPipelineMode::Normal => pipeline.exit(dbc), - ExitPipelineMode::Force => pipeline.force_exit(dbc), - } - debug!("Closed pipeline {:?}.", pipeline_id); } // Randomly close a pipeline -if --random-pipeline-closure-probability is set @@ -2540,7 +2527,7 @@ impl Constellation // Convert a frame to a sendable form to pass to the compositor fn frame_to_sendable(&self, frame_id: FrameId) -> Option { self.frames.get(&frame_id).and_then(|frame: &Frame| { - self.pipelines.get(&frame.pipeline_id).map(|pipeline: &Pipeline| { + self.pipelines.get(&frame.pipeline_id()).map(|pipeline: &Pipeline| { let mut frame_tree = SendableFrameTree { pipeline: pipeline.to_sendable(), size: pipeline.size, @@ -2621,7 +2608,7 @@ impl Constellation match self.frames.get(&top_level_frame_id) { None => warn!("Mozbrowser error after top-level frame closed."), - Some(frame) => match self.pipelines.get(&frame.pipeline_id) { + Some(frame) => match self.pipelines.get(&frame.pipeline_id()) { None => warn!("Mozbrowser error after top-level pipeline closed."), Some(pipeline) => match pipeline.parent_info { None => pipeline.trigger_mozbrowser_event(None, event), @@ -2648,7 +2635,7 @@ impl Constellation pipeline_id: PipelineId, root_frame_id: FrameId) -> bool { self.current_frame_tree_iter(root_frame_id) - .any(|current_frame| current_frame.pipeline_id == pipeline_id) + .any(|current_frame| current_frame.pipeline_id() == pipeline_id) } } diff --git a/components/constellation/frame.rs b/components/constellation/frame.rs index daa443b2b80f..9616053c7c12 100644 --- a/components/constellation/frame.rs +++ b/components/constellation/frame.rs @@ -5,9 +5,11 @@ use msg::constellation_msg::{FrameId, PipelineId}; use pipeline::Pipeline; use servo_url::ServoUrl; +use std::cell::Cell; use std::collections::HashMap; use std::iter::once; use std::mem::replace; +use std::rc::Rc; use std::time::Instant; /// A frame in the frame tree. @@ -23,14 +25,8 @@ pub struct Frame { /// The frame id. pub id: FrameId, - /// The timestamp for the current session history entry - pub instant: Instant, - - /// The pipeline for the current session history entry - pub pipeline_id: PipelineId, - - /// The URL for the current session history entry - pub url: ServoUrl, + /// The current session history entry. + pub current: FrameState, /// The past session history, ordered chronologically. pub prev: Vec, @@ -45,31 +41,23 @@ impl Frame { pub fn new(id: FrameId, pipeline_id: PipelineId, url: ServoUrl) -> Frame { Frame { id: id, - pipeline_id: pipeline_id, - instant: Instant::now(), - url: url, + current: FrameState::new(pipeline_id, url, id), prev: vec!(), next: vec!(), } } - /// Get the current frame state. - pub fn current(&self) -> FrameState { - FrameState { - instant: self.instant, - frame_id: self.id, - pipeline_id: Some(self.pipeline_id), - url: self.url.clone(), - } + /// The active pipeline id of the current session history entry + pub fn pipeline_id(&self) -> PipelineId { + self.current.pipeline_id().expect("Active pipeline cannot be None") } /// Set the current frame entry, and push the current frame entry into the past. pub fn load(&mut self, pipeline_id: PipelineId, url: ServoUrl) { - let current = self.current(); + let current = self.current.clone(); self.prev.push(current); - self.instant = Instant::now(); - self.pipeline_id = pipeline_id; - self.url = url; + self.current.instant = Instant::now(); + self.current.replace_pipeline(pipeline_id, url); } /// Set the future to be empty. @@ -77,11 +65,44 @@ impl Frame { replace(&mut self.next, vec!()) } - /// Update the current entry of the Frame from an entry that has been traversed to. - pub fn update_current(&mut self, pipeline_id: PipelineId, entry: &FrameState) { - self.pipeline_id = pipeline_id; - self.instant = entry.instant; - self.url = entry.url.clone(); + pub fn discard_entry(&self, entry: &FrameState) -> Option { + // Ensure that we are not discarding the active pipeline. + if entry.pipeline_id() != Some(self.pipeline_id()) { + return entry.discard_pipeline() + } + None + } + + pub fn traverse_to_entry(&mut self, entry: FrameState) { + if entry.pipeline_id().is_none() { + return warn!("Attempted to traverse frame {} to entry with a discarded pipeline.", entry.frame_id); + } + debug_assert!(entry.pipeline_id().is_some()); + let mut curr_entry = self.current.clone(); + + if entry.instant > self.current.instant { + // We are traversing to the future. + while let Some(next) = self.next.pop() { + self.prev.push(curr_entry); + curr_entry = next; + if entry.instant <= curr_entry.instant { break; } + } + } else if entry.instant < self.current.instant { + // We are traversing to the past. + while let Some(prev) = self.prev.pop() { + self.next.push(curr_entry); + curr_entry = prev; + if entry.instant >= curr_entry.instant { break; } + } + } + + debug_assert_eq!(entry.instant, curr_entry.instant); + + self.current = entry; + } + + pub fn entry_iter<'a>(&'a self) -> impl Iterator { + self.prev.iter().chain(self.next.iter()).chain(once(&self.current)) } } @@ -95,24 +116,58 @@ pub struct FrameState { /// The timestamp for when the session history entry was created pub instant: Instant, - /// The pipeline for the document in the session history, - /// None if the entry has been discarded - pub pipeline_id: Option, - /// The URL for this entry, used to reload the pipeline if it has been discarded pub url: ServoUrl, /// The frame that this session history entry is part of pub frame_id: FrameId, + + /// The pipeline for the document in the session history + pipeline_id: Rc>>, } impl FrameState { + fn new(pipeline_id: PipelineId, url: ServoUrl, frame_id: FrameId) -> FrameState { + FrameState { + instant: Instant::now(), + pipeline_id: Rc::new(Cell::new(Some(pipeline_id))), + url: url, + frame_id: frame_id, + } + } + /// Updates the entry's pipeline and url. This is used when navigating with replacement /// enabled. pub fn replace_pipeline(&mut self, pipeline_id: PipelineId, url: ServoUrl) { - self.pipeline_id = Some(pipeline_id); + self.pipeline_id = Rc::new(Cell::new(Some(pipeline_id))); self.url = url; } + + fn discard_pipeline(&self) -> Option { + let old_pipeline_id = self.pipeline_id.get(); + self.pipeline_id.set(None); + old_pipeline_id + } + + pub fn update_pipeline(&self, pipeline_id: PipelineId) { + self.pipeline_id.set(Some(pipeline_id)); + } + + pub fn pipeline_id(&self) -> Option { + self.pipeline_id.get() + } +} + +/// Whether the pipeline should be updated or replaced when a naviagtion with replacement +/// matures. +pub enum ReplaceOrUpdate { + /// Replaces the pipeline of the entry. This does not affect any other entries even + /// if they share a pipeline id. + Replace(FrameState), + /// Updates the pipeline of the entry. This will update the pipeline of all entries + /// that share the same pipeline. This is only used when reloading a document that + /// was discarded from the distant history. + Update(FrameState), } /// Represents a pending change in the frame tree, that will be applied @@ -133,7 +188,7 @@ pub struct FrameChange { /// Is the new document replacing the current document (e.g. a reload) /// or pushing it into the session history (e.g. a navigation)? - pub replace: Option, + pub replace: Option, } /// An iterator over a frame tree, returning the fully active frames in @@ -168,10 +223,10 @@ impl<'a> Iterator for FrameTreeIterator<'a> { continue; }, }; - let pipeline = match self.pipelines.get(&frame.pipeline_id) { + let pipeline = match self.pipelines.get(&frame.pipeline_id()) { Some(pipeline) => pipeline, None => { - warn!("Pipeline {:?} iterated after closure.", frame.pipeline_id); + warn!("Pipeline {:?} iterated after closure.", frame.pipeline_id()); continue; }, }; @@ -213,9 +268,8 @@ impl<'a> Iterator for FullFrameTreeIterator<'a> { continue; }, }; - let child_frame_ids = frame.prev.iter().chain(frame.next.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(frame.pipeline_id)) + let child_frame_ids = frame.entry_iter() + .filter_map(|entry| entry.pipeline_id()) .filter_map(|pipeline_id| pipelines.get(&pipeline_id)) .flat_map(|pipeline| pipeline.children.iter()); self.stack.extend(child_frame_ids); From e64556b8317e9762490c121f115182100f0b6205 Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Wed, 15 Feb 2017 10:24:32 -0600 Subject: [PATCH 2/7] Add constellation frame unit tests --- Cargo.lock | 10 ++++ components/constellation/lib.rs | 1 + ports/servo/Cargo.toml | 1 + tests/unit/constellation/Cargo.toml | 15 ++++++ tests/unit/constellation/frame.rs | 71 +++++++++++++++++++++++++++++ tests/unit/constellation/lib.rs | 9 ++++ 6 files changed, 107 insertions(+) create mode 100644 tests/unit/constellation/Cargo.toml create mode 100644 tests/unit/constellation/frame.rs create mode 100644 tests/unit/constellation/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 73737969de29..b36a85b5f62f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,6 +483,15 @@ dependencies = [ "webvr_traits 0.0.1", ] +[[package]] +name = "constellation_tests" +version = "0.0.1" +dependencies = [ + "constellation 0.0.1", + "msg 0.0.1", + "servo_url 0.0.1", +] + [[package]] name = "content-blocker" version = "0.2.2" @@ -2504,6 +2513,7 @@ dependencies = [ "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "browserhtml 0.1.17 (git+https://github.com/browserhtml/browserhtml?branch=crate)", "compiletest_helper 0.0.1", + "constellation_tests 0.0.1", "gfx_tests 0.0.1", "glutin_app 0.0.1", "layout_tests 0.0.1", diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs index 92417412887b..e1856dd6c092 100644 --- a/components/constellation/lib.rs +++ b/components/constellation/lib.rs @@ -53,3 +53,4 @@ pub use constellation::{Constellation, FromCompositorLogger, FromScriptLogger, I pub use pipeline::UnprivilegedPipelineContent; #[cfg(not(target_os = "windows"))] pub use sandboxing::content_process_sandbox_profile; +pub use frame::Frame; diff --git a/ports/servo/Cargo.toml b/ports/servo/Cargo.toml index 082d17db90bc..83b64bf49d2a 100644 --- a/ports/servo/Cargo.toml +++ b/ports/servo/Cargo.toml @@ -15,6 +15,7 @@ bench = false [dev-dependencies] compiletest_helper = {path = "../../tests/compiletest/helper"} +constellation_tests = {path = "../../tests/unit/constellation"} gfx_tests = {path = "../../tests/unit/gfx"} layout_tests = {path = "../../tests/unit/layout"} net_tests = {path = "../../tests/unit/net"} diff --git a/tests/unit/constellation/Cargo.toml b/tests/unit/constellation/Cargo.toml new file mode 100644 index 000000000000..7dfa6dc5295f --- /dev/null +++ b/tests/unit/constellation/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "constellation_tests" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" + +[lib] +name = "constellation_tests" +path = "lib.rs" +doctest = false + +[dependencies] +constellation = {path = "../../../components/constellation"} +msg = {path = "../../../components/msg"} +servo_url = {path = "../../../components/url"} diff --git a/tests/unit/constellation/frame.rs b/tests/unit/constellation/frame.rs new file mode 100644 index 000000000000..83b2b89a8afb --- /dev/null +++ b/tests/unit/constellation/frame.rs @@ -0,0 +1,71 @@ +/* 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 constellation::Frame; +use msg::constellation_msg::{PipelineId, PipelineNamespace, PipelineNamespaceId, FrameId}; +use servo_url::ServoUrl; + +#[test] +fn test_entry_replacement() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let pipeline_id = PipelineId::new(); + let frame_id = FrameId::new(); + let url = ServoUrl::parse("about:blank").expect("Infallible"); + let mut frame = Frame::new(frame_id, pipeline_id, url.clone()); + + frame.prev.push(frame.current.clone()); + + let new_pipeline_id = PipelineId::new(); + // Unlink this entry from the previous entry + frame.current.replace_pipeline(new_pipeline_id, url); + + assert_eq!(frame.prev.len(), 1); + let prev_entry = frame.prev.pop().expect("No previous entry!"); + assert_ne!(prev_entry.pipeline_id(), frame.current.pipeline_id()); +} + +#[test] +fn test_entry_update() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let pipeline_id = PipelineId::new(); + let frame_id = FrameId::new(); + FrameId::install(frame_id); + let url = ServoUrl::parse("about:blank").expect("Infallible"); + let mut frame = Frame::new(frame_id, pipeline_id, url); + + // A clone will link the two entry's pipelines + frame.prev.push(frame.current.clone()); + + let new_pipeline_id = PipelineId::new(); + frame.current.update_pipeline(new_pipeline_id); + assert_eq!(frame.pipeline_id(), new_pipeline_id); + + assert_eq!(frame.prev.len(), 1); + let prev_entry = frame.prev.pop().expect("No previous entry!"); + assert_eq!(prev_entry.pipeline_id(), frame.current.pipeline_id()); +} + +#[test] +fn test_entry_discard() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let pipeline_id = PipelineId::new(); + let frame_id = FrameId::new(); + FrameId::install(frame_id); + let url = ServoUrl::parse("about:blank").expect("Infallible"); + let mut frame = Frame::new(frame_id, pipeline_id, url.clone()); + + // A clone will link the two entry's pipelines + frame.prev.push(frame.current.clone()); + + assert_eq!(frame.prev.len(), 1); + // Cannot discard because this entry shares the same pipeline as the current entry. + let evicted_id = frame.discard_entry(&frame.prev[0]); + assert!(evicted_id.is_none()); + + let new_pipeline_id = PipelineId::new(); + frame.current.replace_pipeline(new_pipeline_id, url); + // Discard should work now that current is no longer linked to this entry. + let evicted_id = frame.discard_entry(&frame.prev[0]); + assert_eq!(evicted_id, Some(pipeline_id)); +} diff --git a/tests/unit/constellation/lib.rs b/tests/unit/constellation/lib.rs new file mode 100644 index 000000000000..4ec4553e8414 --- /dev/null +++ b/tests/unit/constellation/lib.rs @@ -0,0 +1,9 @@ +/* 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/. */ + +extern crate constellation; +extern crate msg; +extern crate servo_url; + +#[cfg(test)] mod frame; From ae338cf3a96c6ef3c502b8005e50dd4e8ec734f4 Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Thu, 26 Jan 2017 09:41:54 -0600 Subject: [PATCH 3/7] Implement history states and hash changes. This change implements handling history state entries and hash change history entries. The constellation is in charge of managing session history and traversals. The History object is in charge of updating all state on the script/dom side of things. The resource thread stores the serialized history state data. This data must be stored outside the script thread as the script thread may be shut down if its document is purged during history pruning. Storing the state data allows for retrieval of the state data at a later time even if the document had to be recreated. Each state is tracked using a state ID which is a UUID. Traversal tasks are now queued on the history traversal task source. The runnable waits synchronously on the constellation to finish handling the traversal. This allows for tests to use setTimeout to wait for a traversal to be handled before the callback has a chance of being called. --- Cargo.lock | 2 + components/constellation/constellation.rs | 106 +++++++++-- components/constellation/frame.rs | 41 +++- components/msg/Cargo.toml | 2 + components/msg/constellation_msg.rs | 11 ++ components/msg/lib.rs | 2 + components/net/resource_thread.rs | 31 +++ components/net_traits/lib.rs | 7 + components/script/dom/bindings/trace.rs | 3 +- components/script/dom/hashchangeevent.rs | 9 + components/script/dom/history.rs | 178 +++++++++++++++++- components/script/dom/htmliframeelement.rs | 3 +- components/script/dom/popstateevent.rs | 9 + components/script/dom/webidls/History.webidl | 8 +- components/script/dom/window.rs | 8 +- components/script/script_thread.rs | 26 ++- .../script/task_source/history_traversal.rs | 64 ++++++- components/script_traits/lib.rs | 18 +- components/script_traits/script_msg.rs | 11 +- 19 files changed, 496 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b36a85b5f62f..a49fc76e7023 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1709,6 +1709,8 @@ dependencies = [ "plugins 0.0.1", "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_rand 0.0.1", + "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "webrender_traits 0.15.0 (git+https://github.com/servo/webrender)", ] diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 1f8da322e5dd..30d4535ca39d 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -85,7 +85,7 @@ use layout_traits::LayoutThreadFactory; use log::{Log, LogLevel, LogLevelFilter, LogMetadata, LogRecord}; use msg::constellation_msg::{FrameId, FrameType, PipelineId}; use msg::constellation_msg::{Key, KeyModifiers, KeyState}; -use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, TraversalDirection}; +use msg::constellation_msg::{PipelineNamespace, PipelineNamespaceId, StateId, TraversalDirection}; use net_traits::{self, IpcSend, ResourceThreads}; use net_traits::image_cache_thread::ImageCacheThread; use net_traits::pub_domains::reg_host; @@ -99,7 +99,7 @@ use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorM use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData}; use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerEventRequest}; use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory}; -use script_traits::{LogEntry, ServiceWorkerMsg, webdriver_msg}; +use script_traits::{LogEntry, ServiceWorkerMsg, webdriver_msg, PushOrReplaceState}; use script_traits::{MozBrowserErrorType, MozBrowserEvent, WebDriverCommandMsg, WindowSizeData}; use script_traits::{SWManagerMsg, ScopeThings, WindowSizeType}; use script_traits::WebVREventMsg; @@ -859,7 +859,7 @@ impl Constellation // Handle a forward or back request FromCompositorMsg::TraverseHistory(pipeline_id, direction) => { debug!("constellation got traverse history message from compositor"); - self.handle_traverse_history_msg(pipeline_id, direction); + self.handle_traverse_history_msg(pipeline_id, direction, None); } FromCompositorMsg::WindowSize(new_size, size_type) => { debug!("constellation got window resize message"); @@ -924,9 +924,9 @@ impl Constellation self.handle_load_complete_msg(pipeline_id) } // Handle a forward or back request - FromScriptMsg::TraverseHistory(pipeline_id, direction) => { + FromScriptMsg::TraverseHistory(pipeline_id, direction, sender) => { debug!("constellation got traverse history message from script"); - self.handle_traverse_history_msg(pipeline_id, direction); + self.handle_traverse_history_msg(pipeline_id, direction, Some(sender)); } // Handle a joint session history length request. FromScriptMsg::JointSessionHistoryLength(pipeline_id, sender) => { @@ -1018,7 +1018,14 @@ impl Constellation debug!("constellation got Alert message"); self.handle_alert(pipeline_id, message, sender); } - + FromScriptMsg::HistoryStateChanged(pipeline_id, state_id, url, replace) => { + debug!("constellation got HistoryStateChanged message"); + self.handle_history_state_changed(pipeline_id, state_id, url, replace); + } + FromScriptMsg::UrlHashChanged(pipeline_id, url) => { + debug!("constellation got UrlHashChanged message"); + self.handle_url_hash_changed(pipeline_id, url); + } FromScriptMsg::ScrollFragmentPoint(pipeline_id, scroll_root_id, point, smooth) => { self.compositor_proxy.send(ToCompositorMsg::ScrollFragmentPoint(pipeline_id, scroll_root_id, @@ -1660,7 +1667,8 @@ impl Constellation fn handle_traverse_history_msg(&mut self, pipeline_id: Option, - direction: TraversalDirection) { + direction: TraversalDirection, + sender: Option>) { let top_level_frame_id = pipeline_id .map(|pipeline_id| self.get_top_level_frame_for_pipeline(pipeline_id)) .unwrap_or(self.root_frame_id); @@ -1692,6 +1700,10 @@ impl Constellation for (_, entry) in table { self.traverse_to_entry(entry); } + // If the traversal was triggered from script, it must be treated synchronously. + if let Some(sender) = sender { + let _ = sender.send(()); + } } fn handle_joint_session_history_length(&self, pipeline_id: PipelineId, sender: IpcSender) { @@ -1958,6 +1970,47 @@ impl Constellation } } + fn handle_history_state_changed(&mut self, + pipeline_id: PipelineId, + state_id: StateId, + url: ServoUrl, + replace: PushOrReplaceState) { + if !self.pipeline_is_in_current_frame(pipeline_id) { + return warn!("History state changed in non-fully active pipeline {}", pipeline_id); + } + let frame_id = match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline.frame_id, + None => return warn!("History state changed after pipeline {} closed.", pipeline_id), + }; + match self.frames.get_mut(&frame_id) { + Some(frame) => { + match replace { + PushOrReplaceState::Push => { + frame.load_state_change(state_id, url); + }, + PushOrReplaceState::Replace => frame.replace_state(state_id, url), + } + }, + None => return warn!("History state changed after frame {} closed.", frame_id), + } + + if replace == PushOrReplaceState::Push { + let top_level_frame_id = self.get_top_level_frame_for_pipeline(pipeline_id); + self.clear_joint_session_future(top_level_frame_id) + } + } + + fn handle_url_hash_changed(&mut self, pipeline_id: PipelineId, url: ServoUrl) { + let frame_id = match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline.frame_id, + None => return warn!("History state changed after pipeline {} closed.", pipeline_id), + }; + match self.frames.get_mut(&frame_id) { + Some(frame) => frame.change_url(url), + None => return warn!("History state changed after frame {} closed.", frame_id), + } + } + // https://html.spec.whatwg.org/multipage/#traverse-the-history fn traverse_to_entry(&mut self, entry: FrameState) { // Step 1. @@ -2020,8 +2073,16 @@ impl Constellation } // Deactivate the old pipeline, and activate the new one. - self.update_activity(old_pipeline_id); - self.update_activity(pipeline_id); + if old_pipeline_id != pipeline_id { + self.update_activity(old_pipeline_id); + self.update_activity(pipeline_id); + } + + if let Some(pipeline) = self.pipelines.get(&pipeline_id) { + let msg = ConstellationControlMsg::UpdateActiveState(pipeline_id, entry.state_id, entry.url.clone()); + // TODO(cbrewster): Handle send error. + let _ = pipeline.event_loop.send(msg); + } // Set paint permissions correctly for the compositor layers. self.send_frame_tree(); @@ -2395,13 +2456,34 @@ impl Constellation .map(|frame| frame.id) .collect(); for frame_id in frame_ids { - let evicted = match self.frames.get_mut(&frame_id) { - Some(frame) => frame.remove_forward_entries(), + let (evicted, current_pipeline) = match self.frames.get_mut(&frame_id) { + Some(frame) => (frame.remove_forward_entries(), frame.pipeline_id()), None => continue, }; + let mut discarded_history_states = vec!(); for entry in evicted { if let Some(pipeline_id) = entry.pipeline_id() { - self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); + if pipeline_id != current_pipeline { + self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); + } else { + if let Some(state_id) = entry.state_id { + discarded_history_states.push(state_id); + } + } + } + } + + if !discarded_history_states.is_empty() { + let result = match self.pipelines.get(¤t_pipeline) { + Some(pipeline) => { + let msg = ConstellationControlMsg::RemoveStateEntries(current_pipeline, + discarded_history_states); + pipeline.event_loop.send(msg) + }, + None => return warn!("Pipeline {} closed after removing state entries", current_pipeline), + }; + if let Err(e) = result { + self.handle_send_error(current_pipeline, e); } } } diff --git a/components/constellation/frame.rs b/components/constellation/frame.rs index 9616053c7c12..f60a56681063 100644 --- a/components/constellation/frame.rs +++ b/components/constellation/frame.rs @@ -2,7 +2,7 @@ * 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 msg::constellation_msg::{FrameId, PipelineId}; +use msg::constellation_msg::{FrameId, PipelineId, StateId}; use pipeline::Pipeline; use servo_url::ServoUrl; use std::cell::Cell; @@ -60,6 +60,31 @@ impl Frame { self.current.replace_pipeline(pipeline_id, url); } + /// Set the current frame entry using the given state id and url and push the current frame + /// entry into the past. The pipeline id of the new entry is the same as the pipeline id + /// of the previous entry. + pub fn load_state_change(&mut self, state_id: StateId, url: ServoUrl) { + let current = self.current.clone(); + self.prev.push(current); + self.current.instant = Instant::now(); + self.current.state_id = Some(state_id); + self.current.url = url; + } + + pub fn change_url(&mut self, url: ServoUrl) { + let current = self.current.clone(); + self.prev.push(current); + self.current.instant = Instant::now(); + self.current.state_id = None; + self.current.url = url; + } + + /// Replace the state id and url of the current entry. + pub fn replace_state(&mut self, state_id: StateId, url: ServoUrl) { + self.current.state_id = Some(state_id); + self.current.url = url; + } + /// Set the future to be empty. pub fn remove_forward_entries(&mut self) -> Vec { replace(&mut self.next, vec!()) @@ -119,6 +144,9 @@ pub struct FrameState { /// The URL for this entry, used to reload the pipeline if it has been discarded pub url: ServoUrl, + /// The state id of the active history state + pub state_id: Option, + /// The frame that this session history entry is part of pub frame_id: FrameId, @@ -132,6 +160,7 @@ impl FrameState { instant: Instant::now(), pipeline_id: Rc::new(Cell::new(Some(pipeline_id))), url: url, + state_id: None, frame_id: frame_id, } } @@ -141,6 +170,7 @@ impl FrameState { pub fn replace_pipeline(&mut self, pipeline_id: PipelineId, url: ServoUrl) { self.pipeline_id = Rc::new(Cell::new(Some(pipeline_id))); self.url = url; + self.state_id = None; } fn discard_pipeline(&self) -> Option { @@ -268,8 +298,15 @@ impl<'a> Iterator for FullFrameTreeIterator<'a> { continue; }, }; - let child_frame_ids = frame.entry_iter() + let mut session_pipelines = frame.entry_iter() .filter_map(|entry| entry.pipeline_id()) + .collect::>(); + + // The session history may contain multiple entries with the same PipelineId. + // These duplicates need to be removed. + session_pipelines.dedup(); + + let child_frame_ids = session_pipelines.iter() .filter_map(|pipeline_id| pipelines.get(&pipeline_id)) .flat_map(|pipeline| pipeline.children.iter()); self.stack.extend(child_frame_ids); diff --git a/components/msg/Cargo.toml b/components/msg/Cargo.toml index 33abb5cdaacb..9b59901bd97d 100644 --- a/components/msg/Cargo.toml +++ b/components/msg/Cargo.toml @@ -17,6 +17,8 @@ heapsize_derive = "0.1" plugins = {path = "../plugins"} serde = "0.8" serde_derive = "0.8" +servo_rand = {path = "../rand"} +uuid = { version = "0.3.1", default-features = false, features = ["serde"] } [dependencies.webrender_traits] git = "https://github.com/servo/webrender" diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index dddfb0c00422..dac17b342f2b 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -5,8 +5,10 @@ //! The high-level interface from script to constellation. Using this abstract interface helps //! reduce coupling between these two components. +use servo_rand::{self, Rng}; use std::cell::Cell; use std::fmt; +use uuid::Uuid; use webrender_traits; #[derive(PartialEq, Eq, Copy, Clone, Debug, Deserialize, Serialize)] @@ -294,6 +296,15 @@ impl fmt::Display for FrameId { } } +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash, Debug, Deserialize, Serialize, HeapSizeOf)] +pub struct StateId(#[ignore_heap_size_of = "no heapsize for uuid"] Uuid); + +impl StateId { + pub fn new() -> StateId { + StateId(servo_rand::thread_rng().gen()) + } +} + // We provide ids just for unit testing. pub const TEST_NAMESPACE: PipelineNamespaceId = PipelineNamespaceId(1234); pub const TEST_PIPELINE_INDEX: PipelineIndex = PipelineIndex(5678); diff --git a/components/msg/lib.rs b/components/msg/lib.rs index 6257340c3857..3356c4391013 100644 --- a/components/msg/lib.rs +++ b/components/msg/lib.rs @@ -13,6 +13,8 @@ extern crate heapsize; #[macro_use] extern crate heapsize_derive; #[macro_use] extern crate serde_derive; +extern crate servo_rand; +extern crate uuid; extern crate webrender_traits; pub mod constellation_msg; diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 925b5e19cdc0..8af854da6b7c 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -16,6 +16,7 @@ use http_loader::HttpState; use hyper::client::pool::Pool; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender}; +use msg::constellation_msg::StateId; use net_traits::{CookieSource, CoreResourceThread}; use net_traits::{CoreResourceMsg, FetchResponseMsg}; use net_traits::{CustomResponseMediator, ResourceId}; @@ -48,6 +49,7 @@ pub struct ResourceGroup { auth_cache: Arc>, hsts_list: Arc>, connector: Arc>, + history_states: Arc>>>, } /// Returns a tuple of (public, private) senders to the new threads. @@ -110,12 +112,14 @@ fn create_resource_groups(config_dir: Option<&Path>) auth_cache: Arc::new(RwLock::new(auth_cache)), hsts_list: Arc::new(RwLock::new(hsts_list.clone())), connector: create_http_connector("certs"), + history_states: Arc::new(RwLock::new(HashMap::new())), }; let private_resource_group = ResourceGroup { cookie_jar: Arc::new(RwLock::new(CookieStorage::new(150))), auth_cache: Arc::new(RwLock::new(AuthCache::new())), hsts_list: Arc::new(RwLock::new(HstsList::new())), connector: create_http_connector("certs"), + history_states: Arc::new(RwLock::new(HashMap::new())), }; (resource_group, private_resource_group) } @@ -169,6 +173,33 @@ impl ResourceChannelManager { let mut cookie_jar = group.cookie_jar.write().unwrap(); consumer.send(cookie_jar.cookies_for_url(&url, source)).unwrap(); } + CoreResourceMsg::SetHistoryState(state_id, state) => { + match group.history_states.write() { + Ok(mut history_states) => { + history_states.insert(state_id, state); + }, + Err(_) => warn!("Error while setting history state."), + } + } + CoreResourceMsg::GetHistoryState(state_id, consumer) => { + match group.history_states.read() { + Ok(history_states) => { + let _ = consumer.send(history_states.get(&state_id).cloned()); + }, + Err(_) => warn!("Error while getting history state."), + } + let _ = consumer.send(None); + } + CoreResourceMsg::RemoveHistoryStates(state_ids) => { + match group.history_states.write() { + Ok(mut history_states) => { + for state_id in state_ids { + history_states.remove(&state_id); + } + }, + Err(_) => warn!("Error while removing unreachable history states."), + } + } CoreResourceMsg::NetworkMediator(mediator_chan) => { self.resource_manager.swmanager_chan = Some(mediator_chan) } diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index 22308edaf249..df88b05f979c 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -42,6 +42,7 @@ use hyper_serde::Serde; use ipc_channel::SerializeError; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; +use msg::constellation_msg::StateId; use request::{Request, RequestInit}; use response::{HttpsState, Response}; use servo_url::ServoUrl; @@ -386,6 +387,12 @@ pub enum CoreResourceMsg { GetCookiesForUrl(ServoUrl, IpcSender>, CookieSource), /// Get a cookie by name for a given originating URL GetCookiesDataForUrl(ServoUrl, IpcSender>>, CookieSource), + /// Set the serialized history state for a given state id + SetHistoryState(StateId, Vec), + /// Get serialized history state for a given state id + GetHistoryState(StateId, IpcSender>>), + /// Remove history states associated with the given state ids + RemoveHistoryStates(Vec), /// Cancel a network request corresponding to a given `ResourceId` Cancel(ResourceId), /// Synchronization message solely for knowing the state of the ResourceChannelManager loop diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index aeddcdabe4b0..97baa0dc06e0 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -58,7 +58,7 @@ use js::glue::{CallObjectTracer, CallValueTracer}; use js::jsapi::{GCTraceKindToAscii, Heap, JSObject, JSTracer, TraceKind}; use js::jsval::JSVal; use js::rust::Runtime; -use msg::constellation_msg::{FrameId, FrameType, PipelineId}; +use msg::constellation_msg::{FrameId, FrameType, PipelineId, StateId}; use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceThreads}; use net_traits::filemanager_thread::RelativePos; use net_traits::image::base::{Image, ImageMetadata}; @@ -329,6 +329,7 @@ unsafe_no_jsmanaged_fields!(PropertyDeclarationBlock); // These three are interdependent, if you plan to put jsmanaged data // in one of these make sure it is propagated properly to containing structs unsafe_no_jsmanaged_fields!(DocumentActivity, FrameId, FrameType, WindowSizeData, WindowSizeType, PipelineId); +unsafe_no_jsmanaged_fields!(StateId); unsafe_no_jsmanaged_fields!(TimerEventId, TimerSource); unsafe_no_jsmanaged_fields!(TimelineMarkerType); unsafe_no_jsmanaged_fields!(WorkerId); diff --git a/components/script/dom/hashchangeevent.rs b/components/script/dom/hashchangeevent.rs index cd4b2b0a71fe..91572819e751 100644 --- a/components/script/dom/hashchangeevent.rs +++ b/components/script/dom/hashchangeevent.rs @@ -11,6 +11,7 @@ use dom::bindings::js::Root; use dom::bindings::reflector::reflect_dom_object; use dom::bindings::str::{DOMString, USVString}; use dom::event::Event; +use dom::eventtarget::EventTarget; use dom::window::Window; use servo_atoms::Atom; @@ -65,6 +66,14 @@ impl HashChangeEvent { init.oldURL.0.clone(), init.newURL.0.clone())) } + + pub fn dispatch(target: &EventTarget, + window: &Window, + old_url: String, + new_url: String) { + let event = HashChangeEvent::new(window, Atom::from("hashchange"), true, false, old_url, new_url); + event.upcast::().fire(target); + } } impl HashChangeEventMethods for HashChangeEvent { diff --git a/components/script/dom/history.rs b/components/script/dom/history.rs index 1a971eae0092..0dd1ab36085d 100644 --- a/components/script/dom/history.rs +++ b/components/script/dom/history.rs @@ -6,20 +6,34 @@ use dom::bindings::codegen::Bindings::HistoryBinding; use dom::bindings::codegen::Bindings::HistoryBinding::HistoryMethods; use dom::bindings::codegen::Bindings::LocationBinding::LocationMethods; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use dom::bindings::error::{Error, ErrorResult}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, Root}; -use dom::bindings::reflector::{Reflector, reflect_dom_object}; +use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object}; +use dom::bindings::str::{DOMString, USVString}; +use dom::bindings::structuredclone::StructuredCloneData; +use dom::eventtarget::EventTarget; use dom::globalscope::GlobalScope; +use dom::hashchangeevent::HashChangeEvent; +use dom::popstateevent::PopStateEvent; use dom::window::Window; use ipc_channel::ipc; -use msg::constellation_msg::TraversalDirection; -use script_traits::ScriptMsg as ConstellationMsg; +use js::jsapi::{HandleValue, JSAutoCompartment, JSContext, Heap}; +use js::jsval::{JSVal, NullValue}; +use msg::constellation_msg::{StateId, TraversalDirection}; +use net_traits::{CoreResourceMsg, IpcSend}; +use script_traits::{PushOrReplaceState, ScriptMsg as ConstellationMsg}; +use servo_url::ServoUrl; +use std::cell::Cell; +use url::Position; // https://html.spec.whatwg.org/multipage/#the-history-interface #[dom_struct] pub struct History { reflector_: Reflector, window: JS, + active_state: Heap, + latest_state: Cell>, } impl History { @@ -27,6 +41,8 @@ impl History { History { reflector_: Reflector::new(), window: JS::from_ref(&window), + active_state: Heap::new(NullValue()), + latest_state: Cell::new(None), } } @@ -35,14 +51,134 @@ impl History { window, HistoryBinding::Wrap) } -} -impl History { - fn traverse_history(&self, direction: TraversalDirection) { + // https://html.spec.whatwg.org/multipage/#dom-history-pushstate + // https://html.spec.whatwg.org/multipage/#dom-history-replacestate + fn update_state(&self, + cx: *mut JSContext, + data: HandleValue, + url: Option, + replace: PushOrReplaceState) -> 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 if push/replace state is being abused. + + // Step 4-5. + let cloned_data = try!(StructuredCloneData::write(cx, data)); + let global_scope = self.window.upcast::(); - let pipeline = global_scope.pipeline_id(); - let msg = ConstellationMsg::TraverseHistory(Some(pipeline), direction); + + let url = match url { + Some(url) => { + // Step 6. + let global_url = global_scope.get_url(); + // 6.1 + let url = match global_url.join(&url.0) { + // 6.2 + Err(_) => return Err(Error::Security), + // 6.3 + Ok(url) => url, + }; + + let document_url = document.url(); + + // 6.4 + if url[Position::BeforeScheme..Position::AfterPort] != + document_url[Position::BeforeScheme..Position::AfterPort] { + return Err(Error::Security); + } + + // 6.5 + if url.origin() != document_url.origin() && (url[Position::BeforePath..Position::AfterQuery] + != document_url[Position::BeforePath..Position::AfterQuery]) { + return Err(Error::Security); + } + url + }, + // Step 7. + None => document.url(), + }; + + // Step 8. + let state_id = match replace { + PushOrReplaceState::Push => StateId::new(), + PushOrReplaceState::Replace => self.latest_state.get().unwrap_or(StateId::new()), + }; + self.latest_state.set(Some(state_id)); + + let state_data = cloned_data.move_to_arraybuffer(); + let _ = global_scope.resource_threads().send(CoreResourceMsg::SetHistoryState(state_id, state_data.clone())); + + let cloned_data = StructuredCloneData::Vector(state_data); + let cx = global_scope.get_cx(); + let globalhandle = global_scope.reflector().get_jsobject(); + rooted!(in(cx) let mut state = NullValue()); + let _ac = JSAutoCompartment::new(cx, globalhandle.get()); + cloned_data.read(global_scope, state.handle_mut()); + self.active_state.set(state.get()); + + // Notify Constellation + let msg = ConstellationMsg::HistoryStateChanged(global_scope.pipeline_id(), state_id, url.clone(), replace); let _ = global_scope.constellation_chan().send(msg); + + // Step 10. + // TODO(cbrewster): We can set the document url without ever notifying the constellation + // This seems like it could be bad in the case of reloading a document that was discarded due to + // being in the distant history. + document.set_url(url); + Ok(()) + } + + pub fn set_active_state(&self, state_id: Option, url: ServoUrl) { + self.latest_state.set(state_id); + match state_id { + Some(state_id) => { + let global_scope = self.window.upcast::(); + let (sender, receiver) = ipc::channel().expect("Failed to create IPC channel."); + let _ = global_scope.resource_threads().send(CoreResourceMsg::GetHistoryState(state_id, sender)); + // The receiver could error out or the received state may be none. + let state_data = receiver.recv().ok().and_then(|s| s).expect("Failed to get state data"); + let cloned_data = StructuredCloneData::Vector(state_data); + + let cx = global_scope.get_cx(); + let globalhandle = global_scope.reflector().get_jsobject(); + rooted!(in(cx) let mut state = NullValue()); + let _ac = JSAutoCompartment::new(cx, globalhandle.get()); + cloned_data.read(global_scope, state.handle_mut()); + self.active_state.set(state.get()); + } + None => self.active_state.set(NullValue()) + }; + PopStateEvent::dispatch_jsval(self.window.upcast::(), &*self.window, self.active_state.handle()); + + let doc_url = self.window.Document().url(); + if doc_url.fragment() != url.fragment() { + let old_url = doc_url.into_string(); + let new_url = url.clone().into_string(); + self.window.Document().set_url(url); + HashChangeEvent::dispatch(self.window.upcast::(), &*self.window, old_url, new_url); + } + } + + pub fn remove_states(&self, state_ids: Vec) { + let global_scope = self.window.upcast::(); + let _ = global_scope.resource_threads().send(CoreResourceMsg::RemoveHistoryStates(state_ids)); + } + + /// Set the active state to `null` + pub fn clear_active_state(&self) { + self.active_state.set(NullValue()); + } + + pub fn traverse_history(&self, direction: TraversalDirection) { + let history_traversal_task_source = self.window.history_traversal_task_source(); + history_traversal_task_source.queue_history_traversal(&*self.window, direction); } } @@ -57,6 +193,12 @@ impl HistoryMethods for History { recv.recv().unwrap() } + #[allow(unsafe_code)] + // https://html.spec.whatwg.org/multipage/#dom-history-state + unsafe fn State(&self, _cx: *mut JSContext) -> JSVal { + self.active_state.get() + } + // https://html.spec.whatwg.org/multipage/#dom-history-go fn Go(&self, delta: i32) { let direction = if delta > 0 { @@ -80,4 +222,24 @@ impl HistoryMethods for History { fn Forward(&self) { self.traverse_history(TraversalDirection::Forward(1)); } + + #[allow(unsafe_code)] + // https://html.spec.whatwg.org/multipage/#dom-history-pushstate + unsafe fn PushState(&self, + cx: *mut JSContext, + data: HandleValue, + _title: DOMString, + url: Option) -> ErrorResult { + self.update_state(cx, data, url, PushOrReplaceState::Push) + } + + #[allow(unsafe_code)] + // https://html.spec.whatwg.org/multipage/#dom-history-replacestate + unsafe fn ReplaceState(&self, + cx: *mut JSContext, + data: HandleValue, + _title: DOMString, + url: Option) -> ErrorResult { + self.update_state(cx, data, url, PushOrReplaceState::Replace) + } } diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 3a1c9daf1c94..a2a81e2cf5c0 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -481,8 +481,7 @@ pub fn Navigate(iframe: &HTMLIFrameElement, direction: TraversalDirection) -> Er if iframe.Mozbrowser() { if iframe.upcast::().is_in_doc_with_browsing_context() { let window = window_from_node(iframe); - let msg = ConstellationMsg::TraverseHistory(iframe.pipeline_id(), direction); - window.upcast::().constellation_chan().send(msg).unwrap(); + window.History().traverse_history(direction); } Ok(()) diff --git a/components/script/dom/popstateevent.rs b/components/script/dom/popstateevent.rs index 1282d847d7c6..3735f282fb14 100644 --- a/components/script/dom/popstateevent.rs +++ b/components/script/dom/popstateevent.rs @@ -12,6 +12,7 @@ use dom::bindings::reflector::reflect_dom_object; use dom::bindings::str::DOMString; use dom::bindings::trace::RootedTraceableBox; use dom::event::Event; +use dom::eventtarget::EventTarget; use dom::window::Window; use js::jsapi::{HandleValue, JSContext}; use js::jsval::JSVal; @@ -64,6 +65,14 @@ impl PopStateEvent { init.parent.cancelable, init.state.handle())) } + + pub fn dispatch_jsval(target: &EventTarget, + window: &Window, + state: HandleValue) { + // TODO(cbrewster): Add popstate to atoms + let event = PopStateEvent::new(window, Atom::from("popstate"), true, false, state); + event.upcast::().fire(target); + } } impl PopStateEventMethods for PopStateEvent { diff --git a/components/script/dom/webidls/History.webidl b/components/script/dom/webidls/History.webidl index c0c1635264a3..918403b35ad4 100644 --- a/components/script/dom/webidls/History.webidl +++ b/components/script/dom/webidls/History.webidl @@ -9,10 +9,12 @@ interface History { readonly attribute unsigned long length; // attribute ScrollRestoration scrollRestoration; - // readonly attribute any state; + readonly attribute any state; void go(optional long delta = 0); void back(); void forward(); - // void pushState(any data, DOMString title, optional USVString? url = null); - // void replaceState(any data, DOMString title, optional USVString? url = null); + [Throws] + void pushState(any data, DOMString title, optional USVString? url = null); + [Throws] + void replaceState(any data, DOMString title, optional USVString? url = null); }; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index d1eba0245e0c..baa0f966e7ee 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -284,7 +284,7 @@ impl Window { self.networking_task_source.clone() } - pub fn history_traversal_task_source(&self) -> Box { + pub fn history_traversal_task_source(&self) -> HistoryTraversalTaskSource { self.history_traversal_task_source.clone() } @@ -1423,6 +1423,12 @@ impl Window { if let Some(fragment) = url.fragment() { doc.check_and_scroll_fragment(fragment); doc.set_url(url.clone()); + self.History().clear_active_state(); + let global_scope = self.upcast::(); + global_scope + .constellation_chan() + .send(ConstellationMsg::UrlHashChanged(global_scope.pipeline_id(), url.clone())) + .unwrap(); return } } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index fa83eaed6319..4fba46014fd7 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -70,7 +70,7 @@ use js::rust::Runtime; use layout_wrapper::ServoLayoutNode; use mem::heap_size_of_self_and_children; use microtask::{MicrotaskQueue, Microtask}; -use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace}; +use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace, StateId}; use net_traits::{CoreResourceMsg, FetchMetadata, FetchResponseListener}; use net_traits::{IpcSend, Metadata, ReferrerPolicy, ResourceThreads}; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheResult, ImageCacheThread}; @@ -113,7 +113,7 @@ use style::dom::{TNode, UnsafeNode}; use style::thread_state; use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource}; use task_source::file_reading::FileReadingTaskSource; -use task_source::history_traversal::HistoryTraversalTaskSource; +use task_source::history_traversal::{HistoryTraversalTask, HistoryTraversalTaskSource}; use task_source::networking::NetworkingTaskSource; use task_source::user_interaction::{UserInteractionTask, UserInteractionTaskSource}; use time::Tm; @@ -249,6 +249,8 @@ pub enum MainThreadScriptMsg { DOMManipulation(DOMManipulationTask), /// Tasks that originate from the user interaction task source UserInteraction(UserInteractionTask), + /// Tasks that originate from the history traversal task source + HistoryTraversal(HistoryTraversalTask), } impl OpaqueSender for Box { @@ -1021,6 +1023,10 @@ impl ScriptThread { self.handle_css_error_reporting(pipeline_id, filename, line, column, msg), ConstellationControlMsg::Reload(pipeline_id) => self.handle_reload(pipeline_id), + ConstellationControlMsg::UpdateActiveState(pipeline_id, state_id, url) => + self.handle_update_active_state(pipeline_id, state_id, url), + ConstellationControlMsg::RemoveStateEntries(pipeline_id, state_ids) => + self.handle_remove_state_entries(pipeline_id, state_ids), ConstellationControlMsg::ExitPipeline(pipeline_id, discard_browsing_context) => self.handle_exit_pipeline_msg(pipeline_id, discard_browsing_context), ConstellationControlMsg::WebVREvent(pipeline_id, event) => @@ -1053,6 +1059,8 @@ impl ScriptThread { task.handle_task(self), MainThreadScriptMsg::UserInteraction(task) => task.handle_task(self), + MainThreadScriptMsg::HistoryTraversal(task) => + task.handle_task(self), } } @@ -2077,6 +2085,20 @@ impl ScriptThread { } } + fn handle_update_active_state(&self, pipeline_id: PipelineId, state_id: Option, url: ServoUrl) { + let window = self.documents.borrow().find_window(pipeline_id); + if let Some(window) = window { + window.History().set_active_state(state_id, url); + } + } + + fn handle_remove_state_entries(&self, pipeline_id: PipelineId, state_ids: Vec) { + let window = self.documents.borrow().find_window(pipeline_id); + if let Some(window) = window { + window.History().remove_states(state_ids); + } + } + fn handle_webvr_event(&self, pipeline_id: PipelineId, event: WebVREventMsg) { let window = self.documents.borrow().find_window(pipeline_id); if let Some(window) = window { diff --git a/components/script/task_source/history_traversal.rs b/components/script/task_source/history_traversal.rs index e5887264cf64..cc5c122477e6 100644 --- a/components/script/task_source/history_traversal.rs +++ b/components/script/task_source/history_traversal.rs @@ -2,19 +2,67 @@ * 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 script_runtime::{ScriptChan, CommonScriptMsg}; -use script_thread::MainThreadScriptMsg; +use dom::bindings::inheritance::Castable; +use dom::bindings::refcounted::Trusted; +use dom::globalscope::GlobalScope; +use dom::window::Window; +use ipc_channel::ipc; +use msg::constellation_msg::TraversalDirection; +use script_thread::{MainThreadScriptMsg, Runnable, RunnableWrapper, ScriptThread}; +use script_traits::ScriptMsg as ConstellationMsg; use std::sync::mpsc::Sender; +use task_source::TaskSource; -#[derive(JSTraceable)] +#[derive(JSTraceable, Clone)] pub struct HistoryTraversalTaskSource(pub Sender); -impl ScriptChan for HistoryTraversalTaskSource { - fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { - self.0.send(MainThreadScriptMsg::Common(msg)).map_err(|_| ()) +impl TaskSource for HistoryTraversalTaskSource { + fn queue_with_wrapper(&self, + msg: Box, + wrapper: &RunnableWrapper) + -> Result<(), ()> + where T: Runnable + Send + 'static { + let msg = HistoryTraversalTask(wrapper.wrap_runnable(msg)); + self.0.send(MainThreadScriptMsg::HistoryTraversal(msg)).map_err(|_| ()) } +} + +impl HistoryTraversalTaskSource { + pub fn queue_history_traversal(&self, window: &Window, direction: TraversalDirection) { + let trusted_window = Trusted::new(window); + let runnable = box HistoryTraversalRunnable { + window: trusted_window, + direction: direction, + }; + let _ = self.queue(runnable, window.upcast()); + } +} + +pub struct HistoryTraversalTask(pub Box); + +impl HistoryTraversalTask { + pub fn handle_task(self, script_thread: &ScriptThread) { + if !self.0.is_cancelled() { + self.0.main_thread_handler(script_thread); + } + } +} + +struct HistoryTraversalRunnable { + window: Trusted, + direction: TraversalDirection, +} + +impl Runnable for HistoryTraversalRunnable { + fn name(&self) -> &'static str { "HistoryTraversalRunnable" } - fn clone(&self) -> Box { - box HistoryTraversalTaskSource((&self.0).clone()) + fn handler(self: Box) { + let window = self.window.root(); + let global_scope = window.upcast::(); + let pipeline = global_scope.pipeline_id(); + let (sender, recv) = ipc::channel().expect("Failed to create ipc channel"); + let msg = ConstellationMsg::TraverseHistory(Some(pipeline), self.direction, sender); + let _ = global_scope.constellation_chan().send(msg); + let _ = recv.recv(); } } diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 5be4cb7d2bc2..2ea3ae5a55d6 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -56,7 +56,7 @@ use hyper::method::Method; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use libc::c_void; use msg::constellation_msg::{FrameId, FrameType, Key, KeyModifiers, KeyState}; -use msg::constellation_msg::{PipelineId, PipelineNamespaceId, TraversalDirection}; +use msg::constellation_msg::{PipelineId, PipelineNamespaceId, StateId, TraversalDirection}; use net_traits::{ReferrerPolicy, ResourceThreads}; use net_traits::image::base::Image; use net_traits::image_cache_thread::ImageCacheThread; @@ -185,6 +185,16 @@ pub struct NewLayoutInfo { pub layout_threads: usize, } +/// When updating the state object of the history, should it replace the current state or push +/// a new entry? +#[derive(PartialEq, Debug, Deserialize, Serialize)] +pub enum PushOrReplaceState { + /// Push a new entry + Push, + /// Replace the current entry + Replace, +} + /// When a pipeline is closed, should its browsing context be discarded too? #[derive(Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum DiscardBrowsingContext { @@ -278,6 +288,10 @@ pub enum ConstellationControlMsg { ReportCSSError(PipelineId, String, usize, usize, String), /// Reload the given page. Reload(PipelineId), + /// Notifies the history to update the active state entry. + UpdateActiveState(PipelineId, Option, ServoUrl), + /// Notifies the history to remove inaccessible state entries. + RemoveStateEntries(PipelineId, Vec), /// Notifies the script thread of a WebVR device event WebVREvent(PipelineId, WebVREventMsg) } @@ -311,6 +325,8 @@ impl fmt::Debug for ConstellationControlMsg { FramedContentChanged(..) => "FramedContentChanged", ReportCSSError(..) => "ReportCSSError", Reload(..) => "Reload", + UpdateActiveState(..) => "UpdateActiveState", + RemoveStateEntries(..) => "RemoveStateEntries", WebVREvent(..) => "WebVREvent", }; write!(formatter, "ConstellationMsg::{}", variant) diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs index d35e23a7bb99..ef7525bc6816 100644 --- a/components/script_traits/script_msg.rs +++ b/components/script_traits/script_msg.rs @@ -10,6 +10,7 @@ use IFrameLoadInfoWithData; use LayoutControlMsg; use LoadData; use MozBrowserEvent; +use PushOrReplaceState; use WorkerGlobalScopeInit; use WorkerScriptLoadOrigin; use canvas_traits::CanvasMsg; @@ -18,7 +19,7 @@ use euclid::point::Point2D; use euclid::size::{Size2D, TypedSize2D}; use gfx_traits::ScrollRootId; use ipc_channel::ipc::IpcSender; -use msg::constellation_msg::{FrameId, PipelineId, TraversalDirection}; +use msg::constellation_msg::{FrameId, PipelineId, StateId, TraversalDirection}; use msg::constellation_msg::{Key, KeyModifiers, KeyState}; use net_traits::CoreResourceMsg; use net_traits::storage_thread::StorageType; @@ -96,8 +97,8 @@ pub enum ScriptMsg { /// Dispatch a mozbrowser event to the parent of this pipeline. /// The first PipelineId is for the parent, the second is for the originating pipeline. MozBrowserEvent(PipelineId, PipelineId, MozBrowserEvent), - /// HTMLIFrameElement Forward or Back traversal. - TraverseHistory(Option, TraversalDirection), + /// Traverses the joint session history. This is a synchronous message. + TraverseHistory(Option, TraversalDirection, IpcSender<()>), /// Gets the length of the joint session history from the constellation. JointSessionHistoryLength(PipelineId, IpcSender), /// Favicon detected @@ -151,6 +152,10 @@ pub enum ScriptMsg { RegisterServiceWorker(ScopeThings, ServoUrl), /// Enter or exit fullscreen SetFullscreenState(bool), + /// Notifies the constellation that a new history state has been pushed or replaced. + HistoryStateChanged(PipelineId, StateId, ServoUrl, PushOrReplaceState), + /// Notifies the constellation that url's hash has changed. + UrlHashChanged(PipelineId, ServoUrl), /// Requests that the compositor shut down. Exit, } From 8d4d85413c1c0d40ec0a91bc2309eeedaa6ac073 Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Fri, 27 Jan 2017 10:15:14 -0600 Subject: [PATCH 4/7] Enable history interface 001 and 002 tests --- etc/ci/former_intermittents_wpt.txt | 2 ++ .../html/browsers/history/the-history-interface/001.html.ini | 1 - .../html/browsers/history/the-history-interface/002.html.ini | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/ci/former_intermittents_wpt.txt b/etc/ci/former_intermittents_wpt.txt index 6001c12b3a23..68daea3b4b46 100644 --- a/etc/ci/former_intermittents_wpt.txt +++ b/etc/ci/former_intermittents_wpt.txt @@ -1,2 +1,4 @@ /_mozilla/mozilla/css/canvas_over_area.html /_mozilla/mozilla/css/iframe/hide_layers2.html +/html/browsers/history/the-history-interface/001.html +/html/browsers/history/the-history-interface/002.html \ No newline at end of file diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini index 86c15b26af03..dc2104d219a5 100644 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini @@ -1,6 +1,5 @@ [001.html] type: testharness - disabled: https://github.com/servo/servo/issues/12580 [history.length should update when setting location.hash] expected: FAIL diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini index c8350da8e2eb..c82a5de2ac7b 100644 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini @@ -1,6 +1,5 @@ [002.html] type: testharness - disabled: https://github.com/servo/servo/issues/12580 [history.length should update when setting location.hash] expected: FAIL From 79e39b0013ce0b935a2f484b9679d3f79e47da1c Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Fri, 3 Feb 2017 14:29:18 -0600 Subject: [PATCH 5/7] Add extra time for document url to be updated --- .../html/browsers/history/the-history-interface/002.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html b/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html index eb0c15aab896..70d796dd2db0 100644 --- a/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html +++ b/tests/wpt/web-platform-tests/html/browsers/history/the-history-interface/002.html @@ -73,7 +73,7 @@ assert_equals( iframe.contentWindow.location.hash.replace(/^#/,''), 'test3' ); }, 'hash should change when replaceState is called with a URL'); history.go(-1); - setTimeout(tests3,50); //.go is queued to end of thread + setTimeout(tests3,100); //.go is queued to end of thread } function tests3() { test(function () { From ce76ac0079bca2155d583c48d0f7d4f37c9caee1 Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Thu, 26 Jan 2017 22:14:29 -0600 Subject: [PATCH 6/7] Update test expectations update test expectations --- ...ration-fragment-scrolling-samedoc.html.ini | 5 -- .../history-traversal/popstate_event.html.ini | 5 -- .../navigating-across-documents/002.html.ini | 5 ++ .../the-history-interface/001.html.ini | 84 +------------------ .../the-history-interface/002.html.ini | 78 ----------------- .../the-history-interface/004.html.ini | 11 --- .../the-history-interface/005.html.ini | 11 --- .../the-history-interface/006.html.ini | 14 ---- .../the-history-interface/007.html.ini | 11 +-- .../the-history-interface/008.html.ini | 8 -- .../the-history-interface/009.html.ini | 14 ---- .../the-history-interface/010.html.ini | 14 ---- .../the-history-interface/011.html.ini | 11 --- .../the-history-interface/012.html.ini | 11 --- .../combination_history_001.html.ini | 5 -- .../combination_history_002.html.ini | 5 -- .../combination_history_003.html.ini | 5 -- .../combination_history_004.html.ini | 5 -- .../combination_history_005.html.ini | 5 -- .../combination_history_006.html.ini | 5 -- .../combination_history_007.html.ini | 5 -- .../history_back.html.ini | 5 -- .../history_forward.html.ini | 5 -- .../history_go_minus.html.ini | 5 -- .../history_go_plus.html.ini | 5 -- .../history_pushstate.html.ini | 5 -- .../history_pushstate_err.html.ini | 5 -- ...history_pushstate_nooptionalparam.html.ini | 5 -- .../history_replacestate.html.ini | 5 -- .../history_replacestate_err.html.ini | 5 -- ...tory_replacestate_nooptionalparam.html.ini | 5 -- .../history_state.html.ini | 5 -- .../location_hash.html.ini | 3 - .../location_search.html.ini | 5 -- .../browsing-context-names/001.html.ini | 3 - .../wpt/metadata/html/dom/interfaces.html.ini | 24 ------ 36 files changed, 9 insertions(+), 398 deletions(-) delete mode 100644 tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/popstate_event.html.ini create mode 100644 tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/002.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/004.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/005.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/006.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/008.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/009.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/010.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/011.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/012.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_001.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_002.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_003.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_004.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_005.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_006.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_007.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_back.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_forward.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_minus.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_plus.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_err.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_err.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-history-interface/history_state.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/history/the-location-interface/location_search.html.ini delete mode 100644 tests/wpt/metadata/html/browsers/windows/browsing-context-names/001.html.ini diff --git a/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html.ini b/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html.ini deleted file mode 100644 index 3322eb2c4412..000000000000 --- a/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[scroll-restoration-fragment-scrolling-samedoc.html] - type: testharness - [Manual scroll restoration should take precedent over scrolling to fragment in cross doc navigation] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/popstate_event.html.ini b/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/popstate_event.html.ini deleted file mode 100644 index bbe5b73b9227..000000000000 --- a/tests/wpt/metadata/html/browsers/browsing-the-web/history-traversal/popstate_event.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[popstate_event.html] - type: testharness - [Queue a task to fire popstate event] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/002.html.ini b/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/002.html.ini new file mode 100644 index 000000000000..d92b60ac8ed4 --- /dev/null +++ b/tests/wpt/metadata/html/browsers/browsing-the-web/navigating-across-documents/002.html.ini @@ -0,0 +1,5 @@ +[002.html] + type: testharness + [Multiple simultaneous navigations] + expected: FAIL + diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini index dc2104d219a5..4f5de2d17083 100644 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-history-interface/001.html.ini @@ -1,101 +1,23 @@ [001.html] type: testharness - [history.length should update when setting location.hash] - expected: FAIL - - [history.pushState must exist] - expected: FAIL - - [history.pushState must exist within iframes] - expected: FAIL - - [initial history.state should be null] - expected: FAIL - - [history.length should update when pushing a state] - expected: FAIL - - [history.state should update after a state is pushed] - expected: FAIL - - [traversing history must traverse pushed states] - expected: FAIL - - [pushState must not be allowed to create invalid URLs] - expected: FAIL - - [pushState must not be allowed to create cross-origin URLs] - expected: FAIL - - [pushState must not be allowed to create cross-origin URLs (about:blank)] - expected: FAIL - - [pushState must not be allowed to create cross-origin URLs (data:URI)] - expected: FAIL - - [pushState should not actually load the new URL] - expected: FAIL - - [security errors are expected to be thrown in the context of the document that owns the history object] - expected: FAIL - - [location.hash must be allowed to change (part 1)] - expected: FAIL - - [location.hash must be allowed to change (part 2)] - expected: FAIL - - [pushState must not alter location.hash when no URL is provided] - expected: FAIL - - [pushState must remove all history after the current state] - expected: FAIL - - [pushState must be able to set location.hash] - expected: FAIL - - [pushState must remove any tasks queued by the history traversal task source] - expected: FAIL - - [pushState must be able to set location.pathname] - expected: FAIL - - [pushState must be able to set absolute URLs to the same host] - expected: FAIL - - [pushState must not be able to use a function as data] - expected: FAIL - - [pushState must not be able to use a DOM node as data] - expected: FAIL - - [pushState must not be able to use an error object as data] - expected: FAIL - - [security errors are expected to be thrown in the context of the document that owns the history object (2)] - expected: FAIL - [pushState must be able to make structured clones of complex objects] expected: FAIL [history.state should also reference a clone of the original object] expected: FAIL - [popstate event should fire when navigation occurs] - expected: FAIL - [popstate event should pass the state data] expected: FAIL [state data should cope with circular object references] expected: FAIL - [state data should be a clone of the original object, not a reference to it] - expected: FAIL - [history.state should also reference a clone of the original object (2)] expected: FAIL [history.state should be a separate clone of the object, not a reference to the object passed to the event handler] expected: FAIL + [pushState must remove any tasks queued by the history traversal task source] + expected: FAIL + diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini index c82a5de2ac7b..8865cc12bf44 100644 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-history-interface/002.html.ini @@ -1,95 +1,17 @@ [002.html] type: testharness - [history.length should update when setting location.hash] - expected: FAIL - - [history.replaceState must exist] - expected: FAIL - - [history.replaceState must exist within iframes] - expected: FAIL - - [initial history.state should be null] - expected: FAIL - - [history.length should not update when replacing a state with no URL] - expected: FAIL - - [history.state should update after a state is pushed] - expected: FAIL - - [hash should not change when replaceState is called without a URL] - expected: FAIL - - [history.length should not update when replacing a state with a URL] - expected: FAIL - - [hash should change when replaceState is called with a URL] - expected: FAIL - - [replaceState must replace the existing state without altering the forward history] - expected: FAIL - - [replaceState must not be allowed to create invalid URLs] - expected: FAIL - - [replaceState must not be allowed to create cross-origin URLs] - expected: FAIL - - [replaceState must not be allowed to create cross-origin URLs (about:blank)] - expected: FAIL - - [replaceState must not be allowed to create cross-origin URLs (data:URI)] - expected: FAIL - - [security errors are expected to be thrown in the context of the document that owns the history object] - expected: FAIL - - [replaceState must be able to set location.pathname] - expected: FAIL - - [replaceState must be able to set absolute URLs to the same host] - expected: FAIL - - [replaceState must not remove any tasks queued by the history traversal task source] - expected: FAIL - - [.go must queue a task with the history traversal task source (run asynchronously)] - expected: FAIL - - [replaceState must not be able to use a function as data] - expected: FAIL - - [replaceState must not be able to use a DOM node as data] - expected: FAIL - - [replaceState must not be able to use an error object as data] - expected: FAIL - - [replaceState should not actually load the new URL] - expected: FAIL - - [security errors are expected to be thrown in the context of the document that owns the history object (2)] - expected: FAIL - [replaceState must be able to make structured clones of complex objects] expected: FAIL [history.state should also reference a clone of the original object] expected: FAIL - [popstate event should fire when navigation occurs] - expected: FAIL - [popstate event should pass the state data] expected: FAIL [state data should cope with circular object references] expected: FAIL - [state data should be a clone of the original object, not a reference to it] - expected: FAIL - [history.state should also reference a clone of the original object (2)] expected: FAIL diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/004.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/004.html.ini deleted file mode 100644 index 278160ab559f..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/004.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[004.html] - type: testharness - [browser needs to support hashchange events for this testcase] - expected: FAIL - - [queued .go commands should all be executed when the queue is processed] - expected: FAIL - - [history position should be calculated when executing, not when calling the .go command] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/005.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/005.html.ini deleted file mode 100644 index 760996066882..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/005.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[005.html] - type: testharness - [history.pushState support is needed for this testcase] - expected: FAIL - - [ should register a listener for the popstate event] - expected: FAIL - - [window.onpopstate should register a listener for the popstate event] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/006.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/006.html.ini deleted file mode 100644 index 505c9b8cf838..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/006.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[006.html] - type: testharness - [history.state should initially be null] - expected: FAIL - - [history.state should still be null onload] - expected: FAIL - - [history.state should still be null after onload] - expected: FAIL - - [writing to history.state should be silently ignored and not throw an error] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/007.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/007.html.ini index 5c3b95e454e7..620417b64b03 100644 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/007.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-history-interface/007.html.ini @@ -1,14 +1,5 @@ [007.html] type: testharness - [history.state should initially be null] - expected: FAIL - - [history.pushState support is needed for this testcase] - expected: FAIL - - [history.state should reflect pushed state] - expected: FAIL - [popstate event should fire before onload fires] expected: FAIL @@ -18,6 +9,6 @@ [history.state should reflect the navigated state onload] expected: FAIL - [history.state should reflect the navigated state after onload] + [popstate event should not fire after onload fires] expected: FAIL diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/008.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/008.html.ini deleted file mode 100644 index 5af01f618f1d..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/008.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[008.html] - type: testharness - [history.pushState URL resolving should be done relative to the document, not the script] - expected: FAIL - - [history.replaceState URL resolving should be done relative to the document, not the script] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/009.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/009.html.ini deleted file mode 100644 index 775bd6adf7d8..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/009.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[009.html] - type: testharness - [HTTP Referer should use the pushed state] - expected: FAIL - - [document.referrer should use the pushed state] - expected: FAIL - - [HTTP Referer should use the replaced state] - expected: FAIL - - [document.referrer should use the replaced state] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/010.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/010.html.ini deleted file mode 100644 index cd895f71c05b..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/010.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[010.html] - type: testharness - [HTTP Referer should use the pushed state (before onload)] - expected: FAIL - - [document.referrer should use the pushed state (before onload)] - expected: FAIL - - [HTTP Referer should use the replaced state (before onload)] - expected: FAIL - - [document.referrer should use the replaced state (before onload)] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/011.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/011.html.ini deleted file mode 100644 index 0867b0401e09..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/011.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[011.html] - type: testharness - [pushState should be able to set the location state] - expected: FAIL - - [pushed location should be reflected immediately] - expected: FAIL - - [pushed location should be retained after the page has loaded] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/012.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/012.html.ini deleted file mode 100644 index b153bc5f21ce..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/012.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[012.html] - type: testharness - [replaceState should be able to set the location state] - expected: FAIL - - [replaced location should be reflected immediately] - expected: FAIL - - [replaced location should be retained after the page has loaded] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_001.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_001.html.ini deleted file mode 100644 index 6c1948b3884f..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_001.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_001.html] - type: testharness - [Combine pushState and replaceSate methods] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_002.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_002.html.ini deleted file mode 100644 index b298a5d7c07c..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_002.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_002.html] - type: testharness - [After calling of pushState method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_003.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_003.html.ini deleted file mode 100644 index 0329eef96dec..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_003.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_003.html] - type: testharness - [After calling of pushState and replaceState methods, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_004.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_004.html.ini deleted file mode 100644 index 703bccbb3347..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_004.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_004.html] - type: testharness - [After calling of back method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_005.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_005.html.ini deleted file mode 100644 index 969bd2d84f1b..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_005.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_005.html] - type: testharness - [After calling of forward method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_006.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_006.html.ini deleted file mode 100644 index 416b9e58d13c..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_006.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_006.html] - type: testharness - [After calling of go method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_007.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_007.html.ini deleted file mode 100644 index 5db6960533d4..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/combination_history_007.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_007.html] - type: testharness - [After calling of back and pushState method, check length] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_back.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_back.html.ini deleted file mode 100644 index 5e01759025de..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_back.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_back.html] - type: testharness - [history back] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_forward.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_forward.html.ini deleted file mode 100644 index d3c12080dfa9..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_forward.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_forward.html] - type: testharness - [history forward] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_minus.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_minus.html.ini deleted file mode 100644 index 01f21b309605..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_minus.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_go_minus.html] - type: testharness - [history go minus] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_plus.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_plus.html.ini deleted file mode 100644 index e2999746053f..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_go_plus.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_go_plus.html] - type: testharness - [history go plus] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate.html.ini deleted file mode 100644 index d9c5e0b38af1..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_pushstate.html] - type: testharness - [history pushState] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_err.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_err.html.ini deleted file mode 100644 index 5a4e399ac99d..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_err.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_pushstate_err.html] - type: testharness - [history pushState SECURITY_ERR] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html.ini deleted file mode 100644 index 40a1deb0cc3e..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_pushstate_nooptionalparam.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_pushstate_nooptionalparam.html] - type: testharness - [history pushState NoOptionalParam] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate.html.ini deleted file mode 100644 index e2068698c0d6..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_replacestate.html] - type: testharness - [history replaceState] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_err.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_err.html.ini deleted file mode 100644 index 316097c5b4d1..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_err.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_replacestate_err.html] - type: testharness - [history replaceState SECURITY_ERR] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html.ini deleted file mode 100644 index d523d0b78d0c..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_replacestate_nooptionalparam.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_replacestate_nooptionalparam.html] - type: testharness - [history replaceStateNoOptionalParam] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_state.html.ini b/tests/wpt/metadata/html/browsers/history/the-history-interface/history_state.html.ini deleted file mode 100644 index 2819759ee82b..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-history-interface/history_state.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_state.html] - type: testharness - [history state] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/history/the-location-interface/location_hash.html.ini b/tests/wpt/metadata/html/browsers/history/the-location-interface/location_hash.html.ini index 2cf94f6e2e68..14fb78311f2e 100644 --- a/tests/wpt/metadata/html/browsers/history/the-location-interface/location_hash.html.ini +++ b/tests/wpt/metadata/html/browsers/history/the-location-interface/location_hash.html.ini @@ -1,8 +1,5 @@ [location_hash.html] type: testharness - [location hash] - expected: FAIL - [Setting location.hash on srcdoc iframe] expected: FAIL diff --git a/tests/wpt/metadata/html/browsers/history/the-location-interface/location_search.html.ini b/tests/wpt/metadata/html/browsers/history/the-location-interface/location_search.html.ini deleted file mode 100644 index fa2ec8514b8e..000000000000 --- a/tests/wpt/metadata/html/browsers/history/the-location-interface/location_search.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[location_search.html] - type: testharness - [location search] - expected: FAIL - diff --git a/tests/wpt/metadata/html/browsers/windows/browsing-context-names/001.html.ini b/tests/wpt/metadata/html/browsers/windows/browsing-context-names/001.html.ini deleted file mode 100644 index b3538369e2e9..000000000000 --- a/tests/wpt/metadata/html/browsers/windows/browsing-context-names/001.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[001.html] - type: testharness - expected: TIMEOUT diff --git a/tests/wpt/metadata/html/dom/interfaces.html.ini b/tests/wpt/metadata/html/dom/interfaces.html.ini index e7f2a58aca8b..2d4a4f0bffed 100644 --- a/tests/wpt/metadata/html/dom/interfaces.html.ini +++ b/tests/wpt/metadata/html/dom/interfaces.html.ini @@ -4524,15 +4524,6 @@ [BarProp interface: attribute visible] expected: FAIL - [History interface: attribute state] - expected: FAIL - - [History interface: operation pushState(any,DOMString,DOMString)] - expected: FAIL - - [History interface: operation replaceState(any,DOMString,DOMString)] - expected: FAIL - [History interface: window.history must inherit property "state" with the proper type (1)] expected: FAIL @@ -4548,15 +4539,9 @@ [History interface: window.history must inherit property "pushState" with the proper type (5)] expected: FAIL - [History interface: calling pushState(any,DOMString,DOMString) on window.history with too few arguments must throw TypeError] - expected: FAIL - [History interface: window.history must inherit property "replaceState" with the proper type (6)] expected: FAIL - [History interface: calling replaceState(any,DOMString,DOMString) on window.history with too few arguments must throw TypeError] - expected: FAIL - [Location interface: calling replace(DOMString) on window.location with too few arguments must throw TypeError] expected: FAIL @@ -6009,15 +5994,6 @@ [History interface: window.history must inherit property "scrollRestoration" with the proper type (1)] expected: FAIL - [History interface: window.history must inherit property "state" with the proper type (2)] - expected: FAIL - - [History interface: window.history must inherit property "pushState" with the proper type (6)] - expected: FAIL - - [History interface: window.history must inherit property "replaceState" with the proper type (7)] - expected: FAIL - [HTMLAllCollection interface: attribute length] expected: FAIL From 13a9509dd307db35729ee099d8a2fb07403d63a0 Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Thu, 16 Feb 2017 21:58:17 -0600 Subject: [PATCH 7/7] Add frame state id unit test --- tests/unit/constellation/frame.rs | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/unit/constellation/frame.rs b/tests/unit/constellation/frame.rs index 83b2b89a8afb..13ce70bfb52b 100644 --- a/tests/unit/constellation/frame.rs +++ b/tests/unit/constellation/frame.rs @@ -4,6 +4,7 @@ use constellation::Frame; use msg::constellation_msg::{PipelineId, PipelineNamespace, PipelineNamespaceId, FrameId}; +use msg::constellation_msg::StateId; use servo_url::ServoUrl; #[test] @@ -69,3 +70,36 @@ fn test_entry_discard() { let evicted_id = frame.discard_entry(&frame.prev[0]); assert_eq!(evicted_id, Some(pipeline_id)); } + +#[test] +fn test_state_id() { + PipelineNamespace::install(PipelineNamespaceId(0)); + let pipeline_id = PipelineId::new(); + let frame_id = FrameId::new(); + FrameId::install(frame_id); + let url = ServoUrl::parse("about:blank").expect("Infallible"); + let mut frame = Frame::new(frame_id, pipeline_id, url.clone()); + + let state_id = StateId::new(); + frame.load_state_change(state_id, url.clone()); + + assert_eq!(frame.prev.len(), 1); + assert_eq!(frame.current.pipeline_id(), frame.prev[0].pipeline_id()); + + let new_pipeline_id = PipelineId::new(); + frame.load(new_pipeline_id, url); + + let evicted_id = frame.discard_entry(&frame.prev[0]); + assert_eq!(evicted_id, Some(pipeline_id)); + // Both entries in the session history (prev) should no longer have a pipeline id + assert!(frame.prev[0].pipeline_id().is_none()); + assert!(frame.prev[1].pipeline_id().is_none()); + assert_eq!(frame.current.pipeline_id(), Some(new_pipeline_id)); + + // Updating the pipeline id of one of the entries should update the pipeline id of + // the other entry. + let reloaded_pipeline_id = PipelineId::new(); + frame.prev[0].update_pipeline(reloaded_pipeline_id); + assert_eq!(frame.prev[0].pipeline_id(), Some(reloaded_pipeline_id)); + assert_eq!(frame.prev[1].pipeline_id(), Some(reloaded_pipeline_id)); +}