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.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::from(&promise);
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, cx: &mut JSContext) -> RootedPromise {
156 let global = self.global();
157
158 let mut realm = CurrentRealm::assert(cx);
161 let promise = Promise::new_in_realm(&mut realm);
162
163 if !self.is_fully_active() || self.fullscreen_element().is_none() {
166 promise.reject_error(
167 cx,
168 Error::Type(
169 c"No fullscreen element to exit or document is not fully active".to_owned(),
170 ),
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::from(&promise);
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 !shadow_root.Host().is_connected()
234 {
235 return None;
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(cx);
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(cx, atom!("fullscreenerror"));
315 promise.reject_error(cx, Error::Type(c"fullscreen is not connected".to_owned()));
316 return;
317 }
318
319 element.set_fullscreen_state(true);
322 document.set_fullscreen_element(Some(&element));
323 document.upcast::<EventTarget>().fire_event_with_params(
324 cx,
325 atom!("fullscreenchange"),
326 EventBubbles::Bubbles,
327 EventCancelable::NotCancelable,
328 EventComposed::Composed,
329 );
330
331 promise.resolve_native(cx, &());
334 }
335}
336
337struct ElementPerformFullscreenExit {
338 element: Trusted<Element>,
339 promise: TrustedPromise,
340}
341
342impl ElementPerformFullscreenExit {
343 fn new(
344 element: Trusted<Element>,
345 promise: TrustedPromise,
346 ) -> Box<ElementPerformFullscreenExit> {
347 Box::new(ElementPerformFullscreenExit { element, promise })
348 }
349}
350
351impl TaskOnce for ElementPerformFullscreenExit {
352 fn run_once(self, cx: &mut js::context::JSContext) {
354 let element = self.element.root();
355 let document = element.owner_document();
356 element.set_fullscreen_state(false);
363 document.set_fullscreen_element(None);
364 document.upcast::<EventTarget>().fire_event_with_params(
365 cx,
366 atom!("fullscreenchange"),
367 EventBubbles::Bubbles,
368 EventCancelable::NotCancelable,
369 EventComposed::Composed,
370 );
371
372 self.promise.root(cx).resolve_native(cx, &());
375 }
376}