script/dom/fullscreen/
lib.rs1use embedder_traits::EmbedderMsg;
6use html5ever::{local_name, ns};
7use js::context::JSContext;
8use js::realm::CurrentRealm;
9use servo_config::pref;
10
11use crate::dom::RootedPromise;
12use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
13use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
14use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods;
15use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
16use crate::dom::bindings::error::Error;
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
19use crate::dom::bindings::reflector::DomGlobal;
20use crate::dom::bindings::root::DomRoot;
21use crate::dom::document::document::Document;
22use crate::dom::document::documentorshadowroot::DocumentOrShadowRoot;
23use crate::dom::element::Element;
24use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
25use crate::dom::event::eventtarget::EventTarget;
26use crate::dom::node::NodeTraits;
27use crate::dom::node::node::Node;
28use crate::dom::promise::Promise;
29use crate::dom::shadowroot::shadowroot::ShadowRoot;
30use crate::dom::types::HTMLDialogElement;
31use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg};
32use crate::runtime::script_runtime::ScriptThreadEventCategory;
33use crate::tasks::task::TaskOnce;
34use crate::tasks::task_source::TaskSourceName;
35
36impl Document {
37 pub(crate) fn enter_fullscreen(
39 &self,
40 cx: &mut CurrentRealm,
41 pending: &Element,
42 ) -> RootedPromise {
43 let promise = Promise::new_in_realm(cx);
50
51 if !self.is_fully_active() {
54 promise.reject_error(cx, Error::Type(c"Document is not fully active".to_owned()));
55 return promise;
56 }
57
58 let mut error = false;
61
62 {
65 match *pending.namespace() {
67 ns!(mathml) => {
68 if *pending.local_name() != local_name!("math") {
69 error = true;
70 }
71 },
72 ns!(svg) => {
73 if *pending.local_name() != local_name!("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
117 .owner_window_unrooted(cx.no_gc())
118 .consume_user_activation();
119 }
120
121 if !error {
127 let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), true);
128 self.send_to_embedder(event);
129 }
130
131 let pipeline_id = self.window().pipeline_id();
134
135 let trusted_pending = Trusted::new(pending);
136 let trusted_pending_doc = Trusted::new(self);
137 let trusted_promise = TrustedPromise::from(&promise);
138 let handler = ElementPerformFullscreenEnter::new(
139 trusted_pending,
140 trusted_pending_doc,
141 trusted_promise,
142 error,
143 );
144 let script_msg = CommonScriptMsg::Task(
145 ScriptThreadEventCategory::EnterFullscreen,
146 handler,
147 Some(pipeline_id),
148 TaskSourceName::DOMManipulation,
149 );
150 let msg = MainThreadScriptMsg::Common(script_msg);
151 self.window().main_thread_script_chan().send(msg).unwrap();
152
153 promise
154 }
155
156 pub(crate) fn exit_fullscreen(&self, cx: &mut JSContext) -> RootedPromise {
158 let global = self.global();
159
160 let mut realm = CurrentRealm::assert(cx);
163 let promise = Promise::new_in_realm(&mut realm);
164
165 if !self.is_fully_active() || self.fullscreen_element().is_none() {
168 promise.reject_error(
169 cx,
170 Error::Type(
171 c"No fullscreen element to exit or document is not fully active".to_owned(),
172 ),
173 );
174 return promise;
175 }
176
177 let element = self.fullscreen_element().unwrap();
180 let window = self.window();
181
182 let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), false);
186 self.send_to_embedder(event);
187
188 let trusted_element = Trusted::new(&*element);
191 let trusted_promise = TrustedPromise::from(&promise);
192 let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise);
193 let pipeline_id = Some(global.pipeline_id());
194 let script_msg = CommonScriptMsg::Task(
195 ScriptThreadEventCategory::ExitFullscreen,
196 handler,
197 pipeline_id,
198 TaskSourceName::DOMManipulation,
199 );
200 let msg = MainThreadScriptMsg::Common(script_msg);
201 window.main_thread_script_chan().send(msg).unwrap();
202
203 promise
204 }
205
206 pub(crate) fn get_allow_fullscreen(&self) -> bool {
207 match self.browsing_context() {
209 None => false,
211 Some(_) => {
212 let window = self.window();
214 if window.is_top_level() {
215 true
216 } else {
217 window
219 .GetFrameElement()
220 .is_some_and(|el| el.has_attribute(&local_name!("allowfullscreen")))
221 }
222 },
223 }
224 }
225}
226
227impl DocumentOrShadowRoot {
228 pub(crate) fn get_fullscreen_element(
230 node: &Node,
231 fullscreen_element: Option<DomRoot<Element>>,
232 ) -> Option<DomRoot<Element>> {
233 if let Some(shadow_root) = node.downcast::<ShadowRoot>() &&
235 !shadow_root.Host().is_connected()
236 {
237 return None;
238 }
239
240 let retargeted = fullscreen_element?
242 .upcast::<EventTarget>()
243 .retarget(node.upcast());
244 let candidate = DomRoot::downcast::<Element>(retargeted).unwrap();
246
247 if *candidate
249 .upcast::<Node>()
250 .GetRootNode(&GetRootNodeOptions::empty()) ==
251 *node
252 {
253 return Some(candidate);
254 }
255
256 None
258 }
259}
260
261impl Element {
262 pub(crate) fn fullscreen_element_ready_check(&self) -> bool {
264 if !self.is_connected() {
265 return false;
266 }
267 self.owner_document().get_allow_fullscreen()
268 }
269}
270
271struct ElementPerformFullscreenEnter {
272 element: Trusted<Element>,
273 document: Trusted<Document>,
274 promise: TrustedPromise,
275 error: bool,
276}
277
278impl ElementPerformFullscreenEnter {
279 fn new(
280 element: Trusted<Element>,
281 document: Trusted<Document>,
282 promise: TrustedPromise,
283 error: bool,
284 ) -> Box<ElementPerformFullscreenEnter> {
285 Box::new(ElementPerformFullscreenEnter {
286 element,
287 document,
288 promise,
289 error,
290 })
291 }
292}
293
294impl TaskOnce for ElementPerformFullscreenEnter {
295 fn run_once(self, cx: &mut js::context::JSContext) {
297 let element = self.element.root();
298 let promise = self.promise.root(cx);
299 let document = element.owner_document();
300
301 if self.document.root() != document ||
310 !element.fullscreen_element_ready_check() ||
311 self.error
312 {
313 document
315 .upcast::<EventTarget>()
316 .fire_event(cx, atom!("fullscreenerror"));
317 promise.reject_error(cx, Error::Type(c"fullscreen is not connected".to_owned()));
318 return;
319 }
320
321 element.set_fullscreen_state(cx.no_gc(), true);
324 document.set_fullscreen_element(Some(&element));
325 document.upcast::<EventTarget>().fire_event_with_params(
326 cx,
327 atom!("fullscreenchange"),
328 EventBubbles::Bubbles,
329 EventCancelable::NotCancelable,
330 EventComposed::Composed,
331 );
332
333 promise.resolve_native(cx, &());
336 }
337}
338
339struct ElementPerformFullscreenExit {
340 element: Trusted<Element>,
341 promise: TrustedPromise,
342}
343
344impl ElementPerformFullscreenExit {
345 fn new(
346 element: Trusted<Element>,
347 promise: TrustedPromise,
348 ) -> Box<ElementPerformFullscreenExit> {
349 Box::new(ElementPerformFullscreenExit { element, promise })
350 }
351}
352
353impl TaskOnce for ElementPerformFullscreenExit {
354 fn run_once(self, cx: &mut js::context::JSContext) {
356 let element = self.element.root();
357 let document = element.owner_document();
358 element.set_fullscreen_state(cx.no_gc(), false);
365 document.set_fullscreen_element(None);
366 document.upcast::<EventTarget>().fire_event_with_params(
367 cx,
368 atom!("fullscreenchange"),
369 EventBubbles::Bubbles,
370 EventCancelable::NotCancelable,
371 EventComposed::Composed,
372 );
373
374 self.promise.root(cx).resolve_native(cx, &());
377 }
378}