mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
Implement non-visible pipeline and iframe visibility methods
This commit is contained in:
parent
ce88b8ed30
commit
2bff131535
14 changed files with 382 additions and 24 deletions
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -943,7 +958,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));
|
||||
|
@ -1482,6 +1498,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>,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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::{Root, LayoutJS};
|
||||
|
@ -66,6 +67,7 @@ pub struct HTMLIFrameElement {
|
|||
subpage_id: Cell<Option<SubpageId>>,
|
||||
sandbox: Cell<Option<u8>>,
|
||||
load_blocker: DOMRefCell<Option<LoadBlocker>>,
|
||||
visibility: Cell<bool>,
|
||||
}
|
||||
|
||||
impl HTMLIFrameElement {
|
||||
|
@ -196,6 +198,7 @@ impl HTMLIFrameElement {
|
|||
subpage_id: Cell::new(None),
|
||||
sandbox: Cell::new(None),
|
||||
load_blocker: DOMRefCell::new(None),
|
||||
visibility: Cell::new(true),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,6 +224,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
|
||||
|
@ -387,6 +410,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -498,6 +526,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)
|
||||
|
|
|
@ -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.mozbrowser.enabled",
|
||||
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"]
|
||||
boolean getVisible();
|
||||
|
||||
//[Throws,
|
||||
// Pref="dom.mozBrowserFramesEnabled",
|
||||
|
|
|
@ -1477,6 +1477,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)
|
||||
|
|
|
@ -139,6 +139,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,
|
||||
}
|
||||
|
@ -157,6 +159,7 @@ impl InProgressLoad {
|
|||
window_size: window_size,
|
||||
clip_rect: None,
|
||||
is_frozen: false,
|
||||
is_visible: true,
|
||||
url: url,
|
||||
}
|
||||
}
|
||||
|
@ -918,6 +921,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) =>
|
||||
|
@ -1217,6 +1224,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() {
|
||||
|
@ -1692,6 +1748,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()
|
||||
|
|
|
@ -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);
|
||||
|
@ -209,6 +210,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());
|
||||
|
||||
|
@ -287,6 +297,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)]
|
||||
|
@ -341,6 +353,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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -404,6 +417,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;
|
||||
|
@ -412,13 +443,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);
|
||||
|
||||
|
|
|
@ -133,6 +133,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
|
||||
|
@ -421,6 +425,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 {
|
||||
|
@ -442,7 +448,8 @@ impl MozBrowserEvent {
|
|||
MozBrowserEvent::ShowModalPrompt(_, _, _, _) => "mozbrowsershowmodalprompt",
|
||||
MozBrowserEvent::TitleChange(_) => "mozbrowsertitlechange",
|
||||
MozBrowserEvent::UsernameAndPasswordRequired => "mozbrowserusernameandpasswordrequired",
|
||||
MozBrowserEvent::OpenSearch => "mozbrowseropensearch"
|
||||
MozBrowserEvent::OpenSearch => "mozbrowseropensearch",
|
||||
MozBrowserEvent::VisibilityChange(_) => "mozbrowservisibilitychange",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6538,6 +6538,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",
|
||||
|
|
6
tests/wpt/mozilla/tests/mozilla/mozbrowser/helper.html
Normal file
6
tests/wpt/mozilla/tests/mozilla/mozbrowser/helper.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<p>test</p>
|
||||
</body>
|
||||
</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>
|
Loading…
Add table
Add a link
Reference in a new issue