servo: Merge #17398 - Improve decisions in compositor over when to draw a frame (from glennw:opt-composite); r=jdm
authorGlenn Watson <github@intuitionlibrary.com>
Wed, 21 Jun 2017 07:47:53 -0700
changeset 365339 e0972fb49258381b0559b41cefa6058e9a3c55f1
parent 365338 e990953fc64ff000727cdfb0cebec79081fca9eb
child 365340 3943c712e1cc71d45b129530b8c6d129a006ff1a
push id91734
push userkwierso@gmail.com
push dateThu, 22 Jun 2017 01:05:37 +0000
treeherdermozilla-inbound@2576a0695305 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #17398 - Improve decisions in compositor over when to draw a frame (from glennw:opt-composite); r=jdm This patch fixes a couple of issues in the compositor: 1) Remove the delayed composition code. Previously, this would schedule a composite for 12ms in the future. This doesn't really make any sense with WR. There's no point in doing a composite unless WR has provided a new frame to be drawn. This fixes issues in several benchmarks where we were doing multiple composite / renders per rAF, which is a waste of CPU time. This *does* make the framerate slower in some cases (such as a slow rAF callback) but it's more correct - otherwise we were just compositing the same frame multiple times for no real benefit. 2) Inform the window of the current animation state of the compositor. Specifically, if an animation (or rAF) is currently active, the window system switches to use event polling, and does not block on the OS-level event loop. In the case of active animation, we just assume that we want to be running as the vsync interval and not blocking. This means the compositor thread only sleeps on vsync during animation, which reduces OS scheduling and results in much smoother animation. Source-Repo: https://github.com/servo/servo Source-Revision: 819a40bfb0fd2e1f4832fa3d773c266e9179ae21
servo/components/compositing/compositor.rs
servo/components/compositing/compositor_thread.rs
servo/components/compositing/delayed_composition.rs
servo/components/compositing/lib.rs
servo/components/compositing/windowing.rs
servo/components/script/dom/document.rs
servo/ports/glutin/window.rs
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -1,17 +1,16 @@
 /* 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 CompositionPipeline;
 use SendableFrameTree;
 use compositor_thread::{CompositorProxy, CompositorReceiver};
 use compositor_thread::{InitialCompositorState, Msg, RenderListener};
-use delayed_composition::DelayedCompositionTimerProxy;
 use euclid::{Point2D, TypedPoint2D, TypedVector2D, TypedRect, ScaleFactor, TypedSize2D};
 use gfx_traits::Epoch;
 use gleam::gl;
 use image::{DynamicImage, ImageFormat, RgbImage};
 use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory};
 use msg::constellation_msg::{Key, KeyModifiers, KeyState, CONTROL};
 use msg::constellation_msg::{PipelineId, PipelineIndex, PipelineNamespaceId, TraversalDirection};
 use net_traits::image::base::{Image, PixelFormat};
@@ -126,19 +125,16 @@ pub struct IOCompositor<Window: WindowMe
     /// "Desktop-style" zoom that resizes the viewport to fit the window.
     page_zoom: ScaleFactor<f32, CSSPixel, DeviceIndependentPixel>,
 
     /// The device pixel ratio for this window.
     scale_factor: ScaleFactor<f32, DeviceIndependentPixel, DevicePixel>,
 
     channel_to_self: CompositorProxy,
 
-    /// A handle to the delayed composition timer.
-    delayed_composition_timer: DelayedCompositionTimerProxy,
-
     /// The type of composition to perform
     composite_target: CompositeTarget,
 
     /// Tracks whether we should composite this frame.
     composition_request: CompositionRequest,
 
     /// Tracks whether we are in the process of shutting down, or have shut down and should close
     /// the compositor.
@@ -202,17 +198,16 @@ struct ScrollZoomEvent {
     phase: ScrollEventPhase,
     /// The number of OS events that have been coalesced together into this one event.
     event_count: u32,
 }
 
 #[derive(PartialEq, Debug)]
 enum CompositionRequest {
     NoCompositingNecessary,
-    DelayedComposite(u64),
     CompositeNow(CompositingReason),
 }
 
 #[derive(Clone, Copy, PartialEq, Debug)]
 enum ShutdownState {
     NotShuttingDown,
     ShuttingDown,
     FinishedShuttingDown,
@@ -358,17 +353,16 @@ impl<Window: WindowMethods> IOCompositor
             port: state.receiver,
             root_pipeline: None,
             pipeline_details: HashMap::new(),
             frame_size: frame_size,
             window_rect: window_rect,
             scale: ScaleFactor::new(1.0),
             scale_factor: scale_factor,
             channel_to_self: state.sender.clone_compositor_proxy(),
-            delayed_composition_timer: DelayedCompositionTimerProxy::new(state.sender),
             composition_request: CompositionRequest::NoCompositingNecessary,
             touch_handler: TouchHandler::new(),
             pending_scroll_zoom_events: Vec::new(),
             waiting_for_results_of_scroll: false,
             composite_target: composite_target,
             shutdown_state: ShutdownState::NotShuttingDown,
             page_zoom: ScaleFactor::new(1.0),
             viewport_zoom: PinchZoomFactor::new(1.0),
@@ -432,18 +426,16 @@ impl<Window: WindowMethods> IOCompositor
         while self.port.try_recv_compositor_msg().is_some() {}
 
         // Tell the profiler, memory profiler, and scrolling timer to shut down.
         if let Ok((sender, receiver)) = ipc::channel() {
             self.time_profiler_chan.send(time::ProfilerMsg::Exit(sender));
             let _ = receiver.recv();
         }
 
-        self.delayed_composition_timer.shutdown();
-
         self.shutdown_state = ShutdownState::FinishedShuttingDown;
     }
 
     fn handle_browser_message(&mut self, msg: Msg) -> bool {
         match (msg, self.shutdown_state) {
             (_, ShutdownState::FinishedShuttingDown) => {
                 error!("compositor shouldn't be handling messages after shutting down");
                 return false
@@ -519,26 +511,16 @@ impl<Window: WindowMethods> IOCompositor
 
             (Msg::AllowNavigation(url, response_chan), ShutdownState::NotShuttingDown) => {
                 let allow = self.window.allow_navigation(url);
                 if let Err(e) = response_chan.send(allow) {
                     warn!("Failed to send allow_navigation result ({}).", e);
                 }
             }
 
-            (Msg::DelayedCompositionTimeout(timestamp), ShutdownState::NotShuttingDown) => {
-                if let CompositionRequest::DelayedComposite(this_timestamp) =
-                    self.composition_request {
-                    if timestamp == this_timestamp {
-                        self.composition_request = CompositionRequest::CompositeNow(
-                            CompositingReason::DelayedCompositeTimeout)
-                    }
-                }
-            }
-
             (Msg::Recomposite(reason), ShutdownState::NotShuttingDown) => {
                 self.composition_request = CompositionRequest::CompositeNow(reason)
             }
 
             (Msg::KeyEvent(ch, key, state, modified), ShutdownState::NotShuttingDown) => {
                 if state == KeyState::Pressed {
                     self.window.handle_key(ch, key, modified);
                 }
@@ -748,28 +730,16 @@ impl<Window: WindowMethods> IOCompositor
         };
         let msg = ConstellationMsg::WindowSize(top_level_browsing_context_id, data, size_type);
 
         if let Err(e) = self.constellation_chan.send(msg) {
             warn!("Sending window resize to constellation failed ({}).", e);
         }
     }
 
-    fn schedule_delayed_composite_if_necessary(&mut self) {
-        match self.composition_request {
-            CompositionRequest::CompositeNow(_) => return,
-            CompositionRequest::DelayedComposite(_) |
-            CompositionRequest::NoCompositingNecessary => {}
-        }
-
-        let timestamp = precise_time_ns();
-        self.delayed_composition_timer.schedule_composite(timestamp);
-        self.composition_request = CompositionRequest::DelayedComposite(timestamp);
-    }
-
     fn scroll_fragment_to_point(&mut self, id: ClipId, point: Point2D<f32>) {
         self.webrender_api.scroll_node_with_id(LayoutPoint::from_untyped(&point), id,
                                                ScrollClamping::ToContentBounds);
     }
 
     fn handle_window_message(&mut self, event: WindowEvent) {
         match event {
             WindowEvent::Idle => {}
@@ -1230,23 +1200,28 @@ impl<Window: WindowMethods> IOCompositor
         let mut pipeline_ids = vec![];
         for (pipeline_id, pipeline_details) in &self.pipeline_details {
             if (pipeline_details.animations_running ||
                 pipeline_details.animation_callbacks_running) &&
                pipeline_details.visible {
                    pipeline_ids.push(*pipeline_id);
             }
         }
+        let animation_state = if pipeline_ids.is_empty() {
+            windowing::AnimationState::Idle
+        } else {
+            windowing::AnimationState::Animating
+        };
+        self.window.set_animation_state(animation_state);
         for pipeline_id in &pipeline_ids {
             self.tick_animations_for_pipeline(*pipeline_id)
         }
     }
 
     fn tick_animations_for_pipeline(&mut self, pipeline_id: PipelineId) {
-        self.schedule_delayed_composite_if_necessary();
         let animation_callbacks_running = self.pipeline_details(pipeline_id).animation_callbacks_running;
         if animation_callbacks_running {
             let msg = ConstellationMsg::TickAnimation(pipeline_id, AnimationTickType::Script);
             if let Err(e) = self.constellation_chan.send(msg) {
                 warn!("Sending tick to constellation failed ({}).", e);
             }
         }
 
@@ -1630,24 +1605,16 @@ impl<Window: WindowMethods> IOCompositor
                 Msg::Recomposite(_) if found_recomposite_msg => {}
                 Msg::Recomposite(_) => {
                     found_recomposite_msg = true;
                     compositor_messages.push(msg)
                 }
                 _ => compositor_messages.push(msg),
             }
         }
-        if found_recomposite_msg {
-            compositor_messages.retain(|msg| {
-                match *msg {
-                    Msg::DelayedCompositionTimeout(_) => false,
-                    _ => true,
-                }
-            })
-        }
         for msg in compositor_messages {
             if !self.handle_browser_message(msg) {
                 break
             }
         }
 
         if self.shutdown_state == ShutdownState::FinishedShuttingDown {
             return false;
@@ -1659,18 +1626,17 @@ impl<Window: WindowMethods> IOCompositor
         }
 
         // If a pinch-zoom happened recently, ask for tiles at the new resolution
         if self.zoom_action && precise_time_s() - self.zoom_time > 0.3 {
             self.zoom_action = false;
         }
 
         match self.composition_request {
-            CompositionRequest::NoCompositingNecessary |
-            CompositionRequest::DelayedComposite(_) => {}
+            CompositionRequest::NoCompositingNecessary => {}
             CompositionRequest::CompositeNow(_) => {
                 self.composite()
             }
         }
 
         if !self.pending_scroll_zoom_events.is_empty() && !self.waiting_for_results_of_scroll {
             self.process_pending_scroll_events()
         }
--- a/servo/components/compositing/compositor_thread.rs
+++ b/servo/components/compositing/compositor_thread.rs
@@ -95,18 +95,16 @@ pub enum Msg {
     /// The load of a page has begun
     LoadStart,
     /// The load of a page has completed
     LoadComplete,
     /// The history state has changed.
     HistoryChanged(Vec<LoadData>, usize),
     /// Wether or not to follow a link
     AllowNavigation(ServoUrl, IpcSender<bool>),
-    /// We hit the delayed composition timeout. (See `delayed_composition.rs`.)
-    DelayedCompositionTimeout(u64),
     /// Composite.
     Recomposite(CompositingReason),
     /// Sends an unconsumed key event back to the compositor.
     KeyEvent(Option<char>, Key, KeyState, KeyModifiers),
     /// Script has handled a touch event, and either prevented or allowed default actions.
     TouchEventProcessed(EventResult),
     /// Changes the cursor.
     SetCursor(Cursor),
@@ -155,17 +153,16 @@ impl Debug for Msg {
             Msg::ScrollFragmentPoint(..) => write!(f, "ScrollFragmentPoint"),
             Msg::ChangeRunningAnimationsState(..) => write!(f, "ChangeRunningAnimationsState"),
             Msg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
             Msg::SetFrameTree(..) => write!(f, "SetFrameTree"),
             Msg::LoadComplete => write!(f, "LoadComplete"),
             Msg::AllowNavigation(..) => write!(f, "AllowNavigation"),
             Msg::LoadStart => write!(f, "LoadStart"),
             Msg::HistoryChanged(..) => write!(f, "HistoryChanged"),
-            Msg::DelayedCompositionTimeout(..) => write!(f, "DelayedCompositionTimeout"),
             Msg::Recomposite(..) => write!(f, "Recomposite"),
             Msg::KeyEvent(..) => write!(f, "KeyEvent"),
             Msg::TouchEventProcessed(..) => write!(f, "TouchEventProcessed"),
             Msg::SetCursor(..) => write!(f, "SetCursor"),
             Msg::CreatePng(..) => write!(f, "CreatePng"),
             Msg::ViewportConstrained(..) => write!(f, "ViewportConstrained"),
             Msg::IsReadyToSaveImageReply(..) => write!(f, "IsReadyToSaveImageReply"),
             Msg::NewFavicon(..) => write!(f, "NewFavicon"),
deleted file mode 100644
--- a/servo/components/compositing/delayed_composition.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-/* 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/. */
-
-//! A timer thread that composites near the end of the frame.
-//!
-//! This is useful when we need to composite next frame but we want to opportunistically give the
-//! painting thread time to paint if it can.
-
-use compositor_thread::{CompositorProxy, Msg};
-use std::sync::mpsc::{Receiver, Sender, channel};
-use std::thread::{self, Builder};
-use std::time::Duration;
-use std::u32;
-use time;
-
-/// The amount of time in nanoseconds that we give to the painting thread to paint. When this
-/// expires, we give up and composite anyway.
-static TIMEOUT: u64 = 12_000_000;
-
-pub struct DelayedCompositionTimerProxy {
-    sender: Sender<ToDelayedCompositionTimerMsg>,
-}
-
-struct DelayedCompositionTimer {
-    compositor_proxy: CompositorProxy,
-    receiver: Receiver<ToDelayedCompositionTimerMsg>,
-}
-
-enum ToDelayedCompositionTimerMsg {
-    Exit,
-    ScheduleComposite(u64),
-}
-
-impl DelayedCompositionTimerProxy {
-    pub fn new(compositor_proxy: CompositorProxy) -> DelayedCompositionTimerProxy {
-        let (to_timer_sender, to_timer_receiver) = channel();
-        Builder::new().spawn(move || {
-            let mut timer = DelayedCompositionTimer {
-                compositor_proxy: compositor_proxy,
-                receiver: to_timer_receiver,
-            };
-            timer.run();
-        }).unwrap();
-        DelayedCompositionTimerProxy {
-            sender: to_timer_sender,
-        }
-    }
-
-    pub fn schedule_composite(&mut self, timestamp: u64) {
-        self.sender.send(ToDelayedCompositionTimerMsg::ScheduleComposite(timestamp)).unwrap()
-    }
-
-    pub fn shutdown(&mut self) {
-        self.sender.send(ToDelayedCompositionTimerMsg::Exit).unwrap()
-    }
-}
-
-impl DelayedCompositionTimer {
-    fn run(&mut self) {
-        'outer: loop {
-            let mut timestamp;
-            loop {
-                match self.receiver.recv() {
-                    Ok(ToDelayedCompositionTimerMsg::ScheduleComposite(this_timestamp)) => {
-                        timestamp = this_timestamp;
-                        break
-                    }
-                    Ok(ToDelayedCompositionTimerMsg::Exit) => break 'outer,
-                    _ => break 'outer,
-                }
-            }
-
-            // Drain all messages from the queue.
-            loop {
-                match self.receiver.try_recv() {
-                    Ok(ToDelayedCompositionTimerMsg::ScheduleComposite(this_timestamp)) => {
-                        timestamp = this_timestamp;
-                        break
-                    }
-                    _ => break,
-                }
-            }
-
-            let target = timestamp + TIMEOUT;
-            let now = time::precise_time_ns();
-            if target > now {
-                let delta_ns = target - now;
-                thread::sleep(duration_from_nanoseconds(delta_ns));
-            }
-            self.compositor_proxy.send(Msg::DelayedCompositionTimeout(timestamp));
-        }
-    }
-}
-
-fn duration_from_nanoseconds(nanos: u64) -> Duration {
-    pub const NANOS_PER_SEC: u32 = 1_000_000_000;
-
-    // Get number of seconds.
-    let secs = nanos / NANOS_PER_SEC as u64;
-
-    // Get number of extra nanoseconds. This should always fit in a u32, but check anyway.
-    let subsec_nanos = nanos % NANOS_PER_SEC as u64;
-    assert!(subsec_nanos <= u32::MAX as u64);
-
-    Duration::new(secs, subsec_nanos as u32)
-}
--- a/servo/components/compositing/lib.rs
+++ b/servo/components/compositing/lib.rs
@@ -30,17 +30,16 @@ use euclid::TypedSize2D;
 use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::PipelineId;
 use msg::constellation_msg::TopLevelBrowsingContextId;
 use script_traits::{ConstellationControlMsg, LayoutControlMsg};
 use style_traits::CSSPixel;
 
 mod compositor;
 pub mod compositor_thread;
-mod delayed_composition;
 mod touch;
 pub mod windowing;
 
 pub struct SendableFrameTree {
     pub pipeline: CompositionPipeline,
     pub size: Option<TypedSize2D<f32, CSSPixel>>,
     pub children: Vec<SendableFrameTree>,
 }
--- a/servo/components/compositing/windowing.rs
+++ b/servo/components/compositing/windowing.rs
@@ -97,16 +97,22 @@ impl Debug for WindowEvent {
             WindowEvent::ResetZoom => write!(f, "ResetZoom"),
             WindowEvent::Navigation(..) => write!(f, "Navigation"),
             WindowEvent::Quit => write!(f, "Quit"),
             WindowEvent::Reload => write!(f, "Reload"),
         }
     }
 }
 
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum AnimationState {
+    Idle,
+    Animating,
+}
+
 pub trait WindowMethods {
     /// Returns the rendering area size in hardware pixels.
     fn framebuffer_size(&self) -> TypedSize2D<u32, DevicePixel>;
     /// Returns the position and size of the window within the rendering area.
     fn window_rect(&self) -> TypedRect<u32, DevicePixel>;
     /// Returns the size of the window in density-independent "px" units.
     fn size(&self) -> TypedSize2D<f32, DeviceIndependentPixel>;
     /// Presents the window to the screen (perhaps by page flipping).
@@ -158,9 +164,15 @@ pub trait WindowMethods {
     /// Does this window support a clipboard
     fn supports_clipboard(&self) -> bool;
 
     /// Add a favicon
     fn set_favicon(&self, url: ServoUrl);
 
     /// Return the GL function pointer trait.
     fn gl(&self) -> Rc<gl::Gl>;
+
+    /// Set whether the application is currently animating.
+    /// Typically, when animations are active, the window
+    /// will want to avoid blocking on UI events, and just
+    /// run the event loop at the vsync interval.
+    fn set_animation_state(&self, _state: AnimationState) {}
 }
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -1533,38 +1533,39 @@ impl Document {
 
     /// https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe
     pub fn request_animation_frame(&self, callback: AnimationFrameCallback) -> u32 {
         let ident = self.animation_frame_ident.get() + 1;
 
         self.animation_frame_ident.set(ident);
         self.animation_frame_list.borrow_mut().push((ident, Some(callback)));
 
-        // No need to send a `ChangeRunningAnimationsState` if we're running animation callbacks:
-        // we're guaranteed to already be in the "animation callbacks present" state.
-        //
-        // This reduces CPU usage by avoiding needless thread wakeups in the common case of
-        // repeated rAF.
-        //
         // TODO: Should tick animation only when document is visible
-        if !self.running_animation_callbacks.get() {
-            if !self.is_faking_animation_frames() {
-                let global_scope = self.window.upcast::<GlobalScope>();
-                let event = ConstellationMsg::ChangeRunningAnimationsState(
-                    global_scope.pipeline_id(),
-                    AnimationState::AnimationCallbacksPresent);
-                global_scope.constellation_chan().send(event).unwrap();
-            } else {
-                let callback = FakeRequestAnimationFrameCallback {
-                    document: Trusted::new(self),
-                };
-                self.global()
-                    .schedule_callback(OneshotTimerCallback::FakeRequestAnimationFrame(callback),
-                                       MsDuration::new(FAKE_REQUEST_ANIMATION_FRAME_DELAY));
-            }
+
+        // If we are running 'fake' animation frames, we unconditionally
+        // set up a one-shot timer for script to execute the rAF callbacks.
+        if self.is_faking_animation_frames() {
+            let callback = FakeRequestAnimationFrameCallback {
+                document: Trusted::new(self),
+            };
+            self.global()
+                .schedule_callback(OneshotTimerCallback::FakeRequestAnimationFrame(callback),
+                                   MsDuration::new(FAKE_REQUEST_ANIMATION_FRAME_DELAY));
+        } else if !self.running_animation_callbacks.get() {
+            // No need to send a `ChangeRunningAnimationsState` if we're running animation callbacks:
+            // we're guaranteed to already be in the "animation callbacks present" state.
+            //
+            // This reduces CPU usage by avoiding needless thread wakeups in the common case of
+            // repeated rAF.
+
+            let global_scope = self.window.upcast::<GlobalScope>();
+            let event = ConstellationMsg::ChangeRunningAnimationsState(
+                global_scope.pipeline_id(),
+                AnimationState::AnimationCallbacksPresent);
+            global_scope.constellation_chan().send(event).unwrap();
         }
 
         ident
     }
 
     /// https://html.spec.whatwg.org/multipage/#dom-window-cancelanimationframe
     pub fn cancel_animation_frame(&self, ident: u32) {
         let mut list = self.animation_frame_list.borrow_mut();
@@ -1591,16 +1592,32 @@ impl Document {
         }
 
         self.running_animation_callbacks.set(false);
 
         let spurious = !self.window.reflow(ReflowGoal::ForDisplay,
                                            ReflowQueryType::NoQuery,
                                            ReflowReason::RequestAnimationFrame);
 
+        if spurious && !was_faking_animation_frames {
+            // If the rAF callbacks did not mutate the DOM, then the
+            // reflow call above means that layout will not be invoked,
+            // and therefore no new frame will be sent to the compositor.
+            // If this happens, the compositor will not tick the animation
+            // and the next rAF will never be called! When this happens
+            // for several frames, then the spurious rAF detection below
+            // will kick in and use a timer to tick the callbacks. However,
+            // for the interim frames where we are deciding whether this rAF
+            // is considered spurious, we need to ensure that the layout
+            // and compositor *do* tick the animation.
+            self.window.force_reflow(ReflowGoal::ForDisplay,
+                                     ReflowQueryType::NoQuery,
+                                     ReflowReason::RequestAnimationFrame);
+        }
+
         // Only send the animation change state message after running any callbacks.
         // This means that if the animation callback adds a new callback for
         // the next frame (which is the common case), we won't send a NoAnimationCallbacksPresent
         // message quickly followed by an AnimationCallbacksPresent message.
         //
         // If this frame was spurious and we've seen too many spurious frames in a row, tell the
         // constellation to stop giving us video refresh callbacks, to save energy. (A spurious
         // animation frame is one in which the callback did not mutate the DOM—that is, an
--- a/servo/ports/glutin/window.rs
+++ b/servo/ports/glutin/window.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 //! A windowing implementation using glutin.
 
 use NestedEventLoopListener;
 use compositing::compositor_thread::EventLoopWaker;
-use compositing::windowing::{MouseWindowEvent, WindowNavigateMsg};
+use compositing::windowing::{AnimationState, MouseWindowEvent, WindowNavigateMsg};
 use compositing::windowing::{WindowEvent, WindowMethods};
 use euclid::{Point2D, Size2D, TypedPoint2D, TypedVector2D, TypedRect, ScaleFactor, TypedSize2D};
 #[cfg(target_os = "windows")]
 use gdi32;
 use gleam::gl;
 use glutin;
 use glutin::{Api, ElementState, Event, GlRequest, MouseButton, MouseScrollDelta, VirtualKeyCode};
 #[cfg(not(target_os = "windows"))]
@@ -191,16 +191,18 @@ pub struct Window {
     #[cfg(target_os = "windows")]
     last_pressed_key: Cell<Option<constellation_msg::Key>>,
 
     /// The list of keys that have been pressed but not yet released, to allow providing
     /// the equivalent ReceivedCharacter data as was received for the press event.
     #[cfg(not(target_os = "windows"))]
     pressed_key_map: RefCell<Vec<(ScanCode, char)>>,
 
+    animation_state: Cell<AnimationState>,
+
     gl: Rc<gl::Gl>,
 }
 
 #[cfg(not(target_os = "windows"))]
 fn window_creation_scale_factor() -> ScaleFactor<f32, DeviceIndependentPixel, DevicePixel> {
     ScaleFactor::new(1.0)
 }
 
@@ -311,16 +313,17 @@ impl Window {
 
             #[cfg(not(target_os = "windows"))]
             pending_key_event_char: Cell::new(None),
             #[cfg(not(target_os = "windows"))]
             pressed_key_map: RefCell::new(vec![]),
             #[cfg(target_os = "windows")]
             last_pressed_key: Cell::new(None),
             gl: gl.clone(),
+            animation_state: Cell::new(AnimationState::Idle),
         };
 
         window.present();
 
         Rc::new(window)
     }
 
     pub fn platform_window(&self) -> glutin::WindowID {
@@ -650,20 +653,24 @@ impl Window {
     }
 
     pub fn wait_events(&self) -> Vec<WindowEvent> {
         use std::mem;
 
         let mut events = mem::replace(&mut *self.event_queue.borrow_mut(), Vec::new());
         let mut close_event = false;
 
+        let poll = self.animation_state.get() == AnimationState::Animating ||
+                   opts::get().output_file.is_some() ||
+                   opts::get().exit_after_load ||
+                   opts::get().headless;
         // When writing to a file then exiting, use event
         // polling so that we don't block on a GUI event
         // such as mouse click.
-        if opts::get().output_file.is_some() || opts::get().exit_after_load || opts::get().headless {
+        if poll {
             match self.kind {
                 WindowKind::Window(ref window) => {
                     while let Some(event) = window.poll_events().next() {
                         close_event = self.handle_window_event(event) || close_event;
                     }
                 }
                 WindowKind::Headless(..) => {}
             }
@@ -1000,16 +1007,20 @@ impl WindowMethods for Window {
             WindowKind::Headless(ref context) => {
                 let size = TypedSize2D::new(context.width, context.height);
                 (size, Point2D::zero())
             }
         }
 
     }
 
+    fn set_animation_state(&self, state: AnimationState) {
+        self.animation_state.set(state);
+    }
+
     fn set_inner_size(&self, size: Size2D<u32>) {
         match self.kind {
             WindowKind::Window(ref window) => {
                 window.set_inner_size(size.width as u32, size.height as u32)
             }
             WindowKind::Headless(..) => {}
         }
     }