aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/script')
-rw-r--r--src/components/script/dom/bindings/utils.rs34
-rw-r--r--src/components/script/dom/node.rs128
-rw-r--r--src/components/script/layout_interface.rs26
-rw-r--r--src/components/script/script_task.rs119
4 files changed, 236 insertions, 71 deletions
diff --git a/src/components/script/dom/bindings/utils.rs b/src/components/script/dom/bindings/utils.rs
index f79e8ae633a..b076810785a 100644
--- a/src/components/script/dom/bindings/utils.rs
+++ b/src/components/script/dom/bindings/utils.rs
@@ -863,26 +863,38 @@ pub fn CreateDOMGlobal(cx: *JSContext, class: *JSClass) -> *JSObject {
}
}
+/// Returns the global object of the realm that the given JS object was created in.
#[fixed_stack_segment]
-fn cx_for_dom_reflector(obj: *JSObject) -> *JSContext {
+fn global_object_for_js_object(obj: *JSObject) -> *Box<window::Window> {
unsafe {
let global = GetGlobalForObjectCrossCompartment(obj);
let clasp = JS_GetClass(global);
assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0);
- //XXXjdm either don't hardcode or sanity assert prototype stuff
- let win = unwrap_object::<*Box<window::Window>>(global, PrototypeList::id::Window, 1);
- match win {
- Ok(win) => {
- match (*win).data.page.js_info {
- Some(ref info) => info.js_context.ptr,
- None => fail!("no JS context for DOM global")
- }
- }
- Err(_) => fail!("found DOM global that doesn't unwrap to Window")
+ // FIXME(jdm): Either don't hardcode or sanity assert prototype stuff.
+ match unwrap_object::<*Box<window::Window>>(global, PrototypeList::id::Window, 1) {
+ Ok(win) => win,
+ Err(_) => fail!("found DOM global that doesn't unwrap to Window"),
}
}
}
+#[fixed_stack_segment]
+fn cx_for_dom_reflector(obj: *JSObject) -> *JSContext {
+ unsafe {
+ let win = global_object_for_js_object(obj);
+ match (*win).data.page.js_info {
+ Some(ref info) => info.js_context.ptr,
+ None => fail!("no JS context for DOM global")
+ }
+ }
+}
+
+/// Returns the global object of the realm that the given DOM object was created in.
+#[fixed_stack_segment]
+pub fn global_object_for_dom_object<T: Reflectable>(obj: &mut T) -> *Box<window::Window> {
+ global_object_for_js_object(obj.reflector().get_jsobject())
+}
+
pub fn cx_for_dom_object<T: Reflectable>(obj: &mut T) -> *JSContext {
cx_for_dom_reflector(obj.reflector().get_jsobject())
}
diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs
index 78533b2b48c..9709680300d 100644
--- a/src/components/script/dom/node.rs
+++ b/src/components/script/dom/node.rs
@@ -7,6 +7,7 @@
use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
use dom::bindings::utils::{DOMString, null_str_as_empty};
use dom::bindings::utils::{ErrorResult, Fallible, NotFound, HierarchyRequest};
+use dom::bindings::utils;
use dom::characterdata::CharacterData;
use dom::document::{AbstractDocument, DocumentTypeId};
use dom::documenttype::DocumentType;
@@ -18,11 +19,13 @@ use dom::htmlimageelement::HTMLImageElement;
use dom::htmliframeelement::HTMLIFrameElement;
use dom::text::Text;
-use std::cast;
-use std::cast::transmute;
-use std::unstable::raw::Box;
use js::jsapi::{JSObject, JSContext};
+use servo_util::slot::{MutSlotRef, Slot, SlotRef};
use servo_util::tree::{TreeNode, TreeNodeRef, TreeNodeRefAsElement};
+use std::cast::transmute;
+use std::cast;
+use std::unstable::raw::Box;
+use std::util;
//
// The basic Node structure
@@ -89,9 +92,87 @@ pub struct Node<View> {
child_list: Option<@mut NodeList>,
/// Layout information. Only the layout task may touch this data.
- layout_data: Option<~Any>,
+ ///
+ /// FIXME(pcwalton): We need to send these back to the layout task to be destroyed when this
+ /// node is finalized.
+ layout_data: LayoutDataRef,
+}
+
+#[unsafe_destructor]
+impl<T> Drop for Node<T> {
+ fn drop(&mut self) {
+ unsafe {
+ let this: &mut Node<ScriptView> = cast::transmute(self);
+ this.reap_layout_data()
+ }
+ }
+}
+
+/// Encapsulates the abstract layout data.
+pub struct LayoutDataRef {
+ priv data: Slot<Option<*()>>,
+}
+
+impl LayoutDataRef {
+ #[inline]
+ pub fn init() -> LayoutDataRef {
+ LayoutDataRef {
+ data: Slot::init(None),
+ }
+ }
+
+ /// Creates a new piece of layout data from a value.
+ #[inline]
+ pub unsafe fn from_data<T>(data: ~T) -> LayoutDataRef {
+ LayoutDataRef {
+ data: Slot::init(Some(cast::transmute(data))),
+ }
+ }
+
+ /// Returns true if this layout data is present or false otherwise.
+ #[inline]
+ pub fn is_present(&self) -> bool {
+ self.data.get().is_some()
+ }
+
+ /// Borrows the layout data immutably, *asserting that there are no mutators*. Bad things will
+ /// happen if you try to mutate the layout data while this is held. This is the only thread-
+ /// safe layout data accessor.
+ ///
+ /// FIXME(pcwalton): Enforce this invariant via the type system. Will require traversal
+ /// functions to be trusted, but c'est la vie.
+ #[inline]
+ pub unsafe fn borrow_unchecked<'a>(&'a self) -> &'a () {
+ cast::transmute(self.data.borrow_unchecked())
+ }
+
+ /// Borrows the layout data immutably. This function is *not* thread-safe.
+ #[inline]
+ pub fn borrow<'a>(&'a self) -> SlotRef<'a,()> {
+ unsafe {
+ cast::transmute(self.data.borrow())
+ }
+ }
+
+ /// Borrows the layout data mutably. This function is *not* thread-safe.
+ ///
+ /// FIXME(pcwalton): We should really put this behind a `MutLayoutView` phantom type, to
+ /// prevent CSS selector matching from mutably accessing nodes it's not supposed to and racing
+ /// on it. This has already resulted in one bug!
+ #[inline]
+ pub fn mutate<'a>(&'a self) -> MutSlotRef<'a,()> {
+ unsafe {
+ cast::transmute(self.data.mutate())
+ }
+ }
}
+/// A trait that represents abstract layout data.
+///
+/// FIXME(pcwalton): Very very unsafe!!! We need to send these back to the layout task to be
+/// destroyed when this node is finalized.
+pub trait TLayoutData {}
+
/// The different types of nodes.
#[deriving(Eq)]
pub enum NodeTypeId {
@@ -319,12 +400,24 @@ impl<'self, View> AbstractNode<View> {
self.transmute_mut(f)
}
+ #[inline]
pub fn is_comment(self) -> bool {
- self.type_id() == CommentNodeTypeId
+ // FIXME(pcwalton): Temporary workaround for the lack of inlining of autogenerated `Eq`
+ // implementations in Rust.
+ match self.type_id() {
+ CommentNodeTypeId => true,
+ _ => false,
+ }
}
+ #[inline]
pub fn is_text(self) -> bool {
- self.type_id() == TextNodeTypeId
+ // FIXME(pcwalton): Temporary workaround for the lack of inlining of autogenerated `Eq`
+ // implementations in Rust.
+ match self.type_id() {
+ TextNodeTypeId => true,
+ _ => false,
+ }
}
pub fn with_imm_text<R>(self, f: &fn(&Text) -> R) -> R {
@@ -364,8 +457,12 @@ impl<'self, View> AbstractNode<View> {
self.transmute_mut(f)
}
+ #[inline]
pub fn is_image_element(self) -> bool {
- self.type_id() == ElementNodeTypeId(HTMLImageElementTypeId)
+ match self.type_id() {
+ ElementNodeTypeId(HTMLImageElementTypeId) => true,
+ _ => false,
+ }
}
pub fn with_imm_image_element<R>(self, f: &fn(&HTMLImageElement) -> R) -> R {
@@ -543,7 +640,16 @@ impl Node<ScriptView> {
owner_doc: doc,
child_list: None,
- layout_data: None,
+ layout_data: LayoutDataRef::init(),
+ }
+ }
+
+ /// Sends layout data, if any, back to the script task to be destroyed.
+ pub unsafe fn reap_layout_data(&mut self) {
+ if self.layout_data.is_present() {
+ let layout_data = util::replace(&mut self.layout_data, LayoutDataRef::init());
+ let js_window = utils::global_object_for_dom_object(self);
+ (*js_window).data.page.reap_dead_layout_data(layout_data)
}
}
}
@@ -1095,12 +1201,12 @@ impl Reflectable for Node<ScriptView> {
/// A bottom-up, parallelizable traversal.
pub trait PostorderNodeTraversal {
/// The operation to perform. Return true to continue or false to stop.
- fn process(&mut self, node: AbstractNode<LayoutView>) -> bool;
+ fn process(&self, node: AbstractNode<LayoutView>) -> bool;
/// Returns true if this node should be pruned. If this returns true, we skip the operation
/// entirely and do not process any descendant nodes. This is called *before* child nodes are
/// visited. The default implementation never prunes any nodes.
- fn should_prune(&mut self, _node: AbstractNode<LayoutView>) -> bool {
+ fn should_prune(&self, _node: AbstractNode<LayoutView>) -> bool {
false
}
}
@@ -1109,7 +1215,7 @@ impl AbstractNode<LayoutView> {
/// Traverses the tree in postorder.
///
/// TODO(pcwalton): Offer a parallel version with a compatible API.
- pub fn traverse_postorder<T:PostorderNodeTraversal>(self, traversal: &mut T) -> bool {
+ pub fn traverse_postorder<T:PostorderNodeTraversal>(self, traversal: &T) -> bool {
if traversal.should_prune(self) {
return true
}
diff --git a/src/components/script/layout_interface.rs b/src/components/script/layout_interface.rs
index 348594ca162..3d9cc5e96e2 100644
--- a/src/components/script/layout_interface.rs
+++ b/src/components/script/layout_interface.rs
@@ -6,15 +6,16 @@
/// coupling between these two components, and enables the DOM to be placed in a separate crate
/// from layout.
-use dom::node::{AbstractNode, ScriptView, LayoutView};
-use script_task::{ScriptChan};
-use std::comm::{Chan, SharedChan};
+use dom::node::{AbstractNode, LayoutDataRef, LayoutView, ScriptView};
+
+use extra::url::Url;
+use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
-use geom::point::Point2D;
+use script_task::{ScriptChan};
use servo_util::geometry::Au;
+use std::comm::{Chan, SharedChan};
use style::Stylesheet;
-use extra::url::Url;
/// Asynchronous messages that script can send to layout.
///
@@ -31,8 +32,19 @@ pub enum Msg {
/// FIXME(pcwalton): As noted below, this isn't very type safe.
QueryMsg(LayoutQuery),
- /// Requests that the layout task shut down and exit.
- ExitMsg,
+ /// Destroys layout data associated with a DOM node.
+ ///
+ /// TODO(pcwalton): Maybe think about batching to avoid message traffic.
+ ReapLayoutDataMsg(LayoutDataRef),
+
+ /// Requests that the layout task enter a quiescent state in which no more messages are
+ /// accepted except `ExitMsg`. A response message will be sent on the supplied channel when
+ /// this happens.
+ PrepareToExitMsg(Chan<()>),
+
+ /// Requests that the layout task immediately shut down. There must be no more nodes left after
+ /// this, or layout will crash.
+ ExitNowMsg,
}
/// Synchronous messages that script can send to layout.
diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs
index 515dd1b0338..438f5429f06 100644
--- a/src/components/script/script_task.rs
+++ b/src/components/script/script_task.rs
@@ -2,11 +2,9 @@
* 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/. */
-/// The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing
-/// and layout tasks.
+//! The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing
+//! and layout tasks.
-use servo_msg::compositor_msg::{ScriptListener, Loading, PerformingLayout};
-use servo_msg::compositor_msg::FinishedLoading;
use dom::bindings::codegen::RegisterBindings;
use dom::bindings::utils::{Reflectable, GlobalStaticData};
use dom::document::AbstractDocument;
@@ -15,29 +13,20 @@ use dom::event::{Event_, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent, M
use dom::event::Event;
use dom::eventtarget::AbstractEventTarget;
use dom::htmldocument::HTMLDocument;
-use dom::window::Window;
+use dom::node::{AbstractNode, LayoutDataRef};
+use dom::window::{TimerData, Window};
+use html::hubbub_html_parser::HtmlParserResult;
+use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredIFrame, HtmlDiscoveredScript};
+use html::hubbub_html_parser;
use layout_interface::{AddStylesheetMsg, DocumentDamage};
use layout_interface::{DocumentDamageLevel, HitTestQuery, HitTestResponse, LayoutQuery};
-use layout_interface::{LayoutChan, MatchSelectorsDocumentDamage, QueryMsg, Reflow};
-use layout_interface::{ReflowDocumentDamage, ReflowForDisplay, ReflowGoal};
-use layout_interface::ReflowMsg;
+use layout_interface::{LayoutChan, MatchSelectorsDocumentDamage, QueryMsg, ReapLayoutDataMsg};
+use layout_interface::{Reflow, ReflowDocumentDamage, ReflowForDisplay, ReflowGoal, ReflowMsg};
use layout_interface;
-use servo_msg::constellation_msg::{ConstellationChan, LoadUrlMsg, NavigationDirection};
-use servo_msg::constellation_msg::{PipelineId, SubpageId};
-use servo_msg::constellation_msg::{LoadIframeUrlMsg, IFrameSandboxed, IFrameUnsandboxed};
-use servo_msg::constellation_msg;
-use std::cell::Cell;
-use std::comm;
-use std::comm::{Port, SharedChan};
-use std::ptr;
-use std::task::{spawn_sched, SingleThreaded};
-use std::util::replace;
-use dom::window::TimerData;
+use extra::future::Future;
+use extra::url::Url;
use geom::size::Size2D;
-use html::hubbub_html_parser::HtmlParserResult;
-use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredIFrame, HtmlDiscoveredScript};
-use html::hubbub_html_parser;
use js::JSVAL_NULL;
use js::global::debug_fns;
use js::glue::RUST_JSVAL_TO_OBJECT;
@@ -45,12 +34,21 @@ use js::jsapi::{JSContext, JSObject};
use js::jsapi::{JS_CallFunctionValue, JS_GetContextPrivate};
use js::rust::{Compartment, Cx};
use js;
+use servo_msg::compositor_msg::{FinishedLoading, Loading, PerformingLayout, ScriptListener};
+use servo_msg::constellation_msg::{ConstellationChan, IFrameSandboxed, IFrameUnsandboxed};
+use servo_msg::constellation_msg::{LoadIframeUrlMsg, LoadUrlMsg, NavigationDirection, PipelineId};
+use servo_msg::constellation_msg::{SubpageId};
+use servo_msg::constellation_msg;
use servo_net::image_cache_task::ImageCacheTask;
use servo_net::resource_task::ResourceTask;
-use servo_util::tree::{TreeNodeRef, ElementLike};
+use servo_util::tree::{TreeNode, TreeNodeRef, ElementLike};
use servo_util::url::make_url;
-use extra::url::Url;
-use extra::future::Future;
+use std::cell::Cell;
+use std::comm::{Port, SharedChan};
+use std::comm;
+use std::ptr;
+use std::task::{spawn_sched, SingleThreaded};
+use std::util::replace;
/// Messages used to control the script task.
pub enum ScriptMsg {
@@ -86,6 +84,7 @@ pub struct NewLayoutInfo {
/// Encapsulates external communication with the script task.
#[deriving(Clone)]
pub struct ScriptChan(SharedChan<ScriptMsg>);
+
impl ScriptChan {
/// Creates a new script chan.
pub fn new(chan: Chan<ScriptMsg>) -> ScriptChan {
@@ -93,7 +92,7 @@ impl ScriptChan {
}
}
-/// Encapsulates a handle to a frame and its associate layout information
+/// Encapsulates a handle to a frame and its associated layout information.
pub struct Page {
/// Pipeline id associated with this page.
id: PipelineId,
@@ -216,8 +215,7 @@ impl<'self> Iterator<@mut Page> for PageTreeIterator<'self> {
impl Page {
/// Adds the given damage.
fn damage(&mut self, level: DocumentDamageLevel) {
- let root = self.frame.get_ref().document.document().
- GetDocumentElement();
+ let root = self.frame.get_ref().document.document().GetDocumentElement();
match root {
None => {},
Some(root) => {
@@ -359,13 +357,18 @@ impl Page {
});
}
+ /// Sends the given layout data back to the layout task to be destroyed.
+ pub unsafe fn reap_dead_layout_data(&self, layout_data: LayoutDataRef) {
+ self.layout_chan.send(ReapLayoutDataMsg(layout_data))
+ }
}
/// Information for one frame in the browsing context.
pub struct Frame {
+ /// The document for this frame.
document: AbstractDocument,
+ /// The window object for this frame.
window: @mut Window,
-
}
/// Encapsulation of the javascript information associated with each frame.
@@ -452,15 +455,16 @@ impl ScriptTask {
}
}
- pub fn create<C: ScriptListener + Send>(id: PipelineId,
- compositor: C,
- layout_chan: LayoutChan,
- port: Port<ScriptMsg>,
- chan: ScriptChan,
- constellation_chan: ConstellationChan,
- resource_task: ResourceTask,
- image_cache_task: ImageCacheTask,
- initial_size: Future<Size2D<uint>>) {
+ pub fn create<C:ScriptListener + Send>(
+ id: PipelineId,
+ compositor: C,
+ layout_chan: LayoutChan,
+ port: Port<ScriptMsg>,
+ chan: ScriptChan,
+ constellation_chan: ConstellationChan,
+ resource_task: ResourceTask,
+ image_cache_task: ImageCacheTask,
+ initial_size: Future<Size2D<uint>>) {
let parms = Cell::new((compositor, layout_chan, port, chan, constellation_chan,
resource_task, image_cache_task, initial_size));
// Since SpiderMonkey is blocking it needs to run in its own thread.
@@ -627,23 +631,23 @@ impl ScriptTask {
true
}
+
/// Handles a request to exit the script task and shut down layout.
/// Returns true if the script task should shut down and false otherwise.
fn handle_exit_pipeline_msg(&mut self, id: PipelineId) -> bool {
// If root is being exited, shut down all pages
if self.page_tree.page.id == id {
for page in self.page_tree.iter() {
- page.join_layout();
- page.layout_chan.send(layout_interface::ExitMsg);
+ shut_down_layout(page)
}
return true
}
+
// otherwise find just the matching page and exit all sub-pages
match self.page_tree.remove(id) {
Some(ref mut page_tree) => {
for page in page_tree.iter() {
- page.join_layout();
- page.layout_chan.send(layout_interface::ExitMsg);
+ shut_down_layout(page)
}
false
}
@@ -862,3 +866,34 @@ impl ScriptTask {
}
}
+/// Shuts down layout for the given page.
+fn shut_down_layout(page: @mut Page) {
+ page.join_layout();
+
+ // Tell the layout task to begin shutting down.
+ let (response_port, response_chan) = comm::stream();
+ page.layout_chan.send(layout_interface::PrepareToExitMsg(response_chan));
+ response_port.recv();
+
+ // Destroy all nodes.
+ //
+ // If there was a leak, the layout task will soon crash safely when it detects that local data
+ // is missing from its heap.
+ //
+ // FIXME(pcwalton): *But*, for now, because we use `@mut` boxes to hold onto Nodes, if this
+ // didn't destroy all the nodes there will be an *exploitable* security vulnerability as the
+ // nodes try to access the destroyed JS context. We need to change this so that the only actor
+ // who can judge a JS object dead (and thus run its drop glue) is the JS engine itself; thus it
+ // will be impossible (absent a serious flaw in the JS engine) for the JS context to be dead
+ // before nodes are.
+ unsafe {
+ let document_node = AbstractNode::from_document(page.frame.as_ref().unwrap().document);
+ for node in document_node.traverse_preorder() {
+ node.mut_node().reap_layout_data()
+ }
+ }
+
+ // Destroy the layout task. If there were node leaks, layout will now crash safely.
+ page.layout_chan.send(layout_interface::ExitNowMsg);
+}
+