diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2016-06-16 08:53:56 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-16 08:53:56 -0500 |
commit | d620ab71c41431c3fb040162f554faefb9abfbd7 (patch) | |
tree | 902c90598821bee9998194bb14f65c7b3e9bbab9 | |
parent | ff67f80f36106cba4a89c48d8c8d59c9880856c7 (diff) | |
parent | 2bff131535987da0330737f78fb23291593db30d (diff) | |
download | servo-d620ab71c41431c3fb040162f554faefb9abfbd7.tar.gz servo-d620ab71c41431c3fb040162f554faefb9abfbd7.zip |
Auto merge of #10225 - jmr0:visibility_api, r=jdm
Implement non-visible pipeline and iframe visibility methods
This addresses #9566 and a good part of #9751, specifically:
* Pipeline has a notion of visibility
* IFrame setVisible/getVisible interface with IFrame's pipeline visibility
* IFrame mozbrowservisibilitychange responds to changes in visibility
* Pipeline visibility is used to limit animations (requestAnimationFrame does not tick animations when hidden) and to increase timer intervals (currently set to a minimum of 1 second while hidden)
Absent for now are any changes to the Document API and general implementation of the Page Visibility API, since the more interesting parts require knowledge of whether the user agent is minimized, OS screen locked, etc.
cc @paulrouget @jdm
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/10225)
<!-- Reviewable:end -->
-rw-r--r-- | components/compositing/compositor.rs | 29 | ||||
-rw-r--r-- | components/compositing/compositor_thread.rs | 3 | ||||
-rw-r--r-- | components/constellation/constellation.rs | 47 | ||||
-rw-r--r-- | components/constellation/pipeline.rs | 30 | ||||
-rw-r--r-- | components/script/dom/htmliframeelement.rs | 54 | ||||
-rw-r--r-- | components/script/dom/webidls/BrowserElement.webidl | 22 | ||||
-rw-r--r-- | components/script/dom/window.rs | 8 | ||||
-rw-r--r-- | components/script/script_thread.rs | 60 | ||||
-rw-r--r-- | components/script/timers.rs | 38 | ||||
-rw-r--r-- | components/script_traits/lib.rs | 9 | ||||
-rw-r--r-- | components/script_traits/script_msg.rs | 4 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 6 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/mozbrowser/helper.html | 6 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/mozbrowser/iframe_visibility.html | 92 |
14 files changed, 383 insertions, 25 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 7b476ab2f55..a987bd7bdd3 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -290,6 +290,9 @@ struct PipelineDetails { /// Whether there are animation callbacks animation_callbacks_running: bool, + + /// Whether this pipeline is visible + visible: bool, } impl PipelineDetails { @@ -299,6 +302,7 @@ impl PipelineDetails { current_epoch: Epoch(0), animations_running: false, animation_callbacks_running: false, + visible: true, } } } @@ -760,6 +764,13 @@ impl<Window: WindowMethods> IOCompositor<Window> { reports_chan.send(reports); } + (Msg::PipelineVisibilityChanged(pipeline_id, visible), ShutdownState::NotShuttingDown) => { + self.pipeline_details(pipeline_id).visible = visible; + if visible { + self.process_animations(); + } + } + (Msg::PipelineExited(pipeline_id, sender), _) => { debug!("Compositor got pipeline exited: {:?}", pipeline_id); self.pending_subpages.remove(&pipeline_id); @@ -795,13 +806,16 @@ impl<Window: WindowMethods> IOCompositor<Window> { animation_state: AnimationState) { match animation_state { AnimationState::AnimationsPresent => { + let visible = self.pipeline_details(pipeline_id).visible; self.pipeline_details(pipeline_id).animations_running = true; - self.composite_if_necessary(CompositingReason::Animation); + if visible { + self.composite_if_necessary(CompositingReason::Animation); + } } AnimationState::AnimationCallbacksPresent => { - if !self.pipeline_details(pipeline_id).animation_callbacks_running { - self.pipeline_details(pipeline_id).animation_callbacks_running = - true; + let visible = self.pipeline_details(pipeline_id).visible; + self.pipeline_details(pipeline_id).animation_callbacks_running = true; + if visible { self.tick_animations_for_pipeline(pipeline_id); } } @@ -1712,9 +1726,10 @@ impl<Window: WindowMethods> IOCompositor<Window> { fn process_animations(&mut self) { 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_ids.push(*pipeline_id); + if (pipeline_details.animations_running || + pipeline_details.animation_callbacks_running) && + pipeline_details.visible { + pipeline_ids.push(*pipeline_id); } } for pipeline_id in &pipeline_ids { diff --git a/components/compositing/compositor_thread.rs b/components/compositing/compositor_thread.rs index 325cdc3d104..d29235858b8 100644 --- a/components/compositing/compositor_thread.rs +++ b/components/compositing/compositor_thread.rs @@ -183,6 +183,8 @@ pub enum Msg { ResizeTo(Size2D<u32>), /// Get scroll offset of a layer GetScrollOffset(PipelineId, LayerId, IpcSender<Point2D<f32>>), + /// Pipeline visibility changed + PipelineVisibilityChanged(PipelineId, bool), /// A pipeline was shut down. // This message acts as a synchronization point between the constellation, // when it shuts down a pipeline, to the compositor; when the compositor @@ -223,6 +225,7 @@ impl Debug for Msg { Msg::GetClientWindow(..) => write!(f, "GetClientWindow"), Msg::MoveTo(..) => write!(f, "MoveTo"), Msg::ResizeTo(..) => write!(f, "ResizeTo"), + Msg::PipelineVisibilityChanged(..) => write!(f, "PipelineVisibilityChanged"), Msg::PipelineExited(..) => write!(f, "PipelineExited"), Msg::GetScrollOffset(..) => write!(f, "GetScrollOffset"), } diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index c726d546e0d..5bef18b062b 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -407,6 +407,12 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> load_data: LoadData) { if self.shutting_down { return; } + let parent_visibility = if let Some((parent_pipeline_id, _, _)) = parent_info { + self.pipelines.get(&parent_pipeline_id).map(|pipeline| pipeline.visible) + } else { + None + }; + let result = Pipeline::spawn::<Message, LTF, STF>(InitialPipelineState { id: pipeline_id, parent_info: parent_info, @@ -427,6 +433,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> load_data: load_data, device_pixel_ratio: self.window_size.device_pixel_ratio, pipeline_namespace_id: self.next_pipeline_namespace_id(), + parent_visibility: parent_visibility, webrender_api_sender: self.webrender_api_sender.clone(), }); @@ -710,6 +717,14 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> } } } + FromScriptMsg::SetVisible(pipeline_id, visible) => { + debug!("constellation got set visible messsage"); + self.handle_set_visible_msg(pipeline_id, visible); + } + FromScriptMsg::VisibilityChangeComplete(pipeline_id, visible) => { + debug!("constellation got set visibility change complete message"); + self.handle_visibility_change_complete(pipeline_id, visible); + } FromScriptMsg::RemoveIFrame(pipeline_id, sender) => { debug!("constellation got remove iframe message"); self.handle_remove_iframe_msg(pipeline_id); @@ -949,7 +964,8 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> let window_size = self.window_size.visible_viewport; let root_pipeline_id = PipelineId::new(); debug_assert!(PipelineId::fake_root_pipeline_id() == root_pipeline_id); - self.new_pipeline(root_pipeline_id, None, Some(window_size), None, LoadData::new(url.clone(), None, None)); + self.new_pipeline(root_pipeline_id, None, Some(window_size), None, + LoadData::new(url.clone(), None, None)); self.handle_load_start_msg(&root_pipeline_id); self.push_pending_frame(root_pipeline_id, None); self.compositor_proxy.send(ToCompositorMsg::ChangePageUrl(root_pipeline_id, url)); @@ -1488,6 +1504,35 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF> } } + fn handle_set_visible_msg(&mut self, pipeline_id: PipelineId, visible: bool) { + let frame_id = self.pipeline_to_frame_map.get(&pipeline_id).map(|frame_id| *frame_id); + let child_pipeline_ids: Vec<PipelineId> = self.current_frame_tree_iter(frame_id) + .map(|frame| frame.current) + .collect(); + for id in child_pipeline_ids { + if let Some(pipeline) = self.pipelines.get_mut(&id) { + pipeline.change_visibility(visible); + } + } + } + + fn handle_visibility_change_complete(&mut self, pipeline_id: PipelineId, visibility: bool) { + let parent_pipeline_info = self.pipelines.get(&pipeline_id).and_then(|source| source.parent_info); + if let Some((parent_pipeline_id, _, _)) = parent_pipeline_info { + let visibility_msg = ConstellationControlMsg::NotifyVisibilityChange(parent_pipeline_id, + pipeline_id, + visibility); + let result = match self.pipelines.get(&parent_pipeline_id) { + None => return warn!("Parent pipeline {:?} closed", parent_pipeline_id), + Some(parent_pipeline) => parent_pipeline.script_chan.send(visibility_msg), + }; + + if let Err(e) = result { + self.handle_send_error(parent_pipeline_id, e); + } + } + } + fn handle_create_canvas_paint_thread_msg( &mut self, size: &Size2D<i32>, diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 578ccae0927..8a48108ca81 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -65,6 +65,9 @@ pub struct Pipeline { pub running_animations: bool, pub children: Vec<FrameId>, pub is_private: bool, + /// Whether this pipeline should be treated as visible for the purposes of scheduling and + /// resource management. + pub visible: bool, } /// Initial setup data needed to construct a pipeline. @@ -112,6 +115,8 @@ pub struct InitialPipelineState { pub load_data: LoadData, /// The ID of the pipeline namespace for this script thread. pub pipeline_namespace_id: PipelineNamespaceId, + /// Pipeline visibility is inherited from parent + pub parent_visibility: Option<bool>, /// Optional webrender api (if enabled). pub webrender_api_sender: Option<webrender_traits::RenderApiSender>, } @@ -250,7 +255,10 @@ impl Pipeline { state.compositor_proxy, chrome_to_paint_chan, state.load_data.url, - state.window_size); + state.window_size, + state.parent_visibility.unwrap_or(true)); + + pipeline.notify_visibility(); Ok((pipeline, child_process)) } @@ -262,7 +270,8 @@ impl Pipeline { compositor_proxy: Box<CompositorProxy + 'static + Send>, chrome_to_paint_chan: Sender<ChromeToPaintMsg>, url: Url, - size: Option<TypedSize2D<PagePx, f32>>) + size: Option<TypedSize2D<PagePx, f32>>, + visible: bool) -> Pipeline { Pipeline { id: id, @@ -277,6 +286,7 @@ impl Pipeline { size: size, running_animations: false, is_private: false, + visible: visible, } } @@ -367,6 +377,22 @@ impl Pipeline { warn!("Sending mozbrowser event to script failed ({}).", e); } } + + fn notify_visibility(&self) { + self.script_chan.send(ConstellationControlMsg::ChangeFrameVisibilityStatus(self.id, self.visible)) + .expect("Pipeline script chan"); + + self.compositor_proxy.send(CompositorMsg::PipelineVisibilityChanged(self.id, self.visible)); + } + + pub fn change_visibility(&mut self, visible: bool) { + if visible == self.visible { + return; + } + self.visible = visible; + self.notify_visibility(); + } + } #[derive(Deserialize, Serialize)] diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 57248c03e97..86146b8e915 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -11,12 +11,13 @@ use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementLocat use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementOpenTabEventDetail; use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementOpenWindowEventDetail; use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementSecurityChangeDetail; +use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementVisibilityChangeEventDetail; use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserShowModalPromptEventDetail; use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding; use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::conversions::ToJSValConvertible; -use dom::bindings::error::{Error, ErrorResult}; +use dom::bindings::error::{Error, ErrorResult, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::inheritance::Castable; use dom::bindings::js::{JS, MutNullableHeap, Root, LayoutJS}; @@ -68,6 +69,7 @@ pub struct HTMLIFrameElement { sandbox: MutNullableHeap<JS<DOMTokenList>>, sandbox_allowance: Cell<Option<u8>>, load_blocker: DOMRefCell<Option<LoadBlocker>>, + visibility: Cell<bool>, } impl HTMLIFrameElement { @@ -199,6 +201,7 @@ impl HTMLIFrameElement { sandbox: Default::default(), sandbox_allowance: Cell::new(None), load_blocker: DOMRefCell::new(None), + visibility: Cell::new(true), } } @@ -224,6 +227,26 @@ impl HTMLIFrameElement { self.pipeline_id.get() } + pub fn change_visibility_status(&self, visibility: bool) { + if self.visibility.get() != visibility { + self.visibility.set(visibility); + + // Visibility changes are only exposed to Mozbrowser iframes + if self.Mozbrowser() { + self.dispatch_mozbrowser_event(MozBrowserEvent::VisibilityChange(visibility)); + } + } + } + + pub fn set_visible(&self, visible: bool) { + if let Some(pipeline_id) = self.pipeline_id.get() { + let window = window_from_node(self); + let window = window.r(); + let msg = ConstellationMsg::SetVisible(pipeline_id, visible); + window.constellation_chan().send(msg).unwrap(); + } + } + /// https://html.spec.whatwg.org/multipage/#iframe-load-event-steps steps 1-4 pub fn iframe_load_event_steps(&self, loaded_pipeline: PipelineId) { // TODO(#9592): assert that the load blocker is present at all times when we @@ -390,6 +413,11 @@ impl MozBrowserEventDetailBuilder for HTMLIFrameElement { returnValue: Some(DOMString::from(return_value)), }.to_jsval(cx, rval) } + MozBrowserEvent::VisibilityChange(visibility) => { + BrowserElementVisibilityChangeEventDetail { + visible: Some(visibility), + }.to_jsval(cx, rval); + } } } } @@ -496,6 +524,30 @@ impl HTMLIFrameElementMethods for HTMLIFrameElement { } } + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/setVisible + fn SetVisible(&self, visible: bool) -> ErrorResult { + if self.Mozbrowser() { + self.set_visible(visible); + Ok(()) + } else { + debug!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top + level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)"); + Err(Error::NotSupported) + } + } + + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/getVisible + fn GetVisible(&self) -> Fallible<bool> { + if self.Mozbrowser() { + Ok(self.visibility.get()) + } else { + debug!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top + level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)"); + Err(Error::NotSupported) + } + } + + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/stop fn Stop(&self) -> ErrorResult { Err(Error::NotSupported) diff --git a/components/script/dom/webidls/BrowserElement.webidl b/components/script/dom/webidls/BrowserElement.webidl index 9351cc9377a..f183b5cf813 100644 --- a/components/script/dom/webidls/BrowserElement.webidl +++ b/components/script/dom/webidls/BrowserElement.webidl @@ -96,20 +96,24 @@ dictionary BrowserElementOpenWindowEventDetail { // Element frameElement; }; +dictionary BrowserElementVisibilityChangeEventDetail { + boolean visible; +}; + BrowserElement implements BrowserElementCommon; BrowserElement implements BrowserElementPrivileged; [NoInterfaceObject] interface BrowserElementCommon { - //[Throws, - // Pref="dom.mozBrowserFramesEnabled", - // CheckAnyPermissions="browser embed-widgets"] - //void setVisible(boolean visible); - - //[Throws, - // Pref="dom.mozBrowserFramesEnabled", - // CheckAnyPermissions="browser embed-widgets"] - //DOMRequest getVisible(); + [Throws, + Pref="dom.mozbrowser.enabled", + CheckAnyPermissions="browser embed-widgets"] + void setVisible(boolean visible); + + [Throws, + Pref="dom.mozbrowser.enabled", + CheckAnyPermissions="browser embed-widgets"] + boolean getVisible(); //[Throws, // Pref="dom.mozBrowserFramesEnabled", diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 34f7c1acdb0..486ad45bde4 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1509,6 +1509,14 @@ impl Window { self.timers.suspend(); } + pub fn slow_down_timers(&self) { + self.timers.slow_down(); + } + + pub fn speed_up_timers(&self) { + self.timers.speed_up(); + } + pub fn need_emit_timeline_marker(&self, timeline_type: TimelineMarkerType) -> bool { let markers = self.devtools_markers.borrow(); markers.contains(&timeline_type) diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 4750a8d6390..3459e5ddcfa 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -140,6 +140,8 @@ struct InProgressLoad { clip_rect: Option<Rect<f32>>, /// Window is frozen (navigated away while loading for example). is_frozen: bool, + /// Window is visible. + is_visible: bool, /// The requested URL of the load. url: Url, } @@ -158,6 +160,7 @@ impl InProgressLoad { window_size: window_size, clip_rect: None, is_frozen: false, + is_visible: true, url: url, } } @@ -919,6 +922,10 @@ impl ScriptThread { self.handle_freeze_msg(pipeline_id), ConstellationControlMsg::Thaw(pipeline_id) => self.handle_thaw_msg(pipeline_id), + ConstellationControlMsg::ChangeFrameVisibilityStatus(pipeline_id, visible) => + self.handle_visibility_change_msg(pipeline_id, visible), + ConstellationControlMsg::NotifyVisibilityChange(containing_id, pipeline_id, visible) => + self.handle_visibility_change_complete_msg(containing_id, pipeline_id, visible), ConstellationControlMsg::MozBrowserEvent(parent_pipeline_id, subpage_id, event) => @@ -1232,6 +1239,55 @@ impl ScriptThread { reports_chan.send(reports); } + /// To slow/speed up timers and manage any other script thread resource based on visibility. + /// Returns true if successful. + fn alter_resource_utilization(&self, id: PipelineId, visible: bool) -> bool { + if let Some(root_context) = self.browsing_context.get() { + if let Some(ref inner_context) = root_context.find(id) { + let window = inner_context.active_window(); + if visible { + window.speed_up_timers(); + } else { + window.slow_down_timers(); + } + return true; + } + } + false + } + + /// Updates iframe element after a change in visibility + fn handle_visibility_change_complete_msg(&self, containing_id: PipelineId, id: PipelineId, visible: bool) { + if let Some(root_context) = self.browsing_context.get() { + if let Some(ref inner_context) = root_context.find(containing_id) { + if let Some(iframe) = inner_context.active_document().find_iframe_by_pipeline(id) { + iframe.change_visibility_status(visible); + } + } + } + } + + /// Handle visibility change message + fn handle_visibility_change_msg(&self, id: PipelineId, visible: bool) { + let resources_altered = self.alter_resource_utilization(id, visible); + + // Separate message sent since parent script thread could be different (Iframe of different + // domain) + self.constellation_chan.send(ConstellationMsg::VisibilityChangeComplete(id, visible)).unwrap(); + + if !resources_altered { + let mut loads = self.incomplete_loads.borrow_mut(); + if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) { + load.is_visible = visible; + return; + } + } else { + return; + } + + warn!("change visibility message sent to nonexistent pipeline"); + } + /// Handles freeze message fn handle_freeze_msg(&self, id: PipelineId) { if let Some(root_context) = self.browsing_context.get() { @@ -1707,6 +1763,10 @@ impl ScriptThread { window.freeze(); } + if !incomplete.is_visible { + self.alter_resource_utilization(browsing_context.pipeline(), false); + } + context_remover.neuter(); document.get_current_parser().unwrap() diff --git a/components/script/timers.rs b/components/script/timers.rs index 504dbb8aaaf..989382e80fe 100644 --- a/components/script/timers.rs +++ b/components/script/timers.rs @@ -22,6 +22,7 @@ use std::cmp::{self, Ord, Ordering}; use std::collections::HashMap; use std::default::Default; use std::rc::Rc; +use util::prefs::get_pref; #[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord, Debug)] pub struct OneshotTimerHandle(i32); @@ -212,6 +213,15 @@ impl OneshotTimers { } } + pub fn slow_down(&self) { + let duration = get_pref("js.timers.minimum_duration").as_u64().unwrap_or(1000); + self.js_timers.set_min_duration(MsDuration::new(duration)); + } + + pub fn speed_up(&self) { + self.js_timers.remove_min_duration(); + } + pub fn suspend(&self) { assert!(self.suspended_since.get().is_none()); @@ -290,6 +300,8 @@ pub struct JsTimers { active_timers: DOMRefCell<HashMap<JsTimerHandle, JsTimerEntry>>, /// The nesting level of the currently executing timer task or 0. nesting_level: Cell<u32>, + /// Used to introduce a minimum delay in event intervals + min_duration: Cell<Option<MsDuration>>, } #[derive(JSTraceable, HeapSizeOf)] @@ -344,6 +356,7 @@ impl JsTimers { next_timer_handle: Cell::new(JsTimerHandle(1)), active_timers: DOMRefCell::new(HashMap::new()), nesting_level: Cell::new(0), + min_duration: Cell::new(None), } } @@ -407,6 +420,24 @@ impl JsTimers { } } + pub fn set_min_duration(&self, duration: MsDuration) { + self.min_duration.set(Some(duration)); + } + + pub fn remove_min_duration(&self) { + self.min_duration.set(None); + } + + // see step 13 of https://html.spec.whatwg.org/multipage/#timer-initialisation-steps + fn user_agent_pad(&self, current_duration: MsDuration) -> MsDuration { + match self.min_duration.get() { + Some(min_duration) => { + cmp::max(min_duration, current_duration) + }, + None => current_duration + } + } + // see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps fn initialize_and_schedule(&self, global: GlobalRef, mut task: JsTimerTask) { let handle = task.handle; @@ -415,13 +446,12 @@ impl JsTimers { // step 6 let nesting_level = self.nesting_level.get(); - // step 7 - let duration = clamp_duration(nesting_level, task.duration); - + // step 7, 13 + let duration = self.user_agent_pad(clamp_duration(nesting_level, task.duration)); // step 8, 9 task.nesting_level = nesting_level + 1; - // essentially step 11-14 + // essentially step 11, 12, and 14 let callback = OneshotTimerCallback::JsTimer(task); let oneshot_handle = global.schedule_callback(callback, duration); diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 07cfaccb84c..cc8a06bf18e 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -163,6 +163,10 @@ pub enum ConstellationControlMsg { Freeze(PipelineId), /// Notifies script thread to resume all its timers Thaw(PipelineId), + /// Notifies script thread whether frame is visible + ChangeFrameVisibilityStatus(PipelineId, bool), + /// Notifies script thread that frame visibility change is complete + NotifyVisibilityChange(PipelineId, PipelineId, bool), /// Notifies script thread that a url should be loaded in this iframe. Navigate(PipelineId, SubpageId, LoadData), /// Requests the script thread forward a mozbrowser event to an iframe it owns @@ -451,6 +455,8 @@ pub enum MozBrowserEvent { UsernameAndPasswordRequired, /// Sent when a link to a search engine is found. OpenSearch, + /// Sent when visibility state changes. + VisibilityChange(bool), } impl MozBrowserEvent { @@ -472,7 +478,8 @@ impl MozBrowserEvent { MozBrowserEvent::ShowModalPrompt(_, _, _, _) => "mozbrowsershowmodalprompt", MozBrowserEvent::TitleChange(_) => "mozbrowsertitlechange", MozBrowserEvent::UsernameAndPasswordRequired => "mozbrowserusernameandpasswordrequired", - MozBrowserEvent::OpenSearch => "mozbrowseropensearch" + MozBrowserEvent::OpenSearch => "mozbrowseropensearch", + MozBrowserEvent::VisibilityChange(_) => "mozbrowservisibilitychange", } } } diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs index 241833bc13f..7fc871eb4cc 100644 --- a/components/script_traits/script_msg.rs +++ b/components/script_traits/script_msg.rs @@ -81,6 +81,10 @@ pub enum ScriptMsg { NodeStatus(Option<String>), /// Notification that this iframe should be removed. RemoveIFrame(PipelineId, Option<IpcSender<()>>), + /// Change pipeline visibility + SetVisible(PipelineId, bool), + /// Notifies constellation that an iframe's visibility has been changed. + VisibilityChangeComplete(PipelineId, bool), /// A load has been requested in an IFrame. ScriptLoadedURLInIFrame(IFrameLoadInfo), /// Requests that the constellation set the contents of the clipboard diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 43761afed12..60c7ad1009a 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -6550,6 +6550,12 @@ "url": "/_mozilla/mozilla/mozbrowser/iframe_reload_twice.html" } ], + "mozilla/mozbrowser/iframe_visibility.html": [ + { + "path": "mozilla/mozbrowser/iframe_visibility.html", + "url": "/_mozilla/mozilla/mozbrowser/iframe_visibility.html" + } + ], "mozilla/mozbrowser/mozbrowser_click_fires_openwindow.html": [ { "path": "mozilla/mozbrowser/mozbrowser_click_fires_openwindow.html", diff --git a/tests/wpt/mozilla/tests/mozilla/mozbrowser/helper.html b/tests/wpt/mozilla/tests/mozilla/mozbrowser/helper.html new file mode 100644 index 00000000000..dee1ea597e5 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/mozbrowser/helper.html @@ -0,0 +1,6 @@ +<!doctype html> +<html> +<body> + <p>test</p> +</body> +</html> diff --git a/tests/wpt/mozilla/tests/mozilla/mozbrowser/iframe_visibility.html b/tests/wpt/mozilla/tests/mozilla/mozbrowser/iframe_visibility.html new file mode 100644 index 00000000000..4681f5db25a --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/mozbrowser/iframe_visibility.html @@ -0,0 +1,92 @@ +<!doctype html> +<meta charset="utf-8"> +<head> +<title>Iframe visibility tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> + <script> + async_test(function(t) { + var expectedVisibilities = [false, true]; + var receivedVisibilities = []; + + var iframe = document.createElement("iframe"); + iframe.mozbrowser = true; + iframe.src = "helper.html"; + + //Alternate the iframe's visibility and fire mozbrowservisibilitychange + iframe.onload = t.step_func(function() { + iframe.setVisible(false); + iframe.setVisible(true); + }); + + iframe.addEventListener("mozbrowservisibilitychange", t.step_func(e => { + assert_equals(iframe.getVisible(), e.detail.visible); + receivedVisibilities.push(e.detail.visible); + if (receivedVisibilities.length == expectedVisibilities.length) { + assert_array_equals(receivedVisibilities, expectedVisibilities); + t.done(); + } + })); + + document.body.appendChild(iframe); + }, "Iframe visibility setter/getter"); + + async_test(function(t) { + var iframe = document.createElement("iframe"); + iframe.mozbrowser = true; + iframe.src = "helper.html"; + var start = null; + document.body.appendChild(iframe); + iframe.onload = t.step_func(function() { + var element = iframe.contentWindow.document.querySelector("p"); + var animationCompletesAfterResumingVisibility = false; + var nonVisibleAnimationStopped = false; + element.style.position = 'relative'; + element.style.right = "0px"; + var step = t.step_func(function(timestamp) { + if (!start) start = timestamp; + var progress = timestamp - start; + element.style.right = Math.min(progress/5, 100) + "px"; + if (progress < 500) { + iframe.contentWindow.requestAnimationFrame(step); + } + }); + + iframe.setVisible(false); + + iframe.contentWindow.setTimeout(t.step_func(function(){ + nonVisibleAnimationStopped = element.style.right === '0px'; + iframe.setVisible(true); + }),1000); + + iframe.contentWindow.setTimeout(t.step_func(function(){ + animationCompletesAfterResumingVisibility = element.style.right === '100px'; + assert_true(nonVisibleAnimationStopped); + assert_true(animationCompletesAfterResumingVisibility); + t.done(); + }),2000); + + iframe.contentWindow.requestAnimationFrame(step); + }); + }, 'Requesting animation frame composites only when frame is visible'); + + async_test(function(t) { + var iframe = document.createElement("iframe"); + iframe.src = "http://web-platform.test:8000/common/blank.html"; + iframe.mozbrowser = true; + iframe.onload = t.step_func(function() { + iframe.addEventListener("mozbrowservisibilitychange", t.step_func(function() { + var startTime = Date.now(); + iframe.contentWindow.setTimeout(t.step_func(function() { + assert_true(Date.now() - startTime >= 1000); + t.done(); + }), 1); + })); + iframe.setVisible(false); + }); + document.body.appendChild(iframe); + }, 'Minimum setTimeout of 1s when pipeline is invisible'); + </script> +</body> |