зеркало из https://github.com/mozilla/gecko-dev.git
servo: Merge #5400 - layout: Implement CSS transitions per CSS-TRANSITIONS § 2 (from pcwalton:transitions-redux); r=glennw
Transition events are not yet supported, and the only animatable properties are `top`, `right`, `bottom`, and `left`. However, all other features of transitions are supported. There are no automated tests at present because I'm not sure how best to test it, but three manual tests are included. r? @glennw Source-Repo: https://github.com/servo/servo Source-Revision: ebdf1d494b6c986e6dfcb7d8fd3f0ffa126523ed
This commit is contained in:
Родитель
b937bffcbb
Коммит
c9e2901806
|
@ -13,32 +13,32 @@ use windowing::{MouseWindowEvent, WindowEvent, WindowMethods, WindowNavigateMsg}
|
|||
|
||||
use geom::point::{Point2D, TypedPoint2D};
|
||||
use geom::rect::{Rect, TypedRect};
|
||||
use geom::size::{Size2D, TypedSize2D};
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use geom::size::{Size2D, TypedSize2D};
|
||||
use gfx::color;
|
||||
use gfx::paint_task::Msg as PaintMsg;
|
||||
use gfx::paint_task::PaintRequest;
|
||||
use layers::geometry::{DevicePixel, LayerPixel};
|
||||
use layers::layers::{BufferRequest, Layer, LayerBuffer, LayerBufferSet};
|
||||
use layers::rendergl;
|
||||
use layers::rendergl::RenderContext;
|
||||
use layers::scene::Scene;
|
||||
use png;
|
||||
use gleam::gl::types::{GLint, GLsizei};
|
||||
use gleam::gl;
|
||||
use script_traits::{ConstellationControlMsg, ScriptControlChan};
|
||||
use layers::geometry::{DevicePixel, LayerPixel};
|
||||
use layers::layers::{BufferRequest, Layer, LayerBuffer, LayerBufferSet};
|
||||
use layers::rendergl::RenderContext;
|
||||
use layers::rendergl;
|
||||
use layers::scene::Scene;
|
||||
use msg::compositor_msg::{Epoch, LayerId};
|
||||
use msg::compositor_msg::{ReadyState, PaintState, ScrollPolicy};
|
||||
use msg::constellation_msg::{ConstellationChan, NavigationDirection};
|
||||
use msg::constellation_msg::Msg as ConstellationMsg;
|
||||
use msg::constellation_msg::{ConstellationChan, NavigationDirection};
|
||||
use msg::constellation_msg::{Key, KeyModifiers, KeyState, LoadData};
|
||||
use msg::constellation_msg::{PipelineId, WindowSizeData};
|
||||
use png;
|
||||
use profile::mem;
|
||||
use profile::time::{self, ProfilerCategory, profile};
|
||||
use script_traits::{ConstellationControlMsg, ScriptControlChan};
|
||||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::mem::replace;
|
||||
use std::mem as std_mem;
|
||||
use std::num::Float;
|
||||
use std::rc::Rc;
|
||||
use std::slice::bytes::copy_memory;
|
||||
|
@ -164,6 +164,9 @@ struct PipelineDetails {
|
|||
|
||||
/// The status of this pipeline's PaintTask.
|
||||
paint_state: PaintState,
|
||||
|
||||
/// Whether animations are running.
|
||||
animations_running: bool,
|
||||
}
|
||||
|
||||
impl PipelineDetails {
|
||||
|
@ -172,6 +175,7 @@ impl PipelineDetails {
|
|||
pipeline: None,
|
||||
ready_state: ReadyState::Blank,
|
||||
paint_state: PaintState::Painting,
|
||||
animations_running: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -272,6 +276,11 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
self.change_paint_state(pipeline_id, paint_state);
|
||||
}
|
||||
|
||||
(Msg::ChangeRunningAnimationsState(pipeline_id, running_animations),
|
||||
ShutdownState::NotShuttingDown) => {
|
||||
self.change_running_animations_state(pipeline_id, running_animations);
|
||||
}
|
||||
|
||||
(Msg::ChangePageTitle(pipeline_id, title), ShutdownState::NotShuttingDown) => {
|
||||
self.change_page_title(pipeline_id, title);
|
||||
}
|
||||
|
@ -311,7 +320,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
self.set_layer_rect(pipeline_id, layer_id, &rect);
|
||||
}
|
||||
|
||||
(Msg::AssignPaintedBuffers(pipeline_id, epoch, replies), ShutdownState::NotShuttingDown) => {
|
||||
(Msg::AssignPaintedBuffers(pipeline_id, epoch, replies),
|
||||
ShutdownState::NotShuttingDown) => {
|
||||
for (layer_id, new_layer_buffer_set) in replies.into_iter() {
|
||||
self.assign_painted_buffers(pipeline_id, layer_id, new_layer_buffer_set, epoch);
|
||||
}
|
||||
|
@ -399,6 +409,18 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
self.window.set_paint_state(paint_state);
|
||||
}
|
||||
|
||||
/// Sets or unsets the animations-running flag for the given pipeline, and schedules a
|
||||
/// recomposite if necessary.
|
||||
fn change_running_animations_state(&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
animations_running: bool) {
|
||||
self.get_or_create_pipeline_details(pipeline_id).animations_running = animations_running;
|
||||
|
||||
if animations_running {
|
||||
self.composite_if_necessary();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_create_pipeline_details<'a>(&'a mut self,
|
||||
pipeline_id: PipelineId)
|
||||
-> &'a mut PipelineDetails {
|
||||
|
@ -867,15 +889,13 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
|
||||
fn process_pending_scroll_events(&mut self) {
|
||||
let had_scroll_events = self.pending_scroll_events.len() > 0;
|
||||
for scroll_event in replace(&mut self.pending_scroll_events, Vec::new()).into_iter() {
|
||||
for scroll_event in std_mem::replace(&mut self.pending_scroll_events,
|
||||
Vec::new()).into_iter() {
|
||||
let delta = scroll_event.delta / self.scene.scale;
|
||||
let cursor = scroll_event.cursor.as_f32() / self.scene.scale;
|
||||
|
||||
match self.scene.root {
|
||||
Some(ref mut layer) => {
|
||||
layer.handle_scroll_event(delta, cursor);
|
||||
}
|
||||
None => {}
|
||||
if let Some(ref mut layer) = self.scene.root {
|
||||
layer.handle_scroll_event(delta, cursor);
|
||||
}
|
||||
|
||||
self.start_scrolling_timer_if_necessary();
|
||||
|
@ -887,6 +907,16 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
}
|
||||
}
|
||||
|
||||
/// If there are any animations running, dispatches appropriate messages to the constellation.
|
||||
fn process_animations(&mut self) {
|
||||
for (pipeline_id, pipeline_details) in self.pipeline_details.iter() {
|
||||
if !pipeline_details.animations_running {
|
||||
continue
|
||||
}
|
||||
self.constellation_chan.0.send(ConstellationMsg::TickAnimation(*pipeline_id)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn device_pixels_per_screen_px(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
|
||||
match opts::get().device_pixels_per_px {
|
||||
Some(device_pixels_per_px) => device_pixels_per_px,
|
||||
|
@ -1086,7 +1116,8 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
|
||||
let mut framebuffer_ids = vec!();
|
||||
let mut texture_ids = vec!();
|
||||
let (width, height) = (self.window_size.width.get() as usize, self.window_size.height.get() as usize);
|
||||
let (width, height) =
|
||||
(self.window_size.width.get() as usize, self.window_size.height.get() as usize);
|
||||
|
||||
if output_image {
|
||||
framebuffer_ids = gl::gen_framebuffers(1);
|
||||
|
@ -1172,6 +1203,7 @@ impl<Window: WindowMethods> IOCompositor<Window> {
|
|||
|
||||
self.composition_request = CompositionRequest::NoCompositingNecessary;
|
||||
self.process_pending_scroll_events();
|
||||
self.process_animations();
|
||||
}
|
||||
|
||||
fn composite_if_necessary(&mut self) {
|
||||
|
|
|
@ -201,6 +201,8 @@ pub enum Msg {
|
|||
ChangePageTitle(PipelineId, Option<String>),
|
||||
/// Alerts the compositor that the current page has changed its URL.
|
||||
ChangePageUrl(PipelineId, Url),
|
||||
/// Alerts the compositor that the given pipeline has changed whether it is running animations.
|
||||
ChangeRunningAnimationsState(PipelineId, bool),
|
||||
/// Alerts the compositor that a `PaintMsg` has been discarded.
|
||||
PaintMsgDiscarded,
|
||||
/// Replaces the current frame tree, typically called during main frame navigation.
|
||||
|
@ -231,6 +233,7 @@ impl Debug for Msg {
|
|||
Msg::AssignPaintedBuffers(..) => write!(f, "AssignPaintedBuffers"),
|
||||
Msg::ChangeReadyState(..) => write!(f, "ChangeReadyState"),
|
||||
Msg::ChangePaintState(..) => write!(f, "ChangePaintState"),
|
||||
Msg::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"),
|
||||
Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
|
||||
Msg::ChangePageUrl(..) => write!(f, "ChangePageUrl"),
|
||||
Msg::PaintMsgDiscarded(..) => write!(f, "PaintMsgDiscarded"),
|
||||
|
|
|
@ -11,26 +11,22 @@ use geom::point::Point2D;
|
|||
use geom::rect::{Rect, TypedRect};
|
||||
use geom::scale_factor::ScaleFactor;
|
||||
use gfx::font_cache_task::FontCacheTask;
|
||||
use layout_traits::LayoutTaskFactory;
|
||||
use layout_traits::{LayoutControlMsg, LayoutTaskFactory};
|
||||
use libc;
|
||||
use script_traits::{CompositorEvent, ConstellationControlMsg};
|
||||
use script_traits::{ScriptControlChan, ScriptTaskFactory};
|
||||
use msg::compositor_msg::LayerId;
|
||||
use msg::constellation_msg::{self, ConstellationChan, Failure};
|
||||
use msg::constellation_msg::{IFrameSandboxState, NavigationDirection};
|
||||
use msg::constellation_msg::{Key, KeyState, KeyModifiers, LoadData};
|
||||
use msg::constellation_msg::{FrameId, PipelineExitType, PipelineId};
|
||||
use msg::constellation_msg::{SubpageId, WindowSizeData, MozBrowserEvent};
|
||||
use msg::constellation_msg::Msg as ConstellationMsg;
|
||||
use msg::constellation_msg::{FrameId, PipelineExitType, PipelineId};
|
||||
use msg::constellation_msg::{IFrameSandboxState, MozBrowserEvent, NavigationDirection};
|
||||
use msg::constellation_msg::{Key, KeyState, KeyModifiers, LoadData};
|
||||
use msg::constellation_msg::{SubpageId, WindowSizeData};
|
||||
use msg::constellation_msg::{self, ConstellationChan, Failure};
|
||||
use net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
|
||||
use net::resource_task::{self, ResourceTask};
|
||||
use net::storage_task::{StorageTask, StorageTaskMsg};
|
||||
use profile::mem;
|
||||
use profile::time;
|
||||
use util::cursor::Cursor;
|
||||
use util::geometry::PagePx;
|
||||
use util::opts;
|
||||
use util::task::spawn_named;
|
||||
use script_traits::{CompositorEvent, ConstellationControlMsg};
|
||||
use script_traits::{ScriptControlChan, ScriptTaskFactory};
|
||||
use std::borrow::ToOwned;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{self, Write};
|
||||
|
@ -38,6 +34,10 @@ use std::marker::PhantomData;
|
|||
use std::mem::replace;
|
||||
use std::sync::mpsc::{Receiver, channel};
|
||||
use url::Url;
|
||||
use util::cursor::Cursor;
|
||||
use util::geometry::PagePx;
|
||||
use util::opts;
|
||||
use util::task::spawn_named;
|
||||
|
||||
/// Maintains the pipelines and navigation context and grants permission to composite.
|
||||
pub struct Constellation<LTF, STF> {
|
||||
|
@ -201,8 +201,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
|
|||
time_profiler_chan: time_profiler_chan,
|
||||
mem_profiler_chan: mem_profiler_chan,
|
||||
window_size: WindowSizeData {
|
||||
visible_viewport: opts::get().initial_window_size.as_f32() * ScaleFactor::new(1.0),
|
||||
initial_viewport: opts::get().initial_window_size.as_f32() * ScaleFactor::new(1.0),
|
||||
visible_viewport: opts::get().initial_window_size.as_f32() *
|
||||
ScaleFactor::new(1.0),
|
||||
initial_viewport: opts::get().initial_window_size.as_f32() *
|
||||
ScaleFactor::new(1.0),
|
||||
device_pixel_ratio: ScaleFactor::new(1.0),
|
||||
},
|
||||
phantom: PhantomData,
|
||||
|
@ -321,7 +323,10 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
|
|||
new_subpage_id,
|
||||
old_subpage_id,
|
||||
sandbox) => {
|
||||
debug!("constellation got iframe URL load message {:?} {:?} {:?}", source_pipeline_id, old_subpage_id, new_subpage_id);
|
||||
debug!("constellation got iframe URL load message {:?} {:?} {:?}",
|
||||
source_pipeline_id,
|
||||
old_subpage_id,
|
||||
new_subpage_id);
|
||||
self.handle_script_loaded_url_in_iframe_msg(url,
|
||||
source_pipeline_id,
|
||||
new_subpage_id,
|
||||
|
@ -329,6 +334,12 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
|
|||
sandbox);
|
||||
}
|
||||
ConstellationMsg::SetCursor(cursor) => self.handle_set_cursor_msg(cursor),
|
||||
ConstellationMsg::ChangeRunningAnimationsState(pipeline_id, animations_running) => {
|
||||
self.handle_change_running_animations_state(pipeline_id, animations_running)
|
||||
}
|
||||
ConstellationMsg::TickAnimation(pipeline_id) => {
|
||||
self.handle_tick_animation(pipeline_id)
|
||||
}
|
||||
// Load a new page, usually -- but not always -- from a mouse click or typed url
|
||||
// If there is already a pending page (self.pending_frames), it will not be overridden;
|
||||
// However, if the id is not encompassed by another change, it will be.
|
||||
|
@ -420,15 +431,19 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
|
|||
debug!("creating replacement pipeline for about:failure");
|
||||
|
||||
let window_rect = self.pipeline(pipeline_id).rect;
|
||||
let new_pipeline_id = self.new_pipeline(parent_info, window_rect, None,
|
||||
LoadData::new(Url::parse("about:failure").unwrap()));
|
||||
let new_pipeline_id =
|
||||
self.new_pipeline(parent_info,
|
||||
window_rect,
|
||||
None,
|
||||
LoadData::new(Url::parse("about:failure").unwrap()));
|
||||
|
||||
self.push_pending_frame(new_pipeline_id, Some(pipeline_id));
|
||||
}
|
||||
|
||||
fn handle_init_load(&mut self, url: Url) {
|
||||
let window_rect = Rect(Point2D::zero(), self.window_size.visible_viewport);
|
||||
let root_pipeline_id = self.new_pipeline(None, Some(window_rect), None, LoadData::new(url));
|
||||
let root_pipeline_id =
|
||||
self.new_pipeline(None, Some(window_rect), None, LoadData::new(url));
|
||||
self.push_pending_frame(root_pipeline_id, None);
|
||||
}
|
||||
|
||||
|
@ -509,6 +524,21 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
|
|||
self.compositor_proxy.send(CompositorMsg::SetCursor(cursor))
|
||||
}
|
||||
|
||||
fn handle_change_running_animations_state(&mut self,
|
||||
pipeline_id: PipelineId,
|
||||
animations_running: bool) {
|
||||
self.compositor_proxy.send(CompositorMsg::ChangeRunningAnimationsState(pipeline_id,
|
||||
animations_running))
|
||||
}
|
||||
|
||||
fn handle_tick_animation(&mut self, pipeline_id: PipelineId) {
|
||||
self.pipeline(pipeline_id)
|
||||
.layout_chan
|
||||
.0
|
||||
.send(LayoutControlMsg::TickAnimationsMsg)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn handle_load_url_msg(&mut self, source_id: PipelineId, load_data: LoadData) {
|
||||
// If this load targets an iframe, its framing element may exist
|
||||
// in a separate script task than the framed document that initiated
|
||||
|
@ -907,7 +937,9 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
|
|||
}
|
||||
|
||||
fn find_subpage(&mut self, containing_pipeline_id: PipelineId, subpage_id: SubpageId) -> &mut Pipeline {
|
||||
let pipeline_id = *self.subpage_map.get(&(containing_pipeline_id, subpage_id)).expect("no subpage pipeline_id");
|
||||
let pipeline_id = *self.subpage_map
|
||||
.get(&(containing_pipeline_id, subpage_id))
|
||||
.expect("no subpage pipeline_id");
|
||||
self.mut_pipeline(pipeline_id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ impl CompositorEventListener for NullCompositor {
|
|||
Msg::AssignPaintedBuffers(..) |
|
||||
Msg::ChangeReadyState(..) |
|
||||
Msg::ChangePaintState(..) |
|
||||
Msg::ChangeRunningAnimationsState(..) |
|
||||
Msg::ScrollFragmentPoint(..) |
|
||||
Msg::LoadComplete |
|
||||
Msg::PaintMsgDiscarded(..) |
|
||||
|
|
|
@ -31,6 +31,7 @@ pub struct Pipeline {
|
|||
pub id: PipelineId,
|
||||
pub parent_info: Option<(PipelineId, SubpageId)>,
|
||||
pub script_chan: ScriptControlChan,
|
||||
/// A channel to layout, for performing reflows and shutdown.
|
||||
pub layout_chan: LayoutControlChan,
|
||||
pub paint_chan: PaintChan,
|
||||
pub layout_shutdown_port: Receiver<()>,
|
||||
|
@ -40,6 +41,9 @@ pub struct Pipeline {
|
|||
/// The title of the most recently-loaded page.
|
||||
pub title: Option<String>,
|
||||
pub rect: Option<TypedRect<PagePx, f32>>,
|
||||
/// Whether this pipeline is currently running animations. Pipelines that are running
|
||||
/// animations cause composites to be continually scheduled.
|
||||
pub running_animations: bool,
|
||||
pub children: Vec<FrameId>,
|
||||
}
|
||||
|
||||
|
@ -113,12 +117,14 @@ impl Pipeline {
|
|||
ScriptControlChan(script_chan)
|
||||
}
|
||||
Some(script_chan) => {
|
||||
let (containing_pipeline_id, subpage_id) = parent_info.expect("script_pipeline != None but subpage_id == None");
|
||||
let (containing_pipeline_id, subpage_id) =
|
||||
parent_info.expect("script_pipeline != None but subpage_id == None");
|
||||
let new_layout_info = NewLayoutInfo {
|
||||
containing_pipeline_id: containing_pipeline_id,
|
||||
new_pipeline_id: id,
|
||||
subpage_id: subpage_id,
|
||||
layout_chan: ScriptTaskFactory::clone_layout_channel(None::<&mut STF>, &layout_pair),
|
||||
layout_chan: ScriptTaskFactory::clone_layout_channel(None::<&mut STF>,
|
||||
&layout_pair),
|
||||
load_data: load_data.clone(),
|
||||
};
|
||||
|
||||
|
@ -186,6 +192,7 @@ impl Pipeline {
|
|||
title: None,
|
||||
children: vec!(),
|
||||
rect: rect,
|
||||
running_animations: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -330,13 +330,13 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
|
|||
})
|
||||
}
|
||||
|
||||
/// Paints one layer and sends the tiles back to the layer.
|
||||
/// Paints one layer and places the painted tiles in `replies`.
|
||||
fn paint(&mut self,
|
||||
replies: &mut Vec<(LayerId, Box<LayerBufferSet>)>,
|
||||
mut tiles: Vec<BufferRequest>,
|
||||
scale: f32,
|
||||
layer_id: LayerId) {
|
||||
profile(time::ProfilerCategory::Painting, None, self.time_profiler_chan.clone(), || {
|
||||
time::profile(time::ProfilerCategory::Painting, None, self.time_profiler_chan.clone(), || {
|
||||
// Bail out if there is no appropriate stacking context.
|
||||
let stacking_context = if let Some(ref stacking_context) = self.root_stacking_context {
|
||||
match display_list::find_stacking_context_with_layer_id(stacking_context,
|
||||
|
@ -559,8 +559,10 @@ impl WorkerThread {
|
|||
paint_context.clear();
|
||||
|
||||
// Draw the display list.
|
||||
profile(time::ProfilerCategory::PaintingPerTile, None,
|
||||
self.time_profiler_sender.clone(), || {
|
||||
time::profile(time::ProfilerCategory::PaintingPerTile,
|
||||
None,
|
||||
self.time_profiler_sender.clone(),
|
||||
|| {
|
||||
stacking_context.optimize_and_draw_into_context(&mut paint_context,
|
||||
&tile_bounds,
|
||||
&matrix,
|
||||
|
|
|
@ -61,9 +61,13 @@ git = "https://github.com/servo/string-cache"
|
|||
[dependencies.png]
|
||||
git = "https://github.com/servo/rust-png"
|
||||
|
||||
[dependencies.clock_ticks]
|
||||
git = "https://github.com/tomaka/clock_ticks"
|
||||
|
||||
[dependencies]
|
||||
encoding = "0.2"
|
||||
url = "0.2.16"
|
||||
bitflags = "*"
|
||||
rustc-serialize = "0.3"
|
||||
libc = "*"
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! CSS transitions and animations.
|
||||
|
||||
use flow::{self, Flow};
|
||||
use incremental::{self, RestyleDamage};
|
||||
|
||||
use clock_ticks;
|
||||
use gfx::display_list::OpaqueNode;
|
||||
use layout_task::{LayoutTask, LayoutTaskData};
|
||||
use msg::constellation_msg::{Msg, PipelineId};
|
||||
use script::layout_interface::Animation;
|
||||
use std::mem;
|
||||
use std::sync::mpsc::Sender;
|
||||
use style::animation::{GetMod, PropertyAnimation};
|
||||
use style::properties::ComputedValues;
|
||||
|
||||
/// Inserts transitions into the queue of running animations as applicable for the given style
|
||||
/// difference. This is called from the layout worker threads.
|
||||
pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>,
|
||||
node: OpaqueNode,
|
||||
old_style: &ComputedValues,
|
||||
new_style: &mut ComputedValues) {
|
||||
for i in range(0, new_style.get_animation().transition_property.0.len()) {
|
||||
// Create any property animations, if applicable.
|
||||
let property_animations = PropertyAnimation::from_transition(i, old_style, new_style);
|
||||
for property_animation in property_animations.into_iter() {
|
||||
// Set the property to the initial value.
|
||||
property_animation.update(new_style, 0.0);
|
||||
|
||||
// Kick off the animation.
|
||||
let now = clock_ticks::precise_time_s();
|
||||
let animation_style = new_style.get_animation();
|
||||
let start_time = now + animation_style.transition_delay.0.get_mod(i).seconds();
|
||||
new_animations_sender.send(Animation {
|
||||
node: node.id(),
|
||||
property_animation: property_animation,
|
||||
start_time: start_time,
|
||||
end_time: start_time +
|
||||
animation_style.transition_duration.0.get_mod(i).seconds(),
|
||||
}).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes any new animations that were discovered after style recalculation.
|
||||
pub fn process_new_animations(rw_data: &mut LayoutTaskData, pipeline_id: PipelineId) {
|
||||
while let Ok(animation) = rw_data.new_animations_receiver.try_recv() {
|
||||
rw_data.running_animations.push(animation)
|
||||
}
|
||||
|
||||
let animations_are_running = !rw_data.running_animations.is_empty();
|
||||
rw_data.constellation_chan
|
||||
.0
|
||||
.send(Msg::ChangeRunningAnimationsState(pipeline_id, animations_are_running))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Recalculates style for an animation. This does *not* run with the DOM lock held.
|
||||
pub fn recalc_style_for_animation(flow: &mut Flow, animation: &Animation) {
|
||||
let mut damage = RestyleDamage::empty();
|
||||
flow.mutate_fragments(&mut |fragment| {
|
||||
if fragment.node.id() != animation.node {
|
||||
return
|
||||
}
|
||||
|
||||
let now = clock_ticks::precise_time_s();
|
||||
let mut progress = (now - animation.start_time) / animation.duration();
|
||||
if progress > 1.0 {
|
||||
progress = 1.0
|
||||
}
|
||||
if progress <= 0.0 {
|
||||
return
|
||||
}
|
||||
|
||||
let mut new_style = fragment.style.clone();
|
||||
animation.property_animation.update(&mut *new_style.make_unique(), progress);
|
||||
damage.insert(incremental::compute_damage(&Some(fragment.style.clone()), &new_style));
|
||||
fragment.style = new_style
|
||||
});
|
||||
|
||||
let base = flow::mut_base(flow);
|
||||
base.restyle_damage.insert(damage);
|
||||
for kid in base.children.iter_mut() {
|
||||
recalc_style_for_animation(kid, animation)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles animation updates.
|
||||
pub fn tick_all_animations(layout_task: &LayoutTask, rw_data: &mut LayoutTaskData) {
|
||||
let running_animations = mem::replace(&mut rw_data.running_animations, Vec::new());
|
||||
let now = clock_ticks::precise_time_s();
|
||||
for running_animation in running_animations.into_iter() {
|
||||
layout_task.tick_animation(running_animation, rw_data);
|
||||
|
||||
if now < running_animation.end_time {
|
||||
// Keep running the animation if it hasn't expired.
|
||||
rw_data.running_animations.push(running_animation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,19 +10,20 @@ use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache};
|
|||
|
||||
use geom::{Rect, Size2D};
|
||||
use gfx::display_list::OpaqueNode;
|
||||
use gfx::font_context::FontContext;
|
||||
use gfx::font_cache_task::FontCacheTask;
|
||||
use script::layout_interface::LayoutChan;
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use gfx::font_context::FontContext;
|
||||
use msg::constellation_msg::ConstellationChan;
|
||||
use net::local_image_cache::LocalImageCache;
|
||||
use util::geometry::Au;
|
||||
use script::layout_interface::{Animation, LayoutChan};
|
||||
use script_traits::UntrustedNodeAddress;
|
||||
use std::boxed;
|
||||
use std::cell::Cell;
|
||||
use std::ptr;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use style::selector_matching::Stylist;
|
||||
use url::Url;
|
||||
use util::geometry::Au;
|
||||
|
||||
struct LocalLayoutContext {
|
||||
font_context: FontContext,
|
||||
|
@ -32,7 +33,8 @@ struct LocalLayoutContext {
|
|||
|
||||
thread_local!(static LOCAL_CONTEXT_KEY: Cell<*mut LocalLayoutContext> = Cell::new(ptr::null_mut()));
|
||||
|
||||
fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *mut LocalLayoutContext {
|
||||
fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext)
|
||||
-> *mut LocalLayoutContext {
|
||||
LOCAL_CONTEXT_KEY.with(|ref r| {
|
||||
if r.get().is_null() {
|
||||
let context = box LocalLayoutContext {
|
||||
|
@ -51,6 +53,7 @@ fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *
|
|||
})
|
||||
}
|
||||
|
||||
/// Layout information shared among all workers. This must be thread-safe.
|
||||
pub struct SharedLayoutContext {
|
||||
/// The local image cache.
|
||||
pub image_cache: Arc<Mutex<LocalImageCache<UntrustedNodeAddress>>>,
|
||||
|
@ -76,7 +79,7 @@ pub struct SharedLayoutContext {
|
|||
pub stylist: *const Stylist,
|
||||
|
||||
/// The root node at which we're starting the layout.
|
||||
pub reflow_root: OpaqueNode,
|
||||
pub reflow_root: Option<OpaqueNode>,
|
||||
|
||||
/// The URL.
|
||||
pub url: Url,
|
||||
|
@ -87,9 +90,14 @@ pub struct SharedLayoutContext {
|
|||
/// Starts at zero, and increased by one every time a layout completes.
|
||||
/// This can be used to easily check for invalid stale data.
|
||||
pub generation: u32,
|
||||
|
||||
/// A channel on which new animations that have been triggered by style recalculation can be
|
||||
/// sent.
|
||||
pub new_animations_sender: Sender<Animation>,
|
||||
}
|
||||
|
||||
pub struct SharedLayoutContextWrapper(pub *const SharedLayoutContext);
|
||||
|
||||
unsafe impl Send for SharedLayoutContextWrapper {}
|
||||
|
||||
pub struct LayoutContext<'a> {
|
||||
|
|
|
@ -6,29 +6,33 @@
|
|||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use animation;
|
||||
use context::SharedLayoutContext;
|
||||
use css::node_style::StyledNode;
|
||||
use incremental::{self, RestyleDamage};
|
||||
use data::{LayoutDataAccess, LayoutDataWrapper};
|
||||
use incremental::{self, RestyleDamage};
|
||||
use opaque_node::OpaqueNodeMethods;
|
||||
use wrapper::{LayoutElement, LayoutNode, TLayoutNode};
|
||||
|
||||
use script::dom::node::NodeTypeId;
|
||||
use script::layout_interface::Animation;
|
||||
use selectors::bloom::BloomFilter;
|
||||
use util::cache::{LRUCache, SimpleHashCache};
|
||||
use util::smallvec::{SmallVec, SmallVec16};
|
||||
use util::arc_ptr_eq;
|
||||
use std::borrow::ToOwned;
|
||||
use std::mem;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::slice::Iter;
|
||||
use string_cache::{Atom, Namespace};
|
||||
use selectors::parser::PseudoElement;
|
||||
use style::selector_matching::{Stylist, DeclarationBlock};
|
||||
use style::node::{TElement, TNode};
|
||||
use style::properties::{ComputedValues, cascade};
|
||||
use selectors::matching::{CommonStyleAffectingAttributeMode, CommonStyleAffectingAttributes};
|
||||
use selectors::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
|
||||
use selectors::parser::PseudoElement;
|
||||
use std::borrow::ToOwned;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
use std::slice::Iter;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Sender;
|
||||
use string_cache::{Atom, Namespace};
|
||||
use style::node::{TElement, TNode};
|
||||
use style::properties::{ComputedValues, cascade};
|
||||
use style::selector_matching::{Stylist, DeclarationBlock};
|
||||
use util::arc_ptr_eq;
|
||||
use util::cache::{LRUCache, SimpleHashCache};
|
||||
use util::smallvec::{SmallVec, SmallVec16};
|
||||
|
||||
pub struct ApplicableDeclarations {
|
||||
pub normal: SmallVec16<DeclarationBlock>,
|
||||
|
@ -399,7 +403,8 @@ pub trait MatchMethods {
|
|||
layout_context: &SharedLayoutContext,
|
||||
parent: Option<LayoutNode>,
|
||||
applicable_declarations: &ApplicableDeclarations,
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache);
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
|
||||
new_animations_sender: &Sender<Animation>);
|
||||
}
|
||||
|
||||
trait PrivateMatchMethods {
|
||||
|
@ -408,8 +413,9 @@ trait PrivateMatchMethods {
|
|||
parent_style: Option<&Arc<ComputedValues>>,
|
||||
applicable_declarations: &[DeclarationBlock],
|
||||
style: &mut Option<Arc<ComputedValues>>,
|
||||
applicable_declarations_cache: &mut
|
||||
ApplicableDeclarationsCache,
|
||||
applicable_declarations_cache:
|
||||
&mut ApplicableDeclarationsCache,
|
||||
new_animations_sender: &Sender<Animation>,
|
||||
shareable: bool)
|
||||
-> RestyleDamage;
|
||||
|
||||
|
@ -425,11 +431,12 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
|||
parent_style: Option<&Arc<ComputedValues>>,
|
||||
applicable_declarations: &[DeclarationBlock],
|
||||
style: &mut Option<Arc<ComputedValues>>,
|
||||
applicable_declarations_cache: &mut
|
||||
ApplicableDeclarationsCache,
|
||||
applicable_declarations_cache:
|
||||
&mut ApplicableDeclarationsCache,
|
||||
new_animations_sender: &Sender<Animation>,
|
||||
shareable: bool)
|
||||
-> RestyleDamage {
|
||||
let this_style;
|
||||
let mut this_style;
|
||||
let cacheable;
|
||||
match parent_style {
|
||||
Some(ref parent_style) => {
|
||||
|
@ -444,7 +451,7 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
|||
Some(&***parent_style),
|
||||
cached_computed_values);
|
||||
cacheable = is_cacheable;
|
||||
this_style = Arc::new(the_style);
|
||||
this_style = the_style
|
||||
}
|
||||
None => {
|
||||
let (the_style, is_cacheable) = cascade(layout_context.screen_size,
|
||||
|
@ -453,22 +460,39 @@ impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
|
|||
None,
|
||||
None);
|
||||
cacheable = is_cacheable;
|
||||
this_style = Arc::new(the_style);
|
||||
this_style = the_style
|
||||
}
|
||||
};
|
||||
|
||||
// Trigger transitions if necessary. This will reset `this_style` back to its old value if
|
||||
// it did trigger a transition.
|
||||
match *style {
|
||||
None => {
|
||||
// This is a newly-created node; we've nothing to transition from!
|
||||
}
|
||||
Some(ref style) => {
|
||||
let node = OpaqueNodeMethods::from_layout_node(self);
|
||||
animation::start_transitions_if_applicable(new_animations_sender,
|
||||
node,
|
||||
&**style,
|
||||
&mut this_style);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate style difference.
|
||||
let this_style = Arc::new(this_style);
|
||||
let damage = incremental::compute_damage(style, &*this_style);
|
||||
|
||||
// Cache the resolved style if it was cacheable.
|
||||
if cacheable {
|
||||
applicable_declarations_cache.insert(applicable_declarations.to_vec(), this_style.clone());
|
||||
}
|
||||
|
||||
// Calculate style difference and write.
|
||||
let damage = incremental::compute_damage(style, &*this_style);
|
||||
// Write in the final style and return the damage done to our caller.
|
||||
*style = Some(this_style);
|
||||
damage
|
||||
}
|
||||
|
||||
|
||||
fn share_style_with_candidate_if_possible(&self,
|
||||
parent_node: Option<LayoutNode>,
|
||||
candidate: &StyleSharingCandidate)
|
||||
|
@ -615,7 +639,8 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
layout_context: &SharedLayoutContext,
|
||||
parent: Option<LayoutNode>,
|
||||
applicable_declarations: &ApplicableDeclarations,
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache) {
|
||||
applicable_declarations_cache: &mut ApplicableDeclarationsCache,
|
||||
new_animations_sender: &Sender<Animation>) {
|
||||
// Get our parent's style. This must be unsafe so that we don't touch the parent's
|
||||
// borrow flags.
|
||||
//
|
||||
|
@ -625,8 +650,12 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
None => None,
|
||||
Some(parent_node) => {
|
||||
let parent_layout_data_ref = parent_node.borrow_layout_data_unchecked();
|
||||
let parent_layout_data = (&*parent_layout_data_ref).as_ref().expect("no parent data!?");
|
||||
let parent_style = parent_layout_data.shared_data.style.as_ref().expect("parent hasn't been styled yet!");
|
||||
let parent_layout_data = (&*parent_layout_data_ref).as_ref()
|
||||
.expect("no parent data!?");
|
||||
let parent_style = parent_layout_data.shared_data
|
||||
.style
|
||||
.as_ref()
|
||||
.expect("parent hasn't been styled yet!");
|
||||
Some(parent_style)
|
||||
}
|
||||
};
|
||||
|
@ -651,6 +680,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
applicable_declarations.normal.as_slice(),
|
||||
&mut layout_data.shared_data.style,
|
||||
applicable_declarations_cache,
|
||||
new_animations_sender,
|
||||
applicable_declarations.normal_shareable);
|
||||
if applicable_declarations.before.len() > 0 {
|
||||
damage = damage | self.cascade_node_pseudo_element(
|
||||
|
@ -659,6 +689,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
&*applicable_declarations.before,
|
||||
&mut layout_data.data.before_style,
|
||||
applicable_declarations_cache,
|
||||
new_animations_sender,
|
||||
false);
|
||||
}
|
||||
if applicable_declarations.after.len() > 0 {
|
||||
|
@ -668,6 +699,7 @@ impl<'ln> MatchMethods for LayoutNode<'ln> {
|
|||
&*applicable_declarations.after,
|
||||
&mut layout_data.data.after_style,
|
||||
applicable_declarations_cache,
|
||||
new_animations_sender,
|
||||
false);
|
||||
}
|
||||
layout_data.data.restyle_damage = damage;
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use animation;
|
||||
use construct::ConstructionResult;
|
||||
use context::{SharedLayoutContext, SharedLayoutContextWrapper};
|
||||
use css::node_style::StyledNode;
|
||||
use display_list_builder::ToGfxColor;
|
||||
use flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
|
||||
use flow_ref::FlowRef;
|
||||
|
@ -49,11 +49,10 @@ use profile::time::{self, ProfilerMetadata, profile};
|
|||
use profile::time::{TimerMetadataFrameType, TimerMetadataReflowType};
|
||||
use script::dom::bindings::js::LayoutJS;
|
||||
use script::dom::node::{LayoutData, Node};
|
||||
use script::layout_interface::ReflowQueryType;
|
||||
use script::layout_interface::{ContentBoxResponse, ContentBoxesResponse};
|
||||
use script::layout_interface::{Animation, ContentBoxResponse, ContentBoxesResponse};
|
||||
use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC};
|
||||
use script::layout_interface::{MouseOverResponse, Msg};
|
||||
use script::layout_interface::{Reflow, ReflowGoal, ScriptLayoutChan, TrustedNodeAddress};
|
||||
use script::layout_interface::{MouseOverResponse, Msg, Reflow, ReflowGoal, ReflowQueryType};
|
||||
use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress};
|
||||
use script_traits::{ConstellationControlMsg, CompositorEvent, OpaqueScriptLayoutChannel};
|
||||
use script_traits::{ScriptControlChan, UntrustedNodeAddress};
|
||||
use std::borrow::ToOwned;
|
||||
|
@ -70,7 +69,7 @@ use style::selector_matching::Stylist;
|
|||
use style::stylesheets::{Origin, Stylesheet, iter_font_face_rules};
|
||||
use url::Url;
|
||||
use util::cursor::Cursor;
|
||||
use util::geometry::Au;
|
||||
use util::geometry::{Au, MAX_RECT};
|
||||
use util::logical_geometry::LogicalPoint;
|
||||
use util::mem::HeapSizeOf;
|
||||
use util::opts;
|
||||
|
@ -83,6 +82,9 @@ use util::workqueue::WorkQueue;
|
|||
///
|
||||
/// This needs to be protected by a mutex so we can do fast RPCs.
|
||||
pub struct LayoutTaskData {
|
||||
/// The root of the flow tree.
|
||||
pub root_flow: Option<FlowRef>,
|
||||
|
||||
/// The local image cache.
|
||||
pub local_image_cache: Arc<Mutex<LocalImageCache<UntrustedNodeAddress>>>,
|
||||
|
||||
|
@ -113,6 +115,16 @@ pub struct LayoutTaskData {
|
|||
|
||||
/// A queued response for the content boxes of a node.
|
||||
pub content_boxes_response: Vec<Rect<Au>>,
|
||||
|
||||
/// The list of currently-running animations.
|
||||
pub running_animations: Vec<Animation>,
|
||||
|
||||
/// Receives newly-discovered animations.
|
||||
pub new_animations_receiver: Receiver<Animation>,
|
||||
|
||||
/// A channel on which new animations that have been triggered by style recalculation can be
|
||||
/// sent.
|
||||
pub new_animations_sender: Sender<Animation>,
|
||||
}
|
||||
|
||||
/// Information needed by the layout task.
|
||||
|
@ -129,7 +141,7 @@ pub struct LayoutTask {
|
|||
/// The port on which we receive messages from the constellation
|
||||
pub pipeline_port: Receiver<LayoutControlMsg>,
|
||||
|
||||
//// The channel to send messages to ourself.
|
||||
/// The channel on which we or others can send messages to ourselves.
|
||||
pub chan: LayoutChan,
|
||||
|
||||
/// The channel on which messages can be sent to the constellation.
|
||||
|
@ -192,39 +204,37 @@ impl ImageResponder<UntrustedNodeAddress> for LayoutImageResponder {
|
|||
impl LayoutTaskFactory for LayoutTask {
|
||||
/// Spawns a new layout task.
|
||||
fn create(_phantom: Option<&mut LayoutTask>,
|
||||
id: PipelineId,
|
||||
url: Url,
|
||||
chan: OpaqueScriptLayoutChannel,
|
||||
pipeline_port: Receiver<LayoutControlMsg>,
|
||||
constellation_chan: ConstellationChan,
|
||||
failure_msg: Failure,
|
||||
script_chan: ScriptControlChan,
|
||||
paint_chan: PaintChan,
|
||||
resource_task: ResourceTask,
|
||||
img_cache_task: ImageCacheTask,
|
||||
font_cache_task: FontCacheTask,
|
||||
time_profiler_chan: time::ProfilerChan,
|
||||
mem_profiler_chan: mem::ProfilerChan,
|
||||
shutdown_chan: Sender<()>) {
|
||||
id: PipelineId,
|
||||
url: Url,
|
||||
chan: OpaqueScriptLayoutChannel,
|
||||
pipeline_port: Receiver<LayoutControlMsg>,
|
||||
constellation_chan: ConstellationChan,
|
||||
failure_msg: Failure,
|
||||
script_chan: ScriptControlChan,
|
||||
paint_chan: PaintChan,
|
||||
resource_task: ResourceTask,
|
||||
img_cache_task: ImageCacheTask,
|
||||
font_cache_task: FontCacheTask,
|
||||
time_profiler_chan: time::ProfilerChan,
|
||||
memory_profiler_chan: mem::ProfilerChan,
|
||||
shutdown_chan: Sender<()>) {
|
||||
let ConstellationChan(con_chan) = constellation_chan.clone();
|
||||
spawn_named_with_send_on_failure("LayoutTask", task_state::LAYOUT, move || {
|
||||
{ // Ensures layout task is destroyed before we send shutdown message
|
||||
let sender = chan.sender();
|
||||
let layout =
|
||||
LayoutTask::new(
|
||||
id,
|
||||
url,
|
||||
chan.receiver(),
|
||||
LayoutChan(sender),
|
||||
pipeline_port,
|
||||
constellation_chan,
|
||||
script_chan,
|
||||
paint_chan,
|
||||
resource_task,
|
||||
img_cache_task,
|
||||
font_cache_task,
|
||||
time_profiler_chan,
|
||||
mem_profiler_chan);
|
||||
let layout = LayoutTask::new(id,
|
||||
url,
|
||||
chan.receiver(),
|
||||
LayoutChan(sender),
|
||||
pipeline_port,
|
||||
constellation_chan,
|
||||
script_chan,
|
||||
paint_chan,
|
||||
resource_task,
|
||||
img_cache_task,
|
||||
font_cache_task,
|
||||
time_profiler_chan,
|
||||
memory_profiler_chan);
|
||||
layout.start();
|
||||
}
|
||||
shutdown_chan.send(()).unwrap();
|
||||
|
@ -297,6 +307,10 @@ impl LayoutTask {
|
|||
let reporter_name = format!("layout-reporter-{}", id.0);
|
||||
mem_profiler_chan.send(mem::ProfilerMsg::RegisterReporter(reporter_name.clone(), reporter));
|
||||
|
||||
|
||||
// Create the channel on which new animations can be sent.
|
||||
let (new_animations_sender, new_animations_receiver) = channel();
|
||||
|
||||
LayoutTask {
|
||||
id: id,
|
||||
url: url,
|
||||
|
@ -315,6 +329,7 @@ impl LayoutTask {
|
|||
first_reflow: Cell::new(true),
|
||||
rw_data: Arc::new(Mutex::new(
|
||||
LayoutTaskData {
|
||||
root_flow: None,
|
||||
local_image_cache: local_image_cache,
|
||||
constellation_chan: constellation_chan,
|
||||
screen_size: screen_size,
|
||||
|
@ -325,6 +340,9 @@ impl LayoutTask {
|
|||
generation: 0,
|
||||
content_box_response: Rect::zero(),
|
||||
content_boxes_response: Vec::new(),
|
||||
running_animations: Vec::new(),
|
||||
new_animations_receiver: new_animations_receiver,
|
||||
new_animations_sender: new_animations_sender,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
@ -341,7 +359,7 @@ impl LayoutTask {
|
|||
fn build_shared_layout_context(&self,
|
||||
rw_data: &LayoutTaskData,
|
||||
screen_size_changed: bool,
|
||||
reflow_root: &LayoutNode,
|
||||
reflow_root: Option<&LayoutNode>,
|
||||
url: &Url)
|
||||
-> SharedLayoutContext {
|
||||
SharedLayoutContext {
|
||||
|
@ -353,9 +371,10 @@ impl LayoutTask {
|
|||
font_cache_task: self.font_cache_task.clone(),
|
||||
stylist: &*rw_data.stylist,
|
||||
url: (*url).clone(),
|
||||
reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root),
|
||||
reflow_root: reflow_root.map(|node| OpaqueNodeMethods::from_layout_node(node)),
|
||||
dirty: Rect::zero(),
|
||||
generation: rw_data.generation,
|
||||
new_animations_sender: rw_data.new_animations_sender.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,8 +408,12 @@ impl LayoutTask {
|
|||
match port_to_read {
|
||||
PortToRead::Pipeline => {
|
||||
match self.pipeline_port.recv().unwrap() {
|
||||
LayoutControlMsg::TickAnimationsMsg => {
|
||||
self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data)
|
||||
}
|
||||
LayoutControlMsg::ExitNowMsg(exit_type) => {
|
||||
self.handle_request_helper(Msg::ExitNow(exit_type), possibly_locked_rw_data)
|
||||
self.handle_request_helper(Msg::ExitNow(exit_type),
|
||||
possibly_locked_rw_data)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -434,8 +457,12 @@ impl LayoutTask {
|
|||
LayoutTaskData>>)
|
||||
-> bool {
|
||||
match request {
|
||||
Msg::AddStylesheet(sheet, mq) => self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data),
|
||||
Msg::LoadStylesheet(url, mq) => self.handle_load_stylesheet(url, mq, possibly_locked_rw_data),
|
||||
Msg::AddStylesheet(sheet, mq) => {
|
||||
self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data)
|
||||
}
|
||||
Msg::LoadStylesheet(url, mq) => {
|
||||
self.handle_load_stylesheet(url, mq, possibly_locked_rw_data)
|
||||
}
|
||||
Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data),
|
||||
Msg::GetRPC(response_chan) => {
|
||||
response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as
|
||||
|
@ -443,10 +470,11 @@ impl LayoutTask {
|
|||
},
|
||||
Msg::Reflow(data) => {
|
||||
profile(time::ProfilerCategory::LayoutPerform,
|
||||
self.profiler_metadata(&*data),
|
||||
self.profiler_metadata(&data.reflow_info),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| self.handle_reflow(&*data, possibly_locked_rw_data));
|
||||
},
|
||||
Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data),
|
||||
Msg::ReapLayoutData(dead_layout_data) => {
|
||||
unsafe {
|
||||
self.handle_reap_layout_data(dead_layout_data)
|
||||
|
@ -486,9 +514,9 @@ impl LayoutTask {
|
|||
reports_chan.send(reports);
|
||||
}
|
||||
|
||||
/// Enters a quiescent state in which no new messages except for `layout_interface::Msg::ReapLayoutData` will be
|
||||
/// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given
|
||||
/// response channel.
|
||||
/// Enters a quiescent state in which no new messages except for
|
||||
/// `layout_interface::Msg::ReapLayoutData` will be processed until an `ExitNowMsg` is
|
||||
/// received. A pong is immediately sent on the given response channel.
|
||||
fn prepare_to_exit<'a>(&'a self,
|
||||
response_chan: Sender<()>,
|
||||
possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
|
||||
|
@ -586,7 +614,6 @@ impl LayoutTask {
|
|||
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
|
||||
}
|
||||
|
||||
/// Retrieves the flow tree root from the root node.
|
||||
fn try_get_layout_root(&self, node: LayoutNode) -> Option<FlowRef> {
|
||||
let mut layout_data_ref = node.mutate_layout_data();
|
||||
let layout_data =
|
||||
|
@ -698,7 +725,7 @@ impl LayoutTask {
|
|||
data: &Reflow,
|
||||
layout_root: &mut FlowRef,
|
||||
shared_layout_context: &mut SharedLayoutContext,
|
||||
rw_data: &mut RWGuard<'a>) {
|
||||
rw_data: &mut LayoutTaskData) {
|
||||
let writing_mode = flow::base(&**layout_root).writing_mode;
|
||||
profile(time::ProfilerCategory::LayoutDispListBuild,
|
||||
self.profiler_metadata(data),
|
||||
|
@ -714,7 +741,6 @@ impl LayoutTask {
|
|||
flow::mut_base(&mut **layout_root).clip =
|
||||
ClippingRegion::from_rect(&data.page_clip_rect);
|
||||
|
||||
let rw_data = &mut **rw_data;
|
||||
match rw_data.parallel_traversal {
|
||||
None => {
|
||||
sequential::build_display_list_for_subtree(layout_root, shared_layout_context);
|
||||
|
@ -767,7 +793,7 @@ impl LayoutTask {
|
|||
|
||||
/// The high-level routine that performs layout tasks.
|
||||
fn handle_reflow<'a>(&'a self,
|
||||
data: &Reflow,
|
||||
data: &ScriptReflow,
|
||||
possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
|
||||
// FIXME: Isolate this transmutation into a "bridge" module.
|
||||
// FIXME(rust#16366): The following line had to be moved because of a
|
||||
|
@ -779,8 +805,7 @@ impl LayoutTask {
|
|||
transmute(&mut node)
|
||||
};
|
||||
|
||||
debug!("layout: received layout request for: {}", data.url.serialize());
|
||||
debug!("layout: parsed Node tree");
|
||||
debug!("layout: received layout request for: {}", data.reflow_info.url.serialize());
|
||||
if log_enabled!(log::DEBUG) {
|
||||
node.dump();
|
||||
}
|
||||
|
@ -796,7 +821,6 @@ impl LayoutTask {
|
|||
// TODO: Calculate the "actual viewport":
|
||||
// http://www.w3.org/TR/css-device-adapt/#actual-viewport
|
||||
let viewport_size = data.window_size.initial_viewport;
|
||||
|
||||
let old_screen_size = rw_data.screen_size;
|
||||
let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()),
|
||||
Au::from_frac32_px(viewport_size.height.get()));
|
||||
|
@ -804,23 +828,19 @@ impl LayoutTask {
|
|||
|
||||
// Handle conditions where the entire flow tree is invalid.
|
||||
let screen_size_changed = current_screen_size != old_screen_size;
|
||||
|
||||
if screen_size_changed {
|
||||
let device = Device::new(MediaType::Screen, data.window_size.initial_viewport);
|
||||
rw_data.stylist.set_device(device);
|
||||
}
|
||||
|
||||
let needs_dirtying = rw_data.stylist.update();
|
||||
|
||||
// If the entire flow tree is invalid, then it will be reflowed anyhow.
|
||||
let needs_dirtying = rw_data.stylist.update();
|
||||
let needs_reflow = screen_size_changed && !needs_dirtying;
|
||||
|
||||
unsafe {
|
||||
if needs_dirtying {
|
||||
LayoutTask::dirty_all_nodes(node);
|
||||
}
|
||||
}
|
||||
|
||||
if needs_reflow {
|
||||
match self.try_get_layout_root(*node) {
|
||||
None => {}
|
||||
|
@ -833,13 +853,14 @@ impl LayoutTask {
|
|||
// Create a layout context for use throughout the following passes.
|
||||
let mut shared_layout_context = self.build_shared_layout_context(&*rw_data,
|
||||
screen_size_changed,
|
||||
node,
|
||||
&data.url);
|
||||
Some(&node),
|
||||
&data.reflow_info.url);
|
||||
|
||||
let mut layout_root = profile(time::ProfilerCategory::LayoutStyleRecalc,
|
||||
self.profiler_metadata(data),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
// Recalculate CSS styles and rebuild flows and fragments.
|
||||
profile(time::ProfilerCategory::LayoutStyleRecalc,
|
||||
self.profiler_metadata(&data.reflow_info),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
// Perform CSS selector matching and flow construction.
|
||||
let rw_data = &mut *rw_data;
|
||||
match rw_data.parallel_traversal {
|
||||
|
@ -847,39 +868,105 @@ impl LayoutTask {
|
|||
sequential::traverse_dom_preorder(*node, &shared_layout_context);
|
||||
}
|
||||
Some(ref mut traversal) => {
|
||||
parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal)
|
||||
parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal);
|
||||
}
|
||||
}
|
||||
|
||||
self.get_layout_root((*node).clone())
|
||||
});
|
||||
|
||||
// Retrieve the (possibly rebuilt) root flow.
|
||||
rw_data.root_flow = Some(self.get_layout_root((*node).clone()));
|
||||
|
||||
// Kick off animations if any were triggered.
|
||||
animation::process_new_animations(&mut *rw_data, self.id);
|
||||
|
||||
// Perform post-style recalculation layout passes.
|
||||
self.perform_post_style_recalc_layout_passes(&data.reflow_info,
|
||||
&mut rw_data,
|
||||
&mut shared_layout_context);
|
||||
|
||||
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
||||
match data.query_type {
|
||||
ReflowQueryType::ContentBoxQuery(node) => {
|
||||
self.process_content_box_request(node, &mut root_flow, &mut rw_data)
|
||||
}
|
||||
ReflowQueryType::ContentBoxesQuery(node) => {
|
||||
self.process_content_boxes_request(node, &mut root_flow, &mut rw_data)
|
||||
}
|
||||
ReflowQueryType::NoQuery => {}
|
||||
}
|
||||
|
||||
|
||||
// Tell script that we're done.
|
||||
//
|
||||
// FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
|
||||
// either select or a filtered recv() that only looks for messages of a given type.
|
||||
data.script_join_chan.send(()).unwrap();
|
||||
let ScriptControlChan(ref chan) = data.script_chan;
|
||||
chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap();
|
||||
}
|
||||
|
||||
fn tick_all_animations<'a>(&'a self,
|
||||
possibly_locked_rw_data: &mut Option<MutexGuard<'a,
|
||||
LayoutTaskData>>) {
|
||||
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
|
||||
animation::tick_all_animations(self, &mut rw_data)
|
||||
}
|
||||
|
||||
pub fn tick_animation<'a>(&'a self, animation: Animation, rw_data: &mut LayoutTaskData) {
|
||||
// FIXME(#5466, pcwalton): These data are lies.
|
||||
let reflow_info = Reflow {
|
||||
goal: ReflowGoal::ForDisplay,
|
||||
url: Url::parse("http://animation.com/").unwrap(),
|
||||
iframe: false,
|
||||
page_clip_rect: MAX_RECT,
|
||||
};
|
||||
|
||||
// Perform an abbreviated style recalc that operates without access to the DOM.
|
||||
let mut layout_context = self.build_shared_layout_context(&*rw_data,
|
||||
false,
|
||||
None,
|
||||
&reflow_info.url);
|
||||
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
||||
profile(time::ProfilerCategory::LayoutStyleRecalc,
|
||||
self.profiler_metadata(&reflow_info),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| animation::recalc_style_for_animation(root_flow.deref_mut(), &animation));
|
||||
|
||||
self.perform_post_style_recalc_layout_passes(&reflow_info,
|
||||
&mut *rw_data,
|
||||
&mut layout_context);
|
||||
}
|
||||
|
||||
fn perform_post_style_recalc_layout_passes<'a>(&'a self,
|
||||
data: &Reflow,
|
||||
rw_data: &mut LayoutTaskData,
|
||||
layout_context: &mut SharedLayoutContext) {
|
||||
let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone();
|
||||
profile(time::ProfilerCategory::LayoutRestyleDamagePropagation,
|
||||
self.profiler_metadata(data),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
if opts::get().nonincremental_layout || layout_root.compute_layout_damage()
|
||||
.contains(REFLOW_ENTIRE_DOCUMENT) {
|
||||
layout_root.reflow_entire_document()
|
||||
if opts::get().nonincremental_layout || root_flow.deref_mut()
|
||||
.compute_layout_damage()
|
||||
.contains(REFLOW_ENTIRE_DOCUMENT) {
|
||||
root_flow.deref_mut().reflow_entire_document()
|
||||
}
|
||||
});
|
||||
|
||||
// Verification of the flow tree, which ensures that all nodes were either marked as leaves
|
||||
// or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to
|
||||
// memory safety but is a useful debugging tool.)
|
||||
self.verify_flow_tree(&mut layout_root);
|
||||
self.verify_flow_tree(&mut root_flow);
|
||||
|
||||
if opts::get().trace_layout {
|
||||
layout_debug::begin_trace(layout_root.clone());
|
||||
layout_debug::begin_trace(root_flow.clone());
|
||||
}
|
||||
|
||||
// Resolve generated content.
|
||||
profile(time::ProfilerCategory::LayoutGeneratedContent,
|
||||
self.profiler_metadata(data),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
sequential::resolve_generated_content(&mut layout_root, &shared_layout_context)
|
||||
});
|
||||
|| sequential::resolve_generated_content(&mut root_flow, &layout_context));
|
||||
|
||||
// Perform the primary layout passes over the flow tree to compute the locations of all
|
||||
// the boxes.
|
||||
|
@ -887,18 +974,17 @@ impl LayoutTask {
|
|||
self.profiler_metadata(data),
|
||||
self.time_profiler_chan.clone(),
|
||||
|| {
|
||||
let rw_data = &mut *rw_data;
|
||||
match rw_data.parallel_traversal {
|
||||
None => {
|
||||
// Sequential mode.
|
||||
self.solve_constraints(&mut layout_root, &shared_layout_context)
|
||||
self.solve_constraints(&mut root_flow, &layout_context)
|
||||
}
|
||||
Some(_) => {
|
||||
// Parallel mode.
|
||||
self.solve_constraints_parallel(data,
|
||||
rw_data,
|
||||
&mut layout_root,
|
||||
&mut shared_layout_context);
|
||||
&mut root_flow,
|
||||
&mut *layout_context);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -907,23 +993,13 @@ impl LayoutTask {
|
|||
match data.goal {
|
||||
ReflowGoal::ForDisplay => {
|
||||
self.build_display_list_for_reflow(data,
|
||||
&mut layout_root,
|
||||
&mut shared_layout_context,
|
||||
&mut rw_data);
|
||||
&mut root_flow,
|
||||
&mut *layout_context,
|
||||
rw_data);
|
||||
}
|
||||
ReflowGoal::ForScriptQuery => {}
|
||||
}
|
||||
|
||||
match data.query_type {
|
||||
ReflowQueryType::ContentBoxQuery(node) => {
|
||||
self.process_content_box_request(node, &mut layout_root, &mut rw_data)
|
||||
}
|
||||
ReflowQueryType::ContentBoxesQuery(node) => {
|
||||
self.process_content_boxes_request(node, &mut layout_root, &mut rw_data)
|
||||
}
|
||||
ReflowQueryType::NoQuery => {}
|
||||
}
|
||||
|
||||
self.first_reflow.set(false);
|
||||
|
||||
if opts::get().trace_layout {
|
||||
|
@ -931,18 +1007,10 @@ impl LayoutTask {
|
|||
}
|
||||
|
||||
if opts::get().dump_flow_tree {
|
||||
layout_root.dump();
|
||||
root_flow.dump();
|
||||
}
|
||||
|
||||
rw_data.generation += 1;
|
||||
|
||||
// Tell script that we're done.
|
||||
//
|
||||
// FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
|
||||
// either select or a filtered recv() that only looks for messages of a given type.
|
||||
data.script_join_chan.send(()).unwrap();
|
||||
let ScriptControlChan(ref chan) = data.script_chan;
|
||||
chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap();
|
||||
}
|
||||
|
||||
unsafe fn dirty_all_nodes(node: &mut LayoutNode) {
|
||||
|
|
|
@ -25,40 +25,45 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[macro_use]extern crate bitflags;
|
||||
extern crate azure;
|
||||
extern crate cssparser;
|
||||
extern crate canvas;
|
||||
extern crate geom;
|
||||
extern crate gfx;
|
||||
extern crate layout_traits;
|
||||
extern crate script;
|
||||
extern crate script_traits;
|
||||
extern crate "rustc-serialize" as rustc_serialize;
|
||||
extern crate png;
|
||||
extern crate style;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
#[macro_use]
|
||||
#[no_link]
|
||||
extern crate "plugins" as servo_plugins;
|
||||
extern crate net;
|
||||
extern crate msg;
|
||||
|
||||
#[macro_use]
|
||||
extern crate profile;
|
||||
extern crate selectors;
|
||||
|
||||
#[macro_use]
|
||||
extern crate util;
|
||||
|
||||
extern crate string_cache;
|
||||
|
||||
extern crate "rustc-serialize" as rustc_serialize;
|
||||
extern crate alloc;
|
||||
extern crate azure;
|
||||
extern crate canvas;
|
||||
extern crate clock_ticks;
|
||||
extern crate collections;
|
||||
extern crate cssparser;
|
||||
extern crate encoding;
|
||||
extern crate geom;
|
||||
extern crate gfx;
|
||||
extern crate layout_traits;
|
||||
extern crate libc;
|
||||
extern crate msg;
|
||||
extern crate net;
|
||||
extern crate png;
|
||||
extern crate script;
|
||||
extern crate script_traits;
|
||||
extern crate selectors;
|
||||
extern crate string_cache;
|
||||
extern crate style;
|
||||
extern crate url;
|
||||
|
||||
// Listed first because of macro definitions
|
||||
pub mod layout_debug;
|
||||
|
||||
pub mod animation;
|
||||
pub mod block;
|
||||
pub mod construct;
|
||||
pub mod context;
|
||||
|
|
|
@ -180,7 +180,8 @@ impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> {
|
|||
node.cascade_node(self.layout_context.shared,
|
||||
parent_opt,
|
||||
&applicable_declarations,
|
||||
self.layout_context.applicable_declarations_cache());
|
||||
self.layout_context.applicable_declarations_cache(),
|
||||
&self.layout_context.shared.new_animations_sender);
|
||||
}
|
||||
|
||||
// Add ourselves to the LRU cache.
|
||||
|
|
|
@ -333,11 +333,16 @@ impl<'ln> LayoutNode<'ln> {
|
|||
/// While doing a reflow, the node at the root has no parent, as far as we're
|
||||
/// concerned. This method returns `None` at the reflow root.
|
||||
pub fn layout_parent_node(self, shared: &SharedLayoutContext) -> Option<LayoutNode<'ln>> {
|
||||
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&self);
|
||||
if opaque_node == shared.reflow_root {
|
||||
None
|
||||
} else {
|
||||
self.parent_node()
|
||||
match shared.reflow_root {
|
||||
None => panic!("layout_parent_node(): This layout has no access to the DOM!"),
|
||||
Some(reflow_root) => {
|
||||
let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&self);
|
||||
if opaque_node == reflow_root {
|
||||
None
|
||||
} else {
|
||||
self.parent_node()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ use url::Url;
|
|||
/// Messages sent to the layout task from the constellation
|
||||
pub enum LayoutControlMsg {
|
||||
ExitNowMsg(PipelineExitType),
|
||||
TickAnimationsMsg,
|
||||
}
|
||||
|
||||
/// A channel wrapper for constellation messages
|
||||
|
|
|
@ -214,6 +214,10 @@ pub enum Msg {
|
|||
SetCursor(Cursor),
|
||||
/// Dispatch a mozbrowser event to a given iframe. Only available in experimental mode.
|
||||
MozBrowserEventMsg(PipelineId, SubpageId, MozBrowserEvent),
|
||||
/// Indicates whether this pipeline is currently running animations.
|
||||
ChangeRunningAnimationsState(PipelineId, bool),
|
||||
/// Requests that the constellation instruct layout to begin a new tick of the animation.
|
||||
TickAnimation(PipelineId),
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API#Events
|
||||
|
|
|
@ -186,4 +186,10 @@ partial interface CSSStyleDeclaration {
|
|||
[TreatNullAs=EmptyString] attribute DOMString zIndex;
|
||||
|
||||
[TreatNullAs=EmptyString] attribute DOMString imageRendering;
|
||||
|
||||
[TreatNullAs=EmptyString] attribute DOMString transition;
|
||||
[TreatNullAs=EmptyString] attribute DOMString transitionDuration;
|
||||
[TreatNullAs=EmptyString] attribute DOMString transitionTimingFunction;
|
||||
[TreatNullAs=EmptyString] attribute DOMString transitionProperty;
|
||||
[TreatNullAs=EmptyString] attribute DOMString transitionDelay;
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@ use dom::performance::Performance;
|
|||
use dom::screen::Screen;
|
||||
use dom::storage::Storage;
|
||||
use layout_interface::{ReflowGoal, ReflowQueryType, LayoutRPC, LayoutChan, Reflow, Msg};
|
||||
use layout_interface::{ContentBoxResponse, ContentBoxesResponse};
|
||||
use layout_interface::{ContentBoxResponse, ContentBoxesResponse, ScriptReflow};
|
||||
use page::Page;
|
||||
use script_task::{TimerSource, ScriptChan};
|
||||
use script_task::ScriptMsg;
|
||||
|
@ -564,17 +564,19 @@ impl<'a> WindowHelpers for JSRef<'a, Window> {
|
|||
}
|
||||
|
||||
// Send new document and relevant styles to layout.
|
||||
let reflow = box Reflow {
|
||||
let reflow = box ScriptReflow {
|
||||
reflow_info: Reflow {
|
||||
goal: goal,
|
||||
url: self.get_url(),
|
||||
iframe: self.parent_info.is_some(),
|
||||
page_clip_rect: self.page_clip_rect.get(),
|
||||
},
|
||||
document_root: root.to_trusted_node_address(),
|
||||
url: self.get_url(),
|
||||
iframe: self.parent_info.is_some(),
|
||||
goal: goal,
|
||||
window_size: window_size,
|
||||
script_chan: self.control_chan.clone(),
|
||||
script_join_chan: join_chan,
|
||||
id: last_reflow_id.get(),
|
||||
query_type: query_type,
|
||||
page_clip_rect: self.page_clip_rect.get(),
|
||||
};
|
||||
|
||||
let LayoutChan(ref chan) = self.layout_chan;
|
||||
|
|
|
@ -10,14 +10,16 @@ use dom::node::LayoutData;
|
|||
|
||||
use geom::point::Point2D;
|
||||
use geom::rect::Rect;
|
||||
use libc::uintptr_t;
|
||||
use msg::constellation_msg::{PipelineExitType, WindowSizeData};
|
||||
use profile::mem::{Reporter, ReportsChan};
|
||||
use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel, UntrustedNodeAddress};
|
||||
use std::any::Any;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::boxed::BoxAny;
|
||||
use style::stylesheets::Stylesheet;
|
||||
use style::animation::PropertyAnimation;
|
||||
use style::media_queries::MediaQueryList;
|
||||
use style::stylesheets::Stylesheet;
|
||||
use url::Url;
|
||||
use util::geometry::Au;
|
||||
|
||||
|
@ -35,11 +37,14 @@ pub enum Msg {
|
|||
SetQuirksMode,
|
||||
|
||||
/// Requests a reflow.
|
||||
Reflow(Box<Reflow>),
|
||||
Reflow(Box<ScriptReflow>),
|
||||
|
||||
/// Get an RPC interface.
|
||||
GetRPC(Sender<Box<LayoutRPC + Send>>),
|
||||
|
||||
/// Requests that the layout task render the next frame of all animations.
|
||||
TickAnimations,
|
||||
|
||||
/// Destroys layout data associated with a DOM node.
|
||||
///
|
||||
/// TODO(pcwalton): Maybe think about batching to avoid message traffic.
|
||||
|
@ -100,14 +105,22 @@ pub enum ReflowQueryType {
|
|||
|
||||
/// Information needed for a reflow.
|
||||
pub struct Reflow {
|
||||
/// The document node.
|
||||
pub document_root: TrustedNodeAddress,
|
||||
/// The goal of reflow: either to render to the screen or to flush layout info for script.
|
||||
pub goal: ReflowGoal,
|
||||
/// The URL of the page.
|
||||
pub url: Url,
|
||||
/// Is the current reflow of an iframe, as opposed to a root window?
|
||||
pub iframe: bool,
|
||||
/// A clipping rectangle for the page, an enlarged rectangle containing the viewport.
|
||||
pub page_clip_rect: Rect<Au>,
|
||||
}
|
||||
|
||||
/// Information needed for a script-initiated reflow.
|
||||
pub struct ScriptReflow {
|
||||
/// General reflow data.
|
||||
pub reflow_info: Reflow,
|
||||
/// The document node.
|
||||
pub document_root: TrustedNodeAddress,
|
||||
/// The channel through which messages can be sent back to the script task.
|
||||
pub script_chan: ScriptControlChan,
|
||||
/// The current window size.
|
||||
|
@ -118,8 +131,6 @@ pub struct Reflow {
|
|||
pub id: u32,
|
||||
/// The type of query if any to perform during this reflow.
|
||||
pub query_type: ReflowQueryType,
|
||||
/// A clipping rectangle for the page, an enlarged rectangle containing the viewport.
|
||||
pub page_clip_rect: Rect<Au>,
|
||||
}
|
||||
|
||||
/// Encapsulates a channel to the layout task.
|
||||
|
@ -165,3 +176,28 @@ impl ScriptLayoutChan for OpaqueScriptLayoutChannel {
|
|||
*receiver.downcast::<Receiver<Msg>>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of an opaque node.
|
||||
pub type OpaqueNode = uintptr_t;
|
||||
|
||||
/// State relating to an animation.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Animation {
|
||||
/// An opaque reference to the DOM node participating in the animation.
|
||||
pub node: OpaqueNode,
|
||||
/// A description of the property animation that is occurring.
|
||||
pub property_animation: PropertyAnimation,
|
||||
/// The start time of the animation, as returned by `time::precise_time_s()`.
|
||||
pub start_time: f64,
|
||||
/// The end time of the animation, as returned by `time::precise_time_s()`.
|
||||
pub end_time: f64,
|
||||
}
|
||||
|
||||
impl Animation {
|
||||
/// Returns the duration of this animation in seconds.
|
||||
#[inline]
|
||||
pub fn duration(&self) -> f64 {
|
||||
self.end_time - self.start_time
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,11 @@ dependencies = [
|
|||
"gleam 0.0.1 (git+https://github.com/servo/gleam)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clock_ticks"
|
||||
version = "0.0.4"
|
||||
source = "git+https://github.com/tomaka/clock_ticks#6a3005279bedc406b13eea09ff92447f05ca0de6"
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.1.1"
|
||||
|
@ -503,6 +508,7 @@ dependencies = [
|
|||
"azure 0.1.0 (git+https://github.com/servo/rust-azure)",
|
||||
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"canvas 0.0.1",
|
||||
"clock_ticks 0.0.4 (git+https://github.com/tomaka/clock_ticks)",
|
||||
"cssparser 0.2.0 (git+https://github.com/servo/rust-cssparser)",
|
||||
"encoding 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use properties::ComputedValues;
|
||||
use properties::longhands::transition_property::computed_value::TransitionProperty;
|
||||
use properties::longhands::transition_timing_function::computed_value::{StartEnd};
|
||||
use properties::longhands::transition_timing_function::computed_value::{TransitionTimingFunction};
|
||||
use properties::longhands::transition_property;
|
||||
use values::computed::{LengthOrPercentageOrAuto, Time};
|
||||
|
||||
use std::num::Float;
|
||||
use util::bezier::Bezier;
|
||||
use util::geometry::Au;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PropertyAnimation {
|
||||
property: AnimatedProperty,
|
||||
timing_function: TransitionTimingFunction,
|
||||
duration: Time,
|
||||
}
|
||||
|
||||
impl PropertyAnimation {
|
||||
/// Creates a new property animation for the given transition index and old and new styles.
|
||||
/// Any number of animations may be returned, from zero (if the property did not animate) to
|
||||
/// one (for a single transition property) to arbitrarily many (for `all`).
|
||||
pub fn from_transition(transition_index: usize,
|
||||
old_style: &ComputedValues,
|
||||
new_style: &mut ComputedValues)
|
||||
-> Vec<PropertyAnimation> {
|
||||
let mut result = Vec::new();
|
||||
let transition_property =
|
||||
new_style.get_animation().transition_property.0[transition_index];
|
||||
if transition_property != TransitionProperty::All {
|
||||
if let Some(property_animation) =
|
||||
PropertyAnimation::from_transition_property(transition_property,
|
||||
transition_index,
|
||||
old_style,
|
||||
new_style) {
|
||||
result.push(property_animation)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
for transition_property in
|
||||
transition_property::computed_value::ALL_TRANSITION_PROPERTIES.iter() {
|
||||
if let Some(property_animation) =
|
||||
PropertyAnimation::from_transition_property(*transition_property,
|
||||
transition_index,
|
||||
old_style,
|
||||
new_style) {
|
||||
result.push(property_animation)
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn from_transition_property(transition_property: TransitionProperty,
|
||||
transition_index: usize,
|
||||
old_style: &ComputedValues,
|
||||
new_style: &mut ComputedValues)
|
||||
-> Option<PropertyAnimation> {
|
||||
let animation_style = new_style.get_animation();
|
||||
let animated_property = match transition_property {
|
||||
TransitionProperty::All => {
|
||||
panic!("Don't use `TransitionProperty::All` with \
|
||||
`PropertyAnimation::from_transition_property`!")
|
||||
}
|
||||
TransitionProperty::Top => {
|
||||
AnimatedProperty::Top(old_style.get_positionoffsets().top,
|
||||
new_style.get_positionoffsets().top)
|
||||
}
|
||||
TransitionProperty::Right => {
|
||||
AnimatedProperty::Right(old_style.get_positionoffsets().right,
|
||||
new_style.get_positionoffsets().right)
|
||||
}
|
||||
TransitionProperty::Bottom => {
|
||||
AnimatedProperty::Bottom(old_style.get_positionoffsets().bottom,
|
||||
new_style.get_positionoffsets().bottom)
|
||||
}
|
||||
TransitionProperty::Left => {
|
||||
AnimatedProperty::Left(old_style.get_positionoffsets().left,
|
||||
new_style.get_positionoffsets().left)
|
||||
}
|
||||
};
|
||||
|
||||
let property_animation = PropertyAnimation {
|
||||
property: animated_property,
|
||||
timing_function:
|
||||
*animation_style.transition_timing_function.0.get_mod(transition_index),
|
||||
duration: *animation_style.transition_duration.0.get_mod(transition_index),
|
||||
};
|
||||
if property_animation.does_not_animate() {
|
||||
None
|
||||
} else {
|
||||
Some(property_animation)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self, style: &mut ComputedValues, time: f64) {
|
||||
let progress = match self.timing_function {
|
||||
TransitionTimingFunction::CubicBezier(p1, p2) => {
|
||||
// See `WebCore::AnimationBase::solveEpsilon(double)` in WebKit.
|
||||
let epsilon = 1.0 / (200.0 * self.duration.seconds());
|
||||
Bezier::new(p1, p2).solve(time, epsilon)
|
||||
}
|
||||
TransitionTimingFunction::Steps(steps, StartEnd::Start) => {
|
||||
(time * (steps as f64)).ceil() / (steps as f64)
|
||||
}
|
||||
TransitionTimingFunction::Steps(steps, StartEnd::End) => {
|
||||
(time * (steps as f64)).floor() / (steps as f64)
|
||||
}
|
||||
};
|
||||
match self.property {
|
||||
AnimatedProperty::Top(ref start, ref end) => {
|
||||
if let Some(value) = start.interpolate(end, progress) {
|
||||
style.mutate_positionoffsets().top = value
|
||||
}
|
||||
}
|
||||
AnimatedProperty::Right(ref start, ref end) => {
|
||||
if let Some(value) = start.interpolate(end, progress) {
|
||||
style.mutate_positionoffsets().right = value
|
||||
}
|
||||
}
|
||||
AnimatedProperty::Bottom(ref start, ref end) => {
|
||||
if let Some(value) = start.interpolate(end, progress) {
|
||||
style.mutate_positionoffsets().bottom = value
|
||||
}
|
||||
}
|
||||
AnimatedProperty::Left(ref start, ref end) => {
|
||||
if let Some(value) = start.interpolate(end, progress) {
|
||||
style.mutate_positionoffsets().left = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn does_not_animate(&self) -> bool {
|
||||
self.property.does_not_animate() || self.duration == Time(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum AnimatedProperty {
|
||||
Top(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
|
||||
Right(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
|
||||
Bottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
|
||||
Left(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto),
|
||||
}
|
||||
|
||||
impl AnimatedProperty {
|
||||
#[inline]
|
||||
fn does_not_animate(&self) -> bool {
|
||||
match *self {
|
||||
AnimatedProperty::Top(ref a, ref b) |
|
||||
AnimatedProperty::Right(ref a, ref b) |
|
||||
AnimatedProperty::Bottom(ref a, ref b) |
|
||||
AnimatedProperty::Left(ref a, ref b) => a == b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait Interpolate {
|
||||
fn interpolate(&self, other: &Self, time: f64) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl Interpolate for Au {
|
||||
#[inline]
|
||||
fn interpolate(&self, other: &Au, time: f64) -> Option<Au> {
|
||||
Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32))
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpolate for f64 {
|
||||
#[inline]
|
||||
fn interpolate(&self, other: &f64, time: f64) -> Option<f64> {
|
||||
Some(*self + (*other - *self) * time)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpolate for LengthOrPercentageOrAuto {
|
||||
#[inline]
|
||||
fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64)
|
||||
-> Option<LengthOrPercentageOrAuto> {
|
||||
match (*self, *other) {
|
||||
(LengthOrPercentageOrAuto::Length(ref this),
|
||||
LengthOrPercentageOrAuto::Length(ref other)) => {
|
||||
this.interpolate(other, time).and_then(|value| {
|
||||
Some(LengthOrPercentageOrAuto::Length(value))
|
||||
})
|
||||
}
|
||||
(LengthOrPercentageOrAuto::Percentage(ref this),
|
||||
LengthOrPercentageOrAuto::Percentage(ref other)) => {
|
||||
this.interpolate(other, time).and_then(|value| {
|
||||
Some(LengthOrPercentageOrAuto::Percentage(value))
|
||||
})
|
||||
}
|
||||
(LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => {
|
||||
Some(LengthOrPercentageOrAuto::Auto)
|
||||
}
|
||||
(_, _) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accesses an element of an array, "wrapping around" using modular arithmetic. This is needed
|
||||
/// to handle values of differing lengths according to CSS-TRANSITIONS § 2.
|
||||
pub trait GetMod {
|
||||
type Item;
|
||||
fn get_mod(&self, i: usize) -> &Self::Item;
|
||||
}
|
||||
|
||||
impl<T> GetMod for Vec<T> {
|
||||
type Item = T;
|
||||
fn get_mod(&self, i: usize) -> &T {
|
||||
&(*self)[i % self.len()]
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ pub mod node;
|
|||
pub mod media_queries;
|
||||
pub mod font_face;
|
||||
pub mod legacy;
|
||||
pub mod animation;
|
||||
|
||||
macro_rules! reexport_computed_values {
|
||||
( $( $name: ident )+ ) => {
|
||||
|
|
|
@ -3377,6 +3377,367 @@ pub mod longhands {
|
|||
}
|
||||
}
|
||||
</%self:longhand>
|
||||
|
||||
${new_style_struct("Animation", is_inherited=False)}
|
||||
|
||||
// TODO(pcwalton): Multiple transitions.
|
||||
<%self:longhand name="transition-duration">
|
||||
use values::specified::Time;
|
||||
|
||||
pub use self::computed_value::T as SpecifiedValue;
|
||||
pub use values::specified::Time as SingleSpecifiedValue;
|
||||
|
||||
pub mod computed_value {
|
||||
use cssparser::ToCss;
|
||||
use text_writer::{self, TextWriter};
|
||||
use values::computed::{Context, ToComputedValue};
|
||||
|
||||
pub use values::computed::Time as SingleComputedValue;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct T(pub Vec<SingleComputedValue>);
|
||||
|
||||
impl ToComputedValue for T {
|
||||
type ComputedValue = T;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, _: &Context) -> T {
|
||||
(*self).clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for T {
|
||||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||||
if self.0.is_empty() {
|
||||
return dest.write_str("none")
|
||||
}
|
||||
for (i, value) in self.0.iter().enumerate() {
|
||||
if i != 0 {
|
||||
try!(dest.write_str(", "))
|
||||
}
|
||||
try!(value.to_css(dest))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> {
|
||||
Time::parse(input)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_initial_value() -> computed_value::T {
|
||||
computed_value::T(vec![get_initial_single_value()])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_initial_single_value() -> Time {
|
||||
Time(0.0)
|
||||
}
|
||||
|
||||
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
|
||||
Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
|
||||
}
|
||||
</%self:longhand>
|
||||
|
||||
// TODO(pcwalton): Lots more timing functions.
|
||||
// TODO(pcwalton): Multiple transitions.
|
||||
<%self:longhand name="transition-timing-function">
|
||||
use self::computed_value::{StartEnd, TransitionTimingFunction};
|
||||
use values::computed::{Context, ToComputedValue};
|
||||
|
||||
use geom::point::Point2D;
|
||||
|
||||
pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue;
|
||||
pub use self::computed_value::T as SpecifiedValue;
|
||||
|
||||
static EASE: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
|
||||
x: 0.25,
|
||||
y: 0.1,
|
||||
}, Point2D {
|
||||
x: 0.25,
|
||||
y: 1.0,
|
||||
});
|
||||
static LINEAR: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
}, Point2D {
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
});
|
||||
static EASE_IN: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
|
||||
x: 0.42,
|
||||
y: 0.0,
|
||||
}, Point2D {
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
});
|
||||
static EASE_OUT: TransitionTimingFunction = TransitionTimingFunction::CubicBezier(Point2D {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
}, Point2D {
|
||||
x: 0.58,
|
||||
y: 1.0,
|
||||
});
|
||||
static EASE_IN_OUT: TransitionTimingFunction =
|
||||
TransitionTimingFunction::CubicBezier(Point2D {
|
||||
x: 0.42,
|
||||
y: 0.0,
|
||||
}, Point2D {
|
||||
x: 0.58,
|
||||
y: 1.0,
|
||||
});
|
||||
static STEP_START: TransitionTimingFunction =
|
||||
TransitionTimingFunction::Steps(1, StartEnd::Start);
|
||||
static STEP_END: TransitionTimingFunction =
|
||||
TransitionTimingFunction::Steps(1, StartEnd::End);
|
||||
|
||||
pub mod computed_value {
|
||||
use cssparser::ToCss;
|
||||
use geom::point::Point2D;
|
||||
use text_writer::{self, TextWriter};
|
||||
|
||||
pub use self::TransitionTimingFunction as SingleComputedValue;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum TransitionTimingFunction {
|
||||
CubicBezier(Point2D<f64>, Point2D<f64>),
|
||||
Steps(u32, StartEnd),
|
||||
}
|
||||
|
||||
impl ToCss for TransitionTimingFunction {
|
||||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||||
match *self {
|
||||
TransitionTimingFunction::CubicBezier(p1, p2) => {
|
||||
try!(dest.write_str("cubic-bezier("));
|
||||
try!(p1.x.to_css(dest));
|
||||
try!(dest.write_str(", "));
|
||||
try!(p1.y.to_css(dest));
|
||||
try!(dest.write_str(", "));
|
||||
try!(p2.x.to_css(dest));
|
||||
try!(dest.write_str(", "));
|
||||
try!(p2.y.to_css(dest));
|
||||
dest.write_str(")")
|
||||
}
|
||||
TransitionTimingFunction::Steps(steps, start_end) => {
|
||||
try!(dest.write_str("steps("));
|
||||
try!(steps.to_css(dest));
|
||||
try!(dest.write_str(", "));
|
||||
try!(start_end.to_css(dest));
|
||||
dest.write_str(")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum StartEnd {
|
||||
Start,
|
||||
End,
|
||||
}
|
||||
|
||||
impl ToCss for StartEnd {
|
||||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||||
match *self {
|
||||
StartEnd::Start => dest.write_str("start"),
|
||||
StartEnd::End => dest.write_str("end"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct T(pub Vec<TransitionTimingFunction>);
|
||||
|
||||
impl ToCss for T {
|
||||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||||
if self.0.is_empty() {
|
||||
return dest.write_str("none")
|
||||
}
|
||||
for (i, value) in self.0.iter().enumerate() {
|
||||
if i != 0 {
|
||||
try!(dest.write_str(", "))
|
||||
}
|
||||
try!(value.to_css(dest))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToComputedValue for SpecifiedValue {
|
||||
type ComputedValue = computed_value::T;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, _: &Context) -> computed_value::T {
|
||||
(*self).clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_initial_value() -> computed_value::T {
|
||||
computed_value::T(vec![get_initial_single_value()])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_initial_single_value() -> TransitionTimingFunction {
|
||||
EASE
|
||||
}
|
||||
|
||||
pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> {
|
||||
if let Ok(function_name) = input.try(|input| input.expect_function()) {
|
||||
return match_ignore_ascii_case! {
|
||||
function_name,
|
||||
"cubic-bezier" => {
|
||||
let (mut p1x, mut p1y, mut p2x, mut p2y) = (0.0, 0.0, 0.0, 0.0);
|
||||
try!(input.parse_nested_block(|input| {
|
||||
p1x = try!(input.expect_number());
|
||||
try!(input.expect_comma());
|
||||
p1y = try!(input.expect_number());
|
||||
try!(input.expect_comma());
|
||||
p2x = try!(input.expect_number());
|
||||
try!(input.expect_comma());
|
||||
p2y = try!(input.expect_number());
|
||||
Ok(())
|
||||
}));
|
||||
let (p1, p2) = (Point2D(p1x, p1y), Point2D(p2x, p2y));
|
||||
Ok(TransitionTimingFunction::CubicBezier(p1, p2))
|
||||
},
|
||||
"steps" => {
|
||||
let (mut step_count, mut start_end) = (0, computed_value::StartEnd::Start);
|
||||
try!(input.parse_nested_block(|input| {
|
||||
step_count = try!(input.expect_integer());
|
||||
try!(input.expect_comma());
|
||||
start_end = try!(match_ignore_ascii_case! {
|
||||
try!(input.expect_ident()),
|
||||
"start" => Ok(computed_value::StartEnd::Start),
|
||||
"end" => Ok(computed_value::StartEnd::End)
|
||||
_ => Err(())
|
||||
});
|
||||
Ok(())
|
||||
}));
|
||||
Ok(TransitionTimingFunction::Steps(step_count as u32, start_end))
|
||||
}
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
match_ignore_ascii_case! {
|
||||
try!(input.expect_ident()),
|
||||
"ease" => Ok(EASE),
|
||||
"linear" => Ok(LINEAR),
|
||||
"ease-in" => Ok(EASE_IN),
|
||||
"ease-out" => Ok(EASE_OUT),
|
||||
"ease-in-out" => Ok(EASE_IN_OUT),
|
||||
"step-start" => Ok(STEP_START),
|
||||
"step-end" => Ok(STEP_END)
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
|
||||
Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
|
||||
}
|
||||
</%self:longhand>
|
||||
|
||||
// TODO(pcwalton): Lots more properties.
|
||||
<%self:longhand name="transition-property">
|
||||
use self::computed_value::TransitionProperty;
|
||||
use values::computed::{ToComputedValue, Context};
|
||||
|
||||
pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue;
|
||||
pub use self::computed_value::T as SpecifiedValue;
|
||||
|
||||
pub mod computed_value {
|
||||
use cssparser::ToCss;
|
||||
use text_writer::{self, TextWriter};
|
||||
|
||||
pub use self::TransitionProperty as SingleComputedValue;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum TransitionProperty {
|
||||
All,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
Left,
|
||||
}
|
||||
|
||||
pub static ALL_TRANSITION_PROPERTIES: [TransitionProperty; 4] = [
|
||||
TransitionProperty::Top,
|
||||
TransitionProperty::Right,
|
||||
TransitionProperty::Bottom,
|
||||
TransitionProperty::Left,
|
||||
];
|
||||
|
||||
impl ToCss for TransitionProperty {
|
||||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||||
match *self {
|
||||
TransitionProperty::All => dest.write_str("all"),
|
||||
TransitionProperty::Top => dest.write_str("top"),
|
||||
TransitionProperty::Right => dest.write_str("right"),
|
||||
TransitionProperty::Bottom => dest.write_str("bottom"),
|
||||
TransitionProperty::Left => dest.write_str("left"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct T(pub Vec<SingleComputedValue>);
|
||||
|
||||
impl ToCss for T {
|
||||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||||
if self.0.is_empty() {
|
||||
return dest.write_str("none")
|
||||
}
|
||||
for (i, value) in self.0.iter().enumerate() {
|
||||
if i != 0 {
|
||||
try!(dest.write_str(", "))
|
||||
}
|
||||
try!(value.to_css(dest))
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_initial_value() -> computed_value::T {
|
||||
computed_value::T(Vec::new())
|
||||
}
|
||||
|
||||
pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> {
|
||||
match_ignore_ascii_case! {
|
||||
try!(input.expect_ident()),
|
||||
"all" => Ok(TransitionProperty::All),
|
||||
"top" => Ok(TransitionProperty::Top),
|
||||
"right" => Ok(TransitionProperty::Right),
|
||||
"bottom" => Ok(TransitionProperty::Bottom),
|
||||
"left" => Ok(TransitionProperty::Left)
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
|
||||
Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one))))
|
||||
}
|
||||
|
||||
impl ToComputedValue for SpecifiedValue {
|
||||
type ComputedValue = computed_value::T;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, _: &Context) -> computed_value::T {
|
||||
(*self).clone()
|
||||
}
|
||||
}
|
||||
</%self:longhand>
|
||||
|
||||
<%self:longhand name="transition-delay">
|
||||
pub use properties::longhands::transition_duration::{SingleSpecifiedValue, SpecifiedValue};
|
||||
pub use properties::longhands::transition_duration::{computed_value};
|
||||
pub use properties::longhands::transition_duration::{get_initial_single_value};
|
||||
pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one};
|
||||
</%self:longhand>
|
||||
}
|
||||
|
||||
|
||||
|
@ -3401,6 +3762,8 @@ pub mod shorthands {
|
|||
Option<longhands::${sub_property.ident}::SpecifiedValue>,
|
||||
% endfor
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
|
||||
${caller.body()}
|
||||
}
|
||||
|
@ -3879,6 +4242,99 @@ pub mod shorthands {
|
|||
overflow_y: Some(overflow_y::SpecifiedValue(overflow)),
|
||||
})
|
||||
</%self:shorthand>
|
||||
|
||||
<%self:shorthand name="transition"
|
||||
sub_properties="transition-property transition-duration transition-timing-function transition-delay">
|
||||
use properties::longhands::{transition_delay, transition_duration, transition_property};
|
||||
use properties::longhands::{transition_timing_function};
|
||||
|
||||
struct SingleTransition {
|
||||
transition_property: transition_property::SingleSpecifiedValue,
|
||||
transition_duration: transition_duration::SingleSpecifiedValue,
|
||||
transition_timing_function: transition_timing_function::SingleSpecifiedValue,
|
||||
transition_delay: transition_delay::SingleSpecifiedValue,
|
||||
}
|
||||
|
||||
fn parse_one_transition(input: &mut Parser) -> Result<SingleTransition,()> {
|
||||
let (mut property, mut duration) = (None, None);
|
||||
let (mut timing_function, mut delay) = (None, None);
|
||||
loop {
|
||||
if property.is_none() {
|
||||
if let Ok(value) = input.try(|input| transition_property::parse_one(input)) {
|
||||
property = Some(value);
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if duration.is_none() {
|
||||
if let Ok(value) = input.try(|input| transition_duration::parse_one(input)) {
|
||||
duration = Some(value);
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if timing_function.is_none() {
|
||||
if let Ok(value) = input.try(|input| {
|
||||
transition_timing_function::parse_one(input)
|
||||
}) {
|
||||
timing_function = Some(value);
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if delay.is_none() {
|
||||
if let Ok(value) = input.try(|input| transition_delay::parse_one(input)) {
|
||||
delay = Some(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if let Some(property) = property {
|
||||
Ok(SingleTransition {
|
||||
transition_property: property,
|
||||
transition_duration:
|
||||
duration.unwrap_or(transition_duration::get_initial_single_value()),
|
||||
transition_timing_function:
|
||||
timing_function.unwrap_or(
|
||||
transition_timing_function::get_initial_single_value()),
|
||||
transition_delay:
|
||||
delay.unwrap_or(transition_delay::get_initial_single_value()),
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
if input.try(|input| input.expect_ident_matching("none")).is_ok() {
|
||||
return Ok(Longhands {
|
||||
transition_property: None,
|
||||
transition_duration: None,
|
||||
transition_timing_function: None,
|
||||
transition_delay: None,
|
||||
})
|
||||
}
|
||||
|
||||
let results = try!(input.parse_comma_separated(parse_one_transition));
|
||||
let (mut properties, mut durations) = (Vec::new(), Vec::new());
|
||||
let (mut timing_functions, mut delays) = (Vec::new(), Vec::new());
|
||||
for result in results.into_iter() {
|
||||
properties.push(result.transition_property);
|
||||
durations.push(result.transition_duration);
|
||||
timing_functions.push(result.transition_timing_function);
|
||||
delays.push(result.transition_delay);
|
||||
}
|
||||
|
||||
Ok(Longhands {
|
||||
transition_property: Some(transition_property::SpecifiedValue(properties)),
|
||||
transition_duration: Some(transition_duration::SpecifiedValue(durations)),
|
||||
transition_timing_function:
|
||||
Some(transition_timing_function::SpecifiedValue(timing_functions)),
|
||||
transition_delay: Some(transition_delay::SpecifiedValue(delays)),
|
||||
})
|
||||
</%self:shorthand>
|
||||
}
|
||||
|
||||
|
||||
|
@ -4328,6 +4784,11 @@ impl ComputedValues {
|
|||
<'a>(&'a self) -> &'a style_structs::${style_struct.name} {
|
||||
&*self.${style_struct.ident}
|
||||
}
|
||||
#[inline]
|
||||
pub fn mutate_${style_struct.name.lower()}
|
||||
<'a>(&'a mut self) -> &'a mut style_structs::${style_struct.name} {
|
||||
&mut *self.${style_struct.ident}.make_unique()
|
||||
}
|
||||
% endfor
|
||||
}
|
||||
|
||||
|
@ -4391,12 +4852,13 @@ fn initial_writing_mode_is_empty() {
|
|||
|
||||
/// Fast path for the function below. Only computes new inherited styles.
|
||||
#[allow(unused_mut)]
|
||||
fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock<Vec<PropertyDeclaration>>],
|
||||
shareable: bool,
|
||||
parent_style: &ComputedValues,
|
||||
cached_style: &ComputedValues,
|
||||
context: &computed::Context)
|
||||
-> ComputedValues {
|
||||
fn cascade_with_cached_declarations(
|
||||
applicable_declarations: &[DeclarationBlock<Vec<PropertyDeclaration>>],
|
||||
shareable: bool,
|
||||
parent_style: &ComputedValues,
|
||||
cached_style: &ComputedValues,
|
||||
context: &computed::Context)
|
||||
-> ComputedValues {
|
||||
% for style_struct in STYLE_STRUCTS:
|
||||
% if style_struct.inherited:
|
||||
let mut style_${style_struct.ident} = parent_style.${style_struct.ident}.clone();
|
||||
|
@ -4415,7 +4877,9 @@ fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock<
|
|||
% for style_struct in STYLE_STRUCTS:
|
||||
% for property in style_struct.longhands:
|
||||
% if property.derived_from is None:
|
||||
PropertyDeclaration::${property.camel_case}(ref ${'_' if not style_struct.inherited else ''}declared_value) => {
|
||||
PropertyDeclaration::${property.camel_case}(ref
|
||||
${'_' if not style_struct.inherited else ''}declared_value)
|
||||
=> {
|
||||
% if style_struct.inherited:
|
||||
if seen.get_${property.ident}() {
|
||||
continue
|
||||
|
@ -4556,9 +5020,13 @@ pub fn cascade(viewport_size: Size2D<Au>,
|
|||
context.font_size = match *value {
|
||||
DeclaredValue::SpecifiedValue(ref specified_value) => {
|
||||
match specified_value.0 {
|
||||
Length::FontRelative(value) => value.to_computed_value(context.inherited_font_size,
|
||||
context.root_font_size),
|
||||
Length::ServoCharacterWidth(value) => value.to_computed_value(context.inherited_font_size),
|
||||
Length::FontRelative(value) => {
|
||||
value.to_computed_value(context.inherited_font_size,
|
||||
context.root_font_size)
|
||||
}
|
||||
Length::ServoCharacterWidth(value) => {
|
||||
value.to_computed_value(context.inherited_font_size)
|
||||
}
|
||||
_ => specified_value.0.to_computed_value(&context)
|
||||
}
|
||||
}
|
||||
|
@ -4568,7 +5036,9 @@ pub fn cascade(viewport_size: Size2D<Au>,
|
|||
}
|
||||
PropertyDeclaration::Color(ref value) => {
|
||||
context.color = match *value {
|
||||
DeclaredValue::SpecifiedValue(ref specified_value) => specified_value.parsed,
|
||||
DeclaredValue::SpecifiedValue(ref specified_value) => {
|
||||
specified_value.parsed
|
||||
}
|
||||
DeclaredValue::Initial => longhands::color::get_initial_value(),
|
||||
DeclaredValue::Inherit => inherited_style.get_color().color.clone(),
|
||||
};
|
||||
|
|
|
@ -862,11 +862,57 @@ pub mod specified {
|
|||
"inset" => inset,
|
||||
"outset" => outset,
|
||||
}
|
||||
|
||||
/// A time in seconds according to CSS-VALUES § 6.2.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||
pub struct Time(pub CSSFloat);
|
||||
|
||||
impl Time {
|
||||
/// Returns the time in fractional seconds.
|
||||
pub fn seconds(self) -> f64 {
|
||||
let Time(seconds) = self;
|
||||
seconds
|
||||
}
|
||||
|
||||
/// Parses a time according to CSS-VALUES § 6.2.
|
||||
fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Time,()> {
|
||||
if unit.eq_ignore_ascii_case("s") {
|
||||
Ok(Time(value))
|
||||
} else if unit.eq_ignore_ascii_case("ms") {
|
||||
Ok(Time(value / 1000.0))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(input: &mut Parser) -> Result<Time,()> {
|
||||
match input.next() {
|
||||
Ok(Token::Dimension(ref value, ref unit)) => {
|
||||
Time::parse_dimension(value.value, unit.as_slice())
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::computed::ToComputedValue for Time {
|
||||
type ComputedValue = Time;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, _: &super::computed::Context) -> Time {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for Time {
|
||||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||||
dest.write_str(format!("{}ms", self.0).as_slice())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub mod computed {
|
||||
pub use super::specified::BorderStyle;
|
||||
pub use super::specified::{BorderStyle, Time};
|
||||
use super::specified::{AngleOrCorner};
|
||||
use super::{specified, CSSFloat};
|
||||
pub use cssparser::Color as CSSColor;
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Parametric Bézier curves.
|
||||
//!
|
||||
//! This is based on `WebCore/platform/graphics/UnitBezier.h` in WebKit.
|
||||
|
||||
use geom::point::Point2D;
|
||||
use std::num::Float;
|
||||
|
||||
const NEWTON_METHOD_ITERATIONS: u8 = 8;
|
||||
|
||||
pub struct Bezier {
|
||||
ax: f64,
|
||||
bx: f64,
|
||||
cx: f64,
|
||||
ay: f64,
|
||||
by: f64,
|
||||
cy: f64,
|
||||
}
|
||||
|
||||
impl Bezier {
|
||||
#[inline]
|
||||
pub fn new(p1: Point2D<f64>, p2: Point2D<f64>) -> Bezier {
|
||||
let cx = 3.0 * p1.x;
|
||||
let bx = 3.0 * (p2.x - p1.x) - cx;
|
||||
|
||||
let cy = 3.0 * p1.y;
|
||||
let by = 3.0 * (p2.y - p1.y) - cy;
|
||||
|
||||
Bezier {
|
||||
ax: 1.0 - cx - bx,
|
||||
bx: bx,
|
||||
cx: cx,
|
||||
ay: 1.0 - cy - by,
|
||||
by: by,
|
||||
cy: cy,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_curve_x(&self, t: f64) -> f64 {
|
||||
// ax * t^3 + bx * t^2 + cx * t
|
||||
((self.ax * t + self.bx) * t + self.cx) * t
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_curve_y(&self, t: f64) -> f64 {
|
||||
((self.ay * t + self.by) * t + self.cy) * t
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_curve_derivative_x(&self, t: f64) -> f64 {
|
||||
(3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
|
||||
// Fast path: Use Newton's method.
|
||||
let mut t = x;
|
||||
for _ in range(0, NEWTON_METHOD_ITERATIONS) {
|
||||
let x2 = self.sample_curve_x(t);
|
||||
if x2.approx_eq(x, epsilon) {
|
||||
return t
|
||||
}
|
||||
let dx = self.sample_curve_derivative_x(t);
|
||||
if dx.approx_eq(0.0, 1e-6) {
|
||||
break
|
||||
}
|
||||
t -= (x2 - x) / dx;
|
||||
}
|
||||
|
||||
// Slow path: Use bisection.
|
||||
let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
|
||||
|
||||
if t < lo {
|
||||
return lo
|
||||
}
|
||||
if t > hi {
|
||||
return hi
|
||||
}
|
||||
|
||||
while lo < hi {
|
||||
let x2 = self.sample_curve_x(t);
|
||||
if x2.approx_eq(x, epsilon) {
|
||||
return t
|
||||
}
|
||||
if x > x2 {
|
||||
lo = t
|
||||
} else {
|
||||
hi = t
|
||||
}
|
||||
t = (hi - lo) / 2.0 + lo
|
||||
}
|
||||
|
||||
t
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn solve(&self, x: f64, epsilon: f64) -> f64 {
|
||||
self.sample_curve_y(self.solve_curve_x(x, epsilon))
|
||||
}
|
||||
}
|
||||
|
||||
trait ApproxEq {
|
||||
fn approx_eq(self, value: Self, epsilon: Self) -> bool;
|
||||
}
|
||||
|
||||
impl ApproxEq for f64 {
|
||||
#[inline]
|
||||
fn approx_eq(self, value: f64, epsilon: f64) -> bool {
|
||||
(self - value).abs() < epsilon
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ pub use selectors::smallvec;
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod bezier;
|
||||
pub mod cache;
|
||||
pub mod cursor;
|
||||
pub mod debug_utils;
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -61,6 +61,11 @@ dependencies = [
|
|||
"gleam 0.0.1 (git+https://github.com/servo/gleam)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clock_ticks"
|
||||
version = "0.0.4"
|
||||
source = "git+https://github.com/tomaka/clock_ticks#6a3005279bedc406b13eea09ff92447f05ca0de6"
|
||||
|
||||
[[package]]
|
||||
name = "compositing"
|
||||
version = "0.0.1"
|
||||
|
@ -433,6 +438,7 @@ dependencies = [
|
|||
"azure 0.1.0 (git+https://github.com/servo/rust-azure)",
|
||||
"bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"canvas 0.0.1",
|
||||
"clock_ticks 0.0.4 (git+https://github.com/tomaka/clock_ticks)",
|
||||
"cssparser 0.2.0 (git+https://github.com/servo/rust-cssparser)",
|
||||
"encoding 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"geom 0.1.0 (git+https://github.com/servo/rust-geom)",
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
section {
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 0;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: cadetblue;
|
||||
transition: all 3s ease;
|
||||
-moz-transition: all 3s ease;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section></section>
|
||||
<script>
|
||||
var sections = document.getElementsByTagName('section');
|
||||
sections[0].setAttribute('style', "left: 0; top: 0");
|
||||
setTimeout(function() {
|
||||
sections[0].setAttribute('style', "left: 512px; top: 512px");
|
||||
}, 0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
section {
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 0;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: cadetblue;
|
||||
transition: left 3s ease, top 1500ms ease;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section></section>
|
||||
<script>
|
||||
var sections = document.getElementsByTagName('section');
|
||||
sections[0].setAttribute('style', "left: 0; top: 0");
|
||||
setTimeout(function() {
|
||||
sections[0].setAttribute('style', "left: 512px; top: 512px");
|
||||
}, 0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
section {
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 0;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: firebrick;
|
||||
transition-property: left;
|
||||
transition-duration: 3s;
|
||||
-moz-transition-property: left;
|
||||
-moz-transition-duration: 3s;
|
||||
-webkit-transition-property: left;
|
||||
-webkit-transition-duration: 3s;
|
||||
}
|
||||
#a {
|
||||
top: 0;
|
||||
transition-timing-function: ease;
|
||||
-moz-transition-timing-function: ease;
|
||||
-webkit-transition-timing-function: ease;
|
||||
}
|
||||
#b {
|
||||
top: 64px;
|
||||
transition-timing-function: linear;
|
||||
-moz-transition-timing-function: linear;
|
||||
-webkit-transition-timing-function: linear;
|
||||
}
|
||||
#c {
|
||||
top: 128px;
|
||||
transition-timing-function: ease-in;
|
||||
-moz-transition-timing-function: ease-in;
|
||||
-webkit-transition-timing-function: ease-in;
|
||||
}
|
||||
#d {
|
||||
top: 192px;
|
||||
transition-timing-function: ease-out;
|
||||
-moz-transition-timing-function: ease-out;
|
||||
-webkit-transition-timing-function: ease-out;
|
||||
}
|
||||
#e {
|
||||
top: 256px;
|
||||
transition-timing-function: ease-in-out;
|
||||
-moz-transition-timing-function: ease-in-out;
|
||||
-webkit-transition-timing-function: ease-in-out;
|
||||
}
|
||||
#f {
|
||||
top: 320px;
|
||||
transition-timing-function: step-start;
|
||||
-moz-transition-timing-function: step-start;
|
||||
-webkit-transition-timing-function: step-start;
|
||||
}
|
||||
#g {
|
||||
top: 356px;
|
||||
transition-timing-function: step-end;
|
||||
-moz-transition-timing-function: step-end;
|
||||
-webkit-transition-timing-function: step-end;
|
||||
}
|
||||
#h {
|
||||
top: 420px;
|
||||
transition-timing-function: steps(3, start);
|
||||
-moz-transition-timing-function: steps(3, start);
|
||||
-webkit-transition-timing-function: steps(3, start);
|
||||
}
|
||||
#i {
|
||||
top: 484px;
|
||||
transition-timing-function: steps(3, end);
|
||||
-moz-transition-timing-function: steps(3, end);
|
||||
-webkit-transition-timing-function: steps(3, end);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section id=a></section>
|
||||
<section id=b></section>
|
||||
<section id=c></section>
|
||||
<section id=d></section>
|
||||
<section id=e></section>
|
||||
<section id=f></section>
|
||||
<section id=g></section>
|
||||
<section id=h></section>
|
||||
<section id=i></section>
|
||||
<script>
|
||||
var sections = document.getElementsByTagName('section');
|
||||
for (var i = 0; i < sections.length; i++)
|
||||
sections[i].setAttribute('style', "left: 0");
|
||||
setTimeout(function() {
|
||||
for (var i = 0; i < sections.length; i++)
|
||||
sections[i].setAttribute('style', "left: 512px");
|
||||
}, 0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Загрузка…
Ссылка в новой задаче