Implement non-visible pipeline and iframe visibility methods

This commit is contained in:
jmr0 2016-03-09 01:24:46 -05:00
parent ce88b8ed30
commit 2bff131535
14 changed files with 382 additions and 24 deletions

View file

@ -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)

View file

@ -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",

View file

@ -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)

View file

@ -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()

View file

@ -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);