script/dom/fullscreen/
lib.rs1use std::rc::Rc;
6
7use embedder_traits::EmbedderMsg;
8use html5ever::{local_name, ns};
9use servo_config::pref;
10
11use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
12use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
13use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods;
14use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
15use crate::dom::bindings::error::Error;
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
18use crate::dom::bindings::reflector::DomGlobal;
19use crate::dom::bindings::root::DomRoot;
20use crate::dom::document::document::Document;
21use crate::dom::document::documentorshadowroot::DocumentOrShadowRoot;
22use crate::dom::element::Element;
23use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
24use crate::dom::event::eventtarget::EventTarget;
25use crate::dom::node::NodeTraits;
26use crate::dom::node::node::Node;
27use crate::dom::promise::Promise;
28use crate::dom::shadowroot::ShadowRoot;
29use crate::dom::types::HTMLDialogElement;
30use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg};
31use crate::realms::{AlreadyInRealm, InRealm};
32use crate::script_runtime::{CanGc, ScriptThreadEventCategory};
33use crate::task::TaskOnce;
34use crate::task_source::TaskSourceName;
35
36impl Document {
37 pub(crate) fn enter_fullscreen(&self, pending: &Element, can_gc: CanGc) -> Rc<Promise> {
39 let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
46 let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
47
48 if !self.is_fully_active() {
51 promise.reject_error(
52 Error::Type(c"Document is not fully active".to_owned()),
53 can_gc,
54 );
55 return promise;
56 }
57
58 let mut error = false;
61
62 {
65 match *pending.namespace() {
67 ns!(mathml) => {
68 if pending.local_name().as_ref() != "math" {
69 error = true;
70 }
71 },
72 ns!(svg) => {
73 if pending.local_name().as_ref() != "svg" {
74 error = true;
75 }
76 },
77 ns!(html) => (),
78 _ => error = true,
79 }
80
81 if pending.is::<HTMLDialogElement>() {
83 error = true;
84 }
85
86 if !pending.fullscreen_element_ready_check() {
88 error = true;
89 }
90
91 if !pending.owner_window().has_transient_activation() {
99 error = true;
100 }
101 }
102
103 if pref!(dom_fullscreen_test) {
104 info!("Tests don't really enter fullscreen.");
107 } else {
108 warn!("Fullscreen not supported yet");
111 }
112
113 if !error {
116 pending.owner_window().consume_user_activation();
117 }
118
119 if !error {
125 let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), true);
126 self.send_to_embedder(event);
127 }
128
129 let pipeline_id = self.window().pipeline_id();
132
133 let trusted_pending = Trusted::new(pending);
134 let trusted_pending_doc = Trusted::new(self);
135 let trusted_promise = TrustedPromise::new(promise.clone());
136 let handler = ElementPerformFullscreenEnter::new(
137 trusted_pending,
138 trusted_pending_doc,
139 trusted_promise,
140 error,
141 );
142 let script_msg = CommonScriptMsg::Task(
143 ScriptThreadEventCategory::EnterFullscreen,
144 handler,
145 Some(pipeline_id),
146 TaskSourceName::DOMManipulation,
147 );
148 let msg = MainThreadScriptMsg::Common(script_msg);
149 self.window().main_thread_script_chan().send(msg).unwrap();
150
151 promise
152 }
153
154 pub(crate) fn exit_fullscreen(&self, can_gc: CanGc) -> Rc<Promise> {
156 let global = self.global();
157
158 let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
161 let promise = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
162
163 if !self.is_fully_active() || self.fullscreen_element().is_none() {
166 promise.reject_error(
167 Error::Type(
168 c"No fullscreen element to exit or document is not fully active".to_owned(),
169 ),
170 can_gc,
171 );
172 return promise;
173 }
174
175 let element = self.fullscreen_element().unwrap();
178 let window = self.window();
179
180 let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), false);
184 self.send_to_embedder(event);
185
186 let trusted_element = Trusted::new(&*element);
189 let trusted_promise = TrustedPromise::new(promise.clone());
190 let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise);
191 let pipeline_id = Some(global.pipeline_id());
192 let script_msg = CommonScriptMsg::Task(
193 ScriptThreadEventCategory::ExitFullscreen,
194 handler,
195 pipeline_id,
196 TaskSourceName::DOMManipulation,
197 );
198 let msg = MainThreadScriptMsg::Common(script_msg);
199 window.main_thread_script_chan().send(msg).unwrap();
200
201 promise
202 }
203
204 pub(crate) fn get_allow_fullscreen(&self) -> bool {
205 match self.browsing_context() {
207 None => false,
209 Some(_) => {
210 let window = self.window();
212 if window.is_top_level() {
213 true
214 } else {
215 window
217 .GetFrameElement()
218 .is_some_and(|el| el.has_attribute(&local_name!("allowfullscreen")))
219 }
220 },
221 }
222 }
223}
224
225impl DocumentOrShadowRoot {
226 pub(crate) fn get_fullscreen_element(
228 node: &Node,
229 fullscreen_element: Option<DomRoot<Element>>,
230 ) -> Option<DomRoot<Element>> {
231 if let Some(shadow_root) = node.downcast::<ShadowRoot>() {
233 if !shadow_root.Host().is_connected() {
234 return None;
235 }
236 }
237
238 let retargeted = fullscreen_element?
240 .upcast::<EventTarget>()
241 .retarget(node.upcast());
242 let candidate = DomRoot::downcast::<Element>(retargeted).unwrap();
244
245 if *candidate
247 .upcast::<Node>()
248 .GetRootNode(&GetRootNodeOptions::empty()) ==
249 *node
250 {
251 return Some(candidate);
252 }
253
254 None
256 }
257}
258
259impl Element {
260 pub(crate) fn fullscreen_element_ready_check(&self) -> bool {
262 if !self.is_connected() {
263 return false;
264 }
265 self.owner_document().get_allow_fullscreen()
266 }
267}
268
269struct ElementPerformFullscreenEnter {
270 element: Trusted<Element>,
271 document: Trusted<Document>,
272 promise: TrustedPromise,
273 error: bool,
274}
275
276impl ElementPerformFullscreenEnter {
277 fn new(
278 element: Trusted<Element>,
279 document: Trusted<Document>,
280 promise: TrustedPromise,
281 error: bool,
282 ) -> Box<ElementPerformFullscreenEnter> {
283 Box::new(ElementPerformFullscreenEnter {
284 element,
285 document,
286 promise,
287 error,
288 })
289 }
290}
291
292impl TaskOnce for ElementPerformFullscreenEnter {
293 fn run_once(self, cx: &mut js::context::JSContext) {
295 let element = self.element.root();
296 let promise = self.promise.root();
297 let document = element.owner_document();
298
299 if self.document.root() != document ||
308 !element.fullscreen_element_ready_check() ||
309 self.error
310 {
311 document
313 .upcast::<EventTarget>()
314 .fire_event(atom!("fullscreenerror"), CanGc::from_cx(cx));
315 promise.reject_error(
316 Error::Type(c"fullscreen is not connected".to_owned()),
317 CanGc::from_cx(cx),
318 );
319 return;
320 }
321
322 element.set_fullscreen_state(true);
325 document.set_fullscreen_element(Some(&element));
326 document.upcast::<EventTarget>().fire_event_with_params(
327 atom!("fullscreenchange"),
328 EventBubbles::Bubbles,
329 EventCancelable::NotCancelable,
330 EventComposed::Composed,
331 CanGc::from_cx(cx),
332 );
333
334 promise.resolve_native(&(), CanGc::from_cx(cx));
337 }
338}
339
340struct ElementPerformFullscreenExit {
341 element: Trusted<Element>,
342 promise: TrustedPromise,
343}
344
345impl ElementPerformFullscreenExit {
346 fn new(
347 element: Trusted<Element>,
348 promise: TrustedPromise,
349 ) -> Box<ElementPerformFullscreenExit> {
350 Box::new(ElementPerformFullscreenExit { element, promise })
351 }
352}
353
354impl TaskOnce for ElementPerformFullscreenExit {
355 fn run_once(self, cx: &mut js::context::JSContext) {
357 let element = self.element.root();
358 let document = element.owner_document();
359 element.set_fullscreen_state(false);
366 document.set_fullscreen_element(None);
367 document.upcast::<EventTarget>().fire_event_with_params(
368 atom!("fullscreenchange"),
369 EventBubbles::Bubbles,
370 EventCancelable::NotCancelable,
371 EventComposed::Composed,
372 CanGc::from_cx(cx),
373 );
374
375 self.promise.root().resolve_native(&(), CanGc::from_cx(cx));
378 }
379}