diff options
author | David Zbarsky <dzbarsky@gmail.com> | 2015-07-27 23:05:18 -0400 |
---|---|---|
committer | David Zbarsky <dzbarsky@gmail.com> | 2015-07-29 20:17:50 -0400 |
commit | e484d6b5e3f378a33fabf5ff64cb9a7f76f3ba68 (patch) | |
tree | 49235a86cc3949b27d2262c8bfb11af5c964a249 | |
parent | 416931f4be43826d3b2a96905c22f626c88b603c (diff) | |
download | servo-e484d6b5e3f378a33fabf5ff64cb9a7f76f3ba68.tar.gz servo-e484d6b5e3f378a33fabf5ff64cb9a7f76f3ba68.zip |
Implement getComputedStyle
24 files changed, 886 insertions, 152 deletions
diff --git a/components/layout/block.rs b/components/layout/block.rs index 91166c6cdc2..c7304865ecb 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -618,7 +618,7 @@ impl BlockFlow { pub fn transform_requires_layer(&self) -> bool { // Check if the transform matrix is 2D or 3D - if let Some(ref transform_list) = self.fragment.style().get_effects().transform { + if let Some(ref transform_list) = self.fragment.style().get_effects().transform.0 { for transform in transform_list { match transform { &transform::ComputedOperation::Perspective(..) => { diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 7e53bfee798..89e88847a22 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -1117,7 +1117,7 @@ impl<'a> FlowConstructor<'a> { fn build_flow_for_list_item(&mut self, node: &ThreadSafeLayoutNode, flotation: float::T) -> ConstructionResult { let flotation = FloatKind::from_property(flotation); - let marker_fragment = match node.style().get_list().list_style_image { + let marker_fragment = match node.style().get_list().list_style_image.0 { Some(ref url) => { let image_info = box ImageFragmentInfo::new(node, Some((*url).clone()), diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index c63e7e1f5ae..9569947da13 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -361,7 +361,7 @@ impl FragmentDisplayListBuilding for Fragment { // Implements background image, per spec: // http://www.w3.org/TR/CSS21/colors.html#background let background = style.get_background(); - match background.background_image { + match background.background_image.0 { None => {} Some(computed::Image::LinearGradient(ref gradient)) => { self.build_display_list_for_background_linear_gradient(display_list, @@ -668,7 +668,7 @@ impl FragmentDisplayListBuilding for Fragment { absolute_bounds: &Rect<Au>, clip: &ClippingRegion) { // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back). - for box_shadow in style.get_effects().box_shadow.iter().rev() { + for box_shadow in style.get_effects().box_shadow.0.iter().rev() { let bounds = shadow_bounds(&absolute_bounds.translate(&Point2D::new(box_shadow.offset_x, box_shadow.offset_y)), box_shadow.blur_radius, @@ -863,7 +863,7 @@ impl FragmentDisplayListBuilding for Fragment { -> ClippingRegion { // Account for `clip` per CSS 2.1 § 11.1.2. let style_clip_rect = match (self.style().get_box().position, - self.style().get_effects().clip) { + self.style().get_effects().clip.0) { (position::T::absolute, Some(style_clip_rect)) => style_clip_rect, _ => return (*parent_clip).clone(), }; @@ -1147,7 +1147,7 @@ impl FragmentDisplayListBuilding for Fragment { let mut transform = Matrix4::identity(); - if let Some(ref operations) = self.style().get_effects().transform { + if let Some(ref operations) = self.style().get_effects().transform.0 { let transform_origin = self.style().get_effects().transform_origin; let transform_origin = Point3D::new(model::specified(transform_origin.horizontal, diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index edc4e0fb66a..721d7ea1573 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -1989,7 +1989,7 @@ impl Fragment { if self.style().get_effects().mix_blend_mode != mix_blend_mode::T::normal { return true } - if self.style().get_effects().transform.is_some() { + if self.style().get_effects().transform.0.is_some() { return true } match self.style().get_used_transform_style() { @@ -2036,7 +2036,7 @@ impl Fragment { let mut overflow = border_box; // Box shadows cause us to draw outside our border box. - for box_shadow in self.style().get_effects().box_shadow.iter() { + for box_shadow in self.style().get_effects().box_shadow.0.iter() { let offset = Point2D::new(box_shadow.offset_x, box_shadow.offset_y); let inflation = box_shadow.spread_radius + box_shadow.blur_radius * BLUR_INFLATION_FACTOR; diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index dec05eef49f..363e35cc72e 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -10,6 +10,7 @@ use animation; use construct::ConstructionResult; use context::{SharedLayoutContext, heap_size_of_local_context}; +use cssparser::ToCss; use data::LayoutDataWrapper; use display_list_builder::ToGfxColor; use flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils}; @@ -55,9 +56,10 @@ use script::dom::node::{LayoutData, Node}; use script::layout_interface::{Animation, ContentBoxResponse, ContentBoxesResponse, NodeGeometryResponse}; use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC, MouseOverResponse}; use script::layout_interface::{NewLayoutTaskInfo, Msg, Reflow, ReflowGoal, ReflowQueryType}; -use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress}; +use script::layout_interface::{ResolvedStyleResponse, ScriptLayoutChan, ScriptReflow, TrustedNodeAddress}; use script_traits::{ConstellationControlMsg, LayoutControlMsg, OpaqueScriptLayoutChannel}; use script_traits::{ScriptControlChan, StylesheetLoadResponder}; +use selectors::parser::PseudoElement; use serde::json; use std::borrow::ToOwned; use std::cell::Cell; @@ -67,20 +69,23 @@ use std::mem::transmute; use std::ops::{Deref, DerefMut}; use std::sync::mpsc::{channel, Sender, Receiver, Select}; use std::sync::{Arc, Mutex, MutexGuard}; +use string_cache::Atom; use style::computed_values::{filter, mix_blend_mode}; use style::media_queries::{MediaType, MediaQueryList, Device}; use style::properties::style_structs; +use style::properties::longhands::{display, position}; use style::selector_matching::Stylist; use style::stylesheets::{Origin, Stylesheet, CSSRuleIteratorExt}; use url::Url; use util::cursor::Cursor; -use util::geometry::{Au, MAX_RECT}; -use util::logical_geometry::LogicalPoint; +use util::geometry::{Au, MAX_RECT, ZERO_POINT}; +use util::logical_geometry::{LogicalPoint, WritingMode}; use util::mem::HeapSizeOf; use util::opts; use util::task::spawn_named_with_send_on_failure; use util::task_state; use util::workqueue::WorkQueue; +use wrapper::ThreadSafeLayoutNode; /// The number of screens of data we're allowed to generate display lists for in each direction. pub const DISPLAY_PORT_SIZE_FACTOR: i32 = 8; @@ -129,6 +134,9 @@ pub struct LayoutTaskData { /// A queued response for the client {top, left, width, height} of a node in pixels. pub client_rect_response: Rect<i32>, + /// A queued response for the resolved style property of an element. + pub resolved_style_response: Option<String>, + /// The list of currently-running animations. pub running_animations: Vec<Animation>, @@ -372,6 +380,7 @@ impl LayoutTask { content_box_response: Rect::zero(), content_boxes_response: Vec::new(), client_rect_response: Rect::zero(), + resolved_style_response: None, running_animations: Vec::new(), visible_rects: Arc::new(HashMap::with_hash_state(Default::default())), new_animations_receiver: new_animations_receiver, @@ -864,6 +873,127 @@ impl LayoutTask { rw_data.client_rect_response = iterator.client_rect; } + // Compute the resolved value of property for a given (pseudo)element. + // Stores the result in rw_data.resolved_style_response. + // https://drafts.csswg.org/cssom/#resolved-value + fn process_resolved_style_request<'a>(&'a self, + requested_node: TrustedNodeAddress, + pseudo: &Option<PseudoElement>, + property: &Atom, + layout_root: &mut FlowRef, + rw_data: &mut RWGuard<'a>) { + // FIXME: Isolate this transmutation into a "bridge" module. + // FIXME(rust#16366): The following line had to be moved because of a + // rustc bug. It should be in the next unsafe block. + let node: LayoutJS<Node> = unsafe { + LayoutJS::from_trusted_node_address(requested_node) + }; + let node: &LayoutNode = unsafe { + transmute(&node) + }; + + let layout_node = ThreadSafeLayoutNode::new(node); + let layout_node = match pseudo { + &Some(PseudoElement::Before) => layout_node.get_before_pseudo(), + &Some(PseudoElement::After) => layout_node.get_after_pseudo(), + _ => Some(layout_node) + }; + + let layout_node = match layout_node { + None => { + // The pseudo doesn't exist, return nothing. Chrome seems to query + // the element itself in this case, Firefox uses the resolved value. + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29006 + rw_data.resolved_style_response = None; + return; + } + Some(layout_node) => layout_node + }; + + let style = &*layout_node.style(); + + let positioned = match style.get_box().position { + position::computed_value::T::relative | + /*position::computed_value::T::sticky |*/ + position::computed_value::T::fixed | + position::computed_value::T::absolute => true, + _ => false + }; + + //TODO: determine whether requested property applies to the element. + // eg. width does not apply to non-replaced inline elements. + // Existing browsers disagree about when left/top/right/bottom apply + // (Chrome seems to think they never apply and always returns resolved values). + // There are probably other quirks. + let applies = true; + + // TODO: we will return neither the computed nor used value for margin and padding. + // Firefox returns blank strings for the computed value of shorthands, + // so this should be web-compatible. + match property.clone() { + atom!("margin-bottom") | atom!("margin-top") | + atom!("margin-left") | atom!("margin-right") | + atom!("padding-bottom") | atom!("padding-top") | + atom!("padding-left") | atom!("padding-right") + if applies && style.get_box().display != display::computed_value::T::none => { + let (margin_padding, side) = match *property { + atom!("margin-bottom") => (MarginPadding::Margin, Side::Bottom), + atom!("margin-top") => (MarginPadding::Margin, Side::Top), + atom!("margin-left") => (MarginPadding::Margin, Side::Left), + atom!("margin-right") => (MarginPadding::Margin, Side::Right), + atom!("padding-bottom") => (MarginPadding::Padding, Side::Bottom), + atom!("padding-top") => (MarginPadding::Padding, Side::Top), + atom!("padding-left") => (MarginPadding::Padding, Side::Left), + atom!("padding-right") => (MarginPadding::Padding, Side::Right), + _ => unreachable!() + }; + let requested_node: OpaqueNode = OpaqueNodeMethods::from_script_node(requested_node); + let mut iterator = + MarginRetrievingFragmentBorderBoxIterator::new(requested_node, + side, + margin_padding, + style.writing_mode); + sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); + rw_data.resolved_style_response = iterator.result.map(|r| r.to_css_string()); + }, + + atom!("bottom") | atom!("top") | atom!("right") | + atom!("left") | atom!("width") | atom!("height") + if applies && positioned && style.get_box().display != display::computed_value::T::none => { + let layout_data = layout_node.borrow_layout_data(); + let position = layout_data.as_ref().map(|layout_data| { + match layout_data.data.flow_construction_result { + ConstructionResult::Flow(ref flow_ref, _) => + flow::base(flow_ref.deref()).stacking_relative_position, + // TODO search parents until we find node with a flow ref. + _ => ZERO_POINT + } + }).unwrap_or(ZERO_POINT); + let property = match *property { + atom!("bottom") => PositionProperty::Bottom, + atom!("top") => PositionProperty::Top, + atom!("left") => PositionProperty::Left, + atom!("right") => PositionProperty::Right, + atom!("width") => PositionProperty::Width, + atom!("height") => PositionProperty::Height, + _ => unreachable!() + }; + let requested_node: OpaqueNode = OpaqueNodeMethods::from_script_node(requested_node); + let mut iterator = + PositionRetrievingFragmentBorderBoxIterator::new(requested_node, + property, + position); + sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); + rw_data.resolved_style_response = iterator.result.map(|r| r.to_css_string()); + }, + // FIXME: implement used value computation for line-height + property => { + rw_data.resolved_style_response = style.computed_value_to_string(property.as_slice()); + } + }; + } + + fn compute_abs_pos_and_build_display_list<'a>(&'a self, data: &Reflow, layout_root: &mut FlowRef, @@ -1052,15 +1182,14 @@ impl LayoutTask { let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone(); match data.query_type { - ReflowQueryType::ContentBoxQuery(node) => { - self.process_content_box_request(node, &mut root_flow, &mut rw_data) - } - ReflowQueryType::ContentBoxesQuery(node) => { - self.process_content_boxes_request(node, &mut root_flow, &mut rw_data) - } - ReflowQueryType::NodeGeometryQuery(node) => { - self.process_node_geometry_request(node, &mut root_flow, &mut rw_data) - } + ReflowQueryType::ContentBoxQuery(node) => + self.process_content_box_request(node, &mut root_flow, &mut rw_data), + ReflowQueryType::ContentBoxesQuery(node) => + self.process_content_boxes_request(node, &mut root_flow, &mut rw_data), + ReflowQueryType::NodeGeometryQuery(node) => + self.process_node_geometry_request(node, &mut root_flow, &mut rw_data), + ReflowQueryType::ResolvedStyleQuery(node, ref pseudo, ref property) => + self.process_resolved_style_request(node, pseudo, property, &mut root_flow, &mut rw_data), ReflowQueryType::NoQuery => {} } @@ -1308,6 +1437,13 @@ impl LayoutRPC for LayoutRPCImpl { } } + /// Retrieves the resolved value for a CSS style property. + fn resolved_style(&self) -> ResolvedStyleResponse { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + ResolvedStyleResponse(rw_data.resolved_style_response.clone()) + } + /// Requests the node containing the point of interest. fn hit_test(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()> { let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y)); @@ -1459,6 +1595,108 @@ impl FragmentBorderBoxIterator for FragmentLocatingFragmentIterator { } } +enum Side { + Left, + Right, + Bottom, + Top +} + +enum MarginPadding { + Margin, + Padding +} + +enum PositionProperty { + Left, + Right, + Top, + Bottom, + Width, + Height, +} + +struct PositionRetrievingFragmentBorderBoxIterator { + node_address: OpaqueNode, + result: Option<Au>, + position: Point2D<Au>, + property: PositionProperty, +} + +impl PositionRetrievingFragmentBorderBoxIterator { + fn new(node_address: OpaqueNode, + property: PositionProperty, + position: Point2D<Au>) -> PositionRetrievingFragmentBorderBoxIterator { + PositionRetrievingFragmentBorderBoxIterator { + node_address: node_address, + position: position, + property: property, + result: None, + } + } +} + +impl FragmentBorderBoxIterator for PositionRetrievingFragmentBorderBoxIterator { + fn process(&mut self, _: &Fragment, border_box: &Rect<Au>) { + self.result = + Some(match self.property { + PositionProperty::Left => self.position.x, + PositionProperty::Top => self.position.y, + PositionProperty::Width => border_box.size.width, + PositionProperty::Height => border_box.size.height, + // TODO: the following 2 calculations are completely wrong. + // They should return the difference between the parent's and this + // fragment's border boxes. + PositionProperty::Right => border_box.max_x() + self.position.x, + PositionProperty::Bottom => border_box.max_y() + self.position.y, + }); + } + + fn should_process(&mut self, fragment: &Fragment) -> bool { + fragment.contains_node(self.node_address) + } +} + +struct MarginRetrievingFragmentBorderBoxIterator { + node_address: OpaqueNode, + result: Option<Au>, + writing_mode: WritingMode, + margin_padding: MarginPadding, + side: Side, +} + +impl MarginRetrievingFragmentBorderBoxIterator { + fn new(node_address: OpaqueNode, side: Side, margin_padding: + MarginPadding, writing_mode: WritingMode) -> MarginRetrievingFragmentBorderBoxIterator { + MarginRetrievingFragmentBorderBoxIterator { + node_address: node_address, + side: side, + margin_padding: margin_padding, + result: None, + writing_mode: writing_mode, + } + } +} + +impl FragmentBorderBoxIterator for MarginRetrievingFragmentBorderBoxIterator { + fn process(&mut self, fragment: &Fragment, _: &Rect<Au>) { + let rect = match self.margin_padding { + MarginPadding::Margin => &fragment.margin, + MarginPadding::Padding => &fragment.border_padding + }; + self.result = Some(match self.side { + Side::Left => rect.left(self.writing_mode), + Side::Right => rect.right(self.writing_mode), + Side::Bottom => rect.bottom(self.writing_mode), + Side::Top => rect.top(self.writing_mode) + }); + } + + fn should_process(&mut self, fragment: &Fragment) -> bool { + fragment.contains_node(self.node_address) + } +} + // The default computed value for background-color is transparent (see // http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we // need to propagate the background color from the root HTML/Body diff --git a/components/layout/text.rs b/components/layout/text.rs index 8ff70d70a07..88756a4f51d 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -167,8 +167,8 @@ impl TextRunScanner { white_space::T::pre => CompressionMode::CompressNone, }; text_transform = inherited_text_style.text_transform; - letter_spacing = inherited_text_style.letter_spacing; - word_spacing = inherited_text_style.word_spacing.unwrap_or(Au(0)); + letter_spacing = inherited_text_style.letter_spacing.0; + word_spacing = inherited_text_style.word_spacing.0.unwrap_or(Au(0)); text_rendering = inherited_text_style.text_rendering; } diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index ad8da0e41b0..741921af652 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -63,6 +63,7 @@ use msg::constellation_msg::ConstellationChan; use net_traits::image::base::Image; use profile_traits::mem::ProfilerChan; use util::str::{LengthOrPercentageOrAuto}; +use selectors::parser::PseudoElement; use serde::{Deserialize, Serialize}; use std::cell::{Cell, UnsafeCell, RefCell}; use std::collections::{HashMap, HashSet}; @@ -304,6 +305,7 @@ no_jsmanaged_fields!(LineCapStyle, LineJoinStyle, CompositionOrBlending); no_jsmanaged_fields!(RepetitionStyle); no_jsmanaged_fields!(WebGLError); no_jsmanaged_fields!(ProfilerChan); +no_jsmanaged_fields!(PseudoElement); impl JSTraceable for Box<ScriptChan+Send> { #[inline] diff --git a/components/script/dom/cssstyledeclaration.rs b/components/script/dom/cssstyledeclaration.rs index d3c1af656d4..99e70f9c387 100644 --- a/components/script/dom/cssstyledeclaration.rs +++ b/components/script/dom/cssstyledeclaration.rs @@ -11,9 +11,10 @@ use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::document::DocumentHelpers; use dom::element::{ElementHelpers, StylePriority}; use dom::htmlelement::HTMLElement; -use dom::node::{window_from_node, document_from_node, NodeDamage}; +use dom::node::{window_from_node, document_from_node, NodeDamage, NodeHelpers}; use dom::window::{Window, WindowHelpers}; use util::str::DOMString; +use selectors::parser::PseudoElement; use string_cache::Atom; use style::properties::{is_supported_property, longhands_from_shorthand, parse_style_attribute}; use style::properties::PropertyDeclaration; @@ -27,6 +28,7 @@ pub struct CSSStyleDeclaration { reflector_: Reflector, owner: JS<HTMLElement>, readonly: bool, + pseudo: Option<PseudoElement>, } #[derive(PartialEq)] @@ -56,17 +58,20 @@ fn serialize_list(list: &Vec<PropertyDeclaration>) -> DOMString { impl CSSStyleDeclaration { pub fn new_inherited(owner: &HTMLElement, + pseudo: Option<PseudoElement>, modification_access: CSSModificationAccess) -> CSSStyleDeclaration { CSSStyleDeclaration { reflector_: Reflector::new(), owner: JS::from_ref(owner), + pseudo: pseudo, readonly: modification_access == CSSModificationAccess::Readonly, } } pub fn new(global: &Window, owner: &HTMLElement, + pseudo: Option<PseudoElement>, modification_access: CSSModificationAccess) -> Root<CSSStyleDeclaration> { - reflect_dom_object(box CSSStyleDeclaration::new_inherited(owner, modification_access), + reflect_dom_object(box CSSStyleDeclaration::new_inherited(owner, pseudo, modification_access), GlobalRef::Window(global), CSSStyleDeclarationBinding::Wrap) } @@ -75,6 +80,7 @@ impl CSSStyleDeclaration { trait PrivateCSSStyleDeclarationHelpers { fn get_declaration(self, property: &Atom) -> Option<PropertyDeclaration>; fn get_important_declaration(self, property: &Atom) -> Option<PropertyDeclaration>; + fn get_computed_style(self, property: &Atom) -> Option<DOMString>; } impl<'a> PrivateCSSStyleDeclarationHelpers for &'a CSSStyleDeclaration { @@ -89,6 +95,13 @@ impl<'a> PrivateCSSStyleDeclarationHelpers for &'a CSSStyleDeclaration { let element = ElementCast::from_ref(owner.r()); element.get_important_inline_style_declaration(property).map(|decl| decl.clone()) } + + fn get_computed_style(self, property: &Atom) -> Option<DOMString> { + let owner = self.owner.root(); + let node = NodeCast::from_ref(owner.r()); + let addr = node.to_trusted_node_address(); + window_from_node(owner.r()).resolved_style_query(addr, self.pseudo.clone(), property) + } } impl<'a> CSSStyleDeclarationMethods for &'a CSSStyleDeclaration { @@ -129,6 +142,11 @@ impl<'a> CSSStyleDeclarationMethods for &'a CSSStyleDeclaration { // Step 1 let property = Atom::from_slice(&property.to_ascii_lowercase()); + if self.readonly { + // Readonly style declarations are used for getComputedStyle. + return self.get_computed_style(&property).unwrap_or("".to_owned()); + } + // Step 2 let longhand_properties = longhands_from_shorthand(&property); if let Some(longhand_properties) = longhand_properties { diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 85574373c30..513a9d39775 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -135,7 +135,7 @@ impl<'a> HTMLElementMethods for &'a HTMLElement { fn Style(self) -> Root<CSSStyleDeclaration> { self.style_decl.or_init(|| { let global = window_from_node(self); - CSSStyleDeclaration::new(global.r(), self, CSSModificationAccess::ReadWrite) + CSSStyleDeclaration::new(global.r(), self, None, CSSModificationAccess::ReadWrite) }) } diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index 54c112cec41..1fa85ffbe63 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -90,6 +90,13 @@ partial interface Window { /*[Replaceable]*/ readonly attribute Performance performance; }; +// https://drafts.csswg.org/cssom/#extensions-to-the-window-interface +partial interface Window { + //CSSStyleDeclaration getComputedStyle(Element elt, optional DOMString? pseudoElt); + [NewObject] + CSSStyleDeclaration getComputedStyle(HTMLElement elt, optional DOMString pseudoElt); +}; + // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface partial interface Window { //MediaQueryList matchMedia(DOMString query); diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index e00164d3c93..d4f2a36eabf 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -20,9 +20,11 @@ use dom::bindings::utils::{GlobalStaticData, Reflectable, WindowProxyHandler}; use dom::browsercontext::BrowsingContext; use dom::console::Console; use dom::crypto::Crypto; +use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration}; use dom::document::{Document, DocumentHelpers}; use dom::element::Element; use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId}; +use dom::htmlelement::HTMLElement; use dom::location::Location; use dom::navigator::Navigator; use dom::node::{window_from_node, TrustedNodeAddress, NodeHelpers}; @@ -30,7 +32,7 @@ use dom::performance::Performance; use dom::screen::Screen; use dom::storage::Storage; use layout_interface::{ReflowGoal, ReflowQueryType, LayoutRPC, LayoutChan, Reflow, Msg}; -use layout_interface::{ContentBoxResponse, ContentBoxesResponse, ScriptReflow}; +use layout_interface::{ContentBoxResponse, ContentBoxesResponse, ResolvedStyleResponse, ScriptReflow}; use page::Page; use script_task::{TimerSource, ScriptChan, ScriptPort, NonWorkerScriptChan}; use script_task::ScriptMsg; @@ -47,6 +49,7 @@ use net_traits::ResourceTask; use net_traits::image_cache_task::{ImageCacheChan, ImageCacheTask}; use net_traits::storage_task::{StorageTask, StorageType}; use profile_traits::mem; +use string_cache::Atom; use util::geometry::{self, Au, MAX_RECT}; use util::{breakpoint, opts}; use util::str::{DOMString,HTML_SPACE_CHARACTERS}; @@ -58,10 +61,12 @@ use js::jsapi::{JSContext, HandleValue}; use js::jsapi::{JS_GC, JS_GetRuntime, JSAutoCompartment, JSAutoRequest}; use js::rust::Runtime; use js::rust::CompileOptionsWrapper; +use selectors::parser::PseudoElement; use url::{Url, UrlParser}; use libc; use rustc_serialize::base64::{FromBase64, ToBase64, STANDARD}; +use std::ascii::AsciiExt; use std::borrow::ToOwned; use std::cell::{Cell, Ref, RefMut, RefCell}; use std::collections::HashSet; @@ -539,6 +544,23 @@ impl<'a> WindowMethods for &'a Window { chan.send(Err(WebDriverJSError::Timeout)).unwrap(); } } + + // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle + fn GetComputedStyle(self, + element: &HTMLElement, + pseudo: Option<DOMString>) -> Root<CSSStyleDeclaration> { + // Steps 1-4. + let pseudo = match pseudo.map(|s| s.to_ascii_lowercase()) { + Some(ref pseudo) if pseudo == ":before" || pseudo == "::before" => + Some(PseudoElement::Before), + Some(ref pseudo) if pseudo == ":after" || pseudo == "::after" => + Some(PseudoElement::After), + _ => None + }; + + // Step 5. + CSSStyleDeclaration::new(self, element, pseudo, CSSModificationAccess::Readonly) + } } pub trait WindowHelpers { @@ -553,6 +575,8 @@ pub trait WindowHelpers { fn content_box_query(self, content_box_request: TrustedNodeAddress) -> Rect<Au>; fn content_boxes_query(self, content_boxes_request: TrustedNodeAddress) -> Vec<Rect<Au>>; fn client_rect_query(self, node_geometry_request: TrustedNodeAddress) -> Rect<i32>; + fn resolved_style_query(self, element: TrustedNodeAddress, + pseudo: Option<PseudoElement>, property: &Atom) -> Option<String>; fn handle_reflow_complete_msg(self, reflow_id: u32); fn set_fragment_name(self, fragment: Option<String>); fn steal_fragment_name(self) -> Option<String>; @@ -791,6 +815,17 @@ impl<'a> WindowHelpers for &'a Window { self.layout_rpc.node_geometry().client_rect } + fn resolved_style_query(self, + element: TrustedNodeAddress, + pseudo: Option<PseudoElement>, + property: &Atom) -> Option<String> { + self.reflow(ReflowGoal::ForScriptQuery, + ReflowQueryType::ResolvedStyleQuery(element, pseudo, property.clone()), + ReflowReason::Query); + let ResolvedStyleResponse(resolved) = self.layout_rpc.resolved_style(); + resolved + } + fn handle_reflow_complete_msg(self, reflow_id: u32) { let last_reflow_id = self.last_reflow_id.get(); if last_reflow_id == reflow_id { @@ -1098,6 +1133,7 @@ fn debug_reflow_events(goal: &ReflowGoal, query_type: &ReflowQueryType, reason: ReflowQueryType::ContentBoxQuery(_n) => "\tContentBoxQuery", ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery", ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery", + ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery", }); debug_msg.push_str(match *reason { diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs index d94f6e4a581..092938b8797 100644 --- a/components/script/layout_interface.rs +++ b/components/script/layout_interface.rs @@ -21,8 +21,10 @@ use net_traits::PendingAsyncLoad; use profile_traits::mem::ReportsChan; use script_traits::{ConstellationControlMsg, LayoutControlMsg, ScriptControlChan}; use script_traits::{OpaqueScriptLayoutChannel, StylesheetLoadResponder, UntrustedNodeAddress}; +use selectors::parser::PseudoElement; use std::any::Any; use std::sync::mpsc::{channel, Receiver, Sender}; +use string_cache::Atom; use style::animation::PropertyAnimation; use style::media_queries::MediaQueryList; use style::stylesheets::Stylesheet; @@ -100,8 +102,11 @@ pub trait LayoutRPC { /// Requests the node containing the point of interest fn hit_test(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()>; fn mouse_over(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()>; + /// Query layout for the resolved value of a given CSS property + fn resolved_style(&self) -> ResolvedStyleResponse; } + pub struct ContentBoxResponse(pub Rect<Au>); pub struct ContentBoxesResponse(pub Vec<Rect<Au>>); pub struct NodeGeometryResponse { @@ -109,6 +114,7 @@ pub struct NodeGeometryResponse { } pub struct HitTestResponse(pub UntrustedNodeAddress); pub struct MouseOverResponse(pub Vec<UntrustedNodeAddress>); +pub struct ResolvedStyleResponse(pub Option<String>); /// Why we're doing reflow. #[derive(PartialEq, Copy, Clone, Debug)] @@ -126,6 +132,7 @@ pub enum ReflowQueryType { ContentBoxQuery(TrustedNodeAddress), ContentBoxesQuery(TrustedNodeAddress), NodeGeometryQuery(TrustedNodeAddress), + ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, Atom), } /// Information needed for a reflow. diff --git a/components/style/animation.rs b/components/style/animation.rs index f094229b931..3065ec5cd4f 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -92,6 +92,14 @@ impl PropertyAnimation { new_style.$structname().$field) } )* + TransitionProperty::Clip => { + AnimatedProperty::Clip(old_style.get_effects().clip.0, + new_style.get_effects().clip.0) + } + TransitionProperty::LetterSpacing => { + AnimatedProperty::LetterSpacing(old_style.get_inheritedtext().letter_spacing.0, + new_style.get_inheritedtext().letter_spacing.0) + } TransitionProperty::TextShadow => { AnimatedProperty::TextShadow(old_style.get_effects().text_shadow.clone(), new_style.get_effects().text_shadow.clone()) @@ -100,6 +108,10 @@ impl PropertyAnimation { AnimatedProperty::Transform(old_style.get_effects().transform.clone(), new_style.get_effects().transform.clone()) } + TransitionProperty::WordSpacing => { + AnimatedProperty::WordSpacing(old_style.get_inheritedtext().word_spacing.0, + new_style.get_inheritedtext().word_spacing.0) + } } } } @@ -117,12 +129,10 @@ impl PropertyAnimation { [BorderTopWidth; get_border; border_top_width], [Bottom; get_positionoffsets; bottom], [Color; get_color; color], - [Clip; get_effects; clip], [FontSize; get_font; font_size], [FontWeight; get_font; font_weight], [Height; get_box; height], [Left; get_positionoffsets; bottom], - [LetterSpacing; get_inheritedtext; letter_spacing], [LineHeight; get_inheritedbox; line_height], [MarginBottom; get_margin; margin_bottom], [MarginLeft; get_margin; margin_left], @@ -145,7 +155,6 @@ impl PropertyAnimation { [VerticalAlign; get_box; vertical_align], [Visibility; get_inheritedbox; visibility], [Width; get_box; width], - [WordSpacing; get_inheritedtext; word_spacing], [ZIndex; get_box; z_index]); let property_animation = PropertyAnimation { @@ -186,7 +195,22 @@ impl PropertyAnimation { } } )* - } + AnimatedProperty::Clip(ref start, ref end) => { + if let Some(value) = start.interpolate(end, progress) { + style.mutate_effects().clip.0 = value + } + } + AnimatedProperty::LetterSpacing(ref start, ref end) => { + if let Some(value) = start.interpolate(end, progress) { + style.mutate_inheritedtext().letter_spacing.0 = value + } + } + AnimatedProperty::WordSpacing(ref start, ref end) => { + if let Some(value) = start.interpolate(end, progress) { + style.mutate_inheritedtext().word_spacing.0 = value + } + } + } }); match_property!( [BackgroundColor; mutate_background; background_color], @@ -202,12 +226,10 @@ impl PropertyAnimation { [BorderTopWidth; mutate_border; border_top_width], [Bottom; mutate_positionoffsets; bottom], [Color; mutate_color; color], - [Clip; mutate_effects; clip], [FontSize; mutate_font; font_size], [FontWeight; mutate_font; font_weight], [Height; mutate_box; height], [Left; mutate_positionoffsets; bottom], - [LetterSpacing; mutate_inheritedtext; letter_spacing], [LineHeight; mutate_inheritedbox; line_height], [MarginBottom; mutate_margin; margin_bottom], [MarginLeft; mutate_margin; margin_left], @@ -232,7 +254,6 @@ impl PropertyAnimation { [VerticalAlign; mutate_box; vertical_align], [Visibility; mutate_inheritedbox; visibility], [Width; mutate_box; width], - [WordSpacing; mutate_inheritedtext; word_spacing], [ZIndex; mutate_box; z_index]); } @@ -765,7 +786,7 @@ fn interpolate_transform_list(from_list: &Vec<TransformOperation>, result.push_all(from_list); } - Some(result) + TransformList(Some(result)) } /// Build an equivalent 'identity transform function list' based @@ -809,7 +830,7 @@ impl Interpolate for TransformList { #[inline] fn interpolate(&self, other: &TransformList, time: f32) -> Option<TransformList> { // http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms - let result = match (self, other) { + let result = match (&self.0, &other.0) { (&Some(ref from_list), &Some(ref to_list)) => { // Two lists of transforms interpolate_transform_list(from_list, &to_list, time) @@ -824,9 +845,9 @@ impl Interpolate for TransformList { let from_list = build_identity_transform_list(to_list); interpolate_transform_list(&from_list, to_list, time) } - (&None, &None) => { + _ => { // http://dev.w3.org/csswg/css-transforms/#none-none-animation - None + TransformList(None) } }; diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs index 354d956bf58..7b8e89a1a2a 100644 --- a/components/style/properties.mako.rs +++ b/components/style/properties.mako.rs @@ -655,7 +655,16 @@ pub mod longhands { } } } - #[inline] + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + computed_value::T::Normal => dest.write_str("normal"), + computed_value::T::Length(length) => length.to_css(dest), + computed_value::T::Number(number) => write!(dest, "{}", number), + } + } + } + #[inline] pub fn get_initial_value() -> computed_value::T { computed_value::T::Normal } impl ToComputedValue for SpecifiedValue { @@ -749,6 +758,17 @@ pub mod longhands { } } } + impl ::cssparser::ToCss for T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + % for keyword in vertical_align_keywords: + T::${to_rust_ident(keyword)} => dest.write_str("${keyword}"), + % endfor + T::Length(value) => value.to_css(dest), + T::Percentage(percentage) => write!(dest, "{}%", percentage * 100.), + } + } + } } #[inline] pub fn get_initial_value() -> computed_value::T { computed_value::T::baseline } @@ -1067,7 +1087,20 @@ pub mod longhands { pub mod computed_value { use url::Url; - pub type T = Option<Url>; + use cssparser::{ToCss, Token}; + use std::fmt; + + #[derive(Clone, PartialEq)] + pub struct T(pub Option<Url>); + + impl ToCss for T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self.0 { + None => dest.write_str("none"), + Some(ref url) => Token::Url(url.to_string().into()).to_css(dest) + } + } + } } impl ToComputedValue for SpecifiedValue { @@ -1076,8 +1109,8 @@ pub mod longhands { #[inline] fn to_computed_value(&self, _context: &Context) -> computed_value::T { match *self { - SpecifiedValue::None => None, - SpecifiedValue::Url(ref url) => Some(url.clone()), + SpecifiedValue::None => computed_value::T(None), + SpecifiedValue::Url(ref url) => computed_value::T(Some(url.clone())), } } } @@ -1091,7 +1124,7 @@ pub mod longhands { } #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } </%self:longhand> @@ -1249,7 +1282,20 @@ pub mod longhands { pub mod computed_value { use values::computed; - pub type T = Option<computed::Image>; + #[derive(Clone, PartialEq)] + pub struct T(pub Option<computed::Image>); + } + + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self.0 { + None => dest.write_str("none"), + Some(computed::Image::Url(ref url)) => + ::cssparser::Token::Url(url.to_string().into()).to_css(dest), + Some(computed::Image::LinearGradient(ref gradient)) => + gradient.to_css(dest) + } + } } #[derive(Clone, PartialEq)] @@ -1266,7 +1312,7 @@ pub mod longhands { #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> { if input.try(|input| input.expect_ident_matching("none")).is_ok() { @@ -1281,8 +1327,9 @@ pub mod longhands { #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { match *self { - SpecifiedValue(None) => None, - SpecifiedValue(Some(ref image)) => Some(image.to_computed_value(context)), + SpecifiedValue(None) => computed_value::T(None), + SpecifiedValue(Some(ref image)) => + computed_value::T(Some(image.to_computed_value(context))), } } } @@ -1318,6 +1365,15 @@ pub mod longhands { } } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.horizontal.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.vertical.to_css(dest)); + Ok(()) + } + } + impl SpecifiedValue { fn new(first: specified::PositionComponent, second: specified::PositionComponent) -> Result<SpecifiedValue, ()> { @@ -1424,6 +1480,16 @@ pub mod longhands { } } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + computed_value::T::Explicit(ref size) => size.to_css(dest), + computed_value::T::Cover => dest.write_str("cover"), + computed_value::T::Contain => dest.write_str("contain"), + } + } + } + #[derive(Clone, PartialEq, Debug)] pub struct SpecifiedExplicitSize { pub width: specified::LengthOrPercentageOrAuto, @@ -1438,6 +1504,15 @@ pub mod longhands { } } + impl ToCss for computed_value::ExplicitSize { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.width.to_css(dest)); + try!(dest.write_str(" ")); + self.height.to_css(dest) + } + } + + #[derive(Clone, PartialEq, Debug)] pub enum SpecifiedValue { Explicit(SpecifiedExplicitSize), @@ -1727,6 +1802,15 @@ pub mod longhands { } } } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + % for weight in range(100, 901, 100): + computed_value::T::Weight${weight} => dest.write_str("${weight}"), + % endfor + } + } + } #[inline] pub fn get_initial_value() -> computed_value::T { computed_value::T::Weight400 // normal @@ -1909,12 +1993,22 @@ pub mod longhands { pub mod computed_value { use util::geometry::Au; - pub type T = Option<Au>; + #[derive(Clone, PartialEq)] + pub struct T(pub Option<Au>); + } + + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self.0 { + None => dest.write_str("normal"), + Some(l) => l.to_css(dest), + } + } } #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } impl ToComputedValue for SpecifiedValue { @@ -1923,8 +2017,9 @@ pub mod longhands { #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { match *self { - SpecifiedValue::Normal => None, - SpecifiedValue::Specified(l) => Some(l.to_computed_value(context)) + SpecifiedValue::Normal => computed_value::T(None), + SpecifiedValue::Specified(l) => + computed_value::T(Some(l.to_computed_value(context))) } } } @@ -1960,12 +2055,22 @@ pub mod longhands { pub mod computed_value { use util::geometry::Au; - pub type T = Option<Au>; + #[derive(Clone, PartialEq)] + pub struct T(pub Option<Au>); + } + + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self.0 { + None => dest.write_str("normal"), + Some(l) => l.to_css(dest), + } + } } #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } impl ToComputedValue for SpecifiedValue { @@ -1974,8 +2079,9 @@ pub mod longhands { #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { match *self { - SpecifiedValue::Normal => None, - SpecifiedValue::Specified(l) => Some(l.to_computed_value(context)) + SpecifiedValue::Normal => computed_value::T(None), + SpecifiedValue::Specified(l) => + computed_value::T(Some(l.to_computed_value(context))) } } } @@ -2098,7 +2204,9 @@ pub mod longhands { <%self:longhand name="-servo-text-decorations-in-effect" derived_from="display text-decoration"> - use cssparser::RGBA; + use cssparser::{RGBA, ToCss}; + use std::fmt; + use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -2114,6 +2222,13 @@ pub mod longhands { pub type T = super::SpecifiedValue; } + impl ToCss for SpecifiedValue { + fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write { + // Web compat doesn't matter here. + Ok(()) + } + } + #[inline] pub fn get_initial_value() -> computed_value::T { SpecifiedValue { @@ -2233,6 +2348,14 @@ pub mod longhands { } } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.horizontal.to_css(dest)); + try!(dest.write_str(" ")); + self.vertical.to_css(dest) + } + } + impl ToComputedValue for SpecifiedValue { type ComputedValue = computed_value::T; @@ -2370,12 +2493,22 @@ pub mod longhands { pub mod computed_value { use util::geometry::Au; - pub type T = Option<Au>; + #[derive(Clone, PartialEq)] + pub struct T(pub Option<Au>); + } + + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self.0 { + None => dest.write_str("auto"), + Some(l) => l.to_css(dest), + } + } } #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } impl ToComputedValue for SpecifiedValue { @@ -2384,8 +2517,9 @@ pub mod longhands { #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { match *self { - SpecifiedValue::Auto => None, - SpecifiedValue::Specified(l) => Some(l.to_computed_value(context)) + SpecifiedValue::Auto => computed_value::T(None), + SpecifiedValue::Specified(l) => + computed_value::T(Some(l.to_computed_value(context))) } } } @@ -2420,12 +2554,22 @@ pub mod longhands { } pub mod computed_value { - pub type T = Option<u32>; + #[derive(Clone, PartialEq)] + pub struct T(pub Option<u32>); + } + + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self.0 { + None => dest.write_str("auto"), + Some(count) => write!(dest, "{}", count), + } + } } #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } impl ToComputedValue for SpecifiedValue { @@ -2434,8 +2578,9 @@ pub mod longhands { #[inline] fn to_computed_value(&self, _context: &Context) -> computed_value::T { match *self { - SpecifiedValue::Auto => None, - SpecifiedValue::Specified(count) => Some(count) + SpecifiedValue::Auto => computed_value::T(None), + SpecifiedValue::Specified(count) => + computed_value::T(Some(count)) } } } @@ -2476,12 +2621,22 @@ pub mod longhands { pub mod computed_value { use util::geometry::Au; - pub type T = Option<Au>; + #[derive(Clone, PartialEq)] + pub struct T(pub Option<Au>); + } + + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self.0 { + None => dest.write_str("normal"), + Some(l) => l.to_css(dest), + } + } } #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } impl ToComputedValue for SpecifiedValue { @@ -2490,8 +2645,9 @@ pub mod longhands { #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { match *self { - SpecifiedValue::Normal => None, - SpecifiedValue::Specified(l) => Some(l.to_computed_value(context)) + SpecifiedValue::Normal => computed_value::T(None), + SpecifiedValue::Specified(l) => + computed_value::T(Some(l.to_computed_value(context))) } } } @@ -2611,7 +2767,8 @@ pub mod longhands { use values::computed; use std::fmt; - pub type T = Vec<BoxShadow>; + #[derive(Clone, PartialEq)] + pub struct T(pub Vec<BoxShadow>); #[derive(Clone, PartialEq, Copy)] pub struct BoxShadow { @@ -2635,9 +2792,44 @@ pub mod longhands { } } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + let mut iter = self.0.iter(); + if let Some(shadow) = iter.next() { + try!(shadow.to_css(dest)); + } else { + try!(dest.write_str("none")); + return Ok(()) + } + for shadow in iter { + try!(dest.write_str(", ")); + try!(shadow.to_css(dest)); + } + Ok(()) + } + } + + impl ToCss for computed_value::BoxShadow { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.inset { + try!(dest.write_str("inset ")); + } + try!(self.blur_radius.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.spread_radius.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.offset_x.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.offset_y.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.color.to_css(dest)); + Ok(()) + } + } + #[inline] pub fn get_initial_value() -> computed_value::T { - Vec::new() + computed_value::T(Vec::new()) } pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> { @@ -2653,7 +2845,7 @@ pub mod longhands { #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { - self.0.iter().map(|value| compute_one_box_shadow(value, context)).collect() + computed_value::T(self.0.iter().map(|value| compute_one_box_shadow(value, context)).collect()) } } @@ -2752,7 +2944,38 @@ pub mod longhands { pub left: Au, } - pub type T = Option<ClipRect>; + #[derive(Clone, PartialEq)] + pub struct T(pub Option<ClipRect>); + } + + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self.0 { + None => dest.write_str("auto"), + Some(rect) => { + try!(dest.write_str("rect(")); + try!(rect.top.to_css(dest)); + try!(dest.write_str(", ")); + if let Some(right) = rect.right { + try!(right.to_css(dest)); + try!(dest.write_str(", ")); + } else { + try!(dest.write_str("auto, ")); + } + + if let Some(bottom) = rect.bottom { + try!(bottom.to_css(dest)); + try!(dest.write_str(", ")); + } else { + try!(dest.write_str("auto, ")); + } + + try!(rect.left.to_css(dest)); + try!(dest.write_str(")")); + Ok(()) + } + } + } } #[derive(Clone, Debug, PartialEq, Copy)] @@ -2780,7 +3003,7 @@ pub mod longhands { try!(dest.write_str("auto, ")); } - if let Some(bottom) = self.right { + if let Some(bottom) = self.bottom { try!(bottom.to_css(dest)); try!(dest.write_str(", ")); } else { @@ -2806,7 +3029,7 @@ pub mod longhands { #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } impl ToComputedValue for SpecifiedValue { @@ -2814,12 +3037,12 @@ pub mod longhands { #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { - self.0.map(|value| computed_value::ClipRect { + computed_value::T(self.0.map(|value| computed_value::ClipRect { top: value.top.to_computed_value(context), right: value.right.map(|right| right.to_computed_value(context)), bottom: value.bottom.map(|bottom| bottom.to_computed_value(context)), left: value.left.to_computed_value(context), - }) + })) } } @@ -2903,6 +3126,36 @@ pub mod longhands { } } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + let mut iter = self.0.iter(); + if let Some(shadow) = iter.next() { + try!(shadow.to_css(dest)); + } else { + try!(dest.write_str("none")); + return Ok(()) + } + for shadow in iter { + try!(dest.write_str(", ")); + try!(shadow.to_css(dest)); + } + Ok(()) + } + } + + impl ToCss for computed_value::TextShadow { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.offset_x.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.offset_y.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.blur_radius.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.color.to_css(dest)); + Ok(()) + } + } + impl ToCss for SpecifiedValue { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { let mut iter = self.0.iter(); @@ -3103,6 +3356,23 @@ pub mod longhands { } } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + let mut iter = self.filters.iter(); + if let Some(filter) = iter.next() { + try!(filter.to_css(dest)); + } else { + try!(dest.write_str("none")); + return Ok(()) + } + for filter in iter { + try!(dest.write_str(" ")); + try!(filter.to_css(dest)); + } + Ok(()) + } + } + impl ToCss for SpecifiedValue { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { let mut iter = self.0.iter(); @@ -3120,6 +3390,31 @@ pub mod longhands { } } + impl ToCss for computed_value::Filter { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + computed_value::Filter::Blur(value) => { + try!(dest.write_str("blur(")); + try!(value.to_css(dest)); + try!(dest.write_str(")")); + } + computed_value::Filter::Brightness(value) => try!(write!(dest, "brightness({})", value)), + computed_value::Filter::Contrast(value) => try!(write!(dest, "contrast({})", value)), + computed_value::Filter::Grayscale(value) => try!(write!(dest, "grayscale({})", value)), + computed_value::Filter::HueRotate(value) => { + try!(dest.write_str("hue-rotate(")); + try!(value.to_css(dest)); + try!(dest.write_str(")")); + } + computed_value::Filter::Invert(value) => try!(write!(dest, "invert({})", value)), + computed_value::Filter::Opacity(value) => try!(write!(dest, "opacity({})", value)), + computed_value::Filter::Saturate(value) => try!(write!(dest, "saturate({})", value)), + computed_value::Filter::Sepia(value) => try!(write!(dest, "sepia({})", value)), + } + Ok(()) + } + } + impl ToCss for SpecifiedFilter { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { match *self { @@ -3253,7 +3548,8 @@ pub mod longhands { Perspective(computed::Length), } - pub type T = Option<Vec<ComputedOperation>>; + #[derive(Clone, Debug, PartialEq)] + pub struct T(pub Option<Vec<ComputedOperation>>); } pub use self::computed_value::ComputedMatrix as SpecifiedMatrix; @@ -3290,6 +3586,13 @@ pub mod longhands { Perspective(specified::Length), } + impl ToCss for computed_value::T { + fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write { + // TODO(pcwalton) + Ok(()) + } + } + impl ToCss for SpecifiedOperation { fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write { // TODO(pcwalton) @@ -3316,7 +3619,7 @@ pub mod longhands { #[inline] pub fn get_initial_value() -> computed_value::T { - None + computed_value::T(None) } pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> { @@ -3548,7 +3851,7 @@ pub mod longhands { #[inline] fn to_computed_value(&self, context: &Context) -> computed_value::T { if self.0.is_empty() { - return None + return computed_value::T(None) } let mut result = vec!(); @@ -3577,7 +3880,7 @@ pub mod longhands { }; } - Some(result) + computed_value::T(Some(result)) } } </%self:longhand> @@ -3612,6 +3915,16 @@ pub mod longhands { depth: Length, } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.horizontal.to_css(dest)); + try!(dest.write_str(" ")); + try!(self.vertical.to_css(dest)); + try!(dest.write_str(" ")); + self.depth.to_css(dest) + } + } + impl ToCss for SpecifiedValue { fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { try!(self.horizontal.to_css(dest)); @@ -3742,6 +4055,14 @@ pub mod longhands { } } + impl ToCss for computed_value::T { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.horizontal.to_css(dest)); + try!(dest.write_str(" ")); + self.vertical.to_css(dest) + } + } + #[derive(Clone, Copy, Debug, PartialEq)] pub struct SpecifiedValue { horizontal: LengthOrPercentage, @@ -5542,7 +5863,7 @@ impl ComputedValues { #[inline] pub fn is_multicol(&self) -> bool { let style = self.get_column(); - style.column_count.is_some() || style.column_width.is_some() + style.column_count.0.is_some() || style.column_width.0.is_some() } #[inline] @@ -5560,13 +5881,13 @@ impl ComputedValues { // TODO(gw): Add clip-path, isolation, mask-image, mask-border-source when supported. if effects.opacity < 1.0 || !effects.filter.is_empty() || - effects.clip.is_some() { + effects.clip.0.is_some() { effects.mix_blend_mode != mix_blend_mode::T::normal || return transform_style::T::flat; } if effects.transform_style == transform_style::T::auto { - if effects.transform.is_some() { + if effects.transform.0.is_some() { return transform_style::T::flat; } if effects.perspective != computed::LengthOrNone::None { @@ -5579,17 +5900,28 @@ impl ComputedValues { } % for style_struct in STYLE_STRUCTS: - #[inline] - pub fn get_${style_struct.name.lower()} - <'a>(&'a self) -> &'a style_structs::${style_struct.name} { - &*self.${style_struct.ident} - } - #[inline] - pub fn mutate_${style_struct.name.lower()} - <'a>(&'a mut self) -> &'a mut style_structs::${style_struct.name} { - &mut *Arc::make_unique(&mut self.${style_struct.ident}) - } + #[inline] + pub fn get_${style_struct.name.lower()} + <'a>(&'a self) -> &'a style_structs::${style_struct.name} { + &*self.${style_struct.ident} + } + #[inline] + pub fn mutate_${style_struct.name.lower()} + <'a>(&'a mut self) -> &'a mut style_structs::${style_struct.name} { + &mut *Arc::make_unique(&mut self.${style_struct.ident}) + } % endfor + + pub fn computed_value_to_string(&self, name: &str) -> Option<String> { + match name { + % for style_struct in STYLE_STRUCTS: + % for longhand in style_struct.longhands: + "${longhand.name}" => Some(self.${style_struct.ident}.${longhand.ident}.to_css_string()), + % endfor + % endfor + _ => None + } + } } diff --git a/components/style/values.rs b/components/style/values.rs index 93d6ccc7e6c..b03a239ae49 100644 --- a/components/style/values.rs +++ b/components/style/values.rs @@ -975,6 +975,16 @@ pub mod computed { } } + impl ::cssparser::ToCss for LengthOrPercentage { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self { + &LengthOrPercentage::Length(length) => length.to_css(dest), + &LengthOrPercentage::Percentage(percentage) + => write!(dest, "{}%", percentage * 100.), + } + } + } + #[derive(PartialEq, Clone, Copy)] pub enum LengthOrPercentageOrAuto { Length(Au), @@ -1010,6 +1020,17 @@ pub mod computed { } } + impl ::cssparser::ToCss for LengthOrPercentageOrAuto { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self { + &LengthOrPercentageOrAuto::Length(length) => length.to_css(dest), + &LengthOrPercentageOrAuto::Percentage(percentage) + => write!(dest, "{}%", percentage * 100.), + &LengthOrPercentageOrAuto::Auto => dest.write_str("auto"), + } + } + } + #[derive(PartialEq, Clone, Copy)] pub enum LengthOrPercentageOrNone { Length(Au), @@ -1045,6 +1066,17 @@ pub mod computed { } } + impl ::cssparser::ToCss for LengthOrPercentageOrNone { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self { + &LengthOrPercentageOrNone::Length(length) => length.to_css(dest), + &LengthOrPercentageOrNone::Percentage(percentage) => + write!(dest, "{}%", percentage * 100.), + &LengthOrPercentageOrNone::None => dest.write_str("none"), + } + } + } + #[derive(PartialEq, Clone, Copy)] pub enum LengthOrNone { Length(Au), @@ -1075,6 +1107,15 @@ pub mod computed { } } + impl ::cssparser::ToCss for LengthOrNone { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match self { + &LengthOrNone::Length(length) => length.to_css(dest), + &LengthOrNone::None => dest.write_str("none"), + } + } + } + impl ToComputedValue for specified::Image { type ComputedValue = Image; @@ -1116,6 +1157,19 @@ pub mod computed { pub stops: Vec<ColorStop>, } + impl ::cssparser::ToCss for LinearGradient { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(dest.write_str("linear-gradient(")); + try!(self.angle_or_corner.to_css(dest)); + for stop in self.stops.iter() { + try!(dest.write_str(", ")); + try!(stop.to_css(dest)); + } + try!(dest.write_str(")")); + Ok(()) + } + } + impl fmt::Debug for LinearGradient { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let _ = write!(f, "{:?}", self.angle_or_corner); @@ -1137,6 +1191,17 @@ pub mod computed { pub position: Option<LengthOrPercentage>, } + impl ::cssparser::ToCss for ColorStop { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.color.to_css(dest)); + if let Some(position) = self.position { + try!(dest.write_str(" ")); + try!(position.to_css(dest)); + } + Ok(()) + } + } + impl fmt::Debug for ColorStop { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let _ = write!(f, "{:?}", self.color); diff --git a/components/util/geometry.rs b/components/util/geometry.rs index 1ed0565e3b4..8af8dce27ab 100644 --- a/components/util/geometry.rs +++ b/components/util/geometry.rs @@ -2,6 +2,8 @@ * 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/. */ +use cssparser::ToCss; + use euclid::length::Length; use euclid::point::Point2D; use euclid::rect::Rect; @@ -122,7 +124,14 @@ impl Encodable for Au { impl fmt::Debug for Au { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}px", self.to_f64_px()) - }} + } +} + +impl ToCss for Au { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + write!(dest, "{}px", self.to_f64_px()) + } +} impl Add for Au { type Output = Au; diff --git a/tests/wpt/metadata-css/css21_dev/html4/events-006.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/events-006.htm.ini index 6d28ce473f0..d50f4dc4c63 100644 --- a/tests/wpt/metadata-css/css21_dev/html4/events-006.htm.ini +++ b/tests/wpt/metadata-css/css21_dev/html4/events-006.htm.ini @@ -1,9 +1,8 @@ [events-006.htm] type: testharness - expected: TIMEOUT [transition padding-left on :before / events] - expected: NOTRUN + expected: FAIL [transition padding-left on :after / events] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/metadata-css/css21_dev/html4/pseudo-elements-001.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/pseudo-elements-001.htm.ini index 42591fa1ae7..784c08016a2 100644 --- a/tests/wpt/metadata-css/css21_dev/html4/pseudo-elements-001.htm.ini +++ b/tests/wpt/metadata-css/css21_dev/html4/pseudo-elements-001.htm.ini @@ -1,15 +1,14 @@ [pseudo-elements-001.htm] type: testharness - expected: TIMEOUT [transition padding-left on :before / values] - expected: NOTRUN + expected: FAIL [transition padding-left on :after / values] - expected: NOTRUN + expected: FAIL [transition padding-left on :before, changing content / values] - expected: NOTRUN + expected: FAIL [transition padding-left on :after, changing content / values] - expected: NOTRUN + expected: FAIL diff --git a/tests/wpt/metadata/dom/nodes/Element-classlist.html.ini b/tests/wpt/metadata/dom/nodes/Element-classlist.html.ini deleted file mode 100644 index de405c47bc8..00000000000 --- a/tests/wpt/metadata/dom/nodes/Element-classlist.html.ini +++ /dev/null @@ -1,20 +0,0 @@ -[Element-classlist.html] - type: testharness - [CSS .foo selectors must not match elements without any class] - expected: FAIL - - [computed style must update when setting .className] - expected: FAIL - - [classList.add must not cause the CSS selector to stop matching] - expected: FAIL - - [classList.remove must not break case-sensitive CSS selector matching] - expected: FAIL - - [classList.toggle must not break case-sensitive CSS selector matching] - expected: FAIL - - [CSS class selectors must stop matching when all classes have been removed] - expected: FAIL - diff --git a/tests/wpt/metadata/html/dom/elements/global-attributes/id-attribute.html.ini b/tests/wpt/metadata/html/dom/elements/global-attributes/id-attribute.html.ini deleted file mode 100644 index 2a63d763353..00000000000 --- a/tests/wpt/metadata/html/dom/elements/global-attributes/id-attribute.html.ini +++ /dev/null @@ -1,20 +0,0 @@ -[id-attribute.html] - type: testharness - [User agents must associate the element with an id value for purposes of CSS.] - expected: FAIL - - [Association for CSS is exact and therefore case-sensitive.] - expected: FAIL - - [Spaces are allowed in an id and still make an association.] - expected: FAIL - - [Non-ASCII is allowed in an id and still make an association for CSS.] - expected: FAIL - - [After setting id via id attribute, CSS association is via the new ID.] - expected: FAIL - - [After setting id via setAttribute attribute, CSS association is via the new ID.] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/edits/the-del-element/del_effect.html.ini b/tests/wpt/metadata/html/semantics/edits/the-del-element/del_effect.html.ini deleted file mode 100644 index e060d1a2697..00000000000 --- a/tests/wpt/metadata/html/semantics/edits/the-del-element/del_effect.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[del_effect.html] - type: testharness - [HTML Test: Text in the del element should be 'line-through'] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/edits/the-ins-element/ins_effect.html.ini b/tests/wpt/metadata/html/semantics/edits/the-ins-element/ins_effect.html.ini deleted file mode 100644 index 6fd52bc7e8b..00000000000 --- a/tests/wpt/metadata/html/semantics/edits/the-ins-element/ins_effect.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[ins_effect.html] - type: testharness - [HTML Test: Text in the ins element should be 'underline'] - expected: FAIL - diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index eb000fe529f..075f3da0da4 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -533,6 +533,12 @@ "url": "/_mozilla/mozilla/getBoundingClientRect.html" } ], + "mozilla/getComputedStyle.html": [ + { + "path": "mozilla/getComputedStyle.html", + "url": "/_mozilla/mozilla/getComputedStyle.html" + } + ], "mozilla/getPropertyPriority.html": [ { "path": "mozilla/getPropertyPriority.html", @@ -974,4 +980,4 @@ "rev": null, "url_base": "/_mozilla/", "version": 2 -}
\ No newline at end of file +} diff --git a/tests/wpt/mozilla/tests/mozilla/getComputedStyle.html b/tests/wpt/mozilla/tests/mozilla/getComputedStyle.html new file mode 100644 index 00000000000..410a8fd79c6 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/getComputedStyle.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<html> + <head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style> + #foo:before { + color: red; + } + + #foo { + width: 50px; + } + </style> + </head> + <body> + <div id="foo"></div> + <script> + test(function() { + var div = document.getElementById("foo"); + var cs = getComputedStyle(div); + assert_equals(cs.getPropertyValue("left"), "auto"); + assert_equals(cs.getPropertyValue("right"), "auto"); + assert_equals(cs.getPropertyValue("top"), "auto"); + assert_equals(cs.getPropertyValue("bottom"), "auto"); + assert_equals(cs.getPropertyValue("width"), "50px"); + assert_equals(cs.getPropertyValue("height"), "auto"); + assert_equals(cs.getPropertyValue("color"), "rgb(0, 0, 0)"); + }, "Element's resolved values"); + + test(function() { + var div = document.getElementById("foo"); + assert_equals(getComputedStyle(div, ':before').getPropertyValue("color"), "rgb(255, 0, 0)"); + assert_equals(getComputedStyle(div, '::before').getPropertyValue("color"), "rgb(255, 0, 0)"); + }, "Existing :before pseudoelement"); + + test(function() { + var div = document.getElementById("foo"); + assert_equals(getComputedStyle(div, ':after').getPropertyValue("color"), ""); + assert_equals(getComputedStyle(div, '::after').getPropertyValue("color"), ""); + }, "Missing :after pseudoelement"); + + </script> + </body> +</html> |