diff options
Diffstat (limited to 'src/components/script')
-rw-r--r-- | src/components/script/dom/bindings/utils.rs | 34 | ||||
-rw-r--r-- | src/components/script/dom/node.rs | 128 | ||||
-rw-r--r-- | src/components/script/layout_interface.rs | 26 | ||||
-rw-r--r-- | src/components/script/script_task.rs | 119 |
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); +} + |