servo: Merge #4960 - Add thaw/freeze messages that can suspend/resume webcontent timers #4907 (from pkondzior:add-script-timers-suspend-resume-functionality); r=jdm
authorPawel Kondzior <pawel@kondzior.com>
Mon, 23 Feb 2015 21:45:46 -0700
changeset 335819 98764c4e78a517b503cf61416a13b28640620cc5
parent 335818 ffd78e200705cd4aa54e5465dfede4b5b7a49a40
child 335820 1c3ab6d1eb423d7cc8e603c036a1712636581780
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
servo: Merge #4960 - Add thaw/freeze messages that can suspend/resume webcontent timers #4907 (from pkondzior:add-script-timers-suspend-resume-functionality); r=jdm Adds free/thaw methods to script_task that let you send suspend/resume messages to web content timers. Fixes #4907 Source-Repo: https://github.com/servo/servo Source-Revision: a3ea3eed47713a4a6faa86c575bc2ac95c5fe135
servo/components/compositing/constellation.rs
servo/components/compositing/pipeline.rs
servo/components/script/dom/window.rs
servo/components/script/script_task.rs
servo/components/script/timers.rs
servo/components/script_traits/lib.rs
--- a/servo/components/compositing/constellation.rs
+++ b/servo/components/compositing/constellation.rs
@@ -829,16 +829,18 @@ impl<LTF: LayoutTaskFactory, STF: Script
         let next_pipeline_id = self.get_next_pipeline_id();
         let next_frame_id = self.get_next_frame_id();
         let pipeline = self.new_pipeline(next_pipeline_id, parent_id, None, load_data);
         self.browse(Some(source_id),
                     Rc::new(FrameTree::new(next_frame_id,
                                            pipeline.clone(),
                                            parent.borrow().clone())),
                     NavigationType::Load);
+        // Send message to ScriptTask that will suspend all timers
+        source_frame.pipeline.borrow().freeze();
         self.pipelines.insert(pipeline.id, pipeline);
     }
 
     fn handle_navigate_msg(&mut self, direction: constellation_msg::NavigationDirection) {
         debug!("received message to navigate {:?}", direction);
 
         // TODO(tkuehn): what is the "critical point" beyond which pending frames
         // should not be cleared? Currently, the behavior is that forward/back
@@ -848,36 +850,39 @@ impl<LTF: LayoutTaskFactory, STF: Script
             NavigationDirection::Forward => {
                 if self.navigation_context.next.is_empty() {
                     debug!("no next page to navigate to");
                     return;
                 } else {
                     let old = self.current_frame().as_ref().unwrap();
                     for frame in old.iter() {
                         frame.pipeline.borrow().revoke_paint_permission();
+                        frame.pipeline.borrow().freeze();
                     }
                 }
                 self.navigation_context.forward(&mut *self.compositor_proxy)
             }
             NavigationDirection::Back => {
                 if self.navigation_context.previous.is_empty() {
                     debug!("no previous page to navigate to");
                     return;
                 } else {
                     let old = self.current_frame().as_ref().unwrap();
                     for frame in old.iter() {
                         frame.pipeline.borrow().revoke_paint_permission();
+                        frame.pipeline.borrow().freeze();
                     }
                 }
                 self.navigation_context.back(&mut *self.compositor_proxy)
             }
         };
 
         for frame in destination_frame.iter() {
             frame.pipeline.borrow().load();
+            frame.pipeline.borrow().thaw();
         }
         self.send_frame_tree_and_grant_paint_permission(destination_frame);
 
     }
 
     fn pipeline_is_in_current_frame(&self, pipeline_id: PipelineId) -> bool {
         self.current_frame().iter()
             .any(|current_frame| current_frame.contains(pipeline_id))
--- a/servo/components/compositing/pipeline.rs
+++ b/servo/components/compositing/pipeline.rs
@@ -186,16 +186,26 @@ impl Pipeline {
             // Wait until all slave tasks have terminated and run destructors
             // NOTE: We don't wait for script task as we don't always own it
             let _ = self.paint_shutdown_port.recv();
             let _ = self.layout_shutdown_port.recv();
         }
 
     }
 
+    pub fn freeze(&self) {
+        let ScriptControlChan(ref script_channel) = self.script_chan;
+        let _ = script_channel.send(ConstellationControlMsg::Freeze(self.id)).unwrap();
+    }
+
+    pub fn thaw(&self) {
+        let ScriptControlChan(ref script_channel) = self.script_chan;
+        let _ = script_channel.send(ConstellationControlMsg::Thaw(self.id)).unwrap();
+    }
+
     pub fn force_exit(&self) {
         let ScriptControlChan(ref script_channel) = self.script_chan;
         let _ = script_channel.send(
             ConstellationControlMsg::ExitPipeline(self.id,
                                                   PipelineExitType::PipelineOnly)).unwrap();
         let _ = self.paint_chan.send(PaintMsg::Exit(None, PipelineExitType::PipelineOnly));
         let LayoutControlChan(ref layout_channel) = self.layout_chan;
         let _ = layout_channel.send(
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -330,16 +330,18 @@ impl<'a> WindowMethods for JSRef<'a, Win
 }
 
 pub trait WindowHelpers {
     fn flush_layout(self, goal: ReflowGoal, query: ReflowQueryType);
     fn init_browser_context(self, doc: JSRef<Document>, frame_element: Option<JSRef<Element>>);
     fn load_url(self, href: DOMString);
     fn handle_fire_timer(self, timer_id: TimerId);
     fn IndexedGetter(self, _index: u32, _found: &mut bool) -> Option<Temporary<Window>>;
+    fn thaw(self);
+    fn freeze(self);
 }
 
 pub trait ScriptHelpers {
     fn evaluate_js_on_global_with_result(self, code: &str) -> JSVal;
     fn evaluate_script_on_global_with_result(self, code: &str, filename: &str) -> JSVal;
 }
 
 impl<'a, T: Reflectable> ScriptHelpers for JSRef<'a, T> {
@@ -400,16 +402,25 @@ impl<'a> WindowHelpers for JSRef<'a, Win
         self.timers.fire_timer(timer_id, self);
         self.flush_layout(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery);
     }
 
     // https://html.spec.whatwg.org/multipage/browsers.html#accessing-other-browsing-contexts
     fn IndexedGetter(self, _index: u32, _found: &mut bool) -> Option<Temporary<Window>> {
         None
     }
+
+    fn thaw(self) {
+        self.timers.resume();
+    }
+
+    fn freeze(self) {
+        self.timers.suspend();
+    }
+
 }
 
 impl Window {
     pub fn new(cx: *mut JSContext,
                page: Rc<Page>,
                script_chan: Box<ScriptChan+Send>,
                control_chan: ScriptControlChan,
                compositor: Box<ScriptListener+'static>,
--- a/servo/components/script/script_task.rs
+++ b/servo/components/script/script_task.rs
@@ -575,16 +575,20 @@ impl ScriptTask {
             ConstellationControlMsg::Viewport(..) =>
                 panic!("should have handled Viewport already"),
             ConstellationControlMsg::Resize(..) =>
                 panic!("should have handled Resize already"),
             ConstellationControlMsg::ExitPipeline(..) =>
                 panic!("should have handled ExitPipeline already"),
             ConstellationControlMsg::GetTitle(pipeline_id) =>
                 self.handle_get_title_msg(pipeline_id),
+            ConstellationControlMsg::Freeze(pipeline_id) =>
+                self.handle_freeze_msg(pipeline_id),
+            ConstellationControlMsg::Thaw(pipeline_id) =>
+                self.handle_thaw_msg(pipeline_id)
         }
     }
 
     fn handle_msg_from_script(&self, msg: ScriptMsg) {
         match msg {
             ScriptMsg::TriggerLoad(id, load_data) =>
                 self.trigger_load(id, load_data),
             ScriptMsg::TriggerFragment(id, fragment) =>
@@ -658,16 +662,36 @@ impl ScriptTask {
         let page = self.page.borrow_mut();
         let page = page.find(id).expect("ScriptTask: received fire timer msg for a
             pipeline ID not associated with this script task. This is a bug.");
         let frame = page.frame();
         let window = frame.as_ref().unwrap().window.root();
         window.r().handle_fire_timer(timer_id);
     }
 
+    /// Handles freeze message
+    fn handle_freeze_msg(&self, id: PipelineId) {
+        let page = self.page.borrow_mut();
+        let page = page.find(id).expect("ScriptTask: received freeze msg for a
+                    pipeline ID not associated with this script task. This is a bug.");
+        let frame = page.frame();
+        let window = frame.as_ref().unwrap().window.root();
+        window.r().freeze();
+    }
+
+    /// Handles thaw message
+    fn handle_thaw_msg(&self, id: PipelineId) {
+        let page = self.page.borrow_mut();
+        let page = page.find(id).expect("ScriptTask: received thaw msg for a
+                            pipeline ID not associated with this script task. This is a bug.");
+        let frame = page.frame();
+        let window = frame.as_ref().unwrap().window.root();
+        window.r().thaw();
+    }
+
     /// Handles a notification that reflow completed.
     fn handle_reflow_complete_msg(&self, pipeline_id: PipelineId, reflow_id: uint) {
         debug!("Script: Reflow {:?} complete for {:?}", reflow_id, pipeline_id);
         let page = self.page.borrow_mut();
         let page = page.find(pipeline_id).expect(
             "ScriptTask: received a load message for a layout channel that is not associated \
              with this script task. This is a bug.");
         let last_reflow_id = page.last_reflow_id.get();
--- a/servo/components/script/timers.rs
+++ b/servo/components/script/timers.rs
@@ -32,17 +32,17 @@ use std::time::duration::Duration;
 #[derive(Copy)]
 pub struct TimerId(i32);
 
 #[jstraceable]
 #[privatize]
 struct TimerHandle {
     handle: TimerId,
     data: TimerData,
-    cancel_chan: Option<Sender<()>>,
+    control_chan: Option<Sender<TimerControlMsg>>,
 }
 
 #[jstraceable]
 #[derive(Clone)]
 pub enum TimerCallback {
     StringTimerCallback(DOMString),
     FunctionTimerCallback(Function)
 }
@@ -51,17 +51,23 @@ impl<H: Writer + Hasher> Hash<H> for Tim
     fn hash(&self, state: &mut H) {
         let TimerId(id) = *self;
         id.hash(state);
     }
 }
 
 impl TimerHandle {
     fn cancel(&mut self) {
-        self.cancel_chan.as_ref().map(|chan| chan.send(()).ok());
+        self.control_chan.as_ref().map(|chan| chan.send(TimerControlMsg::Cancel).ok());
+    }
+    fn suspend(&mut self) {
+        self.control_chan.as_ref().map(|chan| chan.send(TimerControlMsg::Suspend).ok());
+    }
+    fn resume(&mut self) {
+        self.control_chan.as_ref().map(|chan| chan.send(TimerControlMsg::Resume).ok());
     }
 }
 
 #[jstraceable]
 #[privatize]
 pub struct TimerManager {
     active_timers: DOMRefCell<HashMap<TimerId, TimerHandle>>,
     next_timer_handle: Cell<i32>,
@@ -80,16 +86,25 @@ impl Drop for TimerManager {
 // Enum allowing more descriptive values for the is_interval field
 #[jstraceable]
 #[derive(PartialEq, Copy, Clone)]
 pub enum IsInterval {
     Interval,
     NonInterval,
 }
 
+// Messages sent control timers from script task
+#[jstraceable]
+#[derive(PartialEq, Copy, Clone, Show)]
+pub enum TimerControlMsg {
+    Cancel,
+    Suspend,
+    Resume
+}
+
 // Holder for the various JS values associated with setTimeout
 // (ie. function value to invoke and all arguments to pass
 //      to the function when calling it)
 // TODO: Handle rooting during fire_timer when movable GC is turned on
 #[jstraceable]
 #[privatize]
 #[derive(Clone)]
 struct TimerData {
@@ -101,75 +116,102 @@ struct TimerData {
 impl TimerManager {
     pub fn new() -> TimerManager {
         TimerManager {
             active_timers: DOMRefCell::new(HashMap::new()),
             next_timer_handle: Cell::new(0)
         }
     }
 
+    pub fn suspend(&self) {
+        for (_, timer_handle) in self.active_timers.borrow_mut().iter_mut() {
+            timer_handle.suspend();
+        }
+    }
+    pub fn resume(&self) {
+        for (_, timer_handle) in self.active_timers.borrow_mut().iter_mut() {
+            timer_handle.resume();
+        }
+    }
+
     #[allow(unsafe_blocks)]
     pub fn set_timeout_or_interval(&self,
                                   callback: TimerCallback,
                                   arguments: Vec<JSVal>,
                                   timeout: i32,
                                   is_interval: IsInterval,
                                   source: TimerSource,
                                   script_chan: Box<ScriptChan+Send>)
                                   -> i32 {
         let timeout = cmp::max(0, timeout) as u64;
         let handle = self.next_timer_handle.get();
         self.next_timer_handle.set(handle + 1);
 
         // Spawn a new timer task; it will dispatch the `ScriptMsg::FireTimer`
         // to the relevant script handler that will deal with it.
         let tm = Timer::new().unwrap();
-        let (cancel_chan, cancel_port) = channel();
+        let (control_chan, control_port) = channel();
         let spawn_name = match source {
             TimerSource::FromWindow(_) if is_interval == IsInterval::Interval => "Window:SetInterval",
             TimerSource::FromWorker if is_interval == IsInterval::Interval => "Worker:SetInterval",
             TimerSource::FromWindow(_) => "Window:SetTimeout",
             TimerSource::FromWorker => "Worker:SetTimeout",
         }.to_owned();
         spawn_named(spawn_name, move || {
             let mut tm = tm;
             let duration = Duration::milliseconds(timeout as i64);
             let timeout_port = if is_interval == IsInterval::Interval {
                 tm.periodic(duration)
             } else {
                 tm.oneshot(duration)
             };
-            let cancel_port = cancel_port;
+            let control_port = control_port;
 
             let select = Select::new();
             let mut timeout_handle = select.handle(&timeout_port);
             unsafe { timeout_handle.add() };
-            let mut cancel_handle = select.handle(&cancel_port);
-            unsafe { cancel_handle.add() };
+            let mut control_handle = select.handle(&control_port);
+            unsafe { control_handle.add() };
 
             loop {
                 let id = select.wait();
+
                 if id == timeout_handle.id() {
                     timeout_port.recv().unwrap();
                     if script_chan.send(ScriptMsg::FireTimer(source, TimerId(handle))).is_err() {
                         break;
                     }
 
                     if is_interval == IsInterval::NonInterval {
                         break;
                     }
-                } else if id == cancel_handle.id() {
-                    break;
+                } else if id == control_handle.id() {;
+                    match control_port.recv().unwrap() {
+                        TimerControlMsg::Suspend => {
+                            let msg = control_port.recv().unwrap();
+                            match msg {
+                                TimerControlMsg::Suspend => panic!("Nothing to suspend!"),
+                                TimerControlMsg::Resume => {},
+                                TimerControlMsg::Cancel => {
+                                    break;
+                                },
+                            }
+                            },
+                        TimerControlMsg::Resume => panic!("Nothing to resume!"),
+                        TimerControlMsg::Cancel => {
+                            break;
+                        }
+                    }
                 }
             }
         });
         let timer_id = TimerId(handle);
         let timer = TimerHandle {
             handle: timer_id,
-            cancel_chan: Some(cancel_chan),
+            control_chan: Some(control_chan),
             data: TimerData {
                 is_interval: is_interval,
                 callback: callback,
                 args: arguments
             }
         };
         self.active_timers.borrow_mut().insert(timer_id, timer);
         handle
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -65,16 +65,20 @@ pub enum ConstellationControlMsg {
     /// Sends a DOM event.
     SendEvent(PipelineId, CompositorEvent),
     /// Notifies script that reflow is finished.
     ReflowComplete(PipelineId, uint),
     /// Notifies script of the viewport.
     Viewport(PipelineId, Rect<f32>),
     /// Requests that the script task immediately send the constellation the title of a pipeline.
     GetTitle(PipelineId),
+    /// Notifies script task to suspend all its timers
+    Freeze(PipelineId),
+    /// Notifies script task to resume all its timers
+    Thaw(PipelineId)
 }
 
 unsafe impl Send for ConstellationControlMsg {
 }
 
 /// Events from the compositor that the script task needs to know about
 pub enum CompositorEvent {
     ResizeEvent(WindowSizeData),