зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
21280e180f
Коммит
7d7ccbc437
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче