use std::rc::Rc;
use embedder_traits::EmbedderMsg;
use html5ever::{local_name, ns};
use servo_config::pref;
use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::error::Error;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::document::Document;
use crate::dom::document::documentorshadowroot::DocumentOrShadowRoot;
use crate::dom::element::Element;
use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
use crate::dom::event::eventtarget::EventTarget;
use crate::dom::node::NodeTraits;
use crate::dom::node::node::Node;
use crate::dom::promise::Promise;
use crate::dom::shadowroot::ShadowRoot;
use crate::dom::types::HTMLDialogElement;
use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg};
use crate::realms::{AlreadyInRealm, InRealm};
use crate::script_runtime::{CanGc, ScriptThreadEventCategory};
use crate::task::TaskOnce;
use crate::task_source::TaskSourceName;
impl Document {
pub(crate) fn enter_fullscreen(&self, pending: &Element, can_gc: CanGc) -> Rc<Promise> {
let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
if !self.is_fully_active() {
promise.reject_error(
Error::Type(c"Document is not fully active".to_owned()),
can_gc,
);
return promise;
}
let mut error = false;
{
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,
}
if pending.is::<HTMLDialogElement>() {
error = true;
}
if !pending.fullscreen_element_ready_check() {
error = true;
}
if !pending.owner_window().has_transient_activation() {
error = true;
}
}
if pref!(dom_fullscreen_test) {
info!("Tests don't really enter fullscreen.");
} else {
warn!("Fullscreen not supported yet");
}
if !error {
pending.owner_window().consume_user_activation();
}
if !error {
let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), true);
self.send_to_embedder(event);
}
let pipeline_id = self.window().pipeline_id();
let trusted_pending = Trusted::new(pending);
let trusted_pending_doc = Trusted::new(self);
let trusted_promise = TrustedPromise::new(promise.clone());
let handler = ElementPerformFullscreenEnter::new(
trusted_pending,
trusted_pending_doc,
trusted_promise,
error,
);
let script_msg = CommonScriptMsg::Task(
ScriptThreadEventCategory::EnterFullscreen,
handler,
Some(pipeline_id),
TaskSourceName::DOMManipulation,
);
let msg = MainThreadScriptMsg::Common(script_msg);
self.window().main_thread_script_chan().send(msg).unwrap();
promise
}
pub(crate) fn exit_fullscreen(&self, can_gc: CanGc) -> Rc<Promise> {
let global = self.global();
let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
if !self.is_fully_active() || self.fullscreen_element().is_none() {
promise.reject_error(
Error::Type(
c"No fullscreen element to exit or document is not fully active".to_owned(),
),
can_gc,
);
return promise;
}
let element = self.fullscreen_element().unwrap();
let window = self.window();
let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), false);
self.send_to_embedder(event);
let trusted_element = Trusted::new(&*element);
let trusted_promise = TrustedPromise::new(promise.clone());
let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise);
let pipeline_id = Some(global.pipeline_id());
let script_msg = CommonScriptMsg::Task(
ScriptThreadEventCategory::ExitFullscreen,
handler,
pipeline_id,
TaskSourceName::DOMManipulation,
);
let msg = MainThreadScriptMsg::Common(script_msg);
window.main_thread_script_chan().send(msg).unwrap();
promise
}
pub(crate) fn get_allow_fullscreen(&self) -> bool {
match self.browsing_context() {
None => false,
Some(_) => {
let window = self.window();
if window.is_top_level() {
true
} else {
window
.GetFrameElement()
.is_some_and(|el| el.has_attribute(&local_name!("allowfullscreen")))
}
},
}
}
}
impl DocumentOrShadowRoot {
pub(crate) fn get_fullscreen_element(
node: &Node,
fullscreen_element: Option<DomRoot<Element>>,
) -> Option<DomRoot<Element>> {
if let Some(shadow_root) = node.downcast::<ShadowRoot>() {
if !shadow_root.Host().is_connected() {
return None;
}
}
let retargeted = fullscreen_element?
.upcast::<EventTarget>()
.retarget(node.upcast());
let candidate = DomRoot::downcast::<Element>(retargeted).unwrap();
if *candidate
.upcast::<Node>()
.GetRootNode(&GetRootNodeOptions::empty()) ==
*node
{
return Some(candidate);
}
None
}
}
impl Element {
pub(crate) fn fullscreen_element_ready_check(&self) -> bool {
if !self.is_connected() {
return false;
}
self.owner_document().get_allow_fullscreen()
}
}
struct ElementPerformFullscreenEnter {
element: Trusted<Element>,
document: Trusted<Document>,
promise: TrustedPromise,
error: bool,
}
impl ElementPerformFullscreenEnter {
fn new(
element: Trusted<Element>,
document: Trusted<Document>,
promise: TrustedPromise,
error: bool,
) -> Box<ElementPerformFullscreenEnter> {
Box::new(ElementPerformFullscreenEnter {
element,
document,
promise,
error,
})
}
}
impl TaskOnce for ElementPerformFullscreenEnter {
fn run_once(self, cx: &mut js::context::JSContext) {
let element = self.element.root();
let promise = self.promise.root();
let document = element.owner_document();
if self.document.root() != document ||
!element.fullscreen_element_ready_check() ||
self.error
{
document
.upcast::<EventTarget>()
.fire_event(cx, atom!("fullscreenerror"));
promise.reject_error(
Error::Type(c"fullscreen is not connected".to_owned()),
CanGc::from_cx(cx),
);
return;
}
element.set_fullscreen_state(true);
document.set_fullscreen_element(Some(&element));
document.upcast::<EventTarget>().fire_event_with_params(
cx,
atom!("fullscreenchange"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
EventComposed::Composed,
);
promise.resolve_native(&(), CanGc::from_cx(cx));
}
}
struct ElementPerformFullscreenExit {
element: Trusted<Element>,
promise: TrustedPromise,
}
impl ElementPerformFullscreenExit {
fn new(
element: Trusted<Element>,
promise: TrustedPromise,
) -> Box<ElementPerformFullscreenExit> {
Box::new(ElementPerformFullscreenExit { element, promise })
}
}
impl TaskOnce for ElementPerformFullscreenExit {
fn run_once(self, cx: &mut js::context::JSContext) {
let element = self.element.root();
let document = element.owner_document();
element.set_fullscreen_state(false);
document.set_fullscreen_element(None);
document.upcast::<EventTarget>().fire_event_with_params(
cx,
atom!("fullscreenchange"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
EventComposed::Composed,
);
self.promise.root().resolve_native(&(), CanGc::from_cx(cx));
}
}