servo: Merge #5400 - layout: Implement CSS transitions per CSS-TRANSITIONS § 2 (from pcwalton:transitions-redux); r=glennw

Transition events are not yet supported, and the only animatable
properties are `top`, `right`, `bottom`, and `left`. However, all other
features of transitions are supported. There are no automated tests at
present because I'm not sure how best to test it, but three manual tests
are included.

r? @glennw

Source-Repo: https://github.com/servo/servo
Source-Revision: ebdf1d494b6c986e6dfcb7d8fd3f0ffa126523ed
This commit is contained in:
Patrick Walton 2015-03-31 10:39:56 -06:00
Родитель b937bffcbb
Коммит c9e2901806
31 изменённых файлов: 1603 добавлений и 224 удалений

Просмотреть файл

@ -13,32 +13,32 @@ use windowing::{MouseWindowEvent, WindowEvent, WindowMethods, WindowNavigateMsg}
use geom::point::{Point2D, TypedPoint2D};
use geom::rect::{Rect, TypedRect};
use geom::size::{Size2D, TypedSize2D};
use geom::scale_factor::ScaleFactor;
use geom::size::{Size2D, TypedSize2D};
use gfx::color;
use gfx::paint_task::Msg as PaintMsg;
use gfx::paint_task::PaintRequest;
use layers::geometry::{DevicePixel, LayerPixel};
use layers::layers::{BufferRequest, Layer, LayerBuffer, LayerBufferSet};
use layers::rendergl;
use layers::rendergl::RenderContext;
use layers::scene::Scene;
use png;
use gleam::gl::types::{GLint, GLsizei};
use gleam::gl;
use script_traits::{ConstellationControlMsg, ScriptControlChan};
use layers::geometry::{DevicePixel, LayerPixel};
use layers::layers::{BufferRequest, Layer, LayerBuffer, LayerBufferSet};
use layers::rendergl::RenderContext;
use layers::rendergl;
use layers::scene::Scene;
use msg::compositor_msg::{Epoch, LayerId};
use msg::compositor_msg::{ReadyState, PaintState, ScrollPolicy};
use msg::constellation_msg::{ConstellationChan, NavigationDirection};
use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{ConstellationChan, NavigationDirection};
use msg::constellation_msg::{Key, KeyModifiers, KeyState, LoadData};
use msg::constellation_msg::{PipelineId, WindowSizeData};
use png;
use profile::mem;
use profile::time::{self, ProfilerCategory, profile};
use script_traits::{ConstellationControlMsg, ScriptControlChan};
use std::cmp;
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::mem::replace;
use std::mem as std_mem;
use std::num::Float;
use std::rc::Rc;
use std::slice::bytes::copy_memory;
@ -164,6 +164,9 @@ struct PipelineDetails {
/// The status of this pipeline's PaintTask.
paint_state: PaintState,
/// Whether animations are running.
animations_running: bool,
}
impl PipelineDetails {
@ -172,6 +175,7 @@ impl PipelineDetails {
pipeline: None,
ready_state: ReadyState::Blank,
paint_state: PaintState::Painting,
animations_running: false,
}
}
}
@ -272,6 +276,11 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.change_paint_state(pipeline_id, paint_state);
}
(Msg::ChangeRunningAnimationsState(pipeline_id, running_animations),
ShutdownState::NotShuttingDown) => {
self.change_running_animations_state(pipeline_id, running_animations);
}
(Msg::ChangePageTitle(pipeline_id, title), ShutdownState::NotShuttingDown) => {
self.change_page_title(pipeline_id, title);
}
@ -311,7 +320,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.set_layer_rect(pipeline_id, layer_id, &rect);
}
(Msg::AssignPaintedBuffers(pipeline_id, epoch, replies), ShutdownState::NotShuttingDown) => {
(Msg::AssignPaintedBuffers(pipeline_id, epoch, replies),
ShutdownState::NotShuttingDown) => {
for (layer_id, new_layer_buffer_set) in replies.into_iter() {
self.assign_painted_buffers(pipeline_id, layer_id, new_layer_buffer_set, epoch);
}
@ -399,6 +409,18 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.window.set_paint_state(paint_state);
}
/// Sets or unsets the animations-running flag for the given pipeline, and schedules a
/// recomposite if necessary.
fn change_running_animations_state(&mut self,
pipeline_id: PipelineId,
animations_running: bool) {
self.get_or_create_pipeline_details(pipeline_id).animations_running = animations_running;
if animations_running {
self.composite_if_necessary();
}
}
pub fn get_or_create_pipeline_details<'a>(&'a mut self,
pipeline_id: PipelineId)
-> &'a mut PipelineDetails {
@ -867,15 +889,13 @@ impl<Window: WindowMethods> IOCompositor<Window> {
fn process_pending_scroll_events(&mut self) {
let had_scroll_events = self.pending_scroll_events.len() > 0;
for scroll_event in replace(&mut self.pending_scroll_events, Vec::new()).into_iter() {
for scroll_event in std_mem::replace(&mut self.pending_scroll_events,
Vec::new()).into_iter() {
let delta = scroll_event.delta / self.scene.scale;
let cursor = scroll_event.cursor.as_f32() / self.scene.scale;
match self.scene.root {
Some(ref mut layer) => {
layer.handle_scroll_event(delta, cursor);
}
None => {}
if let Some(ref mut layer) = self.scene.root {
layer.handle_scroll_event(delta, cursor);
}
self.start_scrolling_timer_if_necessary();
@ -887,6 +907,16 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}
}
/// If there are any animations running, dispatches appropriate messages to the constellation.
fn process_animations(&mut self) {
for (pipeline_id, pipeline_details) in self.pipeline_details.iter() {
if !pipeline_details.animations_running {
continue
}
self.constellation_chan.0.send(ConstellationMsg::TickAnimation(*pipeline_id)).unwrap();
}
}
fn device_pixels_per_screen_px(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
match opts::get().device_pixels_per_px {
Some(device_pixels_per_px) => device_pixels_per_px,
@ -1086,7 +1116,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
let mut framebuffer_ids = vec!();
let mut texture_ids = vec!();
let (width, height) = (self.window_size.width.get() as usize, self.window_size.height.get() as usize);
let (width, height) =
(self.window_size.width.get() as usize, self.window_size.height.get() as usize);
if output_image {
framebuffer_ids = gl::gen_framebuffers(1);
@ -1172,6 +1203,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
self.composition_request = CompositionRequest::NoCompositingNecessary;
self.process_pending_scroll_events();
self.process_animations();
}
fn composite_if_necessary(&mut self) {

Просмотреть файл

@ -201,6 +201,8 @@ pub enum Msg {
ChangePageTitle(PipelineId, Option<String>),
/// Alerts the compositor that the current page has changed its URL.
ChangePageUrl(PipelineId, Url),
/// Alerts the compositor that the given pipeline has changed whether it is running animations.
ChangeRunningAnimationsState(PipelineId, bool),
/// Alerts the compositor that a `PaintMsg` has been discarded.
PaintMsgDiscarded,
/// Replaces the current frame tree, typically called during main frame navigation.
@ -231,6 +233,7 @@ impl Debug for Msg {
Msg::AssignPaintedBuffers(..) => write!(f, "AssignPaintedBuffers"),
Msg::ChangeReadyState(..) => write!(f, "ChangeReadyState"),
Msg::ChangePaintState(..) => write!(f, "ChangePaintState"),
Msg::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"),
Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
Msg::ChangePageUrl(..) => write!(f, "ChangePageUrl"),
Msg::PaintMsgDiscarded(..) => write!(f, "PaintMsgDiscarded"),

Просмотреть файл

@ -11,26 +11,22 @@ use geom::point::Point2D;
use geom::rect::{Rect, TypedRect};
use geom::scale_factor::ScaleFactor;
use gfx::font_cache_task::FontCacheTask;
use layout_traits::LayoutTaskFactory;
use layout_traits::{LayoutControlMsg, LayoutTaskFactory};
use libc;
use script_traits::{CompositorEvent, ConstellationControlMsg};
use script_traits::{ScriptControlChan, ScriptTaskFactory};
use msg::compositor_msg::LayerId;
use msg::constellation_msg::{self, ConstellationChan, Failure};
use msg::constellation_msg::{IFrameSandboxState, NavigationDirection};
use msg::constellation_msg::{Key, KeyState, KeyModifiers, LoadData};
use msg::constellation_msg::{FrameId, PipelineExitType, PipelineId};
use msg::constellation_msg::{SubpageId, WindowSizeData, MozBrowserEvent};
use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{FrameId, PipelineExitType, PipelineId};
use msg::constellation_msg::{IFrameSandboxState, MozBrowserEvent, NavigationDirection};
use msg::constellation_msg::{Key, KeyState, KeyModifiers, LoadData};
use msg::constellation_msg::{SubpageId, WindowSizeData};
use msg::constellation_msg::{self, ConstellationChan, Failure};
use net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
use net::resource_task::{self, ResourceTask};
use net::storage_task::{StorageTask, StorageTaskMsg};
use profile::mem;
use profile::time;
use util::cursor::Cursor;
use util::geometry::PagePx;
use util::opts;
use util::task::spawn_named;
use script_traits::{CompositorEvent, ConstellationControlMsg};
use script_traits::{ScriptControlChan, ScriptTaskFactory};
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::io::{self, Write};
@ -38,6 +34,10 @@ use std::marker::PhantomData;
use std::mem::replace;
use std::sync::mpsc::{Receiver, channel};
use url::Url;
use util::cursor::Cursor;
use util::geometry::PagePx;
use util::opts;
use util::task::spawn_named;
/// Maintains the pipelines and navigation context and grants permission to composite.
pub struct Constellation<LTF, STF> {
@ -201,8 +201,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
time_profiler_chan: time_profiler_chan,
mem_profiler_chan: mem_profiler_chan,
window_size: WindowSizeData {
visible_viewport: opts::get().initial_window_size.as_f32() * ScaleFactor::new(1.0),
initial_viewport: opts::get().initial_window_size.as_f32() * ScaleFactor::new(1.0),
visible_viewport: opts::get().initial_window_size.as_f32() *
ScaleFactor::new(1.0),
initial_viewport: opts::get().initial_window_size.as_f32() *
ScaleFactor::new(1.0),
device_pixel_ratio: ScaleFactor::new(1.0),
},
phantom: PhantomData,
@ -321,7 +323,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
new_subpage_id,
old_subpage_id,
sandbox) => {
debug!("constellation got iframe URL load message {:?} {:?} {:?}", source_pipeline_id, old_subpage_id, new_subpage_id);
debug!("constellation got iframe URL load message {:?} {:?} {:?}",
source_pipeline_id,
old_subpage_id,
new_subpage_id);
self.handle_script_loaded_url_in_iframe_msg(url,
source_pipeline_id,
new_subpage_id,
@ -329,6 +334,12 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
sandbox);
}
ConstellationMsg::SetCursor(cursor) => self.handle_set_cursor_msg(cursor),
ConstellationMsg::ChangeRunningAnimationsState(pipeline_id, animations_running) => {
self.handle_change_running_animations_state(pipeline_id, animations_running)
}
ConstellationMsg::TickAnimation(pipeline_id) => {
self.handle_tick_animation(pipeline_id)
}
// Load a new page, usually -- but not always -- from a mouse click or typed url
// If there is already a pending page (self.pending_frames), it will not be overridden;
// However, if the id is not encompassed by another change, it will be.
@ -420,15 +431,19 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
debug!("creating replacement pipeline for about:failure");
let window_rect = self.pipeline(pipeline_id).rect;
let new_pipeline_id = self.new_pipeline(parent_info, window_rect, None,
LoadData::new(Url::parse("about:failure").unwrap()));
let new_pipeline_id =
self.new_pipeline(parent_info,
window_rect,
None,
LoadData::new(Url::parse("about:failure").unwrap()));
self.push_pending_frame(new_pipeline_id, Some(pipeline_id));
}
fn handle_init_load(&mut self, url: Url) {
let window_rect = Rect(Point2D::zero(), self.window_size.visible_viewport);
let root_pipeline_id = self.new_pipeline(None, Some(window_rect), None, LoadData::new(url));
let root_pipeline_id =
self.new_pipeline(None, Some(window_rect), None, LoadData::new(url));
self.push_pending_frame(root_pipeline_id, None);
}
@ -509,6 +524,21 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
self.compositor_proxy.send(CompositorMsg::SetCursor(cursor))
}
fn handle_change_running_animations_state(&mut self,
pipeline_id: PipelineId,
animations_running: bool) {
self.compositor_proxy.send(CompositorMsg::ChangeRunningAnimationsState(pipeline_id,
animations_running))
}
fn handle_tick_animation(&mut self, pipeline_id: PipelineId) {
self.pipeline(pipeline_id)
.layout_chan
.0
.send(LayoutControlMsg::TickAnimationsMsg)
.unwrap();
}
fn handle_load_url_msg(&mut self, source_id: PipelineId, load_data: LoadData) {
// If this load targets an iframe, its framing element may exist
// in a separate script task than the framed document that initiated
@ -907,7 +937,9 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
}
fn find_subpage(&mut self, containing_pipeline_id: PipelineId, subpage_id: SubpageId) -> &mut Pipeline {
let pipeline_id = *self.subpage_map.get(&(containing_pipeline_id, subpage_id)).expect("no subpage pipeline_id");
let pipeline_id = *self.subpage_map
.get(&(containing_pipeline_id, subpage_id))
.expect("no subpage pipeline_id");
self.mut_pipeline(pipeline_id)
}
}

Просмотреть файл

@ -98,6 +98,7 @@ impl CompositorEventListener for NullCompositor {
Msg::AssignPaintedBuffers(..) |
Msg::ChangeReadyState(..) |
Msg::ChangePaintState(..) |
Msg::ChangeRunningAnimationsState(..) |
Msg::ScrollFragmentPoint(..) |
Msg::LoadComplete |
Msg::PaintMsgDiscarded(..) |

Просмотреть файл

@ -31,6 +31,7 @@ pub struct Pipeline {
pub id: PipelineId,
pub parent_info: Option<(PipelineId, SubpageId)>,
pub script_chan: ScriptControlChan,
/// A channel to layout, for performing reflows and shutdown.
pub layout_chan: LayoutControlChan,
pub paint_chan: PaintChan,
pub layout_shutdown_port: Receiver<()>,
@ -40,6 +41,9 @@ pub struct Pipeline {
/// The title of the most recently-loaded page.
pub title: Option<String>,
pub rect: Option<TypedRect<PagePx, f32>>,
/// Whether this pipeline is currently running animations. Pipelines that are running
/// animations cause composites to be continually scheduled.
pub running_animations: bool,
pub children: Vec<FrameId>,
}
@ -113,12 +117,14 @@ impl Pipeline {
ScriptControlChan(script_chan)
}
Some(script_chan) => {
let (containing_pipeline_id, subpage_id) = parent_info.expect("script_pipeline != None but subpage_id == None");
let (containing_pipeline_id, subpage_id) =
parent_info.expect("script_pipeline != None but subpage_id == None");
let new_layout_info = NewLayoutInfo {
containing_pipeline_id: containing_pipeline_id,
new_pipeline_id: id,
subpage_id: subpage_id,
layout_chan: ScriptTaskFactory::clone_layout_channel(None::<&mut STF>, &layout_pair),
layout_chan: ScriptTaskFactory::clone_layout_channel(None::<&mut STF>,
&layout_pair),
load_data: load_data.clone(),
};
@ -186,6 +192,7 @@ impl Pipeline {
title: None,
children: vec!(),
rect: rect,
running_animations: false,
}
}

Просмотреть файл

@ -330,13 +330,13 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
})
}
/// Paints one layer and sends the tiles back to the layer.
/// Paints one layer and places the painted tiles in `replies`.
fn paint(&mut self,
replies: &mut Vec<(LayerId, Box<LayerBufferSet>)>,
mut tiles: Vec<BufferRequest>,
scale: f32,
layer_id: LayerId) {
profile(time::ProfilerCategory::Painting, None, self.time_profiler_chan.clone(), || {
time::profile(time::ProfilerCategory::Painting, None, self.time_profiler_chan.clone(), || {
// Bail out if there is no appropriate stacking context.
let stacking_context = if let Some(ref stacking_context) = self.root_stacking_context {
match display_list::find_stacking_context_with_layer_id(stacking_context,
@ -559,8 +559,10 @@ impl WorkerThread {
paint_context.clear();
// Draw the display list.
profile(time::ProfilerCategory::PaintingPerTile, None,
self.time_profiler_sender.clone(), || {
time::profile(time::ProfilerCategory::PaintingPerTile,
None,
self.time_profiler_sender.clone(),
|| {
stacking_context.optimize_and_draw_into_context(&mut paint_context,
&tile_bounds,
&matrix,

Просмотреть файл

@ -61,9 +61,13 @@ git = "https://github.com/servo/string-cache"
[dependencies.png]
git = "https://github.com/servo/rust-png"
[dependencies.clock_ticks]
git = "https://github.com/tomaka/clock_ticks"
[dependencies]
encoding = "0.2"
url = "0.2.16"
bitflags = "*"
rustc-serialize = "0.3"
libc = "*"

Просмотреть файл

@ -0,0 +1,104 @@
/* 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/. */
//! CSS transitions and animations.
use flow::{self, Flow};
use incremental::{self, RestyleDamage};
use clock_ticks;
use gfx::display_list::OpaqueNode;
use layout_task::{LayoutTask, LayoutTaskData};
use msg::constellation_msg::{Msg, PipelineId};
use script::layout_interface::Animation;
use std::mem;
use std::sync::mpsc::Sender;
use style::animation::{GetMod, PropertyAnimation};
use style::properties::ComputedValues;
/// Inserts transitions into the queue of running animations as applicable for the given style
/// difference. This is called from the layout worker threads.
pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>,
node: OpaqueNode,
old_style: &ComputedValues,
new_style: &mut ComputedValues) {
for i in range(0, new_style.get_animation().transition_property.0.len()) {
// Create any property animations, if applicable.
let property_animations = PropertyAnimation::from_transition(i, old_style, new_style);
for property_animation in property_animations.into_iter() {
// Set the property to the initial value.
property_animation.update(new_style, 0.0);
// Kick off the animation.
let now = clock_ticks::precise_time_s();
let animation_style = new_style.get_animation();
let start_time = now + animation_style.transition_delay.0.get_mod(i).seconds();
new_animations_sender.send(Animation {
node: node.id(),
property_animation: property_animation,
start_time: start_time,
end_time: start_time +
animation_style.transition_duration.0.get_mod(i).seconds(),
}).unwrap()
}
}
}
/// Processes any new animations that were discovered after style recalculation.
pub fn process_new_animations(rw_data: &mut LayoutTaskData, pipeline_id: PipelineId) {
while let Ok(animation) = rw_data.new_animations_receiver.try_recv() {
rw_data.running_animations.push(animation)
}
let animations_are_running = !rw_data.running_animations.is_empty();
rw_data.constellation_chan
.0
.send(Msg::ChangeRunningAnimationsState(pipeline_id, animations_are_running))
.unwrap();
}
/// Recalculates style for an animation. This does *not* run with the DOM lock held.
pub fn recalc_style_for_animation(flow: &mut Flow, animation: &Animation) {
let mut damage = RestyleDamage::empty();
flow.mutate_fragments(&mut |fragment| {
if fragment.node.id() != animation.node {
return
}
let now = clock_ticks::precise_time_s();
let mut progress = (now - animation.start_time) / animation.duration();
if progress > 1.0 {
progress = 1.0
}
if progress <= 0.0 {
return
}
let mut new_style = fragment.style.clone();
animation.property_animation.update(&mut *new_style.make_unique(), progress);
damage.insert(incremental::compute_damage(&Some(fragment.style.clone()), &new_style));
fragment.style = new_style
});
let base = flow::mut_base(flow);
base.restyle_damage.insert(damage);
for kid in base.children.iter_mut() {
recalc_style_for_animation(kid, animation)
}
}
/// Handles animation updates.
pub fn tick_all_animations(layout_task: &LayoutTask, rw_data: &mut LayoutTaskData) {
let running_animations = mem::replace(&mut rw_data.running_animations, Vec::new());
let now = clock_ticks::precise_time_s();
for running_animation in running_animations.into_iter() {
layout_task.tick_animation(running_animation, rw_data);
if now < running_animation.end_time {
// Keep running the animation if it hasn't expired.
rw_data.running_animations.push(running_animation)
}
}
}

Просмотреть файл

@ -10,19 +10,20 @@ use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache};
use geom::{Rect, Size2D};
use gfx::display_list::OpaqueNode;
use gfx::font_context::FontContext;
use gfx::font_cache_task::FontCacheTask;
use script::layout_interface::LayoutChan;
use script_traits::UntrustedNodeAddress;
use gfx::font_context::FontContext;
use msg::constellation_msg::ConstellationChan;
use net::local_image_cache::LocalImageCache;
use util::geometry::Au;
use script::layout_interface::{Animation, LayoutChan};
use script_traits::UntrustedNodeAddress;
use std::boxed;
use std::cell::Cell;
use std::ptr;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use style::selector_matching::Stylist;
use url::Url;
use util::geometry::Au;
struct LocalLayoutContext {
font_context: FontContext,
@ -32,7 +33,8 @@ struct LocalLayoutContext {
thread_local!(static LOCAL_CONTEXT_KEY: Cell<*mut LocalLayoutContext> = Cell::new(ptr::null_mut()));
fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *mut LocalLayoutContext {
fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext)
-> *mut LocalLayoutContext {
LOCAL_CONTEXT_KEY.with(|ref r| {
if r.get().is_null() {
let context = box LocalLayoutContext {
@ -51,6 +53,7 @@ fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *
})
}
/// Layout information shared among all workers. This must be thread-safe.
pub struct SharedLayoutContext {
/// The local image cache.
pub image_cache: Arc<Mutex<LocalImageCache<UntrustedNodeAddress>>>,
@ -76,7 +79,7 @@ pub struct SharedLayoutContext {
pub stylist: *const Stylist,
/// The root node at which we're starting the layout.
pub reflow_root: OpaqueNode,
pub reflow_root: Option<OpaqueNode>,
/// The URL.
pub url: Url,
@ -87,9 +90,14 @@ pub struct SharedLayoutContext {
/// Starts at zero, and increased by one every time a layout completes.
/// This can be used to easily check for invalid stale data.
pub generation: u32,
/// A channel on which new animations that have been triggered by style recalculation can be
/// sent.
pub new_animations_sender: Sender<Animation>,
}
pub struct SharedLayoutContextWrapper(pub *const SharedLayoutContext);
unsafe impl Send for SharedLayoutContextWrapper {}
pub struct LayoutContext<'a> {

Просмотреть файл

@ -6,29 +6,33 @@
#![allow(unsafe_code)]
use animation;
use context::SharedLayoutContext;
use css::node_style::StyledNode;
use incremental::{self, RestyleDamage};
use data::{LayoutDataAccess, LayoutDataWrapper};
use incremental::{self, RestyleDamage};
use opaque_node::OpaqueNodeMethods;
use wrapper::{LayoutElement, LayoutNode, TLayoutNode};
use script::dom::node::NodeTypeId;
use script::layout_interface::Animation;
use selectors::bloom::BloomFilter;
use util::cache::{LRUCache, SimpleHashCache};
use util::smallvec::{SmallVec, SmallVec16};
use util::arc_ptr_eq;
use std::borrow::ToOwned;
use std::mem;
use std::hash::{Hash, Hasher};
use std::slice::Iter;
use string_cache::{Atom, Namespace};
use selectors::parser::PseudoElement;
use style::selector_matching::{Stylist, DeclarationBlock};
use style::node::{TElement, TNode};
use style::properties::{ComputedValues, cascade};
use selectors::matching::{CommonStyleAffectingAttributeMode, CommonStyleAffectingAttributes};
use selectors::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
use selectors::parser::PseudoElement;
use std::borrow::ToOwned;
use std::hash::{Hash, Hasher};
use std::mem;
use std::slice::Iter;
use std::sync::Arc;
use std::sync::mpsc::Sender;
use string_cache::{Atom, Namespace};
use style::node::{TElement, TNode};
use style::properties::{ComputedValues, cascade};
use style::selector_matching::{Stylist, DeclarationBlock};
use util::arc_ptr_eq;
use util::cache::{LRUCache, SimpleHashCache};
use util::smallvec::{SmallVec, SmallVec16};
pub struct ApplicableDeclarations {
pub normal: SmallVec16<DeclarationBlock>,
@ -399,7 +403,8 @@ pub trait MatchMethods {
layout_context: &SharedLayoutContext,
parent: Option<LayoutNode>,
applicable_declarations: &ApplicableDeclarations,
applicable_declarations_cache: &mut ApplicableDeclarationsCache);
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
new_animations_sender: &Sender<Animation>);
}
trait PrivateMatchMethods {
@ -408,8 +413,9 @@ trait PrivateMatchMethods {
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache,
applicable_declarations_cache:
&mut ApplicableDeclarationsCache,
new_animations_sender: &Sender<Animation>,
shareable: bool)
-> RestyleDamage;
@ -425,11 +431,12 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
parent_style: Option<&Arc<ComputedValues>>,
applicable_declarations: &[DeclarationBlock],
style: &mut Option<Arc<ComputedValues>>,
applicable_declarations_cache: &mut
ApplicableDeclarationsCache,
applicable_declarations_cache:
&mut ApplicableDeclarationsCache,
new_animations_sender: &Sender<Animation>,
shareable: bool)
-> RestyleDamage {
let this_style;
let mut this_style;
let cacheable;
match parent_style {
Some(ref parent_style) => {
@ -444,7 +451,7 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
Some(&***parent_style),
cached_computed_values);
cacheable = is_cacheable;
this_style = Arc::new(the_style);
this_style = the_style
}
None => {
let (the_style, is_cacheable) = cascade(layout_context.screen_size,
@ -453,22 +460,39 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
None,
None);
cacheable = is_cacheable;
this_style = Arc::new(the_style);
this_style = the_style
}
};
// Trigger transitions if necessary. This will reset `this_style` back to its old value if
// it did trigger a transition.
match *style {
None => {
// This is a newly-created node; we've nothing to transition from!
}
Some(ref style) => {
let node = OpaqueNodeMethods::from_layout_node(self);
animation::start_transitions_if_applicable(new_animations_sender,
node,
&**style,
&mut this_style);
}
}
// Calculate style difference.
let this_style = Arc::new(this_style);
let damage = incremental::compute_damage(style, &*this_style);
// Cache the resolved style if it was cacheable.
if cacheable {
applicable_declarations_cache.insert(applicable_declarations.to_vec(), this_style.clone());
}
// Calculate style difference and write.
let damage = incremental::compute_damage(style, &*this_style);
// Write in the final style and return the damage done to our caller.
*style = Some(this_style);
damage
}
fn share_style_with_candidate_if_possible(&self,
parent_node: Option<LayoutNode>,
candidate: &StyleSharingCandidate)
@ -615,7 +639,8 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
layout_context: &SharedLayoutContext,
parent: Option<LayoutNode>,
applicable_declarations: &ApplicableDeclarations,
applicable_declarations_cache: &mut ApplicableDeclarationsCache) {
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
new_animations_sender: &Sender<Animation>) {
// Get our parent's style. This must be unsafe so that we don't touch the parent's
// borrow flags.
//
@ -625,8 +650,12 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
None => None,
Some(parent_node) => {
let parent_layout_data_ref = parent_node.borrow_layout_data_unchecked();
let parent_layout_data = (&*parent_layout_data_ref).as_ref().expect("no parent data!?");
let parent_style = parent_layout_data.shared_data.style.as_ref().expect("parent hasn't been styled yet!");
let parent_layout_data = (&*parent_layout_data_ref).as_ref()
.expect("no parent data!?");
let parent_style = parent_layout_data.shared_data
.style
.as_ref()
.expect("parent hasn't been styled yet!");
Some(parent_style)
}
};
@ -651,6 +680,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
applicable_declarations.normal.as_slice(),
&mut layout_data.shared_data.style,
applicable_declarations_cache,
new_animations_sender,
applicable_declarations.normal_shareable);
if applicable_declarations.before.len() > 0 {
damage = damage | self.cascade_node_pseudo_element(
@ -659,6 +689,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
&*applicable_declarations.before,
&mut layout_data.data.before_style,
applicable_declarations_cache,
new_animations_sender,
false);
}
if applicable_declarations.after.len() > 0 {
@ -668,6 +699,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
&*applicable_declarations.after,
&mut layout_data.data.after_style,
applicable_declarations_cache,
new_animations_sender,
false);
}
layout_data.data.restyle_damage = damage;

Просмотреть файл

@ -7,9 +7,9 @@
#![allow(unsafe_code)]
use animation;
use construct::ConstructionResult;
use context::{SharedLayoutContext, SharedLayoutContextWrapper};
use css::node_style::StyledNode;
use display_list_builder::ToGfxColor;
use flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
use flow_ref::FlowRef;
@ -49,11 +49,10 @@ use profile::time::{self, ProfilerMetadata, profile};
use profile::time::{TimerMetadataFrameType, TimerMetadataReflowType};
use script::dom::bindings::js::LayoutJS;
use script::dom::node::{LayoutData, Node};
use script::layout_interface::ReflowQueryType;
use script::layout_interface::{ContentBoxResponse, ContentBoxesResponse};
use script::layout_interface::{Animation, ContentBoxResponse, ContentBoxesResponse};
use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC};
use script::layout_interface::{MouseOverResponse, Msg};
use script::layout_interface::{Reflow, ReflowGoal, ScriptLayoutChan, TrustedNodeAddress};
use script::layout_interface::{MouseOverResponse, Msg, Reflow, ReflowGoal, ReflowQueryType};
use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress};
use script_traits::{ConstellationControlMsg, CompositorEvent, OpaqueScriptLayoutChannel};
use script_traits::{ScriptControlChan, UntrustedNodeAddress};
use std::borrow::ToOwned;
@ -70,7 +69,7 @@ use style::selector_matching::Stylist;
use style::stylesheets::{Origin, Stylesheet, iter_font_face_rules};
use url::Url;
use util::cursor::Cursor;
use util::geometry::Au;
use util::geometry::{Au, MAX_RECT};
use util::logical_geometry::LogicalPoint;
use util::mem::HeapSizeOf;
use util::opts;
@ -83,6 +82,9 @@ use util::workqueue::WorkQueue;
///
/// This needs to be protected by a mutex so we can do fast RPCs.
pub struct LayoutTaskData {
/// The root of the flow tree.
pub root_flow: Option<FlowRef>,
/// The local image cache.
pub local_image_cache: Arc<Mutex<LocalImageCache<UntrustedNodeAddress>>>,
@ -113,6 +115,16 @@ pub struct LayoutTaskData {
/// A queued response for the content boxes of a node.
pub content_boxes_response: Vec<Rect<Au>>,
/// The list of currently-running animations.
pub running_animations: Vec<Animation>,
/// Receives newly-discovered animations.
pub new_animations_receiver: Receiver<Animation>,
/// A channel on which new animations that have been triggered by style recalculation can be
/// sent.
pub new_animations_sender: Sender<Animation>,
}
/// Information needed by the layout task.
@ -129,7 +141,7 @@ pub struct LayoutTask {
/// The port on which we receive messages from the constellation
pub pipeline_port: Receiver<LayoutControlMsg>,
//// The channel to send messages to ourself.
/// The channel on which we or others can send messages to ourselves.
pub chan: LayoutChan,
/// The channel on which messages can be sent to the constellation.
@ -192,39 +204,37 @@ impl ImageResponder<UntrustedNodeAddress> for LayoutImageResponder {
impl LayoutTaskFactory for LayoutTask {
/// Spawns a new layout task.
fn create(_phantom: Option<&mut LayoutTask>,
id: PipelineId,
url: Url,
chan: OpaqueScriptLayoutChannel,
pipeline_port: Receiver<LayoutControlMsg>,
constellation_chan: ConstellationChan,
failure_msg: Failure,
script_chan: ScriptControlChan,
paint_chan: PaintChan,
resource_task: ResourceTask,
img_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
time_profiler_chan: time::ProfilerChan,
mem_profiler_chan: mem::ProfilerChan,
shutdown_chan: Sender<()>) {
id: PipelineId,
url: Url,
chan: OpaqueScriptLayoutChannel,
pipeline_port: Receiver<LayoutControlMsg>,
constellation_chan: ConstellationChan,
failure_msg: Failure,
script_chan: ScriptControlChan,
paint_chan: PaintChan,
resource_task: ResourceTask,
img_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
time_profiler_chan: time::ProfilerChan,
memory_profiler_chan: mem::ProfilerChan,
shutdown_chan: Sender<()>) {
let ConstellationChan(con_chan) = constellation_chan.clone();
spawn_named_with_send_on_failure("LayoutTask", task_state::LAYOUT, move || {
{ // Ensures layout task is destroyed before we send shutdown message
let sender = chan.sender();
let layout =
LayoutTask::new(
id,
url,
chan.receiver(),
LayoutChan(sender),
pipeline_port,
constellation_chan,
script_chan,
paint_chan,
resource_task,
img_cache_task,
font_cache_task,
time_profiler_chan,
mem_profiler_chan);
let layout = LayoutTask::new(id,
url,
chan.receiver(),
LayoutChan(sender),
pipeline_port,
constellation_chan,
script_chan,
paint_chan,
resource_task,
img_cache_task,
font_cache_task,
time_profiler_chan,
memory_profiler_chan);
layout.start();
}
shutdown_chan.send(()).unwrap();
@ -297,6 +307,10 @@ impl LayoutTask {
let reporter_name = format!("layout-reporter-{}", id.0);
mem_profiler_chan.send(mem::ProfilerMsg::RegisterReporter(reporter_name.clone(), reporter));
// Create the channel on which new animations can be sent.
let (new_animations_sender, new_animations_receiver) = channel();
LayoutTask {
id: id,
url: url,
@ -315,6 +329,7 @@ impl LayoutTask {
first_reflow: Cell::new(true),
rw_data: Arc::new(Mutex::new(
LayoutTaskData {
root_flow: None,
local_image_cache: local_image_cache,
constellation_chan: constellation_chan,
screen_size: screen_size,
@ -325,6 +340,9 @@ impl LayoutTask {
generation: 0,
content_box_response: Rect::zero(),
content_boxes_response: Vec::new(),
running_animations: Vec::new(),
new_animations_receiver: new_animations_receiver,
new_animations_sender: new_animations_sender,
})),
}
}
@ -341,7 +359,7 @@ impl LayoutTask {
fn build_shared_layout_context(&self,
rw_data: &LayoutTaskData,
screen_size_changed: bool,
reflow_root: &LayoutNode,
reflow_root: Option<&LayoutNode>,
url: &Url)
-> SharedLayoutContext {
SharedLayoutContext {
@ -353,9 +371,10 @@ impl LayoutTask {
font_cache_task: self.font_cache_task.clone(),
stylist: &*rw_data.stylist,
url: (*url).clone(),
reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root),
reflow_root: reflow_root.map(|node| OpaqueNodeMethods::from_layout_node(node)),
dirty: Rect::zero(),
generation: rw_data.generation,
new_animations_sender: rw_data.new_animations_sender.clone(),
}
}
@ -389,8 +408,12 @@ impl LayoutTask {
match port_to_read {
PortToRead::Pipeline => {
match self.pipeline_port.recv().unwrap() {
LayoutControlMsg::TickAnimationsMsg => {
self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data)
}
LayoutControlMsg::ExitNowMsg(exit_type) => {
self.handle_request_helper(Msg::ExitNow(exit_type), possibly_locked_rw_data)
self.handle_request_helper(Msg::ExitNow(exit_type),
possibly_locked_rw_data)
}
}
},
@ -434,8 +457,12 @@ impl LayoutTask {
LayoutTaskData>>)
-> bool {
match request {
Msg::AddStylesheet(sheet, mq) => self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data),
Msg::LoadStylesheet(url, mq) => self.handle_load_stylesheet(url, mq, possibly_locked_rw_data),
Msg::AddStylesheet(sheet, mq) => {
self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data)
}
Msg::LoadStylesheet(url, mq) => {
self.handle_load_stylesheet(url, mq, possibly_locked_rw_data)
}
Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data),
Msg::GetRPC(response_chan) => {
response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as
@ -443,10 +470,11 @@ impl LayoutTask {
},
Msg::Reflow(data) => {
profile(time::ProfilerCategory::LayoutPerform,
self.profiler_metadata(&*data),
self.profiler_metadata(&data.reflow_info),
self.time_profiler_chan.clone(),
|| self.handle_reflow(&*data, possibly_locked_rw_data));
},
Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data),
Msg::ReapLayoutData(dead_layout_data) => {
unsafe {
self.handle_reap_layout_data(dead_layout_data)
@ -486,9 +514,9 @@ impl LayoutTask {
reports_chan.send(reports);
}
/// Enters a quiescent state in which no new messages except for `layout_interface::Msg::ReapLayoutData` will be
/// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given
/// response channel.
/// Enters a quiescent state in which no new messages except for
/// `layout_interface::Msg::ReapLayoutData` will be processed until an `ExitNowMsg` is
/// received. A pong is immediately sent on the given response channel.
fn prepare_to_exit<'a>(&'a self,
response_chan: Sender<()>,
possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
@ -586,7 +614,6 @@ impl LayoutTask {
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
}
/// Retrieves the flow tree root from the root node.
fn try_get_layout_root(&self, node: LayoutNode) -> Option<FlowRef> {
let mut layout_data_ref = node.mutate_layout_data();
let layout_data =
@ -698,7 +725,7 @@ impl LayoutTask {
data: &Reflow,
layout_root: &mut FlowRef,
shared_layout_context: &mut SharedLayoutContext,
rw_data: &mut RWGuard<'a>) {
rw_data: &mut LayoutTaskData) {
let writing_mode = flow::base(&**layout_root).writing_mode;
profile(time::ProfilerCategory::LayoutDispListBuild,
self.profiler_metadata(data),
@ -714,7 +741,6 @@ impl LayoutTask {
flow::mut_base(&mut **layout_root).clip =
ClippingRegion::from_rect(&data.page_clip_rect);
let rw_data = &mut **rw_data;
match rw_data.parallel_traversal {
None => {
sequential::build_display_list_for_subtree(layout_root, shared_layout_context);
@ -767,7 +793,7 @@ impl LayoutTask {
/// The high-level routine that performs layout tasks.
fn handle_reflow<'a>(&'a self,
data: &Reflow,
data: &ScriptReflow,
possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
// FIXME: Isolate this transmutation into a "bridge" module.
// FIXME(rust#16366): The following line had to be moved because of a
@ -779,8 +805,7 @@ impl LayoutTask {
transmute(&mut node)
};
debug!("layout: received layout request for: {}", data.url.serialize());
debug!("layout: parsed Node tree");
debug!("layout: received layout request for: {}", data.reflow_info.url.serialize());
if log_enabled!(log::DEBUG) {
node.dump();
}
@ -796,7 +821,6 @@ impl LayoutTask {
// TODO: Calculate the "actual viewport":
// http://www.w3.org/TR/css-device-adapt/#actual-viewport
let viewport_size = data.window_size.initial_viewport;
let old_screen_size = rw_data.screen_size;
let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()),
Au::from_frac32_px(viewport_size.height.get()));
@ -804,23 +828,19 @@ impl LayoutTask {
// Handle conditions where the entire flow tree is invalid.
let screen_size_changed = current_screen_size != old_screen_size;
if screen_size_changed {
let device = Device::new(MediaType::Screen, data.window_size.initial_viewport);
rw_data.stylist.set_device(device);
}
let needs_dirtying = rw_data.stylist.update();
// If the entire flow tree is invalid, then it will be reflowed anyhow.
let needs_dirtying = rw_data.stylist.update();
let needs_reflow = screen_size_changed && !needs_dirtying;
unsafe {
if needs_dirtying {
LayoutTask::dirty_all_nodes(node);
}
}
if needs_reflow {
match self.try_get_layout_root(*node) {
None => {}
@ -833,13 +853,14 @@ impl LayoutTask {
// Create a layout context for use throughout the following passes.
let mut shared_layout_context = self.build_shared_layout_context(&*rw_data,
screen_size_changed,
node,
&data.url);
Some(&node),
&data.reflow_info.url);
let mut layout_root = profile(time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
|| {
// Recalculate CSS styles and rebuild flows and fragments.
profile(time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(&data.reflow_info),
self.time_profiler_chan.clone(),
|| {
// Perform CSS selector matching and flow construction.
let rw_data = &mut *rw_data;
match rw_data.parallel_traversal {
@ -847,39 +868,105 @@ impl LayoutTask {
sequential::traverse_dom_preorder(*node, &shared_layout_context);
}
Some(ref mut traversal) => {
parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal)
parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal);
}
}
self.get_layout_root((*node).clone())
});
// Retrieve the (possibly rebuilt) root flow.
rw_data.root_flow = Some(self.get_layout_root((*node).clone()));
// Kick off animations if any were triggered.
animation::process_new_animations(&mut *rw_data, self.id);
// Perform post-style recalculation layout passes.
self.perform_post_style_recalc_layout_passes(&data.reflow_info,
&mut rw_data,
&mut shared_layout_context);
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::NoQuery => {}
}
// Tell script that we're done.
//
// FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
// either select or a filtered recv() that only looks for messages of a given type.
data.script_join_chan.send(()).unwrap();
let ScriptControlChan(ref chan) = data.script_chan;
chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap();
}
fn tick_all_animations<'a>(&'a self,
possibly_locked_rw_data: &mut Option<MutexGuard<'a,
LayoutTaskData>>) {
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
animation::tick_all_animations(self, &mut rw_data)
}
pub fn tick_animation<'a>(&'a self, animation: Animation, rw_data: &mut LayoutTaskData) {
// FIXME(#5466, pcwalton): These data are lies.
let reflow_info = Reflow {
goal: ReflowGoal::ForDisplay,
url: Url::parse("http://animation.com/").unwrap(),
iframe: false,
page_clip_rect: MAX_RECT,
};
// Perform an abbreviated style recalc that operates without access to the DOM.
let mut layout_context = self.build_shared_layout_context(&*rw_data,
false,
None,
&reflow_info.url);
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
profile(time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(&reflow_info),
self.time_profiler_chan.clone(),
|| animation::recalc_style_for_animation(root_flow.deref_mut(), &animation));
self.perform_post_style_recalc_layout_passes(&reflow_info,
&mut *rw_data,
&mut layout_context);
}
fn perform_post_style_recalc_layout_passes<'a>(&'a self,
data: &Reflow,
rw_data: &mut LayoutTaskData,
layout_context: &mut SharedLayoutContext) {
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
profile(time::ProfilerCategory::LayoutRestyleDamagePropagation,
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
|| {
if opts::get().nonincremental_layout || layout_root.compute_layout_damage()
.contains(REFLOW_ENTIRE_DOCUMENT) {
layout_root.reflow_entire_document()
if opts::get().nonincremental_layout || root_flow.deref_mut()
.compute_layout_damage()
.contains(REFLOW_ENTIRE_DOCUMENT) {
root_flow.deref_mut().reflow_entire_document()
}
});
// Verification of the flow tree, which ensures that all nodes were either marked as leaves
// or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to
// memory safety but is a useful debugging tool.)
self.verify_flow_tree(&mut layout_root);
self.verify_flow_tree(&mut root_flow);
if opts::get().trace_layout {
layout_debug::begin_trace(layout_root.clone());
layout_debug::begin_trace(root_flow.clone());
}
// Resolve generated content.
profile(time::ProfilerCategory::LayoutGeneratedContent,
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
|| {
sequential::resolve_generated_content(&mut layout_root, &shared_layout_context)
});
|| sequential::resolve_generated_content(&mut root_flow, &layout_context));
// Perform the primary layout passes over the flow tree to compute the locations of all
// the boxes.
@ -887,18 +974,17 @@ impl LayoutTask {
self.profiler_metadata(data),
self.time_profiler_chan.clone(),
|| {
let rw_data = &mut *rw_data;
match rw_data.parallel_traversal {
None => {
// Sequential mode.
self.solve_constraints(&mut layout_root, &shared_layout_context)
self.solve_constraints(&mut root_flow, &layout_context)
}
Some(_) => {
// Parallel mode.
self.solve_constraints_parallel(data,
rw_data,
&mut layout_root,
&mut shared_layout_context);
&mut root_flow,
&mut *layout_context);
}
}
});
@ -907,23 +993,13 @@ impl LayoutTask {
match data.goal {
ReflowGoal::ForDisplay => {
self.build_display_list_for_reflow(data,
&mut layout_root,
&mut shared_layout_context,
&mut rw_data);
&mut root_flow,
&mut *layout_context,
rw_data);
}
ReflowGoal::ForScriptQuery => {}
}
match data.query_type {
ReflowQueryType::ContentBoxQuery(node) => {
self.process_content_box_request(node, &mut layout_root, &mut rw_data)
}
ReflowQueryType::ContentBoxesQuery(node) => {
self.process_content_boxes_request(node, &mut layout_root, &mut rw_data)
}
ReflowQueryType::NoQuery => {}
}
self.first_reflow.set(false);
if opts::get().trace_layout {
@ -931,18 +1007,10 @@ impl LayoutTask {
}
if opts::get().dump_flow_tree {
layout_root.dump();
root_flow.dump();
}
rw_data.generation += 1;
// Tell script that we're done.
//
// FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
// either select or a filtered recv() that only looks for messages of a given type.
data.script_join_chan.send(()).unwrap();
let ScriptControlChan(ref chan) = data.script_chan;
chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap();
}
unsafe fn dirty_all_nodes(node: &mut LayoutNode) {

Просмотреть файл

@ -25,40 +25,45 @@
#[macro_use]
extern crate log;
#[macro_use]extern crate bitflags;
extern crate azure;
extern crate cssparser;
extern crate canvas;
extern crate geom;
extern crate gfx;
extern crate layout_traits;
extern crate script;
extern crate script_traits;
extern crate "rustc-serialize" as rustc_serialize;
extern crate png;
extern crate style;
#[macro_use]
extern crate bitflags;
#[macro_use]
#[no_link]
extern crate "plugins" as servo_plugins;
extern crate net;
extern crate msg;
#[macro_use]
extern crate profile;
extern crate selectors;
#[macro_use]
extern crate util;
extern crate string_cache;
extern crate "rustc-serialize" as rustc_serialize;
extern crate alloc;
extern crate azure;
extern crate canvas;
extern crate clock_ticks;
extern crate collections;
extern crate cssparser;
extern crate encoding;
extern crate geom;
extern crate gfx;
extern crate layout_traits;
extern crate libc;
extern crate msg;
extern crate net;
extern crate png;
extern crate script;
extern crate script_traits;
extern crate selectors;
extern crate string_cache;
extern crate style;
extern crate url;
// Listed first because of macro definitions
pub mod layout_debug;
pub mod animation;
pub mod block;
pub mod construct;
pub mod context;

Просмотреть файл

@ -180,7 +180,8 @@ impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> {
node.cascade_node(self.layout_context.shared,
parent_opt,
&applicable_declarations,
self.layout_context.applicable_declarations_cache());
self.layout_context.applicable_declarations_cache(),
&self.layout_context.shared.new_animations_sender);
}
// Add ourselves to the LRU cache.

Просмотреть файл

@ -333,11 +333,16 @@ impl<'ln> LayoutNode<'ln> {
/// While doing a reflow, the node at the root has no parent, as far as we're
/// concerned. This method returns `None` at the reflow root.
pub fn layout_parent_node(self, shared: &SharedLayoutContext) -> Option<LayoutNode<'ln>> {
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&self);
if opaque_node == shared.reflow_root {
None
} else {
self.parent_node()
match shared.reflow_root {
None => panic!("layout_parent_node(): This layout has no access to the DOM!"),
Some(reflow_root) => {
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&self);
if opaque_node == reflow_root {
None
} else {
self.parent_node()
}
}
}
}

Просмотреть файл

@ -29,6 +29,7 @@ use url::Url;
/// Messages sent to the layout task from the constellation
pub enum LayoutControlMsg {
ExitNowMsg(PipelineExitType),
TickAnimationsMsg,
}
/// A channel wrapper for constellation messages

Просмотреть файл

@ -214,6 +214,10 @@ pub enum Msg {
SetCursor(Cursor),
/// Dispatch a mozbrowser event to a given iframe. Only available in experimental mode.
MozBrowserEventMsg(PipelineId, SubpageId, MozBrowserEvent),
/// Indicates whether this pipeline is currently running animations.
ChangeRunningAnimationsState(PipelineId, bool),
/// Requests that the constellation instruct layout to begin a new tick of the animation.
TickAnimation(PipelineId),
}
// https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API#Events

Просмотреть файл

@ -186,4 +186,10 @@ partial interface CSSStyleDeclaration {
[TreatNullAs=EmptyString] attribute DOMString zIndex;
[TreatNullAs=EmptyString] attribute DOMString imageRendering;
[TreatNullAs=EmptyString] attribute DOMString transition;
[TreatNullAs=EmptyString] attribute DOMString transitionDuration;
[TreatNullAs=EmptyString] attribute DOMString transitionTimingFunction;
[TreatNullAs=EmptyString] attribute DOMString transitionProperty;
[TreatNullAs=EmptyString] attribute DOMString transitionDelay;
};

Просмотреть файл

@ -27,7 +27,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};
use layout_interface::{ContentBoxResponse, ContentBoxesResponse, ScriptReflow};
use page::Page;
use script_task::{TimerSource, ScriptChan};
use script_task::ScriptMsg;
@ -564,17 +564,19 @@ impl<'a> WindowHelpers for JSRef<'a, Window> {
}
// Send new document and relevant styles to layout.
let reflow = box Reflow {
let reflow = box ScriptReflow {
reflow_info: Reflow {
goal: goal,
url: self.get_url(),
iframe: self.parent_info.is_some(),
page_clip_rect: self.page_clip_rect.get(),
},
document_root: root.to_trusted_node_address(),
url: self.get_url(),
iframe: self.parent_info.is_some(),
goal: goal,
window_size: window_size,
script_chan: self.control_chan.clone(),
script_join_chan: join_chan,
id: last_reflow_id.get(),
query_type: query_type,
page_clip_rect: self.page_clip_rect.get(),
};
let LayoutChan(ref chan) = self.layout_chan;

Просмотреть файл

@ -10,14 +10,16 @@ use dom::node::LayoutData;
use geom::point::Point2D;
use geom::rect::Rect;
use libc::uintptr_t;
use msg::constellation_msg::{PipelineExitType, WindowSizeData};
use profile::mem::{Reporter, ReportsChan};
use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel, UntrustedNodeAddress};
use std::any::Any;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::boxed::BoxAny;
use style::stylesheets::Stylesheet;
use style::animation::PropertyAnimation;
use style::media_queries::MediaQueryList;
use style::stylesheets::Stylesheet;
use url::Url;
use util::geometry::Au;
@ -35,11 +37,14 @@ pub enum Msg {
SetQuirksMode,
/// Requests a reflow.
Reflow(Box<Reflow>),
Reflow(Box<ScriptReflow>),
/// Get an RPC interface.
GetRPC(Sender<Box<LayoutRPC + Send>>),
/// Requests that the layout task render the next frame of all animations.
TickAnimations,
/// Destroys layout data associated with a DOM node.
///
/// TODO(pcwalton): Maybe think about batching to avoid message traffic.
@ -100,14 +105,22 @@ pub enum ReflowQueryType {
/// Information needed for a reflow.
pub struct Reflow {
/// The document node.
pub document_root: TrustedNodeAddress,
/// The goal of reflow: either to render to the screen or to flush layout info for script.
pub goal: ReflowGoal,
/// The URL of the page.
pub url: Url,
/// Is the current reflow of an iframe, as opposed to a root window?
pub iframe: bool,
/// A clipping rectangle for the page, an enlarged rectangle containing the viewport.
pub page_clip_rect: Rect<Au>,
}
/// Information needed for a script-initiated reflow.
pub struct ScriptReflow {
/// General reflow data.
pub reflow_info: Reflow,
/// The document node.
pub document_root: TrustedNodeAddress,
/// The channel through which messages can be sent back to the script task.
pub script_chan: ScriptControlChan,
/// The current window size.
@ -118,8 +131,6 @@ pub struct Reflow {
pub id: u32,
/// The type of query if any to perform during this reflow.
pub query_type: ReflowQueryType,
/// A clipping rectangle for the page, an enlarged rectangle containing the viewport.
pub page_clip_rect: Rect<Au>,
}
/// Encapsulates a channel to the layout task.
@ -165,3 +176,28 @@ impl ScriptLayoutChan for OpaqueScriptLayoutChannel {
*receiver.downcast::<Receiver<Msg>>().unwrap()
}
}
/// Type of an opaque node.
pub type OpaqueNode = uintptr_t;
/// State relating to an animation.
#[derive(Copy, Clone)]
pub struct Animation {
/// An opaque reference to the DOM node participating in the animation.
pub node: OpaqueNode,
/// A description of the property animation that is occurring.
pub property_animation: PropertyAnimation,
/// The start time of the animation, as returned by `time::precise_time_s()`.
pub start_time: f64,
/// The end time of the animation, as returned by `time::precise_time_s()`.
pub end_time: f64,
}
impl Animation {
/// Returns the duration of this animation in seconds.
#[inline]
pub fn duration(&self) -> f64 {
self.end_time - self.start_time
}
}

6
servo/components/servo/Cargo.lock сгенерированный
Просмотреть файл

@ -68,6 +68,11 @@ dependencies = [
"gleam 0.0.1 (git+https://github.com/servo/gleam)",
]
[[package]]
name = "clock_ticks"
version = "0.0.4"
source = "git+https://github.com/tomaka/clock_ticks#6a3005279bedc406b13eea09ff92447f05ca0de6"
[[package]]
name = "cocoa"
version = "0.1.1"
@ -503,6 +508,7 @@ dependencies = [
"azure 0.1.0 (git+https://github.com/servo/rust-azure)",
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"canvas 0.0.1",
"clock_ticks 0.0.4 (git+https://github.com/tomaka/clock_ticks)",
"cssparser 0.2.0 (git+https://github.com/servo/rust-cssparser)",
"encoding 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",

Просмотреть файл

@ -0,0 +1,221 @@
/* 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/. */
use properties::ComputedValues;
use properties::longhands::transition_property::computed_value::TransitionProperty;
use properties::longhands::transition_timing_function::computed_value::{StartEnd};
use properties::longhands::transition_timing_function::computed_value::{TransitionTimingFunction};
use properties::longhands::transition_property;
use values::computed::{LengthOrPercentageOrAuto, Time};
use std::num::Float;
use util::bezier::Bezier;
use util::geometry::Au;
#[derive(Copy, Clone, Debug)]
pub struct PropertyAnimation {
property: AnimatedProperty,
timing_function: TransitionTimingFunction,
duration: Time,
}
impl PropertyAnimation {
/// Creates a new property animation for the given transition index and old and new styles.
/// Any number of animations may be returned, from zero (if the property did not animate) to
/// one (for a single transition property) to arbitrarily many (for `all`).
pub fn from_transition(transition_index: usize,
old_style: &ComputedValues,
new_style: &mut ComputedValues)
-> Vec<PropertyAnimation> {
let mut result = Vec::new();
let transition_property =
new_style.get_animation().transition_property.0[transition_index];
if transition_property != TransitionProperty::All {
if let Some(property_animation) =
PropertyAnimation::from_transition_property(transition_property,
transition_index,
old_style,
new_style) {
result.push(property_animation)
}
return result
}
for transition_property in
transition_property::computed_value::ALL_TRANSITION_PROPERTIES.iter() {
if let Some(property_animation) =
PropertyAnimation::from_transition_property(*transition_property,
transition_index,
old_style,
new_style) {
result.push(property_animation)
}
}
result
}
fn from_transition_property(transition_property: TransitionProperty,
transition_index: usize,
old_style: &ComputedValues,
new_style: &mut ComputedValues)
-> Option<PropertyAnimation> {
let animation_style = new_style.get_animation();
let animated_property = match transition_property {
TransitionProperty::All => {
panic!("Don't use `TransitionProperty::All` with \
`PropertyAnimation::from_transition_property`!")
}
TransitionProperty::Top => {
AnimatedProperty::Top(old_style.get_positionoffsets().top,
new_style.get_positionoffsets().top)
}
TransitionProperty::Right => {
AnimatedProperty::Right(old_style.get_positionoffsets().right,
new_style.get_positionoffsets().right)
}
TransitionProperty::Bottom => {
AnimatedProperty::Bottom(old_style.get_positionoffsets().bottom,
new_style.get_positionoffsets().bottom)
}
TransitionProperty::Left => {
AnimatedProperty::Left(old_style.get_positionoffsets().left,
new_style.get_positionoffsets().left)
}
};
let property_animation = PropertyAnimation {
property: animated_property,
timing_function:
*animation_style.transition_timing_function.0.get_mod(transition_index),
duration: *animation_style.transition_duration.0.get_mod(transition_index),
};
if property_animation.does_not_animate() {
None
} else {
Some(property_animation)
}
}
pub fn update(&self, style: &mut ComputedValues, time: f64) {
let progress = match self.timing_function {
TransitionTimingFunction::CubicBezier(p1, p2) => {
// See `WebCore::AnimationBase::solveEpsilon(double)` in WebKit.
let epsilon = 1.0 / (200.0 * self.duration.seconds());
Bezier::new(p1, p2).solve(time, epsilon)
}
TransitionTimingFunction::Steps(steps, StartEnd::Start) => {
(time * (steps as f64)).ceil() / (steps as f64)
}
TransitionTimingFunction::Steps(steps, StartEnd::End) => {
(time * (steps as f64)).floor() / (steps as f64)
}
};
match self.property {
AnimatedProperty::Top(ref start, ref end) => {
if let Some(value) = start.interpolate(end, progress) {
style.mutate_positionoffsets().top = value
}
}
AnimatedProperty::Right(ref start, ref end) => {
if let Some(value) = start.interpolate(end, progress) {
style.mutate_positionoffsets().right = value
}
}
AnimatedProperty::Bottom(ref start, ref end) => {
if let Some(value) = start.interpolate(end, progress) {
style.mutate_positionoffsets().bottom = value
}
}
AnimatedProperty::Left(ref start, ref end) => {
if let Some(value) = start.interpolate(end, progress) {
style.mutate_positionoffsets().left = value
}
}
}
}
#[inline]
fn does_not_animate(&self) -> bool {
self.property.does_not_animate() || self.duration == Time(0.0)
}
}
#[derive(Copy, Clone, Debug)]
enum AnimatedProperty {
Top(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
Right(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
Bottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
Left(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
}
impl AnimatedProperty {
#[inline]
fn does_not_animate(&self) -> bool {
match *self {
AnimatedProperty::Top(ref a, ref b) |
AnimatedProperty::Right(ref a, ref b) |
AnimatedProperty::Bottom(ref a, ref b) |
AnimatedProperty::Left(ref a, ref b) => a == b,
}
}
}
trait Interpolate {
fn interpolate(&self, other: &Self, time: f64) -> Option<Self>;
}
impl Interpolate for Au {
#[inline]
fn interpolate(&self, other: &Au, time: f64) -> Option<Au> {
Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32))
}
}
impl Interpolate for f64 {
#[inline]
fn interpolate(&self, other: &f64, time: f64) -> Option<f64> {
Some(*self + (*other - *self) * time)
}
}
impl Interpolate for LengthOrPercentageOrAuto {
#[inline]
fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64)
-> Option<LengthOrPercentageOrAuto> {
match (*self, *other) {
(LengthOrPercentageOrAuto::Length(ref this),
LengthOrPercentageOrAuto::Length(ref other)) => {
this.interpolate(other, time).and_then(|value| {
Some(LengthOrPercentageOrAuto::Length(value))
})
}
(LengthOrPercentageOrAuto::Percentage(ref this),
LengthOrPercentageOrAuto::Percentage(ref other)) => {
this.interpolate(other, time).and_then(|value| {
Some(LengthOrPercentageOrAuto::Percentage(value))
})
}
(LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => {
Some(LengthOrPercentageOrAuto::Auto)
}
(_, _) => None,
}
}
}
/// Accesses an element of an array, "wrapping around" using modular arithmetic. This is needed
/// to handle values of differing lengths according to CSS-TRANSITIONS § 2.
pub trait GetMod {
type Item;
fn get_mod(&self, i: usize) -> &Self::Item;
}
impl<T> GetMod for Vec<T> {
type Item = T;
fn get_mod(&self, i: usize) -> &T {
&(*self)[i % self.len()]
}
}

Просмотреть файл

@ -50,6 +50,7 @@ pub mod node;
pub mod media_queries;
pub mod font_face;
pub mod legacy;
pub mod animation;
macro_rules! reexport_computed_values {
( $( $name: ident )+ ) => {

Просмотреть файл

@ -3377,6 +3377,367 @@ pub mod longhands {
}
}
</%self:longhand>
${new_style_struct("Animation", is_inherited=False)}
// TODO(pcwalton): Multiple transitions.
<%self:longhand name="transition-duration">
use values::specified::Time;
pub use self::computed_value::T as SpecifiedValue;
pub use values::specified::Time as SingleSpecifiedValue;
pub mod computed_value {
use cssparser::ToCss;
use text_writer::{self, TextWriter};
use values::computed::{Context, ToComputedValue};
pub use values::computed::Time as SingleComputedValue;
#[derive(Clone, PartialEq)]
pub struct T(pub Vec<SingleComputedValue>);
impl ToComputedValue for T {
type ComputedValue = T;
#[inline]
fn to_computed_value(&self, _: &Context) -> T {
(*self).clone()
}
}
impl ToCss for T {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
if self.0.is_empty() {
return dest.write_str("none")
}
for (i, value) in self.0.iter().enumerate() {
if i != 0 {
try!(dest.write_str(", "))
}
try!(value.to_css(dest))
}
Ok(())
}
}
}
#[inline]
pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> {
Time::parse(input)
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T(vec![get_initial_single_value()])
}
#[inline]
pub fn get_initial_single_value() -> Time {
Time(0.0)
}
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
}
</%self:longhand>
// TODO(pcwalton): Lots more timing functions.
// TODO(pcwalton): Multiple transitions.
<%self:longhand name="transition-timing-function">
use self::computed_value::{StartEnd, TransitionTimingFunction};
use values::computed::{Context, ToComputedValue};
use geom::point::Point2D;
pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue;
pub use self::computed_value::T as SpecifiedValue;
static EASE: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
x: 0.25,
y: 0.1,
}, Point2D {
x: 0.25,
y: 1.0,
});
static LINEAR: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
x: 0.0,
y: 0.0,
}, Point2D {
x: 1.0,
y: 1.0,
});
static EASE_IN: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
x: 0.42,
y: 0.0,
}, Point2D {
x: 1.0,
y: 1.0,
});
static EASE_OUT: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
x: 0.0,
y: 0.0,
}, Point2D {
x: 0.58,
y: 1.0,
});
static EASE_IN_OUT: TransitionTimingFunction =
TransitionTimingFunction::CubicBezier(Point2D {
x: 0.42,
y: 0.0,
}, Point2D {
x: 0.58,
y: 1.0,
});
static STEP_START: TransitionTimingFunction =
TransitionTimingFunction::Steps(1, StartEnd::Start);
static STEP_END: TransitionTimingFunction =
TransitionTimingFunction::Steps(1, StartEnd::End);
pub mod computed_value {
use cssparser::ToCss;
use geom::point::Point2D;
use text_writer::{self, TextWriter};
pub use self::TransitionTimingFunction as SingleComputedValue;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TransitionTimingFunction {
CubicBezier(Point2D<f64>, Point2D<f64>),
Steps(u32, StartEnd),
}
impl ToCss for TransitionTimingFunction {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
match *self {
TransitionTimingFunction::CubicBezier(p1, p2) => {
try!(dest.write_str("cubic-bezier("));
try!(p1.x.to_css(dest));
try!(dest.write_str(", "));
try!(p1.y.to_css(dest));
try!(dest.write_str(", "));
try!(p2.x.to_css(dest));
try!(dest.write_str(", "));
try!(p2.y.to_css(dest));
dest.write_str(")")
}
TransitionTimingFunction::Steps(steps, start_end) => {
try!(dest.write_str("steps("));
try!(steps.to_css(dest));
try!(dest.write_str(", "));
try!(start_end.to_css(dest));
dest.write_str(")")
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum StartEnd {
Start,
End,
}
impl ToCss for StartEnd {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
match *self {
StartEnd::Start => dest.write_str("start"),
StartEnd::End => dest.write_str("end"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct T(pub Vec<TransitionTimingFunction>);
impl ToCss for T {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
if self.0.is_empty() {
return dest.write_str("none")
}
for (i, value) in self.0.iter().enumerate() {
if i != 0 {
try!(dest.write_str(", "))
}
try!(value.to_css(dest))
}
Ok(())
}
}
}
impl ToComputedValue for SpecifiedValue {
type ComputedValue = computed_value::T;
#[inline]
fn to_computed_value(&self, _: &Context) -> computed_value::T {
(*self).clone()
}
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T(vec![get_initial_single_value()])
}
#[inline]
pub fn get_initial_single_value() -> TransitionTimingFunction {
EASE
}
pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> {
if let Ok(function_name) = input.try(|input| input.expect_function()) {
return match_ignore_ascii_case! {
function_name,
"cubic-bezier" => {
let (mut p1x, mut p1y, mut p2x, mut p2y) = (0.0, 0.0, 0.0, 0.0);
try!(input.parse_nested_block(|input| {
p1x = try!(input.expect_number());
try!(input.expect_comma());
p1y = try!(input.expect_number());
try!(input.expect_comma());
p2x = try!(input.expect_number());
try!(input.expect_comma());
p2y = try!(input.expect_number());
Ok(())
}));
let (p1, p2) = (Point2D(p1x, p1y), Point2D(p2x, p2y));
Ok(TransitionTimingFunction::CubicBezier(p1, p2))
},
"steps" => {
let (mut step_count, mut start_end) = (0, computed_value::StartEnd::Start);
try!(input.parse_nested_block(|input| {
step_count = try!(input.expect_integer());
try!(input.expect_comma());
start_end = try!(match_ignore_ascii_case! {
try!(input.expect_ident()),
"start" => Ok(computed_value::StartEnd::Start),
"end" => Ok(computed_value::StartEnd::End)
_ => Err(())
});
Ok(())
}));
Ok(TransitionTimingFunction::Steps(step_count as u32, start_end))
}
_ => Err(())
}
}
match_ignore_ascii_case! {
try!(input.expect_ident()),
"ease" => Ok(EASE),
"linear" => Ok(LINEAR),
"ease-in" => Ok(EASE_IN),
"ease-out" => Ok(EASE_OUT),
"ease-in-out" => Ok(EASE_IN_OUT),
"step-start" => Ok(STEP_START),
"step-end" => Ok(STEP_END)
_ => Err(())
}
}
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
}
</%self:longhand>
// TODO(pcwalton): Lots more properties.
<%self:longhand name="transition-property">
use self::computed_value::TransitionProperty;
use values::computed::{ToComputedValue, Context};
pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue;
pub use self::computed_value::T as SpecifiedValue;
pub mod computed_value {
use cssparser::ToCss;
use text_writer::{self, TextWriter};
pub use self::TransitionProperty as SingleComputedValue;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TransitionProperty {
All,
Top,
Right,
Bottom,
Left,
}
pub static ALL_TRANSITION_PROPERTIES: [TransitionProperty; 4] = [
TransitionProperty::Top,
TransitionProperty::Right,
TransitionProperty::Bottom,
TransitionProperty::Left,
];
impl ToCss for TransitionProperty {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
match *self {
TransitionProperty::All => dest.write_str("all"),
TransitionProperty::Top => dest.write_str("top"),
TransitionProperty::Right => dest.write_str("right"),
TransitionProperty::Bottom => dest.write_str("bottom"),
TransitionProperty::Left => dest.write_str("left"),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct T(pub Vec<SingleComputedValue>);
impl ToCss for T {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
if self.0.is_empty() {
return dest.write_str("none")
}
for (i, value) in self.0.iter().enumerate() {
if i != 0 {
try!(dest.write_str(", "))
}
try!(value.to_css(dest))
}
Ok(())
}
}
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T(Vec::new())
}
pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> {
match_ignore_ascii_case! {
try!(input.expect_ident()),
"all" => Ok(TransitionProperty::All),
"top" => Ok(TransitionProperty::Top),
"right" => Ok(TransitionProperty::Right),
"bottom" => Ok(TransitionProperty::Bottom),
"left" => Ok(TransitionProperty::Left)
_ => Err(())
}
}
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
}
impl ToComputedValue for SpecifiedValue {
type ComputedValue = computed_value::T;
#[inline]
fn to_computed_value(&self, _: &Context) -> computed_value::T {
(*self).clone()
}
}
</%self:longhand>
<%self:longhand name="transition-delay">
pub use properties::longhands::transition_duration::{SingleSpecifiedValue, SpecifiedValue};
pub use properties::longhands::transition_duration::{computed_value};
pub use properties::longhands::transition_duration::{get_initial_single_value};
pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one};
</%self:longhand>
}
@ -3401,6 +3762,8 @@ pub mod shorthands {
Option<longhands::${sub_property.ident}::SpecifiedValue>,
% endfor
}
#[allow(unused_variables)]
pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
${caller.body()}
}
@ -3879,6 +4242,99 @@ pub mod shorthands {
overflow_y: Some(overflow_y::SpecifiedValue(overflow)),
})
</%self:shorthand>
<%self:shorthand name="transition"
sub_properties="transition-property transition-duration transition-timing-function transition-delay">
use properties::longhands::{transition_delay, transition_duration, transition_property};
use properties::longhands::{transition_timing_function};
struct SingleTransition {
transition_property: transition_property::SingleSpecifiedValue,
transition_duration: transition_duration::SingleSpecifiedValue,
transition_timing_function: transition_timing_function::SingleSpecifiedValue,
transition_delay: transition_delay::SingleSpecifiedValue,
}
fn parse_one_transition(input: &mut Parser) -> Result<SingleTransition,()> {
let (mut property, mut duration) = (None, None);
let (mut timing_function, mut delay) = (None, None);
loop {
if property.is_none() {
if let Ok(value) = input.try(|input| transition_property::parse_one(input)) {
property = Some(value);
continue
}
}
if duration.is_none() {
if let Ok(value) = input.try(|input| transition_duration::parse_one(input)) {
duration = Some(value);
continue
}
}
if timing_function.is_none() {
if let Ok(value) = input.try(|input| {
transition_timing_function::parse_one(input)
}) {
timing_function = Some(value);
continue
}
}
if delay.is_none() {
if let Ok(value) = input.try(|input| transition_delay::parse_one(input)) {
delay = Some(value);
continue;
}
}
break
}
if let Some(property) = property {
Ok(SingleTransition {
transition_property: property,
transition_duration:
duration.unwrap_or(transition_duration::get_initial_single_value()),
transition_timing_function:
timing_function.unwrap_or(
transition_timing_function::get_initial_single_value()),
transition_delay:
delay.unwrap_or(transition_delay::get_initial_single_value()),
})
} else {
Err(())
}
}
if input.try(|input| input.expect_ident_matching("none")).is_ok() {
return Ok(Longhands {
transition_property: None,
transition_duration: None,
transition_timing_function: None,
transition_delay: None,
})
}
let results = try!(input.parse_comma_separated(parse_one_transition));
let (mut properties, mut durations) = (Vec::new(), Vec::new());
let (mut timing_functions, mut delays) = (Vec::new(), Vec::new());
for result in results.into_iter() {
properties.push(result.transition_property);
durations.push(result.transition_duration);
timing_functions.push(result.transition_timing_function);
delays.push(result.transition_delay);
}
Ok(Longhands {
transition_property: Some(transition_property::SpecifiedValue(properties)),
transition_duration: Some(transition_duration::SpecifiedValue(durations)),
transition_timing_function:
Some(transition_timing_function::SpecifiedValue(timing_functions)),
transition_delay: Some(transition_delay::SpecifiedValue(delays)),
})
</%self:shorthand>
}
@ -4328,6 +4784,11 @@ impl ComputedValues {
<'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 *self.${style_struct.ident}.make_unique()
}
% endfor
}
@ -4391,12 +4852,13 @@ fn initial_writing_mode_is_empty() {
/// Fast path for the function below. Only computes new inherited styles.
#[allow(unused_mut)]
fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock<Vec<PropertyDeclaration>>],
shareable: bool,
parent_style: &ComputedValues,
cached_style: &ComputedValues,
context: &computed::Context)
-> ComputedValues {
fn cascade_with_cached_declarations(
applicable_declarations: &[DeclarationBlock<Vec<PropertyDeclaration>>],
shareable: bool,
parent_style: &ComputedValues,
cached_style: &ComputedValues,
context: &computed::Context)
-> ComputedValues {
% for style_struct in STYLE_STRUCTS:
% if style_struct.inherited:
let mut style_${style_struct.ident} = parent_style.${style_struct.ident}.clone();
@ -4415,7 +4877,9 @@ fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock<
% for style_struct in STYLE_STRUCTS:
% for property in style_struct.longhands:
% if property.derived_from is None:
PropertyDeclaration::${property.camel_case}(ref ${'_' if not style_struct.inherited else ''}declared_value) => {
PropertyDeclaration::${property.camel_case}(ref
${'_' if not style_struct.inherited else ''}declared_value)
=> {
% if style_struct.inherited:
if seen.get_${property.ident}() {
continue
@ -4556,9 +5020,13 @@ pub fn cascade(viewport_size: Size2D<Au>,
context.font_size = match *value {
DeclaredValue::SpecifiedValue(ref specified_value) => {
match specified_value.0 {
Length::FontRelative(value) => value.to_computed_value(context.inherited_font_size,
context.root_font_size),
Length::ServoCharacterWidth(value) => value.to_computed_value(context.inherited_font_size),
Length::FontRelative(value) => {
value.to_computed_value(context.inherited_font_size,
context.root_font_size)
}
Length::ServoCharacterWidth(value) => {
value.to_computed_value(context.inherited_font_size)
}
_ => specified_value.0.to_computed_value(&context)
}
}
@ -4568,7 +5036,9 @@ pub fn cascade(viewport_size: Size2D<Au>,
}
PropertyDeclaration::Color(ref value) => {
context.color = match *value {
DeclaredValue::SpecifiedValue(ref specified_value) => specified_value.parsed,
DeclaredValue::SpecifiedValue(ref specified_value) => {
specified_value.parsed
}
DeclaredValue::Initial => longhands::color::get_initial_value(),
DeclaredValue::Inherit => inherited_style.get_color().color.clone(),
};

Просмотреть файл

@ -862,11 +862,57 @@ pub mod specified {
"inset" => inset,
"outset" => outset,
}
/// A time in seconds according to CSS-VALUES § 6.2.
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Time(pub CSSFloat);
impl Time {
/// Returns the time in fractional seconds.
pub fn seconds(self) -> f64 {
let Time(seconds) = self;
seconds
}
/// Parses a time according to CSS-VALUES § 6.2.
fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Time,()> {
if unit.eq_ignore_ascii_case("s") {
Ok(Time(value))
} else if unit.eq_ignore_ascii_case("ms") {
Ok(Time(value / 1000.0))
} else {
Err(())
}
}
pub fn parse(input: &mut Parser) -> Result<Time,()> {
match input.next() {
Ok(Token::Dimension(ref value, ref unit)) => {
Time::parse_dimension(value.value, unit.as_slice())
}
_ => Err(()),
}
}
}
impl super::computed::ToComputedValue for Time {
type ComputedValue = Time;
#[inline]
fn to_computed_value(&self, _: &super::computed::Context) -> Time {
*self
}
}
impl ToCss for Time {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
dest.write_str(format!("{}ms", self.0).as_slice())
}
}
}
pub mod computed {
pub use super::specified::BorderStyle;
pub use super::specified::{BorderStyle, Time};
use super::specified::{AngleOrCorner};
use super::{specified, CSSFloat};
pub use cssparser::Color as CSSColor;

Просмотреть файл

@ -0,0 +1,116 @@
/* 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/. */
//! Parametric Bézier curves.
//!
//! This is based on `WebCore/platform/graphics/UnitBezier.h` in WebKit.
use geom::point::Point2D;
use std::num::Float;
const NEWTON_METHOD_ITERATIONS: u8 = 8;
pub struct Bezier {
ax: f64,
bx: f64,
cx: f64,
ay: f64,
by: f64,
cy: f64,
}
impl Bezier {
#[inline]
pub fn new(p1: Point2D<f64>, p2: Point2D<f64>) -> Bezier {
let cx = 3.0 * p1.x;
let bx = 3.0 * (p2.x - p1.x) - cx;
let cy = 3.0 * p1.y;
let by = 3.0 * (p2.y - p1.y) - cy;
Bezier {
ax: 1.0 - cx - bx,
bx: bx,
cx: cx,
ay: 1.0 - cy - by,
by: by,
cy: cy,
}
}
#[inline]
fn sample_curve_x(&self, t: f64) -> f64 {
// ax * t^3 + bx * t^2 + cx * t
((self.ax * t + self.bx) * t + self.cx) * t
}
#[inline]
fn sample_curve_y(&self, t: f64) -> f64 {
((self.ay * t + self.by) * t + self.cy) * t
}
#[inline]
fn sample_curve_derivative_x(&self, t: f64) -> f64 {
(3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
}
#[inline]
fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
// Fast path: Use Newton's method.
let mut t = x;
for _ in range(0, NEWTON_METHOD_ITERATIONS) {
let x2 = self.sample_curve_x(t);
if x2.approx_eq(x, epsilon) {
return t
}
let dx = self.sample_curve_derivative_x(t);
if dx.approx_eq(0.0, 1e-6) {
break
}
t -= (x2 - x) / dx;
}
// Slow path: Use bisection.
let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
if t < lo {
return lo
}
if t > hi {
return hi
}
while lo < hi {
let x2 = self.sample_curve_x(t);
if x2.approx_eq(x, epsilon) {
return t
}
if x > x2 {
lo = t
} else {
hi = t
}
t = (hi - lo) / 2.0 + lo
}
t
}
#[inline]
pub fn solve(&self, x: f64, epsilon: f64) -> f64 {
self.sample_curve_y(self.solve_curve_x(x, epsilon))
}
}
trait ApproxEq {
fn approx_eq(self, value: Self, epsilon: Self) -> bool;
}
impl ApproxEq for f64 {
#[inline]
fn approx_eq(self, value: f64, epsilon: f64) -> bool {
(self - value).abs() < epsilon
}
}

Просмотреть файл

@ -41,6 +41,7 @@ pub use selectors::smallvec;
use std::sync::Arc;
pub mod bezier;
pub mod cache;
pub mod cursor;
pub mod debug_utils;

6
servo/ports/cef/Cargo.lock сгенерированный
Просмотреть файл

@ -73,6 +73,11 @@ dependencies = [
"gleam 0.0.1 (git+https://github.com/servo/gleam)",
]
[[package]]
name = "clock_ticks"
version = "0.0.4"
source = "git+https://github.com/tomaka/clock_ticks#6a3005279bedc406b13eea09ff92447f05ca0de6"
[[package]]
name = "cocoa"
version = "0.1.1"
@ -508,6 +513,7 @@ dependencies = [
"azure 0.1.0 (git+https://github.com/servo/rust-azure)",
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"canvas 0.0.1",
"clock_ticks 0.0.4 (git+https://github.com/tomaka/clock_ticks)",
"cssparser 0.2.0 (git+https://github.com/servo/rust-cssparser)",
"encoding 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",

6
servo/ports/gonk/Cargo.lock сгенерированный
Просмотреть файл

@ -61,6 +61,11 @@ dependencies = [
"gleam 0.0.1 (git+https://github.com/servo/gleam)",
]
[[package]]
name = "clock_ticks"
version = "0.0.4"
source = "git+https://github.com/tomaka/clock_ticks#6a3005279bedc406b13eea09ff92447f05ca0de6"
[[package]]
name = "compositing"
version = "0.0.1"
@ -433,6 +438,7 @@ dependencies = [
"azure 0.1.0 (git+https://github.com/servo/rust-azure)",
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"canvas 0.0.1",
"clock_ticks 0.0.4 (git+https://github.com/tomaka/clock_ticks)",
"cssparser 0.2.0 (git+https://github.com/servo/rust-cssparser)",
"encoding 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",

Просмотреть файл

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<style>
section {
position: absolute;
display: block;
left: 0;
width: 64px;
height: 64px;
background: cadetblue;
transition: all 3s ease;
-moz-transition: all 3s ease;
}
</style>
</head>
<body>
<section></section>
<script>
var sections = document.getElementsByTagName('section');
sections[0].setAttribute('style', "left: 0; top: 0");
setTimeout(function() {
sections[0].setAttribute('style', "left: 512px; top: 512px");
}, 0);
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<style>
section {
position: absolute;
display: block;
left: 0;
width: 64px;
height: 64px;
background: cadetblue;
transition: left 3s ease, top 1500ms ease;
}
</style>
</head>
<body>
<section></section>
<script>
var sections = document.getElementsByTagName('section');
sections[0].setAttribute('style', "left: 0; top: 0");
setTimeout(function() {
sections[0].setAttribute('style', "left: 512px; top: 512px");
}, 0);
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,96 @@
<!DOCTYPE html>
<html>
<head>
<style>
section {
position: absolute;
display: block;
left: 0;
width: 64px;
height: 64px;
background: firebrick;
transition-property: left;
transition-duration: 3s;
-moz-transition-property: left;
-moz-transition-duration: 3s;
-webkit-transition-property: left;
-webkit-transition-duration: 3s;
}
#a {
top: 0;
transition-timing-function: ease;
-moz-transition-timing-function: ease;
-webkit-transition-timing-function: ease;
}
#b {
top: 64px;
transition-timing-function: linear;
-moz-transition-timing-function: linear;
-webkit-transition-timing-function: linear;
}
#c {
top: 128px;
transition-timing-function: ease-in;
-moz-transition-timing-function: ease-in;
-webkit-transition-timing-function: ease-in;
}
#d {
top: 192px;
transition-timing-function: ease-out;
-moz-transition-timing-function: ease-out;
-webkit-transition-timing-function: ease-out;
}
#e {
top: 256px;
transition-timing-function: ease-in-out;
-moz-transition-timing-function: ease-in-out;
-webkit-transition-timing-function: ease-in-out;
}
#f {
top: 320px;
transition-timing-function: step-start;
-moz-transition-timing-function: step-start;
-webkit-transition-timing-function: step-start;
}
#g {
top: 356px;
transition-timing-function: step-end;
-moz-transition-timing-function: step-end;
-webkit-transition-timing-function: step-end;
}
#h {
top: 420px;
transition-timing-function: steps(3, start);
-moz-transition-timing-function: steps(3, start);
-webkit-transition-timing-function: steps(3, start);
}
#i {
top: 484px;
transition-timing-function: steps(3, end);
-moz-transition-timing-function: steps(3, end);
-webkit-transition-timing-function: steps(3, end);
}
</style>
</head>
<body>
<section id=a></section>
<section id=b></section>
<section id=c></section>
<section id=d></section>
<section id=e></section>
<section id=f></section>
<section id=g></section>
<section id=h></section>
<section id=i></section>
<script>
var sections = document.getElementsByTagName('section');
for (var i = 0; i < sections.length; i++)
sections[i].setAttribute('style', "left: 0");
setTimeout(function() {
for (var i = 0; i < sections.length; i++)
sections[i].setAttribute('style', "left: 512px");
}, 0);
</script>
</body>
</html>