Auto merge of #20507 - cbrewster:history_transactions, r=asajeffrey

New Session History

<!-- 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. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/20507)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-04-05 19:26:37 -04:00 committed by GitHub
commit 7f3b9ca013
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 500 additions and 465 deletions

1
Cargo.lock generated
View file

@ -456,7 +456,6 @@ dependencies = [
"gfx_traits 0.0.1", "gfx_traits 0.0.1",
"hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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", "layout_traits 0.0.1",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"metrics 0.0.1", "metrics 0.0.1",

View file

@ -23,7 +23,6 @@ gfx = {path = "../gfx"}
gfx_traits = {path = "../gfx_traits"} gfx_traits = {path = "../gfx_traits"}
hyper = "0.10" hyper = "0.10"
ipc-channel = "0.10" ipc-channel = "0.10"
itertools = "0.7"
layout_traits = {path = "../layout_traits"} layout_traits = {path = "../layout_traits"}
log = "0.4" log = "0.4"
metrics = {path = "../metrics"} metrics = {path = "../metrics"}

View file

@ -3,13 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use euclid::TypedSize2D; use euclid::TypedSize2D;
use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, PipelineId}; use msg::constellation_msg::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId};
use pipeline::Pipeline; use pipeline::Pipeline;
use script_traits::LoadData; use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::iter::once;
use std::mem::replace;
use std::time::Instant;
use style_traits::CSSPixel; use style_traits::CSSPixel;
/// The constellation's view of a browsing context. /// The constellation's view of a browsing context.
@ -29,20 +25,10 @@ pub struct BrowsingContext {
/// The size of the frame. /// The size of the frame.
pub size: Option<TypedSize2D<f32, CSSPixel>>, 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. /// The pipeline for the current session history entry.
pub pipeline_id: PipelineId, pub pipeline_id: PipelineId,
/// The load data for the current session history entry. pub pipelines: HashSet<PipelineId>,
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>,
} }
impl BrowsingContext { 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. /// 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, pub fn new(id: BrowsingContextId,
top_level_id: TopLevelBrowsingContextId, top_level_id: TopLevelBrowsingContextId,
pipeline_id: PipelineId, pipeline_id: PipelineId)
load_data: LoadData)
-> BrowsingContext -> BrowsingContext
{ {
let mut pipelines = HashSet::new();
pipelines.insert(pipeline_id);
BrowsingContext { BrowsingContext {
id: id, id: id,
top_level_id: top_level_id, top_level_id: top_level_id,
size: None, size: None,
pipeline_id: pipeline_id, pipeline_id: pipeline_id,
instant: Instant::now(), pipelines,
load_data: load_data,
prev: vec!(),
next: vec!(),
} }
} }
/// Get the current session history entry. pub fn update_current_entry(&mut self, pipeline_id: PipelineId) {
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();
self.pipeline_id = pipeline_id; 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? /// 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 /// An iterator over browsing contexts, returning the descendant
/// contexts whose active documents are fully active, in depth-first /// contexts whose active documents are fully active, in depth-first
/// order. /// order.
@ -217,9 +130,7 @@ impl<'a> Iterator for AllBrowsingContextsIterator<'a> {
continue; continue;
}, },
}; };
let child_browsing_context_ids = browsing_context.prev.iter().chain(browsing_context.next.iter()) let child_browsing_context_ids = browsing_context.pipelines.iter()
.filter_map(|entry| entry.pipeline_id)
.chain(once(browsing_context.pipeline_id))
.filter_map(|pipeline_id| pipelines.get(&pipeline_id)) .filter_map(|pipeline_id| pipelines.get(&pipeline_id))
.flat_map(|pipeline| pipeline.children.iter()); .flat_map(|pipeline| pipeline.children.iter());
self.stack.extend(child_browsing_context_ids); self.stack.extend(child_browsing_context_ids);

View file

@ -91,8 +91,7 @@
use backtrace::Backtrace; use backtrace::Backtrace;
use bluetooth_traits::BluetoothRequest; use bluetooth_traits::BluetoothRequest;
use browsingcontext::{BrowsingContext, SessionHistoryChange, SessionHistoryEntry}; use browsingcontext::{AllBrowsingContextsIterator, BrowsingContext, FullyActiveBrowsingContextsIterator};
use browsingcontext::{FullyActiveBrowsingContextsIterator, AllBrowsingContextsIterator};
use canvas::canvas_paint_thread::CanvasPaintThread; use canvas::canvas_paint_thread::CanvasPaintThread;
use canvas::webgl_thread::WebGLThreads; use canvas::webgl_thread::WebGLThreads;
use canvas_traits::canvas::CanvasId; use canvas_traits::canvas::CanvasId;
@ -110,7 +109,6 @@ use gfx_traits::Epoch;
use ipc_channel::{Error as IpcError}; use ipc_channel::{Error as IpcError};
use ipc_channel::ipc::{self, IpcSender, IpcReceiver}; use ipc_channel::ipc::{self, IpcSender, IpcReceiver};
use ipc_channel::router::ROUTER; use ipc_channel::router::ROUTER;
use itertools::Itertools;
use layout_traits::LayoutThreadFactory; use layout_traits::LayoutThreadFactory;
use log::{Log, Level, LevelFilter, Metadata, Record}; use log::{Log, Level, LevelFilter, Metadata, Record};
use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, PipelineId}; 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_rand::{Rng, SeedableRng, ServoRng, random};
use servo_remutex::ReentrantMutex; use servo_remutex::ReentrantMutex;
use servo_url::{Host, ImmutableOrigin, ServoUrl}; use servo_url::{Host, ImmutableOrigin, ServoUrl};
use session_history::{JointSessionHistory, NeedsToReload, SessionHistoryChange, SessionHistoryDiff};
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cmp::Ordering;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::iter::once;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::process; use std::process;
use std::rc::{Rc, Weak}; 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. /// to become same-origin, at which point they can share DOM objects.
event_loops: HashMap<TopLevelBrowsingContextId, HashMap<Host, Weak<EventLoop>>>, event_loops: HashMap<TopLevelBrowsingContextId, HashMap<Host, Weak<EventLoop>>>,
joint_session_histories: HashMap<TopLevelBrowsingContextId, JointSessionHistory>,
/// The set of all the pipelines in the browser. /// The set of all the pipelines in the browser.
/// (See the `pipeline` module for more details.) /// (See the `pipeline` module for more details.)
pipelines: HashMap<PipelineId, Pipeline>, pipelines: HashMap<PipelineId, Pipeline>,
@ -588,6 +587,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
swmanager_receiver: swmanager_receiver, swmanager_receiver: swmanager_receiver,
swmanager_sender: sw_mgr_clone, swmanager_sender: sw_mgr_clone,
event_loops: HashMap::new(), event_loops: HashMap::new(),
joint_session_histories: HashMap::new(),
pipelines: HashMap::new(), pipelines: HashMap::new(),
browsing_contexts: HashMap::new(), browsing_contexts: HashMap::new(),
pending_changes: vec!(), pending_changes: vec!(),
@ -673,7 +673,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
sandbox: IFrameSandboxState, sandbox: IFrameSandboxState,
is_private: bool) { is_private: bool) {
if self.shutting_down { return; } if self.shutting_down { return; }
debug!("Creating new pipeline {} in browsing context {}.", pipeline_id, browsing_context_id); debug!("Creating new pipeline {} in browsing context {}.", pipeline_id, browsing_context_id);
let (event_loop, host) = match sandbox { 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. /// Create a new browsing context and update the internal bookkeeping.
fn new_browsing_context(&mut self, fn new_browsing_context(&mut self,
browsing_context_id: BrowsingContextId, browsing_context_id: BrowsingContextId,
top_level_id: TopLevelBrowsingContextId, top_level_id: TopLevelBrowsingContextId,
pipeline_id: PipelineId, pipeline_id: PipelineId) {
load_data: LoadData) {
debug!("Creating new browsing context {}", browsing_context_id); 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); self.browsing_contexts.insert(browsing_context_id, browsing_context);
// If a child browsing_context, add it to the parent pipeline. // 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, top_level_browsing_context_id: top_level_browsing_context_id,
browsing_context_id: browsing_context_id, browsing_context_id: browsing_context_id,
new_pipeline_id: new_pipeline_id, new_pipeline_id: new_pipeline_id,
load_data: load_data, replace: None,
replace_instant: None,
}); });
} }
@ -1608,6 +1538,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
if self.focus_pipeline_id.is_none() { if self.focus_pipeline_id.is_none() {
self.focus_pipeline_id = Some(pipeline_id); self.focus_pipeline_id = Some(pipeline_id);
} }
self.joint_session_histories.insert(top_level_browsing_context_id, JointSessionHistory::new());
self.new_pipeline(pipeline_id, self.new_pipeline(pipeline_id,
browsing_context_id, browsing_context_id,
top_level_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, top_level_browsing_context_id: top_level_browsing_context_id,
browsing_context_id: browsing_context_id, browsing_context_id: browsing_context_id,
new_pipeline_id: pipeline_id, new_pipeline_id: pipeline_id,
load_data: load_data, replace: None,
replace_instant: None,
}); });
} }
@ -1710,9 +1640,9 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
(load_data, window_size, is_private) (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) 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 { } else {
None 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, top_level_browsing_context_id: load_info.info.top_level_browsing_context_id,
browsing_context_id: load_info.info.browsing_context_id, browsing_context_id: load_info.info.browsing_context_id,
new_pipeline_id: load_info.info.new_pipeline_id, new_pipeline_id: load_info.info.new_pipeline_id,
load_data: load_data, replace,
replace_instant: replace_instant,
}); });
} }
@ -1741,14 +1670,17 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
let IFrameLoadInfo { let IFrameLoadInfo {
parent_pipeline_id, parent_pipeline_id,
new_pipeline_id, new_pipeline_id,
replace,
browsing_context_id, browsing_context_id,
top_level_browsing_context_id, top_level_browsing_context_id,
is_private, is_private,
..
} = load_info; } = load_info;
let url = ServoUrl::parse("about:blank").expect("infallible"); 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 pipeline = {
let parent_pipeline = match self.pipelines.get(&parent_pipeline_id) { let parent_pipeline = match self.pipelines.get(&parent_pipeline_id) {
Some(parent_pipeline) => parent_pipeline, Some(parent_pipeline) => parent_pipeline,
@ -1765,17 +1697,9 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
layout_sender, layout_sender,
self.compositor_proxy.clone(), self.compositor_proxy.clone(),
is_private || parent_pipeline.is_private, is_private || parent_pipeline.is_private,
url.clone(), url,
parent_pipeline.visible) parent_pipeline.visible,
}; load_data)
// 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
}; };
assert!(!self.pipelines.contains_key(&new_pipeline_id)); 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, top_level_browsing_context_id: top_level_browsing_context_id,
browsing_context_id: browsing_context_id, browsing_context_id: browsing_context_id,
new_pipeline_id: new_pipeline_id, new_pipeline_id: new_pipeline_id,
load_data: load_data, replace: None,
replace_instant: replace_instant,
}); });
} }
@ -1914,16 +1837,18 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
// changes would be overridden by changing the subframe associated with source_id. // changes would be overridden by changing the subframe associated with source_id.
// Create the new pipeline // Create the new pipeline
let (top_level_id, window_size, timestamp) = match self.browsing_contexts.get(&browsing_context_id) { 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.instant), Some(context) => (context.top_level_id, context.size, context.pipeline_id),
None => { None => {
warn!("Browsing context {} loaded after closure.", browsing_context_id); warn!("Browsing context {} loaded after closure.", browsing_context_id);
return None; return None;
} }
}; };
let replace = if replace { Some(NeedsToReload::No(pipeline_id)) } else { None };
let new_pipeline_id = PipelineId::new(); let new_pipeline_id = PipelineId::new();
let sandbox = IFrameSandboxState::IFrameUnsandboxed; let sandbox = IFrameSandboxState::IFrameUnsandboxed;
let replace_instant = if replace { Some(timestamp) } else { None };
self.new_pipeline(new_pipeline_id, self.new_pipeline(new_pipeline_id,
browsing_context_id, browsing_context_id,
top_level_id, top_level_id,
@ -1936,8 +1861,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
top_level_browsing_context_id: top_level_id, top_level_browsing_context_id: top_level_id,
browsing_context_id: browsing_context_id, browsing_context_id: browsing_context_id,
new_pipeline_id: new_pipeline_id, new_pipeline_id: new_pipeline_id,
load_data: load_data, replace,
replace_instant: replace_instant,
}); });
Some(new_pipeline_id) Some(new_pipeline_id)
} }
@ -2004,32 +1928,123 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
top_level_browsing_context_id: TopLevelBrowsingContextId, top_level_browsing_context_id: TopLevelBrowsingContextId,
direction: TraversalDirection) direction: TraversalDirection)
{ {
let mut size = 0; let mut browsing_context_changes = HashMap::<BrowsingContextId, NeedsToReload>::new();
let mut table = HashMap::new(); {
let session_history = self.joint_session_histories
.entry(top_level_browsing_context_id).or_insert(JointSessionHistory::new());
match direction { match direction {
TraversalDirection::Forward(delta) => { TraversalDirection::Forward(forward) => {
for entry in self.joint_session_future(top_level_browsing_context_id).take(delta) { let future_length = session_history.future.len();
size = size + 1;
table.insert(entry.browsing_context_id, entry.clone()); 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 { for (browsing_context_id, pipeline_id) in browsing_context_changes.drain() {
self.traverse_to_entry(entry); 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, top_level_browsing_context_id: TopLevelBrowsingContextId,
sender: IpcSender<u32>) sender: IpcSender<u32>)
{ {
// Initialize length at 1 to count for the current active entry let length = self.joint_session_histories.get(&top_level_browsing_context_id)
let mut length = 1; .map(JointSessionHistory::history_length)
for browsing_context in self.all_browsing_contexts_iter(top_level_browsing_context_id) { .unwrap_or(1);
length += browsing_context.next.len();
length += browsing_context.prev.len();
}
let _ = sender.send(length as u32); 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> { fn handle_remove_iframe_msg(&mut self, browsing_context_id: BrowsingContextId) -> Vec<PipelineId> {
let result = self.all_descendant_browsing_contexts_iter(browsing_context_id) let result = self.all_descendant_browsing_contexts_iter(browsing_context_id)
.flat_map(|browsing_context| browsing_context.next.iter().chain(browsing_context.prev.iter()) .flat_map(|browsing_context| browsing_context.pipelines.iter().cloned()).collect();
.filter_map(|entry| entry.pipeline_id)
.chain(once(browsing_context.pipeline_id)))
.collect();
self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal); self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
result 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) 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()) .flat_map(|browsing_context| browsing_context.pipelines.iter().cloned()).collect();
.filter_map(|entry| entry.pipeline_id)
.chain(once(browsing_context.pipeline_id)))
.collect();
for id in child_pipeline_ids { for id in child_pipeline_ids {
if let Some(pipeline) = self.pipelines.get_mut(&id) { 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) => { WebDriverCommandMsg::Refresh(top_level_browsing_context_id, reply) => {
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
let load_data = match self.browsing_contexts.get(&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), 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); 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) { fn notify_history_changed(&self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
// Send a flat projection of the history. // Send a flat projection of the history.
// The final vector is a concatenation of the LoadData of the past entries, // The final vector is a concatenation of the LoadData of the past entries,
// the current entry and the future entries. // the current entry and the future entries.
// LoadData of inner frames are ignored and replaced with the LoadData of the parent. // LoadData of inner frames are ignored and replaced with the LoadData of the parent.
// Ignore LoadData of non-top-level browsing contexts. let session_history = match self.joint_session_histories.get(&top_level_browsing_context_id) {
let keep_load_data_if_top_browsing_context = |entry: &SessionHistoryEntry| { Some(session_history) => session_history,
match entry.pipeline_id { None => return warn!("Session history does not exist for {}", top_level_browsing_context_id),
None => Some(entry.load_data.clone()), };
Some(pipeline_id) => {
match self.pipelines.get(&pipeline_id) { let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
None => Some(entry.load_data.clone()), let browsing_context = match self.browsing_contexts.get(&browsing_context_id) {
Some(pipeline) => match pipeline.parent_info { Some(browsing_context) => browsing_context,
None => Some(entry.load_data.clone()), None => return warn!("notify_history_changed error after top-level browsing context closed."),
Some(_) => None, };
}
} 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 // If LoadData was ignored, use the LoadData of the previous SessionHistoryEntry, which
// is the LoadData of the parent browsing context. // is the LoadData of the parent browsing context.
let resolve_load_data = |previous_load_data: &mut LoadData, load_data| { let resolve_load_data_future = |previous_load_data: &mut LoadData, diff: &SessionHistoryDiff| {
let load_data = match load_data { let SessionHistoryDiff::BrowsingContextDiff { browsing_context_id, ref new_reloader, .. } = *diff;
None => previous_load_data.clone(),
Some(load_data) => load_data, if browsing_context_id == top_level_browsing_context_id {
}; let load_data = match *new_reloader {
*previous_load_data = load_data.clone(); NeedsToReload::No(pipeline_id) => match self.pipelines.get(&pipeline_id) {
Some(load_data) 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 resolve_load_data_past = |previous_load_data: &mut LoadData, diff: &SessionHistoryDiff| {
let current_load_data = match self.browsing_contexts.get(&browsing_context_id) { let SessionHistoryDiff::BrowsingContextDiff { browsing_context_id, ref old_reloader, .. } = *diff;
Some(browsing_context) => browsing_context.load_data.clone(),
None => return warn!("notify_history_changed error after top-level browsing context closed."), 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) let mut entries: Vec<LoadData> = session_history.past.iter().rev()
.map(&keep_load_data_if_top_browsing_context) .scan(current_load_data.clone(), &resolve_load_data_past).collect();
.scan(current_load_data.clone(), &resolve_load_data)
.collect();
entries.reverse(); entries.reverse();
@ -2450,9 +2355,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
entries.push(current_load_data.clone()); entries.push(current_load_data.clone());
entries.extend(self.joint_session_future(top_level_browsing_context_id) entries.extend(session_history.future.iter().rev()
.map(&keep_load_data_if_top_browsing_context) .scan(current_load_data.clone(), &resolve_load_data_future));
.scan(current_load_data.clone(), &resolve_load_data));
let msg = EmbedderMsg::HistoryChanged(top_level_browsing_context_id, entries, current_index); let msg = EmbedderMsg::HistoryChanged(top_level_browsing_context_id, entries, current_index);
self.embedder_proxy.send(msg); 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); 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 (old_pipeline_id, top_level_id) = match self.browsing_contexts.get_mut(&change.browsing_context_id) {
let entry = SessionHistoryEntry { Some(browsing_context) => {
browsing_context_id: change.browsing_context_id, debug!("Adding pipeline to existing browsing context.");
pipeline_id: Some(change.new_pipeline_id), let old_pipeline_id = browsing_context.pipeline_id;
load_data: change.load_data.clone(), browsing_context.pipelines.insert(change.new_pipeline_id);
instant: instant, browsing_context.update_current_entry(change.new_pipeline_id);
}; (Some(old_pipeline_id), Some(browsing_context.top_level_id))
self.traverse_to_entry(entry); },
(None, false, None) None => {
} else if let Some(browsing_context) = self.browsing_contexts.get_mut(&change.browsing_context_id) { debug!("Adding pipeline to new browsing context.");
debug!("Adding pipeline to existing browsing context."); (None, None)
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)
}; };
if let Some(evicted_id) = evicted_id { match old_pipeline_id {
self.close_pipeline(evicted_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal); 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 { if let Some(top_level_id) = top_level_id {
self.new_browsing_context(change.browsing_context_id, self.trim_history(top_level_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);
} }
self.update_frame_tree_if_active(change.top_level_browsing_context_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) { fn handle_activate_document_msg(&mut self, pipeline_id: PipelineId) {
debug!("Document ready to activate {}", pipeline_id); debug!("Document ready to activate {}", pipeline_id);
@ -2771,8 +2738,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
new_size, new_size,
size_type size_type
)); ));
let pipelines = browsing_context.prev.iter().chain(browsing_context.next.iter()) let pipelines = browsing_context.pipelines.iter()
.filter_map(|entry| entry.pipeline_id) .filter(|pipeline_id| **pipeline_id != pipeline.id)
.filter_map(|pipeline_id| self.pipelines.get(&pipeline_id)); .filter_map(|pipeline_id| self.pipelines.get(&pipeline_id));
for pipeline in pipelines { for pipeline in pipelines {
let _ = pipeline.event_loop.send(ConstellationControlMsg::ResizeInactive( 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) // Close a browsing context (and all children)
fn close_browsing_context(&mut self, browsing_context_id: BrowsingContextId, exit_mode: ExitPipelineMode) { fn close_browsing_context(&mut self, browsing_context_id: BrowsingContextId, exit_mode: ExitPipelineMode) {
debug!("Closing browsing context {}.", browsing_context_id); debug!("Closing browsing context {}.", browsing_context_id);
@ -2861,9 +2810,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
.collect(); .collect();
if let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) { 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.extend(&browsing_context.pipelines)
pipelines_to_close.push(browsing_context.pipeline_id);
pipelines_to_close.extend(browsing_context.prev.iter().filter_map(|state| state.pipeline_id));
} }
for pipeline_id in pipelines_to_close { 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 // Close all pipelines at and beneath a given browsing context
fn close_pipeline(&mut self, pipeline_id: PipelineId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) { fn close_pipeline(&mut self, pipeline_id: PipelineId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) {
debug!("Closing pipeline {:?}.", pipeline_id); 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 // Store information about the browsing contexts to be closed. Then close the
// browsing contexts, before removing ourself from the pipelines hash map. This // browsing contexts, before removing ourself from the pipelines hash map. This
// ordering is vital - so that if close_browsing_context() ends up closing // ordering is vital - so that if close_browsing_context() ends up closing

View file

@ -21,7 +21,6 @@ extern crate gfx;
extern crate gfx_traits; extern crate gfx_traits;
extern crate hyper; extern crate hyper;
extern crate ipc_channel; extern crate ipc_channel;
extern crate itertools;
extern crate layout_traits; extern crate layout_traits;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@ -47,6 +46,7 @@ mod network_listener;
mod pipeline; mod pipeline;
#[cfg(all(not(target_os = "windows"), not(target_os = "ios")))] #[cfg(all(not(target_os = "windows"), not(target_os = "ios")))]
mod sandboxing; mod sandboxing;
mod session_history;
mod timer_scheduler; mod timer_scheduler;
pub use constellation::{Constellation, FromCompositorLogger, FromScriptLogger, InitialConstellationState}; pub use constellation::{Constellation, FromCompositorLogger, FromScriptLogger, InitialConstellationState};

View file

@ -89,6 +89,9 @@ pub struct Pipeline {
/// Whether this pipeline should be treated as visible for the purposes of scheduling and /// Whether this pipeline should be treated as visible for the purposes of scheduling and
/// resource management. /// resource management.
pub visible: bool, pub visible: bool,
/// The Load Data used to create this pipeline.
pub load_data: LoadData,
} }
/// Initial setup data needed to construct a pipeline. /// Initial setup data needed to construct a pipeline.
@ -209,7 +212,7 @@ impl Pipeline {
new_pipeline_id: state.id, new_pipeline_id: state.id,
browsing_context_id: state.browsing_context_id, browsing_context_id: state.browsing_context_id,
top_level_browsing_context_id: state.top_level_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, window_size: window_size,
pipeline_port: pipeline_port, pipeline_port: pipeline_port,
content_process_shutdown_chan: Some(layout_content_process_shutdown_chan.clone()), content_process_shutdown_chan: Some(layout_content_process_shutdown_chan.clone()),
@ -260,7 +263,7 @@ impl Pipeline {
window_size: window_size, window_size: window_size,
layout_to_constellation_chan: state.layout_to_constellation_chan, layout_to_constellation_chan: state.layout_to_constellation_chan,
script_chan: script_chan.clone(), script_chan: script_chan.clone(),
load_data: state.load_data, load_data: state.load_data.clone(),
script_port: script_port, script_port: script_port,
opts: (*opts::get()).clone(), opts: (*opts::get()).clone(),
prefs: PREFS.cloned(), prefs: PREFS.cloned(),
@ -298,7 +301,8 @@ impl Pipeline {
state.compositor_proxy, state.compositor_proxy,
state.is_private, state.is_private,
url, 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 /// Creates a new `Pipeline`, after the script and layout threads have been
@ -312,7 +316,8 @@ impl Pipeline {
compositor_proxy: CompositorProxy, compositor_proxy: CompositorProxy,
is_private: bool, is_private: bool,
url: ServoUrl, url: ServoUrl,
visible: bool) visible: bool,
load_data: LoadData)
-> Pipeline { -> Pipeline {
let pipeline = Pipeline { let pipeline = Pipeline {
id: id, id: id,
@ -327,6 +332,7 @@ impl Pipeline {
running_animations: false, running_animations: false,
visible: visible, visible: visible,
is_private: is_private, is_private: is_private,
load_data: load_data,
}; };
pipeline.notify_visibility(); pipeline.notify_visibility();

View file

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

View file

@ -70701,7 +70701,7 @@
"support" "support"
], ],
"mozilla/servo-max-session-history.html": [ "mozilla/servo-max-session-history.html": [
"14e8b67eea2c1b6302cf5166218ed5b073624ee8", "fc925a10010ee4ae6e318d571e03502e5e9cba83",
"testharness" "testharness"
], ],
"mozilla/sigsegv.html": [ "mozilla/sigsegv.html": [

View file

@ -20,7 +20,7 @@
// The number of pages to go back by. // The number of pages to go back by.
// This should be more than the default session-history.max-length pref, // This should be more than the default session-history.max-length pref,
// to ensure that going back reloads the page. // to ensure that going back reloads the page.
var go_back_by = Math.min(page_number, 20); var go_back_by = Math.min(page_number, 21);
if (history.length < go_forward_by) { if (history.length < go_forward_by) {
// Keep loading new pages until we have loaded enough of them. // Keep loading new pages until we have loaded enough of them.