/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #![allow(unsafe_code)] use atomic_refcell::{AtomicRef, AtomicRefCell}; use data::ElementData; use dom::{LayoutIterator, NodeInfo, TElement, TNode, UnsafeNode}; use dom::{OpaqueNode, PresentationalHintsSynthetizer}; use element_state::ElementState; use error_reporting::StdoutErrorReporter; use gecko::restyle_damage::GeckoRestyleDamage; use gecko::selector_parser::{SelectorImpl, NonTSPseudoClass, PseudoElement}; use gecko::snapshot_helpers; use gecko_bindings::bindings; use gecko_bindings::bindings::{Gecko_DropStyleChildrenIterator, Gecko_MaybeCreateStyleChildrenIterator}; use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetLastChild, Gecko_GetNextStyleChild}; use gecko_bindings::bindings::{Gecko_GetServoDeclarationBlock, Gecko_IsHTMLElementInHTMLDocument}; use gecko_bindings::bindings::{Gecko_IsLink, Gecko_IsRootElement}; use gecko_bindings::bindings::{Gecko_IsUnvisitedLink, Gecko_IsVisitedLink, Gecko_Namespace}; use gecko_bindings::bindings::{RawGeckoElement, RawGeckoNode}; use gecko_bindings::bindings::Gecko_ClassOrClassList; use gecko_bindings::bindings::Gecko_GetStyleContext; use gecko_bindings::bindings::Gecko_SetNodeFlags; use gecko_bindings::bindings::Gecko_StoreStyleDifference; use gecko_bindings::structs; use gecko_bindings::structs::{NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO, NODE_IS_DIRTY_FOR_SERVO}; use gecko_bindings::structs::{nsIAtom, nsIContent, nsStyleContext}; use parking_lot::RwLock; use parser::ParserContextExtraData; use properties::{ComputedValues, parse_style_attribute}; use properties::PropertyDeclarationBlock; use selector_parser::ElementExt; use selectors::Element; use selectors::parser::{AttrSelector, NamespaceConstraint}; use servo_url::ServoUrl; use sink::Push; use std::fmt; use std::ptr; use std::sync::Arc; use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; use stylist::ApplicableDeclarationBlock; // Important: We don't currently refcount the DOM, because the wrapper lifetime // magic guarantees that our LayoutFoo references won't outlive the root, and // we don't mutate any of the references on the Gecko side during restyle. We // could implement refcounting if need be (at a potentially non-trivial // performance cost) by implementing Drop and making LayoutFoo non-Copy. #[derive(Clone, Copy)] pub struct GeckoNode<'ln>(pub &'ln RawGeckoNode); impl<'ln> GeckoNode<'ln> { fn from_content(content: &'ln nsIContent) -> Self { GeckoNode(&content._base) } fn node_info(&self) -> &structs::NodeInfo { debug_assert!(!self.0.mNodeInfo.mRawPtr.is_null()); unsafe { &*self.0.mNodeInfo.mRawPtr } } } impl<'ln> NodeInfo for GeckoNode<'ln> { fn is_element(&self) -> bool { use gecko_bindings::structs::nsINode_BooleanFlag; self.0.mBoolFlags & (1u32 << nsINode_BooleanFlag::NodeIsElement as u32) != 0 } fn is_text_node(&self) -> bool { // This is a DOM constant that isn't going to change. const TEXT_NODE: u16 = 3; self.node_info().mInner.mNodeType == TEXT_NODE } } impl<'ln> TNode for GeckoNode<'ln> { type ConcreteElement = GeckoElement<'ln>; type ConcreteChildrenIterator = GeckoChildrenIterator<'ln>; fn to_unsafe(&self) -> UnsafeNode { (self.0 as *const _ as usize, 0) } unsafe fn from_unsafe(n: &UnsafeNode) -> Self { GeckoNode(&*(n.0 as *mut RawGeckoNode)) } fn dump(self) { if self.is_text_node() { println!("Text ({:?})", &self.0 as *const _); } else { let el = self.as_element().unwrap(); println!("Element {} ({:?})", el.get_local_name(), &el.0 as *const _); } } fn dump_style(self) { unimplemented!() } fn children(self) -> LayoutIterator> { let maybe_iter = unsafe { Gecko_MaybeCreateStyleChildrenIterator(self.0) }; if let Some(iter) = maybe_iter.into_owned_opt() { LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter)) } else { LayoutIterator(GeckoChildrenIterator::Current(self.first_child())) } } fn opaque(&self) -> OpaqueNode { let ptr: usize = self.0 as *const _ as usize; OpaqueNode(ptr) } fn layout_parent_element(self, reflow_root: OpaqueNode) -> Option> { if self.opaque() == reflow_root { None } else { self.parent_node().and_then(|x| x.as_element()) } } fn debug_id(self) -> usize { unimplemented!() } fn as_element(&self) -> Option> { if self.is_element() { unsafe { Some(GeckoElement(&*(self.0 as *const _ as *const RawGeckoElement))) } } else { None } } fn can_be_fragmented(&self) -> bool { // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation // Maybe this isn’t useful for Gecko? false } unsafe fn set_can_be_fragmented(&self, _value: bool) { // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation // Maybe this isn’t useful for Gecko? } fn parent_node(&self) -> Option> { unsafe { self.0.mParent.as_ref().map(GeckoNode) } } fn first_child(&self) -> Option> { unsafe { self.0.mFirstChild.as_ref().map(GeckoNode::from_content) } } fn last_child(&self) -> Option> { unsafe { Gecko_GetLastChild(self.0).map(GeckoNode) } } fn prev_sibling(&self) -> Option> { unsafe { self.0.mPreviousSibling.as_ref().map(GeckoNode::from_content) } } fn next_sibling(&self) -> Option> { unsafe { self.0.mNextSibling.as_ref().map(GeckoNode::from_content) } } fn needs_dirty_on_viewport_size_changed(&self) -> bool { // Gecko's node doesn't have the DIRTY_ON_VIEWPORT_SIZE_CHANGE flag, // so we force them to be dirtied on viewport size change, regardless if // they use viewport percentage size or not. // TODO(shinglyu): implement this in Gecko: https://github.com/servo/servo/pull/11890 true } // TODO(shinglyu): implement this in Gecko: https://github.com/servo/servo/pull/11890 unsafe fn set_dirty_on_viewport_size_changed(&self) {} } // We generally iterate children by traversing the siblings of the first child // like Servo does. However, for nodes with anonymous children, we use a custom // (heavier-weight) Gecko-implemented iterator. pub enum GeckoChildrenIterator<'a> { Current(Option>), GeckoIterator(bindings::StyleChildrenIteratorOwned), } impl<'a> Drop for GeckoChildrenIterator<'a> { fn drop(&mut self) { if let GeckoChildrenIterator::GeckoIterator(ref it) = *self { unsafe { Gecko_DropStyleChildrenIterator(ptr::read(it as *const _)); } } } } impl<'a> Iterator for GeckoChildrenIterator<'a> { type Item = GeckoNode<'a>; fn next(&mut self) -> Option> { match *self { GeckoChildrenIterator::Current(curr) => { let next = curr.and_then(|node| node.next_sibling()); *self = GeckoChildrenIterator::Current(next); curr }, GeckoChildrenIterator::GeckoIterator(ref mut it) => unsafe { Gecko_GetNextStyleChild(it).map(GeckoNode) } } } } #[derive(Clone, Copy)] pub struct GeckoElement<'le>(pub &'le RawGeckoElement); impl<'le> fmt::Debug for GeckoElement<'le> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(write!(f, "<{}", self.get_local_name())); if let Some(id) = self.get_id() { try!(write!(f, " id={}", id)); } write!(f, ">") } } impl<'le> GeckoElement<'le> { pub fn parse_style_attribute(value: &str) -> PropertyDeclarationBlock { // FIXME(bholley): Real base URL and error reporter. let base_url = &*DUMMY_BASE_URL; // FIXME(heycam): Needs real ParserContextExtraData so that URLs parse // properly. let extra_data = ParserContextExtraData::default(); parse_style_attribute(value, &base_url, Box::new(StdoutErrorReporter), extra_data) } fn flags(&self) -> u32 { self.raw_node()._base._base_1.mFlags } fn raw_node(&self) -> &RawGeckoNode { &(self.0)._base._base._base } // FIXME: We can implement this without OOL calls, but we can't easily given // GeckoNode is a raw reference. // // We can use a Cell, but that's a bit of a pain. fn set_flags(&self, flags: u32) { unsafe { Gecko_SetNodeFlags(self.as_node().0, flags) } } pub fn clear_data(&self) { let ptr = self.raw_node().mServoData.get(); if !ptr.is_null() { let data = unsafe { Box::from_raw(self.raw_node().mServoData.get()) }; self.raw_node().mServoData.set(ptr::null_mut()); // Perform a mutable borrow of the data in debug builds. This // serves as an assertion that there are no outstanding borrows // when we destroy the data. debug_assert!({ let _ = data.borrow_mut(); true }); } } pub fn get_pseudo_style(&self, pseudo: &PseudoElement) -> Option> { self.borrow_data().and_then(|data| data.current_styles().pseudos .get(pseudo).map(|c| c.0.clone())) } pub fn ensure_data(&self) -> &AtomicRefCell { match self.get_data() { Some(x) => x, None => { let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::new()))); self.raw_node().mServoData.set(ptr); unsafe { &* ptr } }, } } } lazy_static! { pub static ref DUMMY_BASE_URL: ServoUrl = { ServoUrl::parse("http://www.example.org").unwrap() }; } impl<'le> TElement for GeckoElement<'le> { type ConcreteNode = GeckoNode<'le>; fn as_node(&self) -> Self::ConcreteNode { unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) } } fn style_attribute(&self) -> Option<&Arc>> { let declarations = unsafe { Gecko_GetServoDeclarationBlock(self.0) }; declarations.map(|s| s.as_arc_opt()).unwrap_or(None) } fn get_state(&self) -> ElementState { unsafe { ElementState::from_bits_truncate(Gecko_ElementState(self.0) as u16) } } #[inline] fn has_attr(&self, namespace: &Namespace, attr: &Atom) -> bool { unsafe { bindings::Gecko_HasAttr(self.0, namespace.0.as_ptr(), attr.as_ptr()) } } #[inline] fn attr_equals(&self, namespace: &Namespace, attr: &Atom, val: &Atom) -> bool { unsafe { bindings::Gecko_AttrEquals(self.0, namespace.0.as_ptr(), attr.as_ptr(), val.as_ptr(), /* ignoreCase = */ false) } } fn set_restyle_damage(self, damage: GeckoRestyleDamage) { // FIXME(bholley): Gecko currently relies on the dirty bit being set to // drive the post-traversal. This will go away soon. unsafe { self.set_flags(NODE_IS_DIRTY_FOR_SERVO as u32) } unsafe { Gecko_StoreStyleDifference(self.as_node().0, damage.as_change_hint()) } } fn existing_style_for_restyle_damage<'a>(&'a self, current_cv: Option<&'a Arc>, pseudo: Option<&PseudoElement>) -> Option<&'a nsStyleContext> { if current_cv.is_none() { // Don't bother in doing an ffi call to get null back. return None; } unsafe { let atom_ptr = pseudo.map(|p| p.as_atom().as_ptr()) .unwrap_or(ptr::null_mut()); let context_ptr = Gecko_GetStyleContext(self.as_node().0, atom_ptr); context_ptr.as_ref() } } fn deprecated_dirty_bit_is_set(&self) -> bool { self.flags() & (NODE_IS_DIRTY_FOR_SERVO as u32) != 0 } fn has_dirty_descendants(&self) -> bool { // Return true unconditionally if we're not yet styled. This is a hack // and should go away soon. if self.get_data().is_none() { return true; } self.flags() & (NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32) != 0 } unsafe fn set_dirty_descendants(&self) { self.set_flags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO as u32) } fn store_children_to_process(&self, _: isize) { // This is only used for bottom-up traversal, and is thus a no-op for Gecko. } fn did_process_child(&self) -> isize { panic!("Atomic child count not implemented in Gecko"); } fn borrow_data(&self) -> Option> { self.get_data().map(|x| x.borrow()) } fn get_data(&self) -> Option<&AtomicRefCell> { unsafe { self.raw_node().mServoData.get().as_ref() } } } impl<'le> PartialEq for GeckoElement<'le> { fn eq(&self, other: &Self) -> bool { self.0 as *const _ == other.0 as *const _ } } impl<'le> PresentationalHintsSynthetizer for GeckoElement<'le> { fn synthesize_presentational_hints_for_legacy_attributes(&self, _hints: &mut V) where V: Push { // FIXME(bholley) - Need to implement this. } } impl<'le> ::selectors::Element for GeckoElement<'le> { fn parent_element(&self) -> Option { let parent = self.as_node().parent_node(); parent.and_then(|parent| parent.as_element()) } fn first_child_element(&self) -> Option { let mut child = self.as_node().first_child(); while let Some(child_node) = child { if let Some(el) = child_node.as_element() { return Some(el) } child = child_node.next_sibling(); } None } fn last_child_element(&self) -> Option { let mut child = self.as_node().last_child(); while let Some(child_node) = child { if let Some(el) = child_node.as_element() { return Some(el) } child = child_node.prev_sibling(); } None } fn prev_sibling_element(&self) -> Option { let mut sibling = self.as_node().prev_sibling(); while let Some(sibling_node) = sibling { if let Some(el) = sibling_node.as_element() { return Some(el) } sibling = sibling_node.prev_sibling(); } None } fn next_sibling_element(&self) -> Option { let mut sibling = self.as_node().next_sibling(); while let Some(sibling_node) = sibling { if let Some(el) = sibling_node.as_element() { return Some(el) } sibling = sibling_node.next_sibling(); } None } fn is_root(&self) -> bool { unsafe { Gecko_IsRootElement(self.0) } } fn is_empty(&self) -> bool { // XXX(emilio): Implement this properly. false } fn get_local_name(&self) -> &WeakAtom { unsafe { WeakAtom::new(self.as_node().node_info().mInner.mName.raw()) } } fn get_namespace(&self) -> &WeakNamespace { unsafe { WeakNamespace::new(Gecko_Namespace(self.0)) } } fn match_non_ts_pseudo_class(&self, pseudo_class: NonTSPseudoClass) -> bool { match pseudo_class { // https://github.com/servo/servo/issues/8718 NonTSPseudoClass::AnyLink => unsafe { Gecko_IsLink(self.0) }, NonTSPseudoClass::Link => unsafe { Gecko_IsUnvisitedLink(self.0) }, NonTSPseudoClass::Visited => unsafe { Gecko_IsVisitedLink(self.0) }, NonTSPseudoClass::Active | NonTSPseudoClass::Focus | NonTSPseudoClass::Hover | NonTSPseudoClass::Enabled | NonTSPseudoClass::Disabled | NonTSPseudoClass::Checked | NonTSPseudoClass::ReadWrite | NonTSPseudoClass::Indeterminate => { self.get_state().contains(pseudo_class.state_flag()) }, NonTSPseudoClass::ReadOnly => { !self.get_state().contains(pseudo_class.state_flag()) } } } fn get_id(&self) -> Option { let ptr = unsafe { bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr()) }; if ptr.is_null() { None } else { Some(Atom::from(ptr)) } } fn has_class(&self, name: &Atom) -> bool { snapshot_helpers::has_class(self.0, name, Gecko_ClassOrClassList) } fn each_class(&self, callback: F) where F: FnMut(&Atom) { snapshot_helpers::each_class(self.0, callback, Gecko_ClassOrClassList) } fn is_html_element_in_html_document(&self) -> bool { unsafe { Gecko_IsHTMLElementInHTMLDocument(self.0) } } } pub trait AttrSelectorHelpers { fn ns_or_null(&self) -> *mut nsIAtom; fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom; } impl AttrSelectorHelpers for AttrSelector { fn ns_or_null(&self) -> *mut nsIAtom { match self.namespace { NamespaceConstraint::Any => ptr::null_mut(), NamespaceConstraint::Specific(ref ns) => ns.url.0.as_ptr(), } } fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom { if is_html_element_in_html_document { self.lower_name.as_ptr() } else { self.name.as_ptr() } } } impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { type Impl = SelectorImpl; fn match_attr_has(&self, attr: &AttrSelector) -> bool { unsafe { bindings::Gecko_HasAttr(self.0, attr.ns_or_null(), attr.select_name(self.is_html_element_in_html_document())) } } fn match_attr_equals(&self, attr: &AttrSelector, value: &Atom) -> bool { unsafe { bindings::Gecko_AttrEquals(self.0, attr.ns_or_null(), attr.select_name(self.is_html_element_in_html_document()), value.as_ptr(), /* ignoreCase = */ false) } } fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector, value: &Atom) -> bool { unsafe { bindings::Gecko_AttrEquals(self.0, attr.ns_or_null(), attr.select_name(self.is_html_element_in_html_document()), value.as_ptr(), /* ignoreCase = */ false) } } fn match_attr_includes(&self, attr: &AttrSelector, value: &Atom) -> bool { unsafe { bindings::Gecko_AttrIncludes(self.0, attr.ns_or_null(), attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } fn match_attr_dash(&self, attr: &AttrSelector, value: &Atom) -> bool { unsafe { bindings::Gecko_AttrDashEquals(self.0, attr.ns_or_null(), attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } fn match_attr_prefix(&self, attr: &AttrSelector, value: &Atom) -> bool { unsafe { bindings::Gecko_AttrHasPrefix(self.0, attr.ns_or_null(), attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } fn match_attr_substring(&self, attr: &AttrSelector, value: &Atom) -> bool { unsafe { bindings::Gecko_AttrHasSubstring(self.0, attr.ns_or_null(), attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } fn match_attr_suffix(&self, attr: &AttrSelector, value: &Atom) -> bool { unsafe { bindings::Gecko_AttrHasSuffix(self.0, attr.ns_or_null(), attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } } impl<'le> ElementExt for GeckoElement<'le> { #[inline] fn is_link(&self) -> bool { self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink) } }