From 7d7ccbc437e8e5961d15830d58530ebd706364f7 Mon Sep 17 00:00:00 2001 From: Connor Brewster Date: Thu, 5 Apr 2018 19:26:37 -0400 Subject: [PATCH] servo: Merge #20507 - New Session History (from cbrewster:history_transactions); r=asajeffrey Remaining Work: - [x] Move `LoadData` from `BrowsingContext` to `Pipeline` - [x] Cleanup `*Diff` and `*Changeset` types - [x] Implement pipeline discarding and reloading - [x] Document new code --- - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). - [ ] There are tests for these changes OR - [ ] These changes do not require tests because _____ Source-Repo: https://github.com/servo/servo Source-Revision: 7f3b9ca013140b119cc6e7b18babd4ceecdc2336 --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : 3daf95f98b695a81da2e696a94b52b3b37f87ba0 --- servo/Cargo.lock | 1 - servo/components/constellation/Cargo.toml | 1 - .../constellation/browsingcontext.rs | 107 +-- .../components/constellation/constellation.rs | 672 ++++++++---------- servo/components/constellation/lib.rs | 2 +- servo/components/constellation/pipeline.rs | 14 +- .../constellation/session_history.rs | 164 +++++ 7 files changed, 498 insertions(+), 463 deletions(-) create mode 100644 servo/components/constellation/session_history.rs diff --git a/servo/Cargo.lock b/servo/Cargo.lock index 5a2b918e5f46..b92960b63c2d 100644 --- a/servo/Cargo.lock +++ b/servo/Cargo.lock @@ -456,7 +456,6 @@ dependencies = [ "gfx_traits 0.0.1", "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "layout_traits 0.0.1", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "metrics 0.0.1", diff --git a/servo/components/constellation/Cargo.toml b/servo/components/constellation/Cargo.toml index 0d4c4d483ba0..ee8067086a39 100644 --- a/servo/components/constellation/Cargo.toml +++ b/servo/components/constellation/Cargo.toml @@ -23,7 +23,6 @@ gfx = {path = "../gfx"} gfx_traits = {path = "../gfx_traits"} hyper = "0.10" ipc-channel = "0.10" -itertools = "0.7" layout_traits = {path = "../layout_traits"} log = "0.4" metrics = {path = "../metrics"} diff --git a/servo/components/constellation/browsingcontext.rs b/servo/components/constellation/browsingcontext.rs index a7d617164bcf..4926bbcf7dd5 100644 --- a/servo/components/constellation/browsingcontext.rs +++ b/servo/components/constellation/browsingcontext.rs @@ -3,13 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use euclid::TypedSize2D; -use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, PipelineId}; +use msg::constellation_msg::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId}; use pipeline::Pipeline; -use script_traits::LoadData; -use std::collections::HashMap; -use std::iter::once; -use std::mem::replace; -use std::time::Instant; +use std::collections::{HashMap, HashSet}; use style_traits::CSSPixel; /// The constellation's view of a browsing context. @@ -29,20 +25,10 @@ pub struct BrowsingContext { /// The size of the frame. pub size: Option>, - /// The timestamp for the current session history entry. - pub instant: Instant, - /// The pipeline for the current session history entry. pub pipeline_id: PipelineId, - /// The load data for the current session history entry. - pub load_data: LoadData, - - /// The past session history, ordered chronologically. - pub prev: Vec, - - /// The future session history, ordered reverse chronologically. - pub next: Vec, + pub pipelines: HashSet, } impl BrowsingContext { @@ -50,51 +36,22 @@ impl BrowsingContext { /// Note this just creates the browsing context, it doesn't add it to the constellation's set of browsing contexts. pub fn new(id: BrowsingContextId, top_level_id: TopLevelBrowsingContextId, - pipeline_id: PipelineId, - load_data: LoadData) + pipeline_id: PipelineId) -> BrowsingContext { + let mut pipelines = HashSet::new(); + pipelines.insert(pipeline_id); BrowsingContext { id: id, top_level_id: top_level_id, size: None, pipeline_id: pipeline_id, - instant: Instant::now(), - load_data: load_data, - prev: vec!(), - next: vec!(), + pipelines, } } - /// Get the current session history entry. - pub fn current(&self) -> SessionHistoryEntry { - SessionHistoryEntry { - instant: self.instant, - browsing_context_id: self.id, - pipeline_id: Some(self.pipeline_id), - load_data: self.load_data.clone(), - } - } - - /// Set the current session history entry, and push the current frame entry into the past. - pub fn load(&mut self, pipeline_id: PipelineId, load_data: LoadData) { - let current = self.current(); - self.prev.push(current); - self.instant = Instant::now(); + pub fn update_current_entry(&mut self, pipeline_id: PipelineId) { self.pipeline_id = pipeline_id; - self.load_data = load_data; - } - - /// Set the future to be empty. - pub fn remove_forward_entries(&mut self) -> Vec { - replace(&mut self.next, vec!()) - } - - /// Update the current entry of the BrowsingContext from an entry that has been traversed to. - pub fn update_current(&mut self, pipeline_id: PipelineId, entry: SessionHistoryEntry) { - self.pipeline_id = pipeline_id; - self.instant = entry.instant; - self.load_data = entry.load_data; } /// Is this a top-level browsing context? @@ -103,50 +60,6 @@ impl BrowsingContext { } } -/// An entry in a browsing context's session history. -/// Each entry stores the pipeline id for a document in the session history. -/// -/// When we operate on the joint session history, entries are sorted chronologically, -/// so we timestamp the entries by when the entry was added to the session history. -/// -/// -#[derive(Clone)] -pub struct SessionHistoryEntry { - /// 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 load data for this entry, used to reload the pipeline if it has been discarded - pub load_data: LoadData, - - /// The frame that this session history entry is part of - pub browsing_context_id: BrowsingContextId, -} - -/// Represents a pending change in a session history, that will be applied -/// once the new pipeline has loaded and completed initial layout / paint. -pub struct SessionHistoryChange { - /// The browsing context to change. - pub browsing_context_id: BrowsingContextId, - - /// The top-level browsing context ancestor. - pub top_level_browsing_context_id: TopLevelBrowsingContextId, - - /// The pipeline for the document being loaded. - pub new_pipeline_id: PipelineId, - - /// The data for the document being loaded. - pub load_data: LoadData, - - /// Is the new document replacing the current document (e.g. a reload) - /// or pushing it into the session history (e.g. a navigation)? - /// If it is replacing an existing entry, we store its timestamp. - pub replace_instant: Option, -} - /// An iterator over browsing contexts, returning the descendant /// contexts whose active documents are fully active, in depth-first /// order. @@ -217,9 +130,7 @@ impl<'a> Iterator for AllBrowsingContextsIterator<'a> { continue; }, }; - let child_browsing_context_ids = browsing_context.prev.iter().chain(browsing_context.next.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(browsing_context.pipeline_id)) + let child_browsing_context_ids = browsing_context.pipelines.iter() .filter_map(|pipeline_id| pipelines.get(&pipeline_id)) .flat_map(|pipeline| pipeline.children.iter()); self.stack.extend(child_browsing_context_ids); diff --git a/servo/components/constellation/constellation.rs b/servo/components/constellation/constellation.rs index 033d79a5bdf5..9b3e2e8a160f 100644 --- a/servo/components/constellation/constellation.rs +++ b/servo/components/constellation/constellation.rs @@ -91,8 +91,7 @@ use backtrace::Backtrace; use bluetooth_traits::BluetoothRequest; -use browsingcontext::{BrowsingContext, SessionHistoryChange, SessionHistoryEntry}; -use browsingcontext::{FullyActiveBrowsingContextsIterator, AllBrowsingContextsIterator}; +use browsingcontext::{AllBrowsingContextsIterator, BrowsingContext, FullyActiveBrowsingContextsIterator}; use canvas::canvas_paint_thread::CanvasPaintThread; use canvas::webgl_thread::WebGLThreads; use canvas_traits::canvas::CanvasId; @@ -110,7 +109,6 @@ use gfx_traits::Epoch; use ipc_channel::{Error as IpcError}; use ipc_channel::ipc::{self, IpcSender, IpcReceiver}; use ipc_channel::router::ROUTER; -use itertools::Itertools; use layout_traits::LayoutThreadFactory; use log::{Log, Level, LevelFilter, Metadata, Record}; use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, PipelineId}; @@ -138,10 +136,9 @@ use servo_config::prefs::PREFS; use servo_rand::{Rng, SeedableRng, ServoRng, random}; use servo_remutex::ReentrantMutex; use servo_url::{Host, ImmutableOrigin, ServoUrl}; +use session_history::{JointSessionHistory, NeedsToReload, SessionHistoryChange, SessionHistoryDiff}; use std::borrow::ToOwned; -use std::cmp::Ordering; use std::collections::{HashMap, VecDeque}; -use std::iter::once; use std::marker::PhantomData; use std::process; use std::rc::{Rc, Weak}; @@ -278,6 +275,8 @@ pub struct Constellation { /// to become same-origin, at which point they can share DOM objects. event_loops: HashMap>>, + joint_session_histories: HashMap, + /// The set of all the pipelines in the browser. /// (See the `pipeline` module for more details.) pipelines: HashMap, @@ -588,6 +587,7 @@ impl Constellation swmanager_receiver: swmanager_receiver, swmanager_sender: sw_mgr_clone, event_loops: HashMap::new(), + joint_session_histories: HashMap::new(), pipelines: HashMap::new(), browsing_contexts: HashMap::new(), pending_changes: vec!(), @@ -673,7 +673,6 @@ impl Constellation sandbox: IFrameSandboxState, is_private: bool) { if self.shutting_down { return; } - debug!("Creating new pipeline {} in browsing context {}.", pipeline_id, browsing_context_id); let (event_loop, host) = match sandbox { @@ -798,81 +797,13 @@ impl Constellation } } - /// Get an iterator for the browsing contexts in a tree. - fn all_browsing_contexts_iter(&self, top_level_browsing_context_id: TopLevelBrowsingContextId) - -> AllBrowsingContextsIterator - { - self.all_descendant_browsing_contexts_iter(BrowsingContextId::from(top_level_browsing_context_id)) - } - - #[cfg(feature = "unstable")] - /// The joint session future is the merge of the session future of every - /// browsing_context, sorted chronologically. - fn joint_session_future<'a>(&'a self, top_level_browsing_context_id: TopLevelBrowsingContextId) - -> impl Iterator + 'a - { - self.all_browsing_contexts_iter(top_level_browsing_context_id) - .map(|browsing_context| browsing_context.next.iter().rev()) - .kmerge_by(|a, b| a.instant.cmp(&b.instant) == Ordering::Less) - } - - #[cfg(not(feature = "unstable"))] - /// The joint session future is the merge of the session future of every - /// browsing_context, sorted chronologically. - fn joint_session_future<'a>(&'a self, top_level_browsing_context_id: TopLevelBrowsingContextId) - -> Box + 'a> - { - Box::new( - self.all_browsing_contexts_iter(top_level_browsing_context_id) - .map(|browsing_context| browsing_context.next.iter().rev()) - .kmerge_by(|a, b| a.instant.cmp(&b.instant) == Ordering::Less) - ) - } - - #[cfg(feature = "unstable")] - /// The joint session past is the merge of the session past of every - /// browsing_context, sorted reverse chronologically. - fn joint_session_past<'a>(&'a self, top_level_browsing_context_id: TopLevelBrowsingContextId) - -> impl Iterator + 'a - { - self.all_browsing_contexts_iter(top_level_browsing_context_id) - .map(|browsing_context| browsing_context.prev.iter().rev() - .scan(browsing_context.instant, |prev_instant, entry| { - let instant = *prev_instant; - *prev_instant = entry.instant; - Some((instant, entry)) - })) - .kmerge_by(|a, b| a.0.cmp(&b.0) == Ordering::Greater) - .map(|(_, entry)| entry) - } - - #[cfg(not(feature = "unstable"))] - /// The joint session past is the merge of the session past of every - /// browsing_context, sorted reverse chronologically. - fn joint_session_past<'a>(&'a self, top_level_browsing_context_id: TopLevelBrowsingContextId) - -> Box + 'a> - { - Box::new( - self.all_browsing_contexts_iter(top_level_browsing_context_id) - .map(|browsing_context| browsing_context.prev.iter().rev() - .scan(browsing_context.instant, |prev_instant, entry| { - let instant = *prev_instant; - *prev_instant = entry.instant; - Some((instant, entry)) - })) - .kmerge_by(|a, b| a.0.cmp(&b.0) == Ordering::Greater) - .map(|(_, entry)| entry) - ) - } - /// Create a new browsing context and update the internal bookkeeping. fn new_browsing_context(&mut self, browsing_context_id: BrowsingContextId, top_level_id: TopLevelBrowsingContextId, - pipeline_id: PipelineId, - load_data: LoadData) { + pipeline_id: PipelineId) { debug!("Creating new browsing context {}", browsing_context_id); - let browsing_context = BrowsingContext::new(browsing_context_id, top_level_id, pipeline_id, load_data); + let browsing_context = BrowsingContext::new(browsing_context_id, top_level_id, pipeline_id); self.browsing_contexts.insert(browsing_context_id, browsing_context); // If a child browsing_context, add it to the parent pipeline. @@ -1544,8 +1475,7 @@ impl Constellation top_level_browsing_context_id: top_level_browsing_context_id, browsing_context_id: browsing_context_id, new_pipeline_id: new_pipeline_id, - load_data: load_data, - replace_instant: None, + replace: None, }); } @@ -1608,6 +1538,7 @@ impl Constellation if self.focus_pipeline_id.is_none() { self.focus_pipeline_id = Some(pipeline_id); } + self.joint_session_histories.insert(top_level_browsing_context_id, JointSessionHistory::new()); self.new_pipeline(pipeline_id, browsing_context_id, top_level_browsing_context_id, @@ -1620,8 +1551,7 @@ impl Constellation top_level_browsing_context_id: top_level_browsing_context_id, browsing_context_id: browsing_context_id, new_pipeline_id: pipeline_id, - load_data: load_data, - replace_instant: None, + replace: None, }); } @@ -1710,9 +1640,9 @@ impl Constellation (load_data, window_size, is_private) }; - let replace_instant = if load_info.info.replace { + let replace = if load_info.info.replace { self.browsing_contexts.get(&load_info.info.browsing_context_id) - .map(|browsing_context| browsing_context.instant) + .map(|browsing_context| NeedsToReload::No(browsing_context.pipeline_id)) } else { None }; @@ -1730,8 +1660,7 @@ impl Constellation top_level_browsing_context_id: load_info.info.top_level_browsing_context_id, browsing_context_id: load_info.info.browsing_context_id, new_pipeline_id: load_info.info.new_pipeline_id, - load_data: load_data, - replace_instant: replace_instant, + replace, }); } @@ -1741,14 +1670,17 @@ impl Constellation let IFrameLoadInfo { parent_pipeline_id, new_pipeline_id, - replace, browsing_context_id, top_level_browsing_context_id, is_private, + .. } = load_info; let url = ServoUrl::parse("about:blank").expect("infallible"); + // TODO: Referrer? + let load_data = LoadData::new(url.clone(), Some(parent_pipeline_id), None, None); + let pipeline = { let parent_pipeline = match self.pipelines.get(&parent_pipeline_id) { Some(parent_pipeline) => parent_pipeline, @@ -1765,17 +1697,9 @@ impl Constellation layout_sender, self.compositor_proxy.clone(), is_private || parent_pipeline.is_private, - url.clone(), - parent_pipeline.visible) - }; - - // TODO: Referrer? - let load_data = LoadData::new(url, Some(parent_pipeline_id), None, None); - - let replace_instant = if replace { - self.browsing_contexts.get(&browsing_context_id).map(|browsing_context| browsing_context.instant) - } else { - None + url, + parent_pipeline.visible, + load_data) }; assert!(!self.pipelines.contains_key(&new_pipeline_id)); @@ -1785,8 +1709,7 @@ impl Constellation top_level_browsing_context_id: top_level_browsing_context_id, browsing_context_id: browsing_context_id, new_pipeline_id: new_pipeline_id, - load_data: load_data, - replace_instant: replace_instant, + replace: None, }); } @@ -1914,16 +1837,18 @@ impl Constellation // changes would be overridden by changing the subframe associated with source_id. // Create the new pipeline - let (top_level_id, window_size, timestamp) = match self.browsing_contexts.get(&browsing_context_id) { - Some(context) => (context.top_level_id, context.size, context.instant), + let (top_level_id, window_size, pipeline_id) = match self.browsing_contexts.get(&browsing_context_id) { + Some(context) => (context.top_level_id, context.size, context.pipeline_id), None => { warn!("Browsing context {} loaded after closure.", browsing_context_id); return None; } }; + + let replace = if replace { Some(NeedsToReload::No(pipeline_id)) } else { None }; + let new_pipeline_id = PipelineId::new(); let sandbox = IFrameSandboxState::IFrameUnsandboxed; - let replace_instant = if replace { Some(timestamp) } else { None }; self.new_pipeline(new_pipeline_id, browsing_context_id, top_level_id, @@ -1936,8 +1861,7 @@ impl Constellation top_level_browsing_context_id: top_level_id, browsing_context_id: browsing_context_id, new_pipeline_id: new_pipeline_id, - load_data: load_data, - replace_instant: replace_instant, + replace, }); Some(new_pipeline_id) } @@ -2004,32 +1928,123 @@ impl Constellation top_level_browsing_context_id: TopLevelBrowsingContextId, direction: TraversalDirection) { - let mut size = 0; - let mut table = HashMap::new(); + let mut browsing_context_changes = HashMap::::new(); + { + let session_history = self.joint_session_histories + .entry(top_level_browsing_context_id).or_insert(JointSessionHistory::new()); - match direction { - TraversalDirection::Forward(delta) => { - for entry in self.joint_session_future(top_level_browsing_context_id).take(delta) { - size = size + 1; - table.insert(entry.browsing_context_id, entry.clone()); + match direction { + TraversalDirection::Forward(forward) => { + let future_length = session_history.future.len(); + + if future_length < forward { + return warn!("Cannot traverse that far into the future."); + } + + for diff in session_history.future.drain(future_length - forward..).rev() { + match diff { + SessionHistoryDiff::BrowsingContextDiff { browsing_context_id, ref new_reloader, .. } => { + browsing_context_changes.insert(browsing_context_id, new_reloader.clone()); + } + } + session_history.past.push(diff); + } + }, + TraversalDirection::Back(back) => { + let past_length = session_history.past.len(); + + if past_length < back { + return warn!("Cannot traverse that far into the past."); + } + + for diff in session_history.past.drain(past_length - back..).rev() { + match diff { + SessionHistoryDiff::BrowsingContextDiff { browsing_context_id, ref old_reloader, .. } => { + browsing_context_changes.insert(browsing_context_id, old_reloader.clone()); + } + } + session_history.future.push(diff); + } } - if size < delta { - return debug!("Traversing forward too much."); - } - }, - TraversalDirection::Back(delta) => { - for entry in self.joint_session_past(top_level_browsing_context_id).take(delta) { - size = size + 1; - table.insert(entry.browsing_context_id, entry.clone()); - } - if size < delta { - return debug!("Traversing back too much."); - } - }, + } } - for (_, entry) in table { - self.traverse_to_entry(entry); + for (browsing_context_id, pipeline_id) in browsing_context_changes.drain() { + self.update_browsing_context(browsing_context_id, pipeline_id); + } + + self.notify_history_changed(top_level_browsing_context_id); + + self.trim_history(top_level_browsing_context_id); + self.update_frame_tree_if_active(top_level_browsing_context_id); + } + + fn update_browsing_context(&mut self, browsing_context_id: BrowsingContextId, new_reloader: NeedsToReload) { + let new_pipeline_id = match new_reloader { + NeedsToReload::No(pipeline_id) => pipeline_id, + NeedsToReload::Yes(pipeline_id, load_data) => { + debug!("Reloading document {} in browsing context {}.", pipeline_id, browsing_context_id); + + // TODO: Save the sandbox state so it can be restored here. + let sandbox = IFrameSandboxState::IFrameUnsandboxed; + let new_pipeline_id = PipelineId::new(); + let (top_level_id, parent_info, window_size, is_private) = + match self.browsing_contexts.get(&browsing_context_id) + { + Some(browsing_context) => match self.pipelines.get(&browsing_context.pipeline_id) { + Some(pipeline) => ( + browsing_context.top_level_id, + pipeline.parent_info, + browsing_context.size, + pipeline.is_private + ), + None => ( + browsing_context.top_level_id, + None, + browsing_context.size, + false + ), + }, + None => return warn!("No browsing context to traverse!"), + }; + self.new_pipeline(new_pipeline_id, browsing_context_id, top_level_id, parent_info, + window_size, load_data.clone(), sandbox, is_private); + self.add_pending_change(SessionHistoryChange { + top_level_browsing_context_id: top_level_id, + browsing_context_id: browsing_context_id, + new_pipeline_id: new_pipeline_id, + replace: Some(NeedsToReload::Yes(pipeline_id, load_data.clone())) + }); + return; + }, + }; + + let old_pipeline_id = match self.browsing_contexts.get_mut(&browsing_context_id) { + Some(browsing_context) => { + let old_pipeline_id = browsing_context.pipeline_id; + browsing_context.update_current_entry(new_pipeline_id); + old_pipeline_id + }, + None => { + return warn!("Browsing context {} was closed during traversal", browsing_context_id); + } + }; + + let parent_info = self.pipelines.get(&old_pipeline_id).and_then(|pipeline| pipeline.parent_info); + + self.update_activity(old_pipeline_id); + self.update_activity(new_pipeline_id); + + if let Some(parent_pipeline_id) = parent_info { + let msg = ConstellationControlMsg::UpdatePipelineId(parent_pipeline_id, browsing_context_id, + new_pipeline_id, UpdatePipelineIdReason::Traversal); + let result = match self.pipelines.get(&parent_pipeline_id) { + None => return warn!("Pipeline {} child traversed after closure", parent_pipeline_id), + Some(pipeline) => pipeline.event_loop.send(msg), + }; + if let Err(e) = result { + self.handle_send_error(parent_pipeline_id, e); + } } } @@ -2037,12 +2052,9 @@ impl Constellation top_level_browsing_context_id: TopLevelBrowsingContextId, sender: IpcSender) { - // Initialize length at 1 to count for the current active entry - let mut length = 1; - for browsing_context in self.all_browsing_contexts_iter(top_level_browsing_context_id) { - length += browsing_context.next.len(); - length += browsing_context.prev.len(); - } + let length = self.joint_session_histories.get(&top_level_browsing_context_id) + .map(JointSessionHistory::history_length) + .unwrap_or(1); let _ = sender.send(length as u32); } @@ -2158,10 +2170,7 @@ impl Constellation fn handle_remove_iframe_msg(&mut self, browsing_context_id: BrowsingContextId) -> Vec { let result = self.all_descendant_browsing_contexts_iter(browsing_context_id) - .flat_map(|browsing_context| browsing_context.next.iter().chain(browsing_context.prev.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(browsing_context.pipeline_id))) - .collect(); + .flat_map(|browsing_context| browsing_context.pipelines.iter().cloned()).collect(); self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal); result } @@ -2173,10 +2182,7 @@ impl Constellation }; let child_pipeline_ids: Vec = self.all_descendant_browsing_contexts_iter(browsing_context_id) - .flat_map(|browsing_context| browsing_context.prev.iter().chain(browsing_context.next.iter()) - .filter_map(|entry| entry.pipeline_id) - .chain(once(browsing_context.pipeline_id))) - .collect(); + .flat_map(|browsing_context| browsing_context.pipelines.iter().cloned()).collect(); for id in child_pipeline_ids { if let Some(pipeline) = self.pipelines.get_mut(&id) { @@ -2235,7 +2241,10 @@ impl Constellation WebDriverCommandMsg::Refresh(top_level_browsing_context_id, reply) => { let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); let load_data = match self.browsing_contexts.get(&browsing_context_id) { - Some(browsing_context) => browsing_context.load_data.clone(), + Some(browsing_context) => match self.pipelines.get(&browsing_context.pipeline_id) { + Some(pipeline) => pipeline.load_data.clone(), + None => return warn!("Pipeline {} refresh after closure.", browsing_context.pipeline_id), + }, None => return warn!("Browsing context {} Refresh after closure.", browsing_context_id), }; self.load_url_for_webdriver(top_level_browsing_context_id, load_data, reply, true); @@ -2277,172 +2286,68 @@ impl Constellation } } - // https://html.spec.whatwg.org/multipage/#traverse-the-history - fn traverse_to_entry(&mut self, entry: SessionHistoryEntry) { - // Step 1. - let browsing_context_id = entry.browsing_context_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. - debug!("Reloading document {} in browsing context {}.", entry.load_data.url, entry.browsing_context_id); - // TODO: save the sandbox state so it can be restored here. - let sandbox = IFrameSandboxState::IFrameUnsandboxed; - let new_pipeline_id = PipelineId::new(); - let load_data = entry.load_data; - let (top_level_id, parent_info, window_size, is_private) = - match self.browsing_contexts.get(&browsing_context_id) - { - Some(browsing_context) => match self.pipelines.get(&browsing_context.pipeline_id) { - Some(pipeline) => (browsing_context.top_level_id, - pipeline.parent_info, - browsing_context.size, - pipeline.is_private), - None => (browsing_context.top_level_id, - None, - browsing_context.size, - false), - }, - None => return warn!("no browsing context to traverse"), - }; - self.new_pipeline(new_pipeline_id, browsing_context_id, top_level_id, parent_info, - window_size, load_data.clone(), sandbox, is_private); - self.add_pending_change(SessionHistoryChange { - top_level_browsing_context_id: top_level_id, - browsing_context_id: browsing_context_id, - new_pipeline_id: new_pipeline_id, - load_data: load_data, - replace_instant: Some(entry.instant), - }); - return; - } - }; - - // Check if the currently focused pipeline is the pipeline being replaced - // (or a child of it). This has to be done here, before the current - // frame tree is modified below. - let update_focus_pipeline = self.focused_pipeline_is_descendant_of(entry.browsing_context_id); - - let (old_pipeline_id, replaced_pipeline_id, top_level_id) = - match self.browsing_contexts.get_mut(&browsing_context_id) - { - Some(browsing_context) => { - let old_pipeline_id = browsing_context.pipeline_id; - let top_level_id = browsing_context.top_level_id; - let mut curr_entry = browsing_context.current(); - - if entry.instant > browsing_context.instant { - // We are traversing to the future. - while let Some(next) = browsing_context.next.pop() { - browsing_context.prev.push(curr_entry); - curr_entry = next; - if entry.instant <= curr_entry.instant { break; } - } - } else if entry.instant < browsing_context.instant { - // We are traversing to the past. - while let Some(prev) = browsing_context.prev.pop() { - browsing_context.next.push(curr_entry); - curr_entry = prev; - if entry.instant >= curr_entry.instant { break; } - } - } - - debug_assert_eq!(entry.instant, curr_entry.instant); - - let replaced_pipeline_id = curr_entry.pipeline_id; - - browsing_context.update_current(pipeline_id, entry); - - (old_pipeline_id, replaced_pipeline_id, top_level_id) - }, - None => return warn!("no browsing context to traverse"), - }; - - let parent_info = self.pipelines.get(&old_pipeline_id) - .and_then(|pipeline| pipeline.parent_info); - - // If the currently focused pipeline is the one being changed (or a child - // of the pipeline being changed) then update the focus pipeline to be - // the replacement. - if update_focus_pipeline { - self.focus_pipeline_id = Some(pipeline_id); - } - - // If we replaced a pipeline, close it. - if let Some(replaced_pipeline_id) = replaced_pipeline_id { - if replaced_pipeline_id != pipeline_id { - self.close_pipeline(replaced_pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); - } - } - - // Deactivate the old pipeline, and activate the new one. - self.update_activity(old_pipeline_id); - self.update_activity(pipeline_id); - self.notify_history_changed(top_level_id); - - self.update_frame_tree_if_active(top_level_id); - - // Update the owning iframe to point to the new pipeline id. - // This makes things like contentDocument work correctly. - if let Some(parent_pipeline_id) = parent_info { - let msg = ConstellationControlMsg::UpdatePipelineId(parent_pipeline_id, - browsing_context_id, pipeline_id, UpdatePipelineIdReason::Traversal); - let result = match self.pipelines.get(&parent_pipeline_id) { - None => return warn!("Pipeline {:?} child traversed after closure.", parent_pipeline_id), - Some(pipeline) => pipeline.event_loop.send(msg), - }; - if let Err(e) = result { - self.handle_send_error(parent_pipeline_id, e); - } - } - } - fn notify_history_changed(&self, top_level_browsing_context_id: TopLevelBrowsingContextId) { // Send a flat projection of the history. // The final vector is a concatenation of the LoadData of the past entries, // the current entry and the future entries. // LoadData of inner frames are ignored and replaced with the LoadData of the parent. - // Ignore LoadData of non-top-level browsing contexts. - let keep_load_data_if_top_browsing_context = |entry: &SessionHistoryEntry| { - match entry.pipeline_id { - None => Some(entry.load_data.clone()), - Some(pipeline_id) => { - match self.pipelines.get(&pipeline_id) { - None => Some(entry.load_data.clone()), - Some(pipeline) => match pipeline.parent_info { - None => Some(entry.load_data.clone()), - Some(_) => None, - } - } - } - } + let session_history = match self.joint_session_histories.get(&top_level_browsing_context_id) { + Some(session_history) => session_history, + None => return warn!("Session history does not exist for {}", top_level_browsing_context_id), + }; + + let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); + let browsing_context = match self.browsing_contexts.get(&browsing_context_id) { + Some(browsing_context) => browsing_context, + None => return warn!("notify_history_changed error after top-level browsing context closed."), + }; + + let current_load_data = match self.pipelines.get(&browsing_context.pipeline_id) { + Some(pipeline) => pipeline.load_data.clone(), + None => return warn!("Pipeline {} refresh after closure.", browsing_context.pipeline_id), }; // If LoadData was ignored, use the LoadData of the previous SessionHistoryEntry, which // is the LoadData of the parent browsing context. - let resolve_load_data = |previous_load_data: &mut LoadData, load_data| { - let load_data = match load_data { - None => previous_load_data.clone(), - Some(load_data) => load_data, - }; - *previous_load_data = load_data.clone(); - Some(load_data) + let resolve_load_data_future = |previous_load_data: &mut LoadData, diff: &SessionHistoryDiff| { + let SessionHistoryDiff::BrowsingContextDiff { browsing_context_id, ref new_reloader, .. } = *diff; + + if browsing_context_id == top_level_browsing_context_id { + let load_data = match *new_reloader { + NeedsToReload::No(pipeline_id) => match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline.load_data.clone(), + None => previous_load_data.clone(), + }, + NeedsToReload::Yes(_, ref load_data) => load_data.clone(), + }; + *previous_load_data = load_data.clone(); + Some(load_data) + } else { + Some(previous_load_data.clone()) + } }; - let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); - let current_load_data = match self.browsing_contexts.get(&browsing_context_id) { - Some(browsing_context) => browsing_context.load_data.clone(), - None => return warn!("notify_history_changed error after top-level browsing context closed."), + let resolve_load_data_past = |previous_load_data: &mut LoadData, diff: &SessionHistoryDiff| { + let SessionHistoryDiff::BrowsingContextDiff { browsing_context_id, ref old_reloader, .. } = *diff; + + if browsing_context_id == top_level_browsing_context_id { + let load_data = match *old_reloader { + NeedsToReload::No(pipeline_id) => match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline.load_data.clone(), + None => previous_load_data.clone(), + }, + NeedsToReload::Yes(_, ref load_data) => load_data.clone(), + }; + *previous_load_data = load_data.clone(); + Some(load_data) + } else { + Some(previous_load_data.clone()) + } }; - let mut entries: Vec = self.joint_session_past(top_level_browsing_context_id) - .map(&keep_load_data_if_top_browsing_context) - .scan(current_load_data.clone(), &resolve_load_data) - .collect(); + let mut entries: Vec = session_history.past.iter().rev() + .scan(current_load_data.clone(), &resolve_load_data_past).collect(); entries.reverse(); @@ -2450,9 +2355,8 @@ impl Constellation entries.push(current_load_data.clone()); - entries.extend(self.joint_session_future(top_level_browsing_context_id) - .map(&keep_load_data_if_top_browsing_context) - .scan(current_load_data.clone(), &resolve_load_data)); + entries.extend(session_history.future.iter().rev() + .scan(current_load_data.clone(), &resolve_load_data_future)); let msg = EmbedderMsg::HistoryChanged(top_level_browsing_context_id, entries, current_index); self.embedder_proxy.send(msg); @@ -2484,55 +2388,118 @@ impl Constellation self.focus_pipeline_id = Some(change.new_pipeline_id); } - let (evicted_id, new_context, navigated) = if let Some(instant) = change.replace_instant { - debug!("Replacing pipeline in existing browsing context with timestamp {:?}.", instant); - let entry = SessionHistoryEntry { - browsing_context_id: change.browsing_context_id, - pipeline_id: Some(change.new_pipeline_id), - load_data: change.load_data.clone(), - instant: instant, - }; - self.traverse_to_entry(entry); - (None, false, None) - } else if let Some(browsing_context) = self.browsing_contexts.get_mut(&change.browsing_context_id) { - debug!("Adding pipeline to existing browsing context."); - let old_pipeline_id = browsing_context.pipeline_id; - browsing_context.load(change.new_pipeline_id, change.load_data.clone()); - let evicted_id = browsing_context.prev.len() - .checked_sub(PREFS.get("session-history.max-length").as_u64().unwrap_or(20) as usize) - .and_then(|index| browsing_context.prev.get_mut(index)) - .and_then(|entry| entry.pipeline_id.take()); - (evicted_id, false, Some(old_pipeline_id)) - } else { - debug!("Adding pipeline to new browsing context."); - (None, true, None) + + let (old_pipeline_id, top_level_id) = match self.browsing_contexts.get_mut(&change.browsing_context_id) { + Some(browsing_context) => { + debug!("Adding pipeline to existing browsing context."); + let old_pipeline_id = browsing_context.pipeline_id; + browsing_context.pipelines.insert(change.new_pipeline_id); + browsing_context.update_current_entry(change.new_pipeline_id); + (Some(old_pipeline_id), Some(browsing_context.top_level_id)) + }, + None => { + debug!("Adding pipeline to new browsing context."); + (None, None) + } }; - if let Some(evicted_id) = evicted_id { - self.close_pipeline(evicted_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); + match old_pipeline_id { + None => { + self.new_browsing_context(change.browsing_context_id, + change.top_level_browsing_context_id, + change.new_pipeline_id); + self.update_activity(change.new_pipeline_id); + self.notify_history_changed(change.top_level_browsing_context_id); + }, + Some(old_pipeline_id) => { + // Deactivate the old pipeline, and activate the new one. + let pipelines_to_close = if let Some(replace_reloader) = change.replace { + let session_history = self.joint_session_histories + .entry(change.top_level_browsing_context_id).or_insert(JointSessionHistory::new()); + session_history.replace(replace_reloader.clone(), + NeedsToReload::No(change.new_pipeline_id)); + + match replace_reloader { + NeedsToReload::No(pipeline_id) => vec![pipeline_id], + NeedsToReload::Yes(..) => vec![], + } + } else { + let session_history = self.joint_session_histories + .entry(change.top_level_browsing_context_id).or_insert(JointSessionHistory::new()); + let diff = SessionHistoryDiff::BrowsingContextDiff { + browsing_context_id: change.browsing_context_id, + new_reloader: NeedsToReload::No(change.new_pipeline_id), + old_reloader: NeedsToReload::No(old_pipeline_id), + }; + + session_history.push_diff(diff).into_iter() + .map(|SessionHistoryDiff::BrowsingContextDiff { new_reloader, .. }| new_reloader) + .filter_map(|pipeline_id| pipeline_id.alive_pipeline_id()) + .collect::>() + }; + + for pipeline_id in pipelines_to_close { + self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); + } + + self.update_activity(old_pipeline_id); + self.update_activity(change.new_pipeline_id); + self.notify_history_changed(change.top_level_browsing_context_id); + } } - if new_context { - self.new_browsing_context(change.browsing_context_id, - change.top_level_browsing_context_id, - change.new_pipeline_id, - change.load_data); - self.update_activity(change.new_pipeline_id); - self.notify_history_changed(change.top_level_browsing_context_id); - }; - - if let Some(old_pipeline_id) = navigated { - // Deactivate the old pipeline, and activate the new one. - self.update_activity(old_pipeline_id); - self.update_activity(change.new_pipeline_id); - // Clear the joint session future - self.clear_joint_session_future(change.top_level_browsing_context_id); - self.notify_history_changed(change.top_level_browsing_context_id); + if let Some(top_level_id) = top_level_id { + self.trim_history(top_level_id); } self.update_frame_tree_if_active(change.top_level_browsing_context_id); } + fn trim_history(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) { + let pipelines_to_evict = { + let session_history = self.joint_session_histories.entry(top_level_browsing_context_id) + .or_insert(JointSessionHistory::new()); + + let history_length = PREFS.get("session-history.max-length").as_u64().unwrap_or(20) as usize; + + // The past is stored with older entries at the front. + // We reverse the iter so that newer entries are at the front and then + // skip _n_ entries and evict the remaining entries. + let mut pipelines_to_evict = session_history.past.iter().rev() + .map(|diff| diff.alive_old_pipeline()) + .skip(history_length) + .filter_map(|maybe_pipeline| maybe_pipeline) + .collect::>(); + + // The future is stored with oldest entries front, so we must + // reverse the iterator like we do for the `past`. + pipelines_to_evict.extend(session_history.future.iter().rev() + .map(|diff| diff.alive_new_pipeline()) + .skip(history_length) + .filter_map(|maybe_pipeline| maybe_pipeline)); + + pipelines_to_evict + }; + + let mut dead_pipelines = vec![]; + for evicted_id in pipelines_to_evict { + let load_data = match self.pipelines.get(&evicted_id) { + Some(pipeline) => pipeline.load_data.clone(), + None => continue, + }; + + dead_pipelines.push((evicted_id, NeedsToReload::Yes(evicted_id, load_data))); + self.close_pipeline(evicted_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); + } + + let session_history = self.joint_session_histories.entry(top_level_browsing_context_id) + .or_insert(JointSessionHistory::new()); + + for (alive_id, dead) in dead_pipelines { + session_history.replace(NeedsToReload::No(alive_id), dead); + } + } + fn handle_activate_document_msg(&mut self, pipeline_id: PipelineId) { debug!("Document ready to activate {}", pipeline_id); @@ -2771,8 +2738,8 @@ impl Constellation new_size, size_type )); - let pipelines = browsing_context.prev.iter().chain(browsing_context.next.iter()) - .filter_map(|entry| entry.pipeline_id) + let pipelines = browsing_context.pipelines.iter() + .filter(|pipeline_id| **pipeline_id != pipeline.id) .filter_map(|pipeline_id| self.pipelines.get(&pipeline_id)); for pipeline in pipelines { let _ = pipeline.event_loop.send(ConstellationControlMsg::ResizeInactive( @@ -2799,24 +2766,6 @@ impl Constellation } } - fn clear_joint_session_future(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) { - let browsing_context_ids: Vec = - self.all_browsing_contexts_iter(top_level_browsing_context_id) - .map(|browsing_context| browsing_context.id) - .collect(); - for browsing_context_id in browsing_context_ids { - let evicted = match self.browsing_contexts.get_mut(&browsing_context_id) { - Some(browsing_context) => browsing_context.remove_forward_entries(), - None => continue, - }; - for entry in evicted { - if let Some(pipeline_id) = entry.pipeline_id { - self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); - } - } - } - } - // Close a browsing context (and all children) fn close_browsing_context(&mut self, browsing_context_id: BrowsingContextId, exit_mode: ExitPipelineMode) { debug!("Closing browsing context {}.", browsing_context_id); @@ -2861,9 +2810,7 @@ impl Constellation .collect(); if let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) { - pipelines_to_close.extend(browsing_context.next.iter().filter_map(|state| state.pipeline_id)); - pipelines_to_close.push(browsing_context.pipeline_id); - pipelines_to_close.extend(browsing_context.prev.iter().filter_map(|state| state.pipeline_id)); + pipelines_to_close.extend(&browsing_context.pipelines) } for pipeline_id in pipelines_to_close { @@ -2876,6 +2823,15 @@ impl Constellation // Close all pipelines at and beneath a given browsing context fn close_pipeline(&mut self, pipeline_id: PipelineId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) { debug!("Closing pipeline {:?}.", pipeline_id); + + // Sever connection to browsing context + let browsing_context_id = self.pipelines.get(&pipeline_id).map(|pipeline| pipeline.browsing_context_id); + if let Some(browsing_context) = browsing_context_id + .and_then(|browsing_context_id| self.browsing_contexts.get_mut(&browsing_context_id)) + { + browsing_context.pipelines.remove(&pipeline_id); + } + // Store information about the browsing contexts to be closed. Then close the // browsing contexts, before removing ourself from the pipelines hash map. This // ordering is vital - so that if close_browsing_context() ends up closing diff --git a/servo/components/constellation/lib.rs b/servo/components/constellation/lib.rs index a0fd36325a89..6ae9837e95fe 100644 --- a/servo/components/constellation/lib.rs +++ b/servo/components/constellation/lib.rs @@ -21,7 +21,6 @@ extern crate gfx; extern crate gfx_traits; extern crate hyper; extern crate ipc_channel; -extern crate itertools; extern crate layout_traits; #[macro_use] extern crate log; @@ -47,6 +46,7 @@ mod network_listener; mod pipeline; #[cfg(all(not(target_os = "windows"), not(target_os = "ios")))] mod sandboxing; +mod session_history; mod timer_scheduler; pub use constellation::{Constellation, FromCompositorLogger, FromScriptLogger, InitialConstellationState}; diff --git a/servo/components/constellation/pipeline.rs b/servo/components/constellation/pipeline.rs index 99603cad8058..0e54e6649ba1 100644 --- a/servo/components/constellation/pipeline.rs +++ b/servo/components/constellation/pipeline.rs @@ -89,6 +89,9 @@ pub struct Pipeline { /// Whether this pipeline should be treated as visible for the purposes of scheduling and /// resource management. pub visible: bool, + + /// The Load Data used to create this pipeline. + pub load_data: LoadData, } /// Initial setup data needed to construct a pipeline. @@ -209,7 +212,7 @@ impl Pipeline { new_pipeline_id: state.id, browsing_context_id: state.browsing_context_id, top_level_browsing_context_id: state.top_level_browsing_context_id, - load_data: state.load_data, + load_data: state.load_data.clone(), window_size: window_size, pipeline_port: pipeline_port, content_process_shutdown_chan: Some(layout_content_process_shutdown_chan.clone()), @@ -260,7 +263,7 @@ impl Pipeline { window_size: window_size, layout_to_constellation_chan: state.layout_to_constellation_chan, script_chan: script_chan.clone(), - load_data: state.load_data, + load_data: state.load_data.clone(), script_port: script_port, opts: (*opts::get()).clone(), prefs: PREFS.cloned(), @@ -298,7 +301,8 @@ impl Pipeline { state.compositor_proxy, state.is_private, url, - state.prev_visibility.unwrap_or(true))) + state.prev_visibility.unwrap_or(true), + state.load_data)) } /// Creates a new `Pipeline`, after the script and layout threads have been @@ -312,7 +316,8 @@ impl Pipeline { compositor_proxy: CompositorProxy, is_private: bool, url: ServoUrl, - visible: bool) + visible: bool, + load_data: LoadData) -> Pipeline { let pipeline = Pipeline { id: id, @@ -327,6 +332,7 @@ impl Pipeline { running_animations: false, visible: visible, is_private: is_private, + load_data: load_data, }; pipeline.notify_visibility(); diff --git a/servo/components/constellation/session_history.rs b/servo/components/constellation/session_history.rs new file mode 100644 index 000000000000..67210a450b97 --- /dev/null +++ b/servo/components/constellation/session_history.rs @@ -0,0 +1,164 @@ +/* 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 msg::constellation_msg::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId}; +use script_traits::LoadData; +use std::{fmt, mem}; +use std::cmp::PartialEq; + +/// Represents the joint session history +/// https://html.spec.whatwg.org/multipage/#joint-session-history +#[derive(Debug)] +pub struct JointSessionHistory { + /// Diffs used to traverse to past entries. Oldest entries are at the back, + /// the most recent entries are at the front. + pub past: Vec, + + /// Diffs used to traverse to future entries. Oldest entries are at the back, + /// the most recent entries are at the front. + pub future: Vec, +} + +impl JointSessionHistory { + pub fn new() -> JointSessionHistory { + JointSessionHistory { + past: Vec::new(), + future: Vec::new(), + } + } + + pub fn history_length(&self) -> usize { + self.past.len() + 1 + self.future.len() + } + + pub fn push_diff(&mut self, diff: SessionHistoryDiff) -> Vec { + self.past.push(diff); + mem::replace(&mut self.future, vec![]) + } + + pub fn replace(&mut self, old_reloader: NeedsToReload, new_reloader: NeedsToReload) { + for diff in self.past.iter_mut().chain(self.future.iter_mut()) { + diff.replace(&old_reloader, &new_reloader); + } + } +} + +/// Represents a pending change in a session history, that will be applied +/// once the new pipeline has loaded and completed initial layout / paint. +pub struct SessionHistoryChange { + /// The browsing context to change. + pub browsing_context_id: BrowsingContextId, + + /// The top-level browsing context ancestor. + pub top_level_browsing_context_id: TopLevelBrowsingContextId, + + /// The pipeline for the document being loaded. + pub new_pipeline_id: PipelineId, + + /// The old pipeline that the new pipeline should replace. + pub replace: Option, +} + +/// Represents a pipeline or discarded pipeline in a history entry. +#[derive(Clone, Debug)] +pub enum NeedsToReload { + /// Represents a pipeline that has not been discarded + No(PipelineId), + /// Represents a pipeline that has been discarded and must be reloaded with the given `LoadData` + /// if ever traversed to. + Yes(PipelineId, LoadData), +} + +impl fmt::Display for NeedsToReload { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + NeedsToReload::No(pipeline_id) => write!(fmt, "Alive({})", pipeline_id), + NeedsToReload::Yes(pipeline_id, ..) => write!(fmt, "Dead({})", pipeline_id), + } + } +} + +impl NeedsToReload { + pub fn alive_pipeline_id(&self) -> Option { + match *self { + NeedsToReload::No(pipeline_id) => Some(pipeline_id), + NeedsToReload::Yes(..) => None, + } + } +} + +// Custom `PartialEq` that only compares the `PipelineId`s of the same variants while ignoring `LoadData` +impl PartialEq for NeedsToReload { + fn eq(&self, other: &NeedsToReload) -> bool { + match *self { + NeedsToReload::No(pipeline_id) => { + match *other { + NeedsToReload::No(other_pipeline_id) => pipeline_id == other_pipeline_id, + _ => false, + } + }, + NeedsToReload::Yes(pipeline_id, _) => { + match *other { + NeedsToReload::Yes(other_pipeline_id, _) => pipeline_id == other_pipeline_id, + _ => false, + } + } + } + } +} + + +/// Represents a the difference between two adjacent session history entries. +#[derive(Debug)] +pub enum SessionHistoryDiff { + /// Represents a diff where the active pipeline of an entry changed. + BrowsingContextDiff { + /// The browsing context whose pipeline changed + browsing_context_id: BrowsingContextId, + /// The previous pipeline (used when traversing into the past) + old_reloader: NeedsToReload, + /// The next pipeline (used when traversing into the future) + new_reloader: NeedsToReload, + }, +} + +impl SessionHistoryDiff { + /// Returns the old pipeline id if that pipeline is still alive, otherwise returns `None` + pub fn alive_old_pipeline(&self) -> Option { + match *self { + SessionHistoryDiff::BrowsingContextDiff { ref old_reloader, .. } => { + match *old_reloader { + NeedsToReload::No(pipeline_id) => Some(pipeline_id), + NeedsToReload::Yes(..) => None, + } + } + } + } + + /// Returns the new pipeline id if that pipeline is still alive, otherwise returns `None` + pub fn alive_new_pipeline(&self) -> Option { + match *self { + SessionHistoryDiff::BrowsingContextDiff { ref new_reloader, .. } => { + match *new_reloader { + NeedsToReload::No(pipeline_id) => Some(pipeline_id), + NeedsToReload::Yes(..) => None, + } + } + } + } + + /// Replaces all occurances of the replaced pipeline with a new pipeline + pub fn replace(&mut self, replaced_reloader: &NeedsToReload, reloader: &NeedsToReload) { + match *self { + SessionHistoryDiff::BrowsingContextDiff { ref mut old_reloader, ref mut new_reloader, .. } => { + if *old_reloader == *replaced_reloader { + *old_reloader = reloader.clone(); + } + if *new_reloader == *replaced_reloader { + *new_reloader = reloader.clone(); + } + } + } + } +}