servo: Merge #20507 - New Session History (from cbrewster:history_transactions); r=asajeffrey

<!-- Please describe your changes on the following line: -->
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

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [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).

<!-- Either: -->
- [ ] There are tests for these changes OR
- [ ] These changes do not require tests because _____

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

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
This commit is contained in:
Connor Brewster 2018-04-05 19:26:37 -04:00
Родитель 21280e180f
Коммит 7d7ccbc437
7 изменённых файлов: 498 добавлений и 463 удалений

1
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",

Просмотреть файл

@ -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"}

Просмотреть файл

@ -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<TypedSize2D<f32, CSSPixel>>,
/// 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<SessionHistoryEntry>,
/// The future session history, ordered reverse chronologically.
pub next: Vec<SessionHistoryEntry>,
pub pipelines: HashSet<PipelineId>,
}
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<SessionHistoryEntry> {
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.
///
/// <https://html.spec.whatwg.org/multipage/#session-history-entry>
#[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<PipelineId>,
/// 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<Instant>,
}
/// 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);

Просмотреть файл

@ -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<Message, LTF, STF> {
/// to become same-origin, at which point they can share DOM objects.
event_loops: HashMap<TopLevelBrowsingContextId, HashMap<Host, Weak<EventLoop>>>,
joint_session_histories: HashMap<TopLevelBrowsingContextId, JointSessionHistory>,
/// The set of all the pipelines in the browser.
/// (See the `pipeline` module for more details.)
pipelines: HashMap<PipelineId, Pipeline>,
@ -588,6 +587,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
}
}
/// 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<Item = &'a SessionHistoryEntry> + '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<Iterator<Item = &'a SessionHistoryEntry> + '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<Item = &'a SessionHistoryEntry> + '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<Iterator<Item = &'a SessionHistoryEntry> + '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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
(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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
// 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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
top_level_browsing_context_id: TopLevelBrowsingContextId,
direction: TraversalDirection)
{
let mut size = 0;
let mut table = HashMap::new();
let mut browsing_context_changes = HashMap::<BrowsingContextId, NeedsToReload>::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<Message, LTF, STF> Constellation<Message, LTF, STF>
top_level_browsing_context_id: TopLevelBrowsingContextId,
sender: IpcSender<u32>)
{
// 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<Message, LTF, STF> Constellation<Message, LTF, STF>
fn handle_remove_iframe_msg(&mut self, browsing_context_id: BrowsingContextId) -> Vec<PipelineId> {
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<Message, LTF, STF> Constellation<Message, LTF, STF>
};
let child_pipeline_ids: Vec<PipelineId> = 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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
}
}
// 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<LoadData> = 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<LoadData> = session_history.past.iter().rev()
.scan(current_load_data.clone(), &resolve_load_data_past).collect();
entries.reverse();
@ -2450,9 +2355,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
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::<Vec<_>>()
};
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::<Vec<_>>();
// 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<Message, LTF, STF> Constellation<Message, LTF, STF>
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<Message, LTF, STF> Constellation<Message, LTF, STF>
}
}
fn clear_joint_session_future(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
let browsing_context_ids: Vec<BrowsingContextId> =
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<Message, LTF, STF> Constellation<Message, LTF, STF>
.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<Message, LTF, STF> Constellation<Message, LTF, STF>
// 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

Просмотреть файл

@ -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};

Просмотреть файл

@ -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();

Просмотреть файл

@ -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<SessionHistoryDiff>,
/// Diffs used to traverse to future entries. Oldest entries are at the back,
/// the most recent entries are at the front.
pub future: Vec<SessionHistoryDiff>,
}
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<SessionHistoryDiff> {
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<NeedsToReload>,
}
/// 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<PipelineId> {
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<PipelineId> {
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<PipelineId> {
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();
}
}
}
}
}