Add support for fullscreen #10102

This commit is contained in:
Jansen Jan 2016-12-08 14:02:08 +01:00
parent c3c086e521
commit 55f0e56224
33 changed files with 454 additions and 30 deletions

View file

@ -27,7 +27,7 @@ use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, Nod
use dom::bindings::js::{JS, LayoutJS, MutNullableHeap, Root};
use dom::bindings::js::RootedReference;
use dom::bindings::num::Finite;
use dom::bindings::refcounted::Trusted;
use dom::bindings::refcounted::{Trusted, TrustedPromise};
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::str::{DOMString, USVString};
use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml_name_type};
@ -39,7 +39,7 @@ use dom::customevent::CustomEvent;
use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType;
use dom::domimplementation::DOMImplementation;
use dom::element::{Element, ElementCreator};
use dom::element::{Element, ElementCreator, ElementPerformFullscreenEnter, ElementPerformFullscreenExit};
use dom::errorevent::ErrorEvent;
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventdispatcher::EventStatus;
@ -74,6 +74,7 @@ use dom::pagetransitionevent::PageTransitionEvent;
use dom::popstateevent::PopStateEvent;
use dom::processinginstruction::ProcessingInstruction;
use dom::progressevent::ProgressEvent;
use dom::promise::Promise;
use dom::range::Range;
use dom::servoparser::ServoParser;
use dom::storageevent::StorageEvent;
@ -105,6 +106,7 @@ use net_traits::response::HttpsState;
use num_traits::ToPrimitive;
use origin::Origin;
use script_layout_interface::message::{Msg, ReflowQueryType};
use script_runtime::{CommonScriptMsg, ScriptThreadEventCategory};
use script_thread::{MainThreadScriptMsg, Runnable};
use script_traits::{AnimationState, CompositorEvent, MouseButton, MouseEventType, MozBrowserEvent};
use script_traits::{ScriptMsg as ConstellationMsg, TouchpadPressurePhase};
@ -290,6 +292,8 @@ pub struct Document {
///
/// See also: https://github.com/servo/servo/issues/10110
dom_count: Cell<u32>,
/// Entry node for fullscreen.
fullscreen_element: MutNullableHeap<JS<Element>>,
}
#[derive(JSTraceable, HeapSizeOf)]
@ -1907,6 +1911,7 @@ impl Document {
last_click_info: DOMRefCell::new(None),
ignore_destructive_writes_counter: Default::default(),
dom_count: Cell::new(1),
fullscreen_element: MutNullableHeap::new(None),
}
}
@ -2092,6 +2097,110 @@ impl Document {
self.ignore_destructive_writes_counter.set(
self.ignore_destructive_writes_counter.get() - 1);
}
// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
#[allow(unrooted_must_root)]
pub fn enter_fullscreen(&self, pending: &Element) -> Rc<Promise> {
// Step 1
let promise = Promise::new(self.global().r());
let mut error = false;
// Step 4
// check namespace
match *pending.namespace() {
ns!(mathml) => {
if pending.local_name().as_ref() != "math" {
error = true;
}
}
ns!(svg) => {
if pending.local_name().as_ref() != "svg" {
error = true;
}
}
ns!(html) => (),
_ => error = true,
}
// fullscreen element ready check
if !pending.fullscreen_element_ready_check() {
error = true;
}
// TODO fullscreen is supported
// TODO This algorithm is allowed to request fullscreen.
// Step 5 Parallel start
let window = self.window();
// Step 6
if !error {
let event = ConstellationMsg::SetFullscreenState(true);
window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
}
// Step 7
let trusted_pending = Trusted::new(pending);
let trusted_promise = TrustedPromise::new(promise.clone());
let handler = ElementPerformFullscreenEnter::new(trusted_pending, trusted_promise, error);
let script_msg = CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::EnterFullscreen, handler);
let msg = MainThreadScriptMsg::Common(script_msg);
window.main_thread_script_chan().send(msg).unwrap();
promise
}
// https://fullscreen.spec.whatwg.org/#exit-fullscreen
#[allow(unrooted_must_root)]
pub fn exit_fullscreen(&self) -> Rc<Promise> {
let global = self.global();
// Step 1
let promise = Promise::new(global.r());
// Step 2
if self.fullscreen_element.get().is_none() {
promise.reject_error(global.get_cx(), Error::Type(String::from("fullscreen is null")));
return promise
}
// TODO Step 3-6
let element = self.fullscreen_element.get().unwrap();
// Step 7 Parallel start
let window = self.window();
// Step 8
let event = ConstellationMsg::SetFullscreenState(false);
window.upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
// Step 9
let trusted_element = Trusted::new(element.r());
let trusted_promise = TrustedPromise::new(promise.clone());
let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise);
let script_msg = CommonScriptMsg::RunnableMsg(ScriptThreadEventCategory::ExitFullscreen, handler);
let msg = MainThreadScriptMsg::Common(script_msg);
window.main_thread_script_chan().send(msg).unwrap();
promise
}
pub fn set_fullscreen_element(&self, element: Option<&Element>) {
self.fullscreen_element.set(element);
}
pub fn get_allow_fullscreen(&self) -> bool {
// https://html.spec.whatwg.org/multipage/#allowed-to-use
match self.browsing_context() {
// Step 1
None => false,
Some(_) => {
// Step 2
let window = self.window();
if window.is_top_level() {
true
} else {
// Step 3
window.GetFrameElement().map_or(false, |el| el.has_attribute(&local_name!("allowfullscreen")))
}
}
}
}
}
@ -3109,6 +3218,34 @@ impl DocumentMethods for Document {
// https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers
document_and_element_event_handlers!();
// https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenerror
event_handler!(fullscreenerror, GetOnfullscreenerror, SetOnfullscreenerror);
// https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenchange
event_handler!(fullscreenchange, GetOnfullscreenchange, SetOnfullscreenchange);
// https://fullscreen.spec.whatwg.org/#dom-document-fullscreenenabled
fn FullscreenEnabled(&self) -> bool {
self.get_allow_fullscreen()
}
// https://fullscreen.spec.whatwg.org/#dom-document-fullscreen
fn Fullscreen(&self) -> bool {
self.fullscreen_element.get().is_some()
}
// https://fullscreen.spec.whatwg.org/#dom-document-fullscreenelement
fn GetFullscreenElement(&self) -> Option<Root<Element>> {
// TODO ShadowRoot
self.fullscreen_element.get()
}
#[allow(unrooted_must_root)]
// https://fullscreen.spec.whatwg.org/#dom-document-exitfullscreen
fn ExitFullscreen(&self) -> Rc<Promise> {
self.exit_fullscreen()
}
}
fn update_with_current_time_ms(marker: &Cell<u64>) {

View file

@ -24,6 +24,8 @@ use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use dom::bindings::js::{JS, LayoutJS, MutNullableHeap};
use dom::bindings::js::{Root, RootedReference};
use dom::bindings::refcounted::{Trusted, TrustedPromise};
use dom::bindings::reflector::DomObject;
use dom::bindings::str::DOMString;
use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml_name_type};
use dom::bindings::xmlname::XMLName::InvalidXMLName;
@ -35,6 +37,7 @@ use dom::domrect::DOMRect;
use dom::domrectlist::DOMRectList;
use dom::domtokenlist::DOMTokenList;
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::htmlanchorelement::HTMLAnchorElement;
use dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers};
use dom::htmlbuttonelement::HTMLButtonElement;
@ -62,18 +65,23 @@ use dom::node::{CLICK_IN_PROGRESS, ChildrenMutation, LayoutNodeHelpers, Node};
use dom::node::{NodeDamage, SEQUENTIALLY_FOCUSABLE, UnbindContext};
use dom::node::{document_from_node, window_from_node};
use dom::nodelist::NodeList;
use dom::promise::Promise;
use dom::servoparser::ServoParser;
use dom::text::Text;
use dom::validation::Validatable;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use dom::window::ReflowReason;
use html5ever::serialize;
use html5ever::serialize::SerializeOpts;
use html5ever::serialize::TraversalScope;
use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
use html5ever::tree_builder::{LimitedQuirks, NoQuirks, Quirks};
use html5ever_atoms::{Prefix, LocalName, Namespace, QualName};
use js::jsapi::{HandleValue, JSAutoCompartment};
use parking_lot::RwLock;
use ref_filter_map::ref_filter_map;
use script_layout_interface::message::ReflowQueryType;
use script_thread::Runnable;
use selectors::matching::{ElementFlags, MatchingReason, matches};
use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
use selectors::parser::{AttrSelector, NamespaceConstraint};
@ -84,9 +92,11 @@ use std::cell::{Cell, Ref};
use std::convert::TryFrom;
use std::default::Default;
use std::fmt;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::context::ReflowGoal;
use style::dom::TRestyleDamage;
use style::element_state::*;
use style::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
@ -1300,6 +1310,15 @@ impl Element {
}
}
}
// https://fullscreen.spec.whatwg.org/#fullscreen-element-ready-check
pub fn fullscreen_element_ready_check(&self) -> bool {
if !self.is_connected() {
return false
}
let document = document_from_node(self);
document.get_allow_fullscreen()
}
}
impl ElementMethods for Element {
@ -2056,6 +2075,13 @@ impl ElementMethods for Element {
None => return Err(Error::NotSupported)
}
}
// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
#[allow(unrooted_must_root)]
fn RequestFullscreen(&self) -> Rc<Promise> {
let doc = document_from_node(self);
doc.enter_fullscreen(self)
}
}
pub fn fragment_affecting_attributes() -> [LocalName; 3] {
@ -2167,6 +2193,10 @@ impl VirtualMethods for Element {
}
let doc = document_from_node(self);
let fullscreen = doc.GetFullscreenElement();
if fullscreen.r() == Some(self) {
doc.exit_fullscreen();
}
if let Some(ref value) = *self.id_attribute.borrow() {
doc.unregister_named_element(self, value.clone());
}
@ -2307,6 +2337,7 @@ impl<'a> ::selectors::Element for Root<Element> {
NonTSPseudoClass::Active |
NonTSPseudoClass::Focus |
NonTSPseudoClass::Fullscreen |
NonTSPseudoClass::Hover |
NonTSPseudoClass::Enabled |
NonTSPseudoClass::Disabled |
@ -2582,7 +2613,22 @@ impl Element {
}
pub fn set_target_state(&self, value: bool) {
self.set_state(IN_TARGET_STATE, value)
self.set_state(IN_TARGET_STATE, value)
}
pub fn fullscreen_state(&self) -> bool {
self.state.get().contains(IN_FULLSCREEN_STATE)
}
pub fn set_fullscreen_state(&self, value: bool) {
self.set_state(IN_FULLSCREEN_STATE, value)
}
/// https://dom.spec.whatwg.org/#connected
pub fn is_connected(&self) -> bool {
let node = self.upcast::<Node>();
let root = node.GetRootNode();
root.is::<Document>()
}
}
@ -2713,3 +2759,104 @@ impl TagName {
*self.ptr.borrow_mut() = None;
}
}
pub struct ElementPerformFullscreenEnter {
element: Trusted<Element>,
promise: TrustedPromise,
error: bool,
}
impl ElementPerformFullscreenEnter {
pub fn new(element: Trusted<Element>, promise: TrustedPromise, error: bool) -> Box<ElementPerformFullscreenEnter> {
box ElementPerformFullscreenEnter {
element: element,
promise: promise,
error: error,
}
}
}
impl Runnable for ElementPerformFullscreenEnter {
fn name(&self) -> &'static str { "ElementPerformFullscreenEnter" }
#[allow(unrooted_must_root)]
fn handler(self: Box<ElementPerformFullscreenEnter>) {
let element = self.element.root();
let document = document_from_node(element.r());
// Step 7.1
if self.error || !element.fullscreen_element_ready_check() {
// JSAutoCompartment needs to be manually made.
// Otherwise, Servo will crash.
let promise = self.promise.root();
let promise_cx = promise.global().get_cx();
let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get());
document.upcast::<EventTarget>().fire_event(atom!("fullscreenerror"));
promise.reject_error(promise.global().get_cx(), Error::Type(String::from("fullscreen is not connected")));
return
}
// TODO Step 7.2-4
// Step 7.5
element.set_fullscreen_state(true);
document.set_fullscreen_element(Some(&element));
document.window().reflow(ReflowGoal::ForDisplay,
ReflowQueryType::NoQuery,
ReflowReason::ElementStateChanged);
// Step 7.6
document.upcast::<EventTarget>().fire_event(atom!("fullscreenchange"));
// Step 7.7
// JSAutoCompartment needs to be manually made.
// Otherwise, Servo will crash.
let promise = self.promise.root();
let promise_cx = promise.global().get_cx();
let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get());
promise.resolve(promise.global().get_cx(), HandleValue::undefined());
}
}
pub struct ElementPerformFullscreenExit {
element: Trusted<Element>,
promise: TrustedPromise,
}
impl ElementPerformFullscreenExit {
pub fn new(element: Trusted<Element>, promise: TrustedPromise) -> Box<ElementPerformFullscreenExit> {
box ElementPerformFullscreenExit {
element: element,
promise: promise,
}
}
}
impl Runnable for ElementPerformFullscreenExit {
fn name(&self) -> &'static str { "ElementPerformFullscreenExit" }
#[allow(unrooted_must_root)]
fn handler(self: Box<ElementPerformFullscreenExit>) {
let element = self.element.root();
let document = document_from_node(element.r());
// TODO Step 9.1-5
// Step 9.6
element.set_fullscreen_state(false);
document.window().reflow(ReflowGoal::ForDisplay,
ReflowQueryType::NoQuery,
ReflowReason::ElementStateChanged);
document.set_fullscreen_element(None);
// Step 9.8
document.upcast::<EventTarget>().fire_event(atom!("fullscreenchange"));
// Step 9.10
let promise = self.promise.root();
// JSAutoCompartment needs to be manually made.
// Otherwise, Servo will crash.
let promise_cx = promise.global().get_cx();
let _ac = JSAutoCompartment::new(promise_cx, promise.reflector().get_jsobject().get());
promise.resolve(promise.global().get_cx(), HandleValue::undefined());
}
}

View file

@ -15,7 +15,7 @@ use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementVisib
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::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
use dom::bindings::conversions::ToJSValConvertible;
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::inheritance::Castable;
@ -544,6 +544,11 @@ impl HTMLIFrameElementMethods for HTMLIFrameElement {
element.set_bool_attribute(&local_name!("mozbrowser"), value);
}
// https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen
make_bool_getter!(AllowFullscreen, "allowfullscreen");
// https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen
make_bool_setter!(SetAllowFullscreen, "allowfullscreen");
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/goBack
fn GoBack(&self) -> ErrorResult {
Navigate(self, TraversalDirection::Back(1))

View file

@ -190,3 +190,15 @@ partial interface Document {
partial interface Document {
[SameObject] readonly attribute StyleSheetList styleSheets;
};
// https://fullscreen.spec.whatwg.org/#api
partial interface Document {
[LenientSetter] readonly attribute boolean fullscreenEnabled;
[LenientSetter] readonly attribute Element? fullscreenElement;
[LenientSetter] readonly attribute boolean fullscreen; // historical
Promise<void> exitFullscreen();
attribute EventHandler onfullscreenchange;
attribute EventHandler onfullscreenerror;
};

View file

@ -110,6 +110,11 @@ partial interface Element {
attribute DOMString outerHTML;
};
// https://fullscreen.spec.whatwg.org/#api
partial interface Element {
Promise<void> requestFullscreen();
};
Element implements ChildNode;
Element implements NonDocumentTypeChildNode;
Element implements ParentNode;

View file

@ -13,7 +13,7 @@ interface HTMLIFrameElement : HTMLElement {
[SameObject, PutForwards=value]
readonly attribute DOMTokenList sandbox;
// attribute boolean seamless;
// attribute boolean allowFullscreen;
attribute boolean allowFullscreen;
attribute DOMString width;
attribute DOMString height;
readonly attribute Document? contentDocument;

View file

@ -666,6 +666,7 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
NonTSPseudoClass::Active |
NonTSPseudoClass::Focus |
NonTSPseudoClass::Fullscreen |
NonTSPseudoClass::Hover |
NonTSPseudoClass::Enabled |
NonTSPseudoClass::Disabled |

View file

@ -76,7 +76,9 @@ pub enum ScriptThreadEventCategory {
UpdateReplacedElement,
WebSocketEvent,
WorkerEvent,
ServiceWorkerEvent
ServiceWorkerEvent,
EnterFullscreen,
ExitFullscreen,
}
/// An interface for receiving ScriptMsg values in an event loop. Used for synchronous DOM

View file

@ -946,7 +946,9 @@ impl ScriptThread {
ScriptThreadEventCategory::TimerEvent => ProfilerCategory::ScriptTimerEvent,
ScriptThreadEventCategory::WebSocketEvent => ProfilerCategory::ScriptWebSocketEvent,
ScriptThreadEventCategory::WorkerEvent => ProfilerCategory::ScriptWorkerEvent,
ScriptThreadEventCategory::ServiceWorkerEvent => ProfilerCategory::ScriptServiceWorkerEvent
ScriptThreadEventCategory::ServiceWorkerEvent => ProfilerCategory::ScriptServiceWorkerEvent,
ScriptThreadEventCategory::EnterFullscreen => ProfilerCategory::ScriptEnterFullscreen,
ScriptThreadEventCategory::ExitFullscreen => ProfilerCategory::ScriptExitFullscreen,
};
profile(profiler_cat, None, self.time_profiler_chan.clone(), f)
} else {