mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
servo: Merge #14312 - Implement discarding Document objects to reclaim space (from asajeffrey:script-discard-documents); r=cbrewster
<!-- Please describe your changes on the following line: --> This PR implements document discarding. Active documents are kept alive strongly, but inactive documents are only kept alive weakly. When a document is GCd, it is marked as discarded, and if it is every reactivated, a reload of the URL is triggered. Note that this PR is pretty aggressive about discarding, and can any inactive document (other than those being kept alive by other same-origin pipelines). We might want to damp it down a bit. Also note that this interacts with browser.html in that the reloading triggered by reactivating a document triggers mozbrowser events. To test this, I added a `-Zdiscard-inactive-documents` debug flag, which discards all inactive documents, even ones which are reachable through other same-origin pipelines. --- <!-- 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 - [X] These changes fix #14262. - [X] These changes do not require tests because we should be able to use the existing tests with `-Zdiscard-inactive-documents`. <!-- 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: 16b0da5004fd730de87883daa35a78b6af01f042
This commit is contained in:
parent
a996fde1ee
commit
71d121887c
@ -68,6 +68,9 @@ pub struct Opts {
|
||||
|
||||
pub output_file: Option<String>,
|
||||
|
||||
/// How much session history to keep in each tab.
|
||||
pub max_session_history: usize,
|
||||
|
||||
/// Replace unpaires surrogates in DOM strings with U+FFFD.
|
||||
/// See https://github.com/servo/servo/issues/6564
|
||||
pub replace_surrogates: bool,
|
||||
@ -518,6 +521,7 @@ pub fn default_opts() -> Opts {
|
||||
userscripts: None,
|
||||
user_stylesheets: Vec::new(),
|
||||
output_file: None,
|
||||
max_session_history: 16,
|
||||
replace_surrogates: false,
|
||||
gc_profile: false,
|
||||
load_webfonts_synchronously: false,
|
||||
@ -611,6 +615,7 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
||||
"Probability of randomly closing a pipeline (for testing constellation hardening).",
|
||||
"0.0");
|
||||
opts.optopt("", "random-pipeline-closure-seed", "A fixed seed for repeatbility of random pipeline closure.", "");
|
||||
opts.optopt("", "max-session-history", "Maximum amount of session history to store in each tab.", "16");
|
||||
opts.optmulti("Z", "debug",
|
||||
"A comma-separated string of debug options. Pass help to show available options.", "");
|
||||
opts.optflag("h", "help", "Print this message");
|
||||
@ -779,6 +784,10 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
||||
}
|
||||
};
|
||||
|
||||
let max_session_history = opt_match.opt_str("max-session-history").map(|max| {
|
||||
max.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: --max-session-history ({})", err)))
|
||||
}).unwrap_or(16);
|
||||
|
||||
if opt_match.opt_present("M") {
|
||||
MULTIPROCESS.store(true, Ordering::SeqCst)
|
||||
}
|
||||
@ -820,6 +829,7 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
|
||||
userscripts: opt_match.opt_default("userscripts", ""),
|
||||
user_stylesheets: user_stylesheets,
|
||||
output_file: opt_match.opt_str("o"),
|
||||
max_session_history: max_session_history,
|
||||
replace_surrogates: debug_options.replace_surrogates,
|
||||
gc_profile: debug_options.gc_profile,
|
||||
load_webfonts_synchronously: debug_options.load_webfonts_synchronously,
|
||||
|
@ -75,7 +75,7 @@ use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg};
|
||||
use euclid::scale_factor::ScaleFactor;
|
||||
use euclid::size::{Size2D, TypedSize2D};
|
||||
use event_loop::EventLoop;
|
||||
use frame::{Frame, FrameChange, FrameTreeIterator, FullFrameTreeIterator};
|
||||
use frame::{Frame, FrameChange, FrameState, FrameTreeIterator, FullFrameTreeIterator};
|
||||
use gfx::font_cache_thread::FontCacheThread;
|
||||
use gfx_traits::Epoch;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
@ -608,7 +608,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
.map(|pipeline| pipeline.visible);
|
||||
|
||||
let prev_visibility = self.frames.get(&frame_id)
|
||||
.and_then(|frame| self.pipelines.get(&frame.current.pipeline_id))
|
||||
.and_then(|frame| self.pipelines.get(&frame.pipeline_id))
|
||||
.map(|pipeline| pipeline.visible)
|
||||
.or(parent_visibility);
|
||||
|
||||
@ -685,16 +685,16 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
}
|
||||
|
||||
/// The joint session future is the merge of the session future of every
|
||||
/// frame in the frame tree, sorted reverse chronologically.
|
||||
fn joint_session_future(&self, frame_id_root: FrameId) -> Vec<(Instant, FrameId, PipelineId)> {
|
||||
let mut future = vec!();
|
||||
for frame in self.full_frame_tree_iter(frame_id_root) {
|
||||
future.extend(frame.next.iter().map(|entry| (entry.instant, entry.frame_id, entry.pipeline_id)));
|
||||
}
|
||||
/// frame in the frame tree, sorted chronologically.
|
||||
fn joint_session_future<'a>(&'a self, frame_id_root: FrameId) -> impl Iterator<Item=FrameState> {
|
||||
let mut future: Vec<FrameState> = self.full_frame_tree_iter(frame_id_root)
|
||||
.flat_map(|frame| frame.next.iter().cloned())
|
||||
.collect();
|
||||
|
||||
// reverse sorting
|
||||
future.sort_by(|a, b| b.cmp(a));
|
||||
future
|
||||
// Sort the joint session future by the timestamp that the pipeline was navigated to
|
||||
// in chronological order
|
||||
future.sort_by(|a, b| a.instant.cmp(&b.instant));
|
||||
future.into_iter()
|
||||
}
|
||||
|
||||
/// Is the joint session future empty?
|
||||
@ -704,19 +704,20 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
}
|
||||
|
||||
/// The joint session past is the merge of the session past of every
|
||||
/// frame in the frame tree, sorted chronologically.
|
||||
fn joint_session_past(&self, frame_id_root: FrameId) -> Vec<(Instant, FrameId, PipelineId)> {
|
||||
let mut past = vec!();
|
||||
for frame in self.full_frame_tree_iter(frame_id_root) {
|
||||
let mut prev_instant = frame.current.instant;
|
||||
for entry in frame.prev.iter().rev() {
|
||||
past.push((prev_instant, entry.frame_id, entry.pipeline_id));
|
||||
prev_instant = entry.instant;
|
||||
}
|
||||
}
|
||||
/// frame in the frame tree, sorted reverse chronologically.
|
||||
fn joint_session_past<'a>(&self, frame_id_root: FrameId) -> impl Iterator<Item=FrameState> {
|
||||
let mut past: Vec<(Instant, FrameState)> = self.full_frame_tree_iter(frame_id_root)
|
||||
.flat_map(|frame| frame.prev.iter().rev().scan(frame.instant, |prev_instant, entry| {
|
||||
let instant = *prev_instant;
|
||||
*prev_instant = entry.instant;
|
||||
Some((instant, entry.clone()))
|
||||
}))
|
||||
.collect();
|
||||
|
||||
past.sort();
|
||||
past
|
||||
// Sort the joint session past by the timestamp that the pipeline was navigated from
|
||||
// in reverse chronological order
|
||||
past.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
past.into_iter().map(|(_, entry)| entry)
|
||||
}
|
||||
|
||||
/// Is the joint session past empty?
|
||||
@ -726,8 +727,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
}
|
||||
|
||||
/// Create a new frame and update the internal bookkeeping.
|
||||
fn new_frame(&mut self, frame_id: FrameId, pipeline_id: PipelineId) {
|
||||
let frame = Frame::new(frame_id, pipeline_id);
|
||||
fn new_frame(&mut self, frame_id: FrameId, pipeline_id: PipelineId, url: ServoUrl) {
|
||||
let frame = Frame::new(frame_id, pipeline_id, url);
|
||||
self.frames.insert(frame_id, frame);
|
||||
|
||||
// If a child frame, add it to the parent pipeline.
|
||||
@ -1228,7 +1229,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
// Notify the browser chrome that the pipeline has failed
|
||||
self.trigger_mozbrowsererror(top_level_frame_id, reason, backtrace);
|
||||
|
||||
let pipeline_id = self.frames.get(&top_level_frame_id).map(|frame| frame.current.pipeline_id);
|
||||
let pipeline_id = self.frames.get(&top_level_frame_id).map(|frame| frame.pipeline_id);
|
||||
let pipeline_url = pipeline_id.and_then(|id| self.pipelines.get(&id).map(|pipeline| pipeline.url.clone()));
|
||||
let parent_info = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.parent_info));
|
||||
let window_size = pipeline_id.and_then(|id| self.pipelines.get(&id).and_then(|pipeline| pipeline.size));
|
||||
@ -1246,14 +1247,15 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
warn!("creating replacement pipeline for about:failure");
|
||||
|
||||
let new_pipeline_id = PipelineId::new();
|
||||
let load_data = LoadData::new(failure_url, None, None);
|
||||
let load_data = LoadData::new(failure_url.clone(), None, None);
|
||||
let sandbox = IFrameSandboxState::IFrameSandboxed;
|
||||
self.new_pipeline(new_pipeline_id, top_level_frame_id, parent_info, window_size, load_data, sandbox, false);
|
||||
self.pending_frames.push(FrameChange {
|
||||
frame_id: top_level_frame_id,
|
||||
old_pipeline_id: pipeline_id,
|
||||
new_pipeline_id: new_pipeline_id,
|
||||
replace: false,
|
||||
url: failure_url,
|
||||
replace: None,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1286,7 +1288,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
frame_id: self.root_frame_id,
|
||||
old_pipeline_id: None,
|
||||
new_pipeline_id: root_pipeline_id,
|
||||
replace: false,
|
||||
url: url.clone(),
|
||||
replace: None,
|
||||
});
|
||||
self.compositor_proxy.send(ToCompositorMsg::ChangePageUrl(root_pipeline_id, url));
|
||||
}
|
||||
@ -1377,7 +1380,21 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
(load_data, window_size, is_private)
|
||||
};
|
||||
|
||||
let replace = if load_info.info.replace {
|
||||
self.frames.get(&load_info.info.frame_id).map(|frame| frame.current())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Create the new pipeline, attached to the parent and push to pending frames
|
||||
self.pending_frames.push(FrameChange {
|
||||
frame_id: load_info.info.frame_id,
|
||||
old_pipeline_id: load_info.old_pipeline_id,
|
||||
new_pipeline_id: load_info.info.new_pipeline_id,
|
||||
url: load_data.url.clone(),
|
||||
replace: replace,
|
||||
});
|
||||
|
||||
self.new_pipeline(load_info.info.new_pipeline_id,
|
||||
load_info.info.frame_id,
|
||||
Some((load_info.info.parent_pipeline_id, load_info.info.frame_type)),
|
||||
@ -1385,13 +1402,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
load_data,
|
||||
load_info.sandbox,
|
||||
is_private);
|
||||
|
||||
self.pending_frames.push(FrameChange {
|
||||
frame_id: load_info.info.frame_id,
|
||||
old_pipeline_id: load_info.old_pipeline_id,
|
||||
new_pipeline_id: load_info.info.new_pipeline_id,
|
||||
replace: load_info.info.replace,
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_script_loaded_about_blank_in_iframe_msg(&mut self,
|
||||
@ -1406,6 +1416,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
is_private,
|
||||
} = load_info;
|
||||
|
||||
let url = ServoUrl::parse("about:blank").expect("infallible");
|
||||
|
||||
let pipeline = {
|
||||
let parent_pipeline = match self.pipelines.get(&parent_pipeline_id) {
|
||||
Some(parent_pipeline) => parent_pipeline,
|
||||
@ -1414,7 +1426,6 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
|
||||
let script_sender = parent_pipeline.event_loop.clone();
|
||||
|
||||
let url = ServoUrl::parse("about:blank").expect("infallible");
|
||||
Pipeline::new(new_pipeline_id,
|
||||
frame_id,
|
||||
Some((parent_pipeline_id, frame_type)),
|
||||
@ -1422,11 +1433,17 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
layout_sender,
|
||||
self.compositor_proxy.clone_compositor_proxy(),
|
||||
is_private || parent_pipeline.is_private,
|
||||
url,
|
||||
url.clone(),
|
||||
None,
|
||||
parent_pipeline.visible)
|
||||
};
|
||||
|
||||
let replace = if replace {
|
||||
self.frames.get(&frame_id).map(|frame| frame.current())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
assert!(!self.pipelines.contains_key(&new_pipeline_id));
|
||||
self.pipelines.insert(new_pipeline_id, pipeline);
|
||||
|
||||
@ -1434,6 +1451,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
frame_id: frame_id,
|
||||
old_pipeline_id: None,
|
||||
new_pipeline_id: new_pipeline_id,
|
||||
url: url,
|
||||
replace: replace,
|
||||
});
|
||||
}
|
||||
@ -1488,7 +1506,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
|
||||
match self.frames.get(&self.root_frame_id) {
|
||||
None => warn!("Alert sent after root frame closure."),
|
||||
Some(root_frame) => match self.pipelines.get(&root_frame.current.pipeline_id) {
|
||||
Some(root_frame) => match self.pipelines.get(&root_frame.pipeline_id) {
|
||||
None => warn!("Alert sent after root pipeline closure."),
|
||||
Some(root_pipeline) => root_pipeline.trigger_mozbrowser_event(Some(top_level_frame_id), event),
|
||||
}
|
||||
@ -1564,13 +1582,19 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
let new_pipeline_id = PipelineId::new();
|
||||
let root_frame_id = self.root_frame_id;
|
||||
let sandbox = IFrameSandboxState::IFrameUnsandboxed;
|
||||
self.new_pipeline(new_pipeline_id, root_frame_id, None, window_size, load_data, sandbox, false);
|
||||
let replace = if replace {
|
||||
self.frames.get(&frame_id).map(|frame| frame.current())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.pending_frames.push(FrameChange {
|
||||
frame_id: root_frame_id,
|
||||
old_pipeline_id: Some(source_id),
|
||||
new_pipeline_id: new_pipeline_id,
|
||||
url: load_data.url.clone(),
|
||||
replace: replace,
|
||||
});
|
||||
self.new_pipeline(new_pipeline_id, root_frame_id, None, window_size, load_data, sandbox, false);
|
||||
|
||||
// Send message to ScriptThread that will suspend all timers
|
||||
match self.pipelines.get(&source_id) {
|
||||
@ -1616,34 +1640,32 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
.map(|pipeline_id| self.get_top_level_frame_for_pipeline(pipeline_id))
|
||||
.unwrap_or(self.root_frame_id);
|
||||
|
||||
let mut traversal_info = HashMap::new();
|
||||
let mut size = 0;
|
||||
let mut table = HashMap::new();
|
||||
|
||||
match direction {
|
||||
TraversalDirection::Forward(delta) => {
|
||||
let mut future = self.joint_session_future(top_level_frame_id);
|
||||
for _ in 0..delta {
|
||||
match future.pop() {
|
||||
Some((_, frame_id, pipeline_id)) => {
|
||||
traversal_info.insert(frame_id, pipeline_id);
|
||||
},
|
||||
None => return warn!("invalid traversal delta"),
|
||||
}
|
||||
for entry in self.joint_session_future(top_level_frame_id).take(delta) {
|
||||
size = size + 1;
|
||||
table.insert(entry.frame_id, entry);
|
||||
}
|
||||
if size < delta {
|
||||
return debug!("Traversing forward too much.");
|
||||
}
|
||||
},
|
||||
TraversalDirection::Back(delta) => {
|
||||
let mut past = self.joint_session_past(top_level_frame_id);
|
||||
for _ in 0..delta {
|
||||
match past.pop() {
|
||||
Some((_, frame_id, pipeline_id)) => {
|
||||
traversal_info.insert(frame_id, pipeline_id);
|
||||
},
|
||||
None => return warn!("invalid traversal delta"),
|
||||
}
|
||||
for entry in self.joint_session_past(top_level_frame_id).take(delta) {
|
||||
size = size + 1;
|
||||
table.insert(entry.frame_id, entry);
|
||||
}
|
||||
if size < delta {
|
||||
return debug!("Traversing back too much.");
|
||||
}
|
||||
},
|
||||
};
|
||||
for (frame_id, pipeline_id) in traversal_info {
|
||||
self.traverse_frame_to_pipeline(frame_id, pipeline_id);
|
||||
}
|
||||
|
||||
for (_, entry) in table {
|
||||
self.traverse_to_entry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1664,7 +1686,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
// frame's current pipeline. If neither exist, fall back to sending to
|
||||
// the compositor below.
|
||||
let root_pipeline_id = self.frames.get(&self.root_frame_id)
|
||||
.map(|root_frame| root_frame.current.pipeline_id);
|
||||
.map(|root_frame| root_frame.pipeline_id);
|
||||
let pipeline_id = self.focus_pipeline_id.or(root_pipeline_id);
|
||||
|
||||
match pipeline_id {
|
||||
@ -1689,7 +1711,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
fn handle_reload_msg(&mut self) {
|
||||
// Send Reload constellation msg to root script channel.
|
||||
let root_pipeline_id = self.frames.get(&self.root_frame_id)
|
||||
.map(|root_frame| root_frame.current.pipeline_id);
|
||||
.map(|root_frame| root_frame.pipeline_id);
|
||||
|
||||
if let Some(pipeline_id) = root_pipeline_id {
|
||||
let msg = ConstellationControlMsg::Reload(pipeline_id);
|
||||
@ -1734,7 +1756,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
resp_chan: IpcSender<Option<PipelineId>>) {
|
||||
let frame_id = frame_id.unwrap_or(self.root_frame_id);
|
||||
let current_pipeline_id = self.frames.get(&frame_id)
|
||||
.map(|frame| frame.current.pipeline_id);
|
||||
.map(|frame| frame.pipeline_id);
|
||||
let pipeline_id_loaded = self.pending_frames.iter().rev()
|
||||
.find(|x| x.old_pipeline_id == current_pipeline_id)
|
||||
.map(|x| x.new_pipeline_id)
|
||||
@ -1806,11 +1828,11 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
};
|
||||
|
||||
let child_pipeline_ids: Vec<PipelineId> = self.full_frame_tree_iter(frame_id)
|
||||
.flat_map(|frame| frame.next.iter()
|
||||
.chain(frame.prev.iter())
|
||||
.chain(once(&frame.current)))
|
||||
.map(|state| state.pipeline_id)
|
||||
.collect();
|
||||
.flat_map(|frame| frame.prev.iter().chain(frame.next.iter())
|
||||
.filter_map(|entry| entry.pipeline_id)
|
||||
.chain(once(frame.pipeline_id)))
|
||||
.collect();
|
||||
|
||||
for id in child_pipeline_ids {
|
||||
if let Some(pipeline) = self.pipelines.get_mut(&id) {
|
||||
pipeline.change_visibility(visible);
|
||||
@ -1909,7 +1931,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
},
|
||||
WebDriverCommandMsg::TakeScreenshot(pipeline_id, reply) => {
|
||||
let current_pipeline_id = self.frames.get(&self.root_frame_id)
|
||||
.map(|root_frame| root_frame.current.pipeline_id);
|
||||
.map(|root_frame| root_frame.pipeline_id);
|
||||
if Some(pipeline_id) == current_pipeline_id {
|
||||
self.compositor_proxy.send(ToCompositorMsg::CreatePng(reply));
|
||||
} else {
|
||||
@ -1921,63 +1943,95 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
}
|
||||
}
|
||||
|
||||
fn traverse_frame_to_pipeline(&mut self, frame_id: FrameId, next_pipeline_id: PipelineId) {
|
||||
// https://html.spec.whatwg.org/multipage/#traverse-the-history
|
||||
fn traverse_to_entry(&mut self, entry: FrameState) {
|
||||
// Step 1.
|
||||
let frame_id = entry.frame_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 {} for frame {}.", entry.url, frame_id);
|
||||
// TODO: referrer?
|
||||
let load_data = LoadData::new(entry.url.clone(), None, None);
|
||||
// TODO: save the sandbox state so it can be restored here.
|
||||
let sandbox = IFrameSandboxState::IFrameUnsandboxed;
|
||||
let new_pipeline_id = PipelineId::new();
|
||||
let (old_pipeline_id, parent_info, window_size, is_private) = match self.frames.get(&frame_id) {
|
||||
Some(frame) => match self.pipelines.get(&frame.pipeline_id) {
|
||||
Some(pipeline) => (frame.pipeline_id, pipeline.parent_info, pipeline.size, pipeline.is_private),
|
||||
None => (frame.pipeline_id, None, None, false),
|
||||
},
|
||||
None => return warn!("no frame to traverse"),
|
||||
};
|
||||
self.new_pipeline(new_pipeline_id, frame_id, parent_info, window_size, load_data, sandbox, is_private);
|
||||
self.pending_frames.push(FrameChange {
|
||||
frame_id: frame_id,
|
||||
old_pipeline_id: Some(old_pipeline_id),
|
||||
new_pipeline_id: new_pipeline_id,
|
||||
url: entry.url.clone(),
|
||||
replace: Some(entry),
|
||||
});
|
||||
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_in_tree(frame_id);
|
||||
let update_focus_pipeline = self.focused_pipeline_in_tree(entry.frame_id);
|
||||
|
||||
let prev_pipeline_id = match self.frames.get_mut(&frame_id) {
|
||||
let old_pipeline_id = match self.frames.get_mut(&frame_id) {
|
||||
Some(frame) => {
|
||||
let prev = frame.current.pipeline_id;
|
||||
let old_pipeline_id = frame.pipeline_id;
|
||||
let mut curr_entry = frame.current();
|
||||
|
||||
// Check that this frame contains the pipeline passed in, so that this does not
|
||||
// change Frame's state before realizing `next_pipeline_id` is invalid.
|
||||
if frame.next.iter().find(|entry| next_pipeline_id == entry.pipeline_id).is_some() {
|
||||
frame.prev.push(frame.current.clone());
|
||||
while let Some(entry) = frame.next.pop() {
|
||||
if entry.pipeline_id == next_pipeline_id {
|
||||
frame.current = entry;
|
||||
break;
|
||||
} else {
|
||||
frame.prev.push(entry);
|
||||
}
|
||||
if entry.instant > frame.instant {
|
||||
// We are traversing to the future.
|
||||
while let Some(next) = frame.next.pop() {
|
||||
frame.prev.push(curr_entry);
|
||||
curr_entry = next;
|
||||
if entry.instant <= curr_entry.instant { break; }
|
||||
}
|
||||
} else if frame.prev.iter().find(|entry| next_pipeline_id == entry.pipeline_id).is_some() {
|
||||
frame.next.push(frame.current.clone());
|
||||
while let Some(entry) = frame.prev.pop() {
|
||||
if entry.pipeline_id == next_pipeline_id {
|
||||
frame.current = entry;
|
||||
break;
|
||||
} else {
|
||||
frame.next.push(entry);
|
||||
}
|
||||
} else if entry.instant < frame.instant {
|
||||
// We are traversing to the past.
|
||||
while let Some(prev) = frame.prev.pop() {
|
||||
frame.next.push(curr_entry);
|
||||
curr_entry = prev;
|
||||
if entry.instant >= curr_entry.instant { break; }
|
||||
}
|
||||
} else if prev != next_pipeline_id {
|
||||
return warn!("Tried to traverse frame {:?} to pipeline {:?} it does not contain.",
|
||||
frame_id, next_pipeline_id);
|
||||
}
|
||||
|
||||
prev
|
||||
debug_assert_eq!(entry.instant, curr_entry.instant);
|
||||
|
||||
frame.pipeline_id = pipeline_id;
|
||||
frame.instant = entry.instant;
|
||||
frame.url = entry.url.clone();
|
||||
|
||||
old_pipeline_id
|
||||
},
|
||||
None => return warn!("no frame to traverse"),
|
||||
};
|
||||
|
||||
let pipeline_info = self.pipelines.get(&prev_pipeline_id).and_then(|p| p.parent_info);
|
||||
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(next_pipeline_id);
|
||||
self.focus_pipeline_id = Some(pipeline_id);
|
||||
}
|
||||
|
||||
// Suspend the old pipeline, and resume the new one.
|
||||
if let Some(prev_pipeline) = self.pipelines.get(&prev_pipeline_id) {
|
||||
prev_pipeline.freeze();
|
||||
if let Some(pipeline) = self.pipelines.get(&old_pipeline_id) {
|
||||
pipeline.freeze();
|
||||
}
|
||||
if let Some(next_pipeline) = self.pipelines.get(&next_pipeline_id) {
|
||||
next_pipeline.thaw();
|
||||
if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
|
||||
pipeline.thaw();
|
||||
}
|
||||
|
||||
// Set paint permissions correctly for the compositor layers.
|
||||
@ -1985,10 +2039,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
|
||||
// Update the owning iframe to point to the new pipeline id.
|
||||
// This makes things like contentDocument work correctly.
|
||||
if let Some((parent_pipeline_id, _)) = pipeline_info {
|
||||
let msg = ConstellationControlMsg::UpdatePipelineId(parent_pipeline_id,
|
||||
frame_id,
|
||||
next_pipeline_id);
|
||||
if let Some((parent_pipeline_id, _)) = parent_info {
|
||||
let msg = ConstellationControlMsg::UpdatePipelineId(parent_pipeline_id, frame_id, pipeline_id);
|
||||
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),
|
||||
@ -1999,7 +2051,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
|
||||
// If this is an iframe, send a mozbrowser location change event.
|
||||
// This is the result of a back/forward traversal.
|
||||
self.trigger_mozbrowserlocationchange(next_pipeline_id);
|
||||
self.trigger_mozbrowserlocationchange(pipeline_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2049,33 +2101,39 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
}
|
||||
}
|
||||
|
||||
if self.frames.contains_key(&frame_change.frame_id) {
|
||||
if frame_change.replace {
|
||||
let evicted = self.frames.get_mut(&frame_change.frame_id).map(|frame| {
|
||||
frame.replace_current(frame_change.new_pipeline_id)
|
||||
});
|
||||
if let Some(evicted) = evicted {
|
||||
self.close_pipeline(evicted.pipeline_id, ExitPipelineMode::Normal);
|
||||
}
|
||||
} else {
|
||||
if let Some(ref mut frame) = self.frames.get_mut(&frame_change.frame_id) {
|
||||
frame.load(frame_change.new_pipeline_id);
|
||||
}
|
||||
}
|
||||
let (evicted_id, new_frame, clear_future, location_changed) = if let Some(mut entry) = frame_change.replace {
|
||||
debug!("Replacing pipeline in existing frame.");
|
||||
let evicted_id = entry.pipeline_id;
|
||||
entry.pipeline_id = Some(frame_change.new_pipeline_id);
|
||||
self.traverse_to_entry(entry);
|
||||
(evicted_id, false, false, false)
|
||||
} else if let Some(frame) = self.frames.get_mut(&frame_change.frame_id) {
|
||||
debug!("Adding pipeline to existing frame.");
|
||||
frame.load(frame_change.new_pipeline_id, frame_change.url.clone());
|
||||
let evicted_id = frame.prev.get_mut(opts::get().max_session_history)
|
||||
.and_then(|entry| entry.pipeline_id.take());
|
||||
(evicted_id, false, true, true)
|
||||
} else {
|
||||
// The new pipeline is in a new frame with no history
|
||||
self.new_frame(frame_change.frame_id, frame_change.new_pipeline_id);
|
||||
(None, true, false, true)
|
||||
};
|
||||
|
||||
if let Some(evicted_id) = evicted_id {
|
||||
self.close_pipeline(evicted_id, ExitPipelineMode::Normal);
|
||||
}
|
||||
|
||||
if !frame_change.replace {
|
||||
// If this is an iframe, send a mozbrowser location change event.
|
||||
// This is the result of a link being clicked and a navigation completing.
|
||||
self.trigger_mozbrowserlocationchange(frame_change.new_pipeline_id);
|
||||
if new_frame {
|
||||
self.new_frame(frame_change.frame_id, frame_change.new_pipeline_id, frame_change.url);
|
||||
};
|
||||
|
||||
if clear_future {
|
||||
let top_level_frame_id = self.get_top_level_frame_for_pipeline(frame_change.new_pipeline_id);
|
||||
self.clear_joint_session_future(top_level_frame_id);
|
||||
}
|
||||
|
||||
if location_changed {
|
||||
self.trigger_mozbrowserlocationchange(frame_change.new_pipeline_id);
|
||||
}
|
||||
|
||||
// Build frame tree
|
||||
self.send_frame_tree();
|
||||
}
|
||||
@ -2114,7 +2172,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
if let Some(frame) = self.frames.get(&self.root_frame_id) {
|
||||
// Send Resize (or ResizeInactive) messages to each
|
||||
// pipeline in the frame tree.
|
||||
let pipeline_id = frame.current.pipeline_id;
|
||||
let pipeline_id = frame.pipeline_id;
|
||||
let pipeline = match self.pipelines.get(&pipeline_id) {
|
||||
None => return warn!("Pipeline {:?} resized after closing.", pipeline_id),
|
||||
Some(pipeline) => pipeline,
|
||||
@ -2124,14 +2182,10 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
new_size,
|
||||
size_type
|
||||
));
|
||||
for entry in frame.prev.iter().chain(&frame.next) {
|
||||
let pipeline = match self.pipelines.get(&entry.pipeline_id) {
|
||||
None => {
|
||||
warn!("Inactive pipeline {:?} resized after closing.", pipeline_id);
|
||||
continue;
|
||||
},
|
||||
Some(pipeline) => pipeline,
|
||||
};
|
||||
let pipelines = frame.prev.iter().chain(frame.next.iter())
|
||||
.filter_map(|entry| entry.pipeline_id)
|
||||
.filter_map(|pipeline_id| self.pipelines.get(&pipeline_id));
|
||||
for pipeline in pipelines {
|
||||
let _ = pipeline.event_loop.send(ConstellationControlMsg::ResizeInactive(
|
||||
pipeline.id,
|
||||
new_size
|
||||
@ -2200,7 +2254,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
// are met, then the output image should not change and a reftest
|
||||
// screenshot can safely be written.
|
||||
for frame in self.current_frame_tree_iter(self.root_frame_id) {
|
||||
let pipeline_id = frame.current.pipeline_id;
|
||||
let pipeline_id = frame.pipeline_id;
|
||||
debug!("Checking readiness of frame {}, pipeline {}.", frame.id, pipeline_id);
|
||||
|
||||
let pipeline = match self.pipelines.get(&pipeline_id) {
|
||||
@ -2228,7 +2282,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
}
|
||||
|
||||
// See if this pipeline has reached idle script state yet.
|
||||
match self.document_states.get(&frame.current.pipeline_id) {
|
||||
match self.document_states.get(&frame.pipeline_id) {
|
||||
Some(&DocumentState::Idle) => {}
|
||||
Some(&DocumentState::Pending) | None => {
|
||||
return ReadyToSave::DocumentLoading;
|
||||
@ -2248,7 +2302,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
}
|
||||
|
||||
// Get the epoch that the compositor has drawn for this pipeline.
|
||||
let compositor_epoch = pipeline_states.get(&frame.current.pipeline_id);
|
||||
let compositor_epoch = pipeline_states.get(&frame.pipeline_id);
|
||||
match compositor_epoch {
|
||||
Some(compositor_epoch) => {
|
||||
// Synchronously query the layout thread to see if the current
|
||||
@ -2280,38 +2334,27 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
}
|
||||
|
||||
fn clear_joint_session_future(&mut self, frame_id: FrameId) {
|
||||
let mut evicted_pipelines = vec!();
|
||||
let mut frames_to_clear = vec!(frame_id);
|
||||
while let Some(frame_id) = frames_to_clear.pop() {
|
||||
let frame = match self.frames.get_mut(&frame_id) {
|
||||
Some(frame) => frame,
|
||||
None => {
|
||||
warn!("Removed forward history after frame {:?} closure.", frame_id);
|
||||
continue;
|
||||
}
|
||||
let frame_ids: Vec<FrameId> = self.full_frame_tree_iter(frame_id)
|
||||
.map(|frame| frame.id)
|
||||
.collect();
|
||||
for frame_id in frame_ids {
|
||||
let evicted = match self.frames.get_mut(&frame_id) {
|
||||
Some(frame) => frame.remove_forward_entries(),
|
||||
None => continue,
|
||||
};
|
||||
evicted_pipelines.extend(frame.remove_forward_entries());
|
||||
for entry in frame.next.iter().chain(frame.prev.iter()).chain(once(&frame.current)) {
|
||||
let pipeline = match self.pipelines.get(&entry.pipeline_id) {
|
||||
Some(pipeline) => pipeline,
|
||||
None => {
|
||||
warn!("Removed forward history after pipeline {:?} closure.", entry.pipeline_id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
frames_to_clear.extend_from_slice(&pipeline.children);
|
||||
for entry in evicted {
|
||||
if let Some(pipeline_id) = entry.pipeline_id {
|
||||
self.close_pipeline(pipeline_id, ExitPipelineMode::Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
for entry in evicted_pipelines {
|
||||
self.close_pipeline(entry.pipeline_id, ExitPipelineMode::Normal);
|
||||
}
|
||||
}
|
||||
|
||||
// Close a frame (and all children)
|
||||
fn close_frame(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) {
|
||||
debug!("Closing frame {}.", frame_id);
|
||||
let parent_info = self.frames.get(&frame_id)
|
||||
.and_then(|frame| self.pipelines.get(&frame.current.pipeline_id))
|
||||
.and_then(|frame| self.pipelines.get(&frame.pipeline_id))
|
||||
.and_then(|pipeline| pipeline.parent_info);
|
||||
|
||||
self.close_frame_children(frame_id, exit_mode);
|
||||
@ -2344,9 +2387,9 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
.collect();
|
||||
|
||||
if let Some(frame) = self.frames.get(&frame_id) {
|
||||
pipelines_to_close.extend(frame.next.iter().map(|state| state.pipeline_id));
|
||||
pipelines_to_close.push(frame.current.pipeline_id);
|
||||
pipelines_to_close.extend(frame.prev.iter().map(|state| state.pipeline_id));
|
||||
pipelines_to_close.extend(frame.next.iter().filter_map(|state| state.pipeline_id));
|
||||
pipelines_to_close.push(frame.pipeline_id);
|
||||
pipelines_to_close.extend(frame.prev.iter().filter_map(|state| state.pipeline_id));
|
||||
}
|
||||
|
||||
for pipeline_id in pipelines_to_close {
|
||||
@ -2430,7 +2473,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
// Convert a frame to a sendable form to pass to the compositor
|
||||
fn frame_to_sendable(&self, frame_id: FrameId) -> Option<SendableFrameTree> {
|
||||
self.frames.get(&frame_id).and_then(|frame: &Frame| {
|
||||
self.pipelines.get(&frame.current.pipeline_id).map(|pipeline: &Pipeline| {
|
||||
self.pipelines.get(&frame.pipeline_id).map(|pipeline: &Pipeline| {
|
||||
let mut frame_tree = SendableFrameTree {
|
||||
pipeline: pipeline.to_sendable(),
|
||||
size: pipeline.size,
|
||||
@ -2511,7 +2554,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
|
||||
match self.frames.get(&top_level_frame_id) {
|
||||
None => warn!("Mozbrowser error after top-level frame closed."),
|
||||
Some(frame) => match self.pipelines.get(&frame.current.pipeline_id) {
|
||||
Some(frame) => match self.pipelines.get(&frame.pipeline_id) {
|
||||
None => warn!("Mozbrowser error after top-level pipeline closed."),
|
||||
Some(pipeline) => match pipeline.parent_info {
|
||||
None => pipeline.trigger_mozbrowser_event(None, event),
|
||||
@ -2538,7 +2581,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
|
||||
pipeline_id: PipelineId,
|
||||
root_frame_id: FrameId) -> bool {
|
||||
self.current_frame_tree_iter(root_frame_id)
|
||||
.any(|current_frame| current_frame.current.pipeline_id == pipeline_id)
|
||||
.any(|current_frame| current_frame.pipeline_id == pipeline_id)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use msg::constellation_msg::{FrameId, PipelineId};
|
||||
use pipeline::Pipeline;
|
||||
use servo_url::ServoUrl;
|
||||
use std::collections::HashMap;
|
||||
use std::iter::once;
|
||||
use std::mem::replace;
|
||||
@ -22,12 +23,18 @@ pub struct Frame {
|
||||
/// The frame id.
|
||||
pub id: FrameId,
|
||||
|
||||
/// The timestamp for the current session history entry
|
||||
pub instant: Instant,
|
||||
|
||||
/// The pipeline for the current session history entry
|
||||
pub pipeline_id: PipelineId,
|
||||
|
||||
/// The URL for the current session history entry
|
||||
pub url: ServoUrl,
|
||||
|
||||
/// The past session history, ordered chronologically.
|
||||
pub prev: Vec<FrameState>,
|
||||
|
||||
/// The currently active session history entry.
|
||||
pub current: FrameState,
|
||||
|
||||
/// The future session history, ordered reverse chronologically.
|
||||
pub next: Vec<FrameState>,
|
||||
}
|
||||
@ -35,30 +42,40 @@ pub struct Frame {
|
||||
impl Frame {
|
||||
/// Create a new frame.
|
||||
/// Note this just creates the frame, it doesn't add it to the frame tree.
|
||||
pub fn new(id: FrameId, pipeline_id: PipelineId) -> Frame {
|
||||
pub fn new(id: FrameId, pipeline_id: PipelineId, url: ServoUrl) -> Frame {
|
||||
Frame {
|
||||
id: id,
|
||||
pipeline_id: pipeline_id,
|
||||
instant: Instant::now(),
|
||||
url: url,
|
||||
prev: vec!(),
|
||||
current: FrameState::new(pipeline_id, id),
|
||||
next: vec!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current frame state.
|
||||
pub fn current(&self) -> FrameState {
|
||||
FrameState {
|
||||
instant: self.instant,
|
||||
frame_id: self.id,
|
||||
pipeline_id: Some(self.pipeline_id),
|
||||
url: self.url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the current frame entry, and push the current frame entry into the past.
|
||||
pub fn load(&mut self, pipeline_id: PipelineId) {
|
||||
self.prev.push(self.current.clone());
|
||||
self.current = FrameState::new(pipeline_id, self.id);
|
||||
pub fn load(&mut self, pipeline_id: PipelineId, url: ServoUrl) {
|
||||
let current = self.current();
|
||||
self.prev.push(current);
|
||||
self.instant = Instant::now();
|
||||
self.pipeline_id = pipeline_id;
|
||||
self.url = url;
|
||||
}
|
||||
|
||||
/// Set the future to be empty.
|
||||
pub fn remove_forward_entries(&mut self) -> Vec<FrameState> {
|
||||
replace(&mut self.next, vec!())
|
||||
}
|
||||
|
||||
/// Set the current frame entry, and drop the current frame entry.
|
||||
pub fn replace_current(&mut self, pipeline_id: PipelineId) -> FrameState {
|
||||
replace(&mut self.current, FrameState::new(pipeline_id, self.id))
|
||||
}
|
||||
}
|
||||
|
||||
/// An entry in a frame's session history.
|
||||
@ -70,23 +87,18 @@ impl Frame {
|
||||
pub struct FrameState {
|
||||
/// The timestamp for when the session history entry was created
|
||||
pub instant: Instant,
|
||||
/// The pipeline for the document in the session history
|
||||
pub pipeline_id: PipelineId,
|
||||
|
||||
/// The pipeline for the document in the session history,
|
||||
/// None if the entry has been discarded
|
||||
pub pipeline_id: Option<PipelineId>,
|
||||
|
||||
/// The URL for this entry, used to reload the pipeline if it has been discarded
|
||||
pub url: ServoUrl,
|
||||
|
||||
/// The frame that this session history entry is part of
|
||||
pub frame_id: FrameId,
|
||||
}
|
||||
|
||||
impl FrameState {
|
||||
/// Create a new session history entry.
|
||||
fn new(pipeline_id: PipelineId, frame_id: FrameId) -> FrameState {
|
||||
FrameState {
|
||||
instant: Instant::now(),
|
||||
pipeline_id: pipeline_id,
|
||||
frame_id: frame_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a pending change in the frame tree, that will be applied
|
||||
/// once the new pipeline has loaded and completed initial layout / paint.
|
||||
pub struct FrameChange {
|
||||
@ -100,9 +112,12 @@ pub struct FrameChange {
|
||||
/// The pipeline for the document being loaded.
|
||||
pub new_pipeline_id: PipelineId,
|
||||
|
||||
/// The URL for the document being loaded.
|
||||
pub url: ServoUrl,
|
||||
|
||||
/// Is the new document replacing the current document (e.g. a reload)
|
||||
/// or pushing it into the session history (e.g. a navigation)?
|
||||
pub replace: bool,
|
||||
pub replace: Option<FrameState>,
|
||||
}
|
||||
|
||||
/// An iterator over a frame tree, returning the fully active frames in
|
||||
@ -137,14 +152,14 @@ impl<'a> Iterator for FrameTreeIterator<'a> {
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let pipeline = match self.pipelines.get(&frame.current.pipeline_id) {
|
||||
let pipeline = match self.pipelines.get(&frame.pipeline_id) {
|
||||
Some(pipeline) => pipeline,
|
||||
None => {
|
||||
warn!("Pipeline {:?} iterated after closure.", frame.current.pipeline_id);
|
||||
warn!("Pipeline {:?} iterated after closure.", frame.pipeline_id);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
self.stack.extend(pipeline.children.iter().map(|&c| c));
|
||||
self.stack.extend(pipeline.children.iter());
|
||||
return Some(frame)
|
||||
}
|
||||
}
|
||||
@ -169,6 +184,7 @@ pub struct FullFrameTreeIterator<'a> {
|
||||
impl<'a> Iterator for FullFrameTreeIterator<'a> {
|
||||
type Item = &'a Frame;
|
||||
fn next(&mut self) -> Option<&'a Frame> {
|
||||
let pipelines = self.pipelines;
|
||||
loop {
|
||||
let frame_id = match self.stack.pop() {
|
||||
Some(frame_id) => frame_id,
|
||||
@ -181,11 +197,12 @@ impl<'a> Iterator for FullFrameTreeIterator<'a> {
|
||||
continue;
|
||||
},
|
||||
};
|
||||
for entry in frame.prev.iter().chain(frame.next.iter()).chain(once(&frame.current)) {
|
||||
if let Some(pipeline) = self.pipelines.get(&entry.pipeline_id) {
|
||||
self.stack.extend(pipeline.children.iter().map(|&c| c));
|
||||
}
|
||||
}
|
||||
let child_frame_ids = frame.prev.iter().chain(frame.next.iter())
|
||||
.filter_map(|entry| entry.pipeline_id)
|
||||
.chain(once(frame.pipeline_id))
|
||||
.filter_map(|pipeline_id| pipelines.get(&pipeline_id))
|
||||
.flat_map(|pipeline| pipeline.children.iter());
|
||||
self.stack.extend(child_frame_ids);
|
||||
return Some(frame)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![feature(box_syntax)]
|
||||
#![feature(conservative_impl_trait)]
|
||||
#![feature(mpsc_select)]
|
||||
#![feature(plugin)]
|
||||
#![feature(proc_macro)]
|
||||
|
@ -901,18 +901,24 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn clear_js_runtime(&self) {
|
||||
// We tear down the active document, which causes all the attached
|
||||
// nodes to dispose of their layout data. This messages the layout
|
||||
// thread, informing it that it can safely free the memory.
|
||||
self.Document().upcast::<Node>().teardown();
|
||||
|
||||
// The above code may not catch all DOM objects
|
||||
// (e.g. DOM objects removed from the tree that haven't
|
||||
// been collected yet). Forcing a GC here means that
|
||||
// those DOM objects will be able to call dispose()
|
||||
// to free their layout data before the layout thread
|
||||
// exits. Without this, those remaining objects try to
|
||||
// send a message to free their layout data to the
|
||||
// layout thread when the script thread is dropped,
|
||||
// which causes a panic!
|
||||
// The above code may not catch all DOM objects (e.g. DOM
|
||||
// objects removed from the tree that haven't been collected
|
||||
// yet). There should not be any such DOM nodes with layout
|
||||
// data, but if there are, then when they are dropped, they
|
||||
// will attempt to send a message to the closed layout thread.
|
||||
// This causes memory safety issues, because the DOM node uses
|
||||
// the layout channel from its window, and the window has
|
||||
// already been GC'd. For nodes which do not have a live
|
||||
// pointer, we can avoid this by GCing now:
|
||||
self.Gc();
|
||||
// but there may still be nodes being kept alive by user
|
||||
// script.
|
||||
// TODO: ensure that this doesn't happen!
|
||||
|
||||
self.current_state.set(WindowState::Zombie);
|
||||
*self.js_runtime.borrow_mut() = None;
|
||||
@ -1445,6 +1451,15 @@ impl Window {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn freeze(&self) {
|
||||
self.upcast::<GlobalScope>().suspend();
|
||||
// A hint to the JS runtime that now would be a good time to
|
||||
// GC any unreachable objects generated by user script,
|
||||
// or unattached DOM nodes. Attached DOM nodes can't be GCd yet,
|
||||
// as the document might be thawed later.
|
||||
self.Gc();
|
||||
}
|
||||
|
||||
pub fn thaw(&self) {
|
||||
self.upcast::<GlobalScope>().resume();
|
||||
|
||||
|
@ -1346,7 +1346,7 @@ impl ScriptThread {
|
||||
fn handle_freeze_msg(&self, id: PipelineId) {
|
||||
let window = self.documents.borrow().find_window(id);
|
||||
if let Some(window) = window {
|
||||
window.upcast::<GlobalScope>().suspend();
|
||||
window.freeze();
|
||||
return;
|
||||
}
|
||||
let mut loads = self.incomplete_loads.borrow_mut();
|
||||
|
@ -228,18 +228,20 @@ impl OneshotTimers {
|
||||
}
|
||||
|
||||
pub fn suspend(&self) {
|
||||
assert!(self.suspended_since.get().is_none());
|
||||
// Suspend is idempotent: do nothing if the timers are already suspended.
|
||||
if self.suspended_since.get().is_some() {
|
||||
return warn!("Suspending an already suspended timer.");
|
||||
}
|
||||
|
||||
self.suspended_since.set(Some(precise_time_ms()));
|
||||
self.invalidate_expected_event_id();
|
||||
}
|
||||
|
||||
pub fn resume(&self) {
|
||||
assert!(self.suspended_since.get().is_some());
|
||||
|
||||
// Suspend is idempotent: do nothing if the timers are already suspended.
|
||||
let additional_offset = match self.suspended_since.get() {
|
||||
Some(suspended_since) => precise_time_ms() - suspended_since,
|
||||
None => panic!("Timers are not suspended.")
|
||||
None => return warn!("Resuming an already resumed timer."),
|
||||
};
|
||||
|
||||
self.suspension_offset.set(self.suspension_offset.get() + additional_offset);
|
||||
|
@ -24,7 +24,7 @@ use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use url::{Url, Origin, Position};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf, Serialize, Deserialize))]
|
||||
pub struct ServoUrl(Arc<Url>);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user