aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/compositing/compositor.rs29
-rw-r--r--components/compositing/compositor_thread.rs3
-rw-r--r--components/constellation/constellation.rs47
-rw-r--r--components/constellation/pipeline.rs30
-rw-r--r--components/script/dom/htmliframeelement.rs54
-rw-r--r--components/script/dom/webidls/BrowserElement.webidl22
-rw-r--r--components/script/dom/window.rs8
-rw-r--r--components/script/script_thread.rs60
-rw-r--r--components/script/timers.rs38
-rw-r--r--components/script_traits/lib.rs9
-rw-r--r--components/script_traits/script_msg.rs4
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json6
-rw-r--r--tests/wpt/mozilla/tests/mozilla/mozbrowser/helper.html6
-rw-r--r--tests/wpt/mozilla/tests/mozilla/mozbrowser/iframe_visibility.html92
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>