Skip to content

Commit

Permalink
Implement browsing context discarding.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Jeffrey committed Jan 5, 2017
1 parent 143dfc8 commit 7c2de62
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 161 deletions.
60 changes: 26 additions & 34 deletions components/constellation/constellation.rs
Expand Up @@ -95,7 +95,7 @@ use profile_traits::mem;
use profile_traits::time;
use rand::{Rng, SeedableRng, StdRng, random};
use script_traits::{AnimationState, AnimationTickType, CompositorEvent};
use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg};
use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg, DiscardBrowsingContext};
use script_traits::{DocumentState, LayoutControlMsg, LoadData};
use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerEventRequest};
use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory};
Expand Down Expand Up @@ -975,13 +975,11 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
debug!("constellation got set visibility change complete message");
self.handle_visibility_change_complete(pipeline_id, visible);
}
FromScriptMsg::RemoveIFrame(pipeline_id, sender) => {
FromScriptMsg::RemoveIFrame(frame_id, sender) => {
debug!("constellation got remove iframe message");
self.handle_remove_iframe_msg(pipeline_id);
if let Some(sender) = sender {
if let Err(e) = sender.send(()) {
warn!("Error replying to remove iframe ({})", e);
}
let removed_pipeline_ids = self.handle_remove_iframe_msg(frame_id);
if let Err(e) = sender.send(removed_pipeline_ids) {
warn!("Error replying to remove iframe ({})", e);
}
}
FromScriptMsg::NewFavicon(url) => {
Expand Down Expand Up @@ -1126,7 +1124,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
debug!("Removing pending frame {}.", pending.frame_id);
self.close_frame(pending.frame_id, ExitPipelineMode::Normal);
debug!("Removing pending pipeline {}.", pending.new_pipeline_id);
self.close_pipeline(pending.new_pipeline_id, ExitPipelineMode::Normal);
self.close_pipeline(pending.new_pipeline_id, DiscardBrowsingContext::Yes, ExitPipelineMode::Normal);
}

// In case there are frames which weren't attached to the frame tree, we close them.
Expand All @@ -1140,7 +1138,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
let pipeline_ids: Vec<PipelineId> = self.pipelines.keys().cloned().collect();
for pipeline_id in pipeline_ids {
debug!("Removing detached pipeline {}.", pipeline_id);
self.close_pipeline(pipeline_id, ExitPipelineMode::Normal);
self.close_pipeline(pipeline_id, DiscardBrowsingContext::Yes, ExitPipelineMode::Normal);
}
}

Expand Down Expand Up @@ -1234,7 +1232,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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));

self.close_frame_children(top_level_frame_id, ExitPipelineMode::Force);
self.close_frame_children(top_level_frame_id, DiscardBrowsingContext::No, ExitPipelineMode::Force);

let failure_url = ServoUrl::parse("about:failure").expect("infallible");

Expand Down Expand Up @@ -1805,20 +1803,14 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
self.focus_parent_pipeline(pipeline_id);
}

fn handle_remove_iframe_msg(&mut self, pipeline_id: PipelineId) {
let frame_id = self.pipelines.get(&pipeline_id).map(|pipeline| pipeline.frame_id);
match frame_id {
Some(frame_id) => {
// This iframe has already loaded and been added to the frame tree.
self.close_frame(frame_id, ExitPipelineMode::Normal);
}
None => {
// This iframe is currently loading / painting for the first time.
// In this case, it doesn't exist in the frame tree, but the pipeline
// still needs to be shut down.
self.close_pipeline(pipeline_id, ExitPipelineMode::Normal);
}
}
fn handle_remove_iframe_msg(&mut self, frame_id: FrameId) -> Vec<PipelineId> {
let result = self.full_frame_tree_iter(frame_id)
.flat_map(|frame| frame.next.iter().chain(frame.prev.iter())
.filter_map(|entry| entry.pipeline_id)
.chain(once(frame.pipeline_id)))
.collect();
self.close_frame(frame_id, ExitPipelineMode::Normal);
result
}

fn handle_set_visible_msg(&mut self, pipeline_id: PipelineId, visible: bool) {
Expand Down Expand Up @@ -2118,7 +2110,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
};

if let Some(evicted_id) = evicted_id {
self.close_pipeline(evicted_id, ExitPipelineMode::Normal);
self.close_pipeline(evicted_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal);
}

if new_frame {
Expand Down Expand Up @@ -2344,7 +2336,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
};
for entry in evicted {
if let Some(pipeline_id) = entry.pipeline_id {
self.close_pipeline(pipeline_id, ExitPipelineMode::Normal);
self.close_pipeline(pipeline_id, DiscardBrowsingContext::No, ExitPipelineMode::Normal);
}
}
}
Expand All @@ -2357,7 +2349,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
.and_then(|frame| self.pipelines.get(&frame.pipeline_id))
.and_then(|pipeline| pipeline.parent_info);

self.close_frame_children(frame_id, exit_mode);
self.close_frame_children(frame_id, DiscardBrowsingContext::Yes, exit_mode);

self.event_loops.remove(&frame_id);
if self.frames.remove(&frame_id).is_none() {
Expand All @@ -2375,7 +2367,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
}

// Close the children of a frame
fn close_frame_children(&mut self, frame_id: FrameId, exit_mode: ExitPipelineMode) {
fn close_frame_children(&mut self, frame_id: FrameId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) {
debug!("Closing frame children {}.", frame_id);
// Store information about the pipelines to be closed. Then close the
// pipelines, before removing ourself from the frames hash map. This
Expand All @@ -2393,18 +2385,18 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
}

for pipeline_id in pipelines_to_close {
self.close_pipeline(pipeline_id, exit_mode);
self.close_pipeline(pipeline_id, dbc, exit_mode);
}

debug!("Closed frame children {}.", frame_id);
}

// Close all pipelines at and beneath a given frame
fn close_pipeline(&mut self, pipeline_id: PipelineId, exit_mode: ExitPipelineMode) {
fn close_pipeline(&mut self, pipeline_id: PipelineId, dbc: DiscardBrowsingContext, exit_mode: ExitPipelineMode) {
debug!("Closing pipeline {:?}.", pipeline_id);
// Store information about the frames to be closed. Then close the
// frames, before removing ourself from the pipelines hash map. This
// ordering is vital - so that if close_frames() ends up closing
// ordering is vital - so that if close_frame() ends up closing
// any child pipelines, they can be removed from the parent pipeline correctly.
let frames_to_close = {
let mut frames_to_close = vec!();
Expand Down Expand Up @@ -2438,8 +2430,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>

// Inform script, compositor that this pipeline has exited.
match exit_mode {
ExitPipelineMode::Normal => pipeline.exit(),
ExitPipelineMode::Force => pipeline.force_exit(),
ExitPipelineMode::Normal => pipeline.exit(dbc),
ExitPipelineMode::Force => pipeline.force_exit(dbc),
}
debug!("Closed pipeline {:?}.", pipeline_id);
}
Expand All @@ -2463,7 +2455,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
// Note that we deliberately do not do any of the tidying up
// associated with closing a pipeline. The constellation should cope!
warn!("Randomly closing pipeline {}.", pipeline_id);
pipeline.force_exit();
pipeline.force_exit(DiscardBrowsingContext::No);
}
}
}
Expand Down
12 changes: 7 additions & 5 deletions components/constellation/pipeline.rs
Expand Up @@ -20,7 +20,7 @@ use net_traits::{IpcSend, ResourceThreads};
use net_traits::image_cache_thread::ImageCacheThread;
use profile_traits::mem as profile_mem;
use profile_traits::time;
use script_traits::{ConstellationControlMsg, InitialScriptState};
use script_traits::{ConstellationControlMsg, DiscardBrowsingContext, InitialScriptState};
use script_traits::{LayoutControlMsg, LayoutMsg, LoadData, MozBrowserEvent};
use script_traits::{NewLayoutInfo, SWManagerMsg, SWManagerSenders, ScriptMsg};
use script_traits::{ScriptThreadFactory, TimerEventRequest, WindowSizeData};
Expand Down Expand Up @@ -328,7 +328,7 @@ impl Pipeline {

/// A normal exit of the pipeline, which waits for the compositor,
/// and delegates layout shutdown to the script thread.
pub fn exit(&self) {
pub fn exit(&self, discard_bc: DiscardBrowsingContext) {
debug!("pipeline {:?} exiting", self.id);

// The compositor wants to know when pipelines shut down too.
Expand All @@ -345,15 +345,17 @@ impl Pipeline {

// Script thread handles shutting down layout, and layout handles shutting down the painter.
// For now, if the script thread has failed, we give up on clean shutdown.
if let Err(e) = self.event_loop.send(ConstellationControlMsg::ExitPipeline(self.id)) {
let msg = ConstellationControlMsg::ExitPipeline(self.id, discard_bc);
if let Err(e) = self.event_loop.send(msg) {
warn!("Sending script exit message failed ({}).", e);
}
}

/// A forced exit of the shutdown, which does not wait for the compositor,
/// or for the script thread to shut down layout.
pub fn force_exit(&self) {
if let Err(e) = self.event_loop.send(ConstellationControlMsg::ExitPipeline(self.id)) {
pub fn force_exit(&self, discard_bc: DiscardBrowsingContext) {
let msg = ConstellationControlMsg::ExitPipeline(self.id, discard_bc);
if let Err(e) = self.event_loop.send(msg) {
warn!("Sending script exit message failed ({}).", e);
}
if let Err(e) = self.layout_chan.send(LayoutControlMsg::ExitNow) {
Expand Down
39 changes: 8 additions & 31 deletions components/script/dom/browsingcontext.rs
Expand Up @@ -3,16 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::bindings::conversions::{ToJSValConvertible, root_from_handleobject};
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, MutNullableJS, Root, RootedReference};
use dom::bindings::js::{JS, Root, RootedReference};
use dom::bindings::proxyhandler::{fill_property_descriptor, get_property_descriptor};
use dom::bindings::reflector::{DomObject, MutDomObject, Reflector};
use dom::bindings::trace::JSTraceable;
use dom::bindings::utils::WindowProxyHandler;
use dom::bindings::utils::get_array_index_from_id;
use dom::document::Document;
use dom::element::Element;
use dom::globalscope::GlobalScope;
use dom::window::Window;
use js::JSCLASS_IS_GLOBAL;
use js::glue::{CreateWrapperProxyHandler, ProxyTraps, NewWindowProxy};
Expand All @@ -26,7 +23,6 @@ use js::jsapi::{MutableHandle, MutableHandleObject, MutableHandleValue};
use js::jsapi::{ObjectOpResult, PropertyDescriptor};
use js::jsval::{UndefinedValue, PrivateValue};
use js::rust::get_object_class;
use msg::constellation_msg::PipelineId;
use std::cell::Cell;

#[dom_struct]
Expand All @@ -40,10 +36,8 @@ pub struct BrowsingContext {
/// Indicates if reflow is required when reloading.
needs_reflow: Cell<bool>,

/// The current active document.
/// Note that the session history is stored in the constellation,
/// in the script thread we just track the current active document.
active_document: MutNullableJS<Document>,
/// Has this browsing context been discarded?
discarded: Cell<bool>,

/// The containing iframe element, if this is a same-origin iframe
frame_element: Option<JS<Element>>,
Expand All @@ -54,7 +48,7 @@ impl BrowsingContext {
BrowsingContext {
reflector: Reflector::new(),
needs_reflow: Cell::new(true),
active_document: Default::default(),
discarded: Cell::new(false),
frame_element: frame_element.map(JS::from_ref),
}
}
Expand Down Expand Up @@ -84,20 +78,12 @@ impl BrowsingContext {
}
}

pub fn set_active_document(&self, document: &Document) {
self.active_document.set(Some(document))
pub fn discard(&self) {
self.discarded.set(true);
}

pub fn active_document(&self) -> Root<Document> {
self.active_document.get().expect("No active document.")
}

pub fn maybe_active_document(&self) -> Option<Root<Document>> {
self.active_document.get()
}

pub fn active_window(&self) -> Root<Window> {
Root::from_ref(self.active_document().window())
pub fn is_discarded(&self) -> bool {
self.discarded.get()
}

pub fn frame_element(&self) -> Option<&Element> {
Expand All @@ -115,15 +101,6 @@ impl BrowsingContext {
self.needs_reflow.set(status);
old
}

pub fn active_pipeline_id(&self) -> Option<PipelineId> {
self.active_document.get()
.map(|doc| doc.window().upcast::<GlobalScope>().pipeline_id())
}

pub fn unset_active_document(&self) {
self.active_document.set(None)
}
}

#[allow(unsafe_code)]
Expand Down
53 changes: 25 additions & 28 deletions components/script/dom/document.rs
Expand Up @@ -190,6 +190,7 @@ pub struct Document {
last_modified: Option<String>,
encoding: Cell<EncodingRef>,
is_html_document: bool,
is_fully_active: Cell<bool>,
url: DOMRefCell<ServoUrl>,
quirks_mode: Cell<QuirksMode>,
/// Caches for the getElement methods
Expand Down Expand Up @@ -385,20 +386,17 @@ impl Document {
self.trigger_mozbrowser_event(MozBrowserEvent::SecurityChange(https_state));
}

// https://html.spec.whatwg.org/multipage/#active-document
pub fn is_active(&self) -> bool {
self.browsing_context().map_or(false, |context| {
self == &*context.active_document()
})
}

// https://html.spec.whatwg.org/multipage/#fully-active
pub fn is_fully_active(&self) -> bool {
if !self.is_active() {
return false;
}
// FIXME: It should also check whether the browser context is top-level or not
true
self.is_fully_active.get()
}

pub fn fully_activate(&self) {
self.is_fully_active.set(true)
}

pub fn fully_deactivate(&self) {
self.is_fully_active.set(false)
}

pub fn origin(&self) -> &Origin {
Expand Down Expand Up @@ -1693,11 +1691,16 @@ impl Document {
self.current_parser.get()
}

/// Find an iframe element in the document.
pub fn find_iframe(&self, frame_id: FrameId) -> Option<Root<HTMLIFrameElement>> {
/// Iterate over all iframes in the document.
pub fn iter_iframes(&self) -> impl Iterator<Item=Root<HTMLIFrameElement>> {
self.upcast::<Node>()
.traverse_preorder()
.filter_map(Root::downcast::<HTMLIFrameElement>)
}

/// Find an iframe element in the document.
pub fn find_iframe(&self, frame_id: FrameId) -> Option<Root<HTMLIFrameElement>> {
self.iter_iframes()
.find(|node| node.frame_id() == frame_id)
}

Expand Down Expand Up @@ -1864,6 +1867,7 @@ impl Document {
// https://dom.spec.whatwg.org/#concept-document-encoding
encoding: Cell::new(UTF_8),
is_html_document: is_html_document == IsHTMLDocument::HTMLDocument,
is_fully_active: Cell::new(false),
id_map: DOMRefCell::new(HashMap::new()),
tag_map: DOMRefCell::new(HashMap::new()),
tagns_map: DOMRefCell::new(HashMap::new()),
Expand Down Expand Up @@ -2261,19 +2265,12 @@ impl DocumentMethods for Document {

// https://html.spec.whatwg.org/multipage/#dom-document-hasfocus
fn HasFocus(&self) -> bool {
match self.browsing_context() {
Some(browsing_context) => {
// Step 2.
let candidate = browsing_context.active_document();
// Step 3.
if &*candidate == self {
true
} else {
false //TODO Step 4.
}
}
None => false,
// Step 1-2.
if self.window().parent_info().is_none() && self.is_fully_active() {
return true;
}
// TODO Step 3.
false
}

// https://html.spec.whatwg.org/multipage/#relaxing-the-same-origin-restriction
Expand Down Expand Up @@ -3190,8 +3187,8 @@ impl DocumentMethods for Document {

// Step 2.
// TODO: handle throw-on-dynamic-markup-insertion counter.

if !self.is_active() {
// FIXME: this should check for being active rather than fully active
if !self.is_fully_active() {
// Step 3.
return Ok(());
}
Expand Down

0 comments on commit 7c2de62

Please sign in to comment.