mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
layout: Implement CSS transitions per CSS-TRANSITIONS § 2.
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.
This commit is contained in:
parent
c1cc31b9d6
commit
66dd8c8a6c
31 changed files with 1603 additions and 224 deletions
|
@ -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 = "*"
|
||||
|
||||
|
|
104
components/layout/animation.rs
Normal file
104
components/layout/animation.rs
Normal file
|
@ -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
components/servo/Cargo.lock
generated
6
components/servo/Cargo.lock
generated
|
@ -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)",
|
||||
|
|
221
components/style/animation.rs
Normal file
221
components/style/animation.rs
Normal file
|
@ -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;
|
||||
|
|
116
components/util/bezier.rs
Normal file
116
components/util/bezier.rs
Normal file
|
@ -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
ports/cef/Cargo.lock
generated
6
ports/cef/Cargo.lock
generated
|
@ -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
ports/gonk/Cargo.lock
generated
6
ports/gonk/Cargo.lock
generated
|
@ -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)",
|
||||
|
|
29
tests/html/transition_all.html
Normal file
29
tests/html/transition_all.html
Normal file
|
@ -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>
|
||||
|
||||
|
28
tests/html/transition_multiple.html
Normal file
28
tests/html/transition_multiple.html
Normal file
|
@ -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>
|
||||
|
||||
|
96
tests/html/transition_simple.html
Normal file
96
tests/html/transition_simple.html
Normal file
|
@ -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>
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue