script/dom/fullscreen/
lib.rs1use std::rc::Rc;
6
7use embedder_traits::EmbedderMsg;
8use html5ever::{local_name, ns};
9use js::context::JSContext;
10use js::realm::CurrentRealm;
11use servo_config::pref;
12
13use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
14use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
15use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootMethods;
16use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
17use crate::dom::bindings::error::Error;
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
20use crate::dom::bindings::reflector::DomGlobal;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::document::document::Document;
23use crate::dom::document::documentorshadowroot::DocumentOrShadowRoot;
24use crate::dom::element::Element;
25use crate::dom::event::event::{EventBubbles, EventCancelable, EventComposed};
26use crate::dom::event::eventtarget::EventTarget;
27use crate::dom::node::NodeTraits;
28use crate::dom::node::node::Node;
29use crate::dom::promise::Promise;
30use crate::dom::shadowroot::ShadowRoot;
31use crate::dom::types::HTMLDialogElement;
32use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg};
33use crate::script_runtime::ScriptThreadEventCategory;
34use crate::task::TaskOnce;
35use crate::task_source::TaskSourceName;
36
37impl Document {
38 pub(crate) fn enter_fullscreen(&self, cx: &mut CurrentRealm, pending: &Element) -> Rc<Promise> {
40 let promise = Promise::new_in_realm(cx);
47
48 if !self.is_fully_active() {
51 promise.reject_error(cx, Error::Type(c"Document is not fully active".to_owned()));
52 return promise;
53 }
54
55 let mut error = false;
58
59 {
62 match *pending.namespace() {
64 ns!(mathml) => {
65 if pending.local_name().as_ref() != "math" {
66 error = true;
67 }
68 },
69 ns!(svg) => {
70 if pending.local_name().as_ref() != "svg" {
71 error = true;
72 }
73 },
74 ns!(html) => (),
75 _ => error = true,
76 }
77
78 if pending.is::<HTMLDialogElement>() {
80 error = true;
81 }
82
83 if !pending.fullscreen_element_ready_check() {
85 error = true;
86 }
87
88 if !pending.owner_window().has_transient_activation() {
96 error = true;
97 }
98 }
99
100 if pref!(dom_fullscreen_test) {
101 info!("Tests don't really enter fullscreen.");
104 } else {
105 warn!("Fullscreen not supported yet");
108 }
109
110 if !error {
113 pending.owner_window().consume_user_activation();
114 }
115
116 if !error {
122 let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), true);
123 self.send_to_embedder(event);
124 }
125
126 let pipeline_id = self.window().pipeline_id();
129
130 let trusted_pending = Trusted::new(pending);
131 let trusted_pending_doc = Trusted::new(self);
132 let trusted_promise = TrustedPromise::new(promise.clone());
133 let handler = ElementPerformFullscreenEnter::new(
134 trusted_pending,
135 trusted_pending_doc,
136 trusted_promise,
137 error,
138 );
139 let script_msg = CommonScriptMsg::Task(
140 ScriptThreadEventCategory::EnterFullscreen,
141 handler,
142 Some(pipeline_id),
143 TaskSourceName::DOMManipulation,
144 );
145 let msg = MainThreadScriptMsg::Common(script_msg);
146 self.window().main_thread_script_chan().send(msg).unwrap();
147
148 promise
149 }
150
151 pub(crate) fn exit_fullscreen(&self, cx: &mut JSContext) -> Rc<Promise> {
153 let global = self.global();
154
155 let mut realm = CurrentRealm::assert(cx);
158 let promise = Promise::new_in_realm(&mut realm);
159
160 if !self.is_fully_active() || self.fullscreen_element().is_none() {
163 promise.reject_error(
164 cx,
165 Error::Type(
166 c"No fullscreen element to exit or document is not fully active".to_owned(),
167 ),
168 );
169 return promise;
170 }
171
172 let element = self.fullscreen_element().unwrap();
175 let window = self.window();
176
177 let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), false);
181 self.send_to_embedder(event);
182
183 let trusted_element = Trusted::new(&*element);
186 let trusted_promise = TrustedPromise::new(promise.clone());
187 let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise);
188 let pipeline_id = Some(global.pipeline_id());
189 let script_msg = CommonScriptMsg::Task(
190 ScriptThreadEventCategory::ExitFullscreen,
191 handler,
192 pipeline_id,
193 TaskSourceName::DOMManipulation,
194 );
195 let msg = MainThreadScriptMsg::Common(script_msg);
196 window.main_thread_script_chan().send(msg).unwrap();
197
198 promise
199 }
200
201 pub(crate) fn get_allow_fullscreen(&self) -> bool {
202 match self.browsing_context() {
204 None => false,
206 Some(_) => {
207 let window = self.window();
209 if window.is_top_level() {
210 true
211 } else {
212 window
214 .GetFrameElement()
215 .is_some_and(|el| el.has_attribute(&local_name!("allowfullscreen")))
216 }
217 },
218 }
219 }
220}
221
222impl DocumentOrShadowRoot {
223 pub(crate) fn get_fullscreen_element(
225 node: &Node,
226 fullscreen_element: Option<DomRoot<Element>>,
227 ) -> Option<DomRoot<Element>> {
228 if let Some(shadow_root) = node.downcast::<ShadowRoot>() &&
230 !shadow_root.Host().is_connected()
231 {
232 return None;
233 }
234
235 let retargeted = fullscreen_element?
237 .upcast::<EventTarget>()
238 .retarget(node.upcast());
239 let candidate = DomRoot::downcast::<Element>(retargeted).unwrap();
241
242 if *candidate
244 .upcast::<Node>()
245 .GetRootNode(&GetRootNodeOptions::empty()) ==
246 *node
247 {
248 return Some(candidate);
249 }
250
251 None
253 }
254}
255
256impl Element {
257 pub(crate) fn fullscreen_element_ready_check(&self) -> bool {
259 if !self.is_connected() {
260 return false;
261 }
262 self.owner_document().get_allow_fullscreen()
263 }
264}
265
266struct ElementPerformFullscreenEnter {
267 element: Trusted<Element>,
268 document: Trusted<Document>,
269 promise: TrustedPromise,
270 error: bool,
271}
272
273impl ElementPerformFullscreenEnter {
274 fn new(
275 element: Trusted<Element>,
276 document: Trusted<Document>,
277 promise: TrustedPromise,
278 error: bool,
279 ) -> Box<ElementPerformFullscreenEnter> {
280 Box::new(ElementPerformFullscreenEnter {
281 element,
282 document,
283 promise,
284 error,
285 })
286 }
287}
288
289impl TaskOnce for ElementPerformFullscreenEnter {
290 fn run_once(self, cx: &mut js::context::JSContext) {
292 let element = self.element.root();
293 let promise = self.promise.root();
294 let document = element.owner_document();
295
296 if self.document.root() != document ||
305 !element.fullscreen_element_ready_check() ||
306 self.error
307 {
308 document
310 .upcast::<EventTarget>()
311 .fire_event(cx, atom!("fullscreenerror"));
312 promise.reject_error(cx, Error::Type(c"fullscreen is not connected".to_owned()));
313 return;
314 }
315
316 element.set_fullscreen_state(true);
319 document.set_fullscreen_element(Some(&element));
320 document.upcast::<EventTarget>().fire_event_with_params(
321 cx,
322 atom!("fullscreenchange"),
323 EventBubbles::Bubbles,
324 EventCancelable::NotCancelable,
325 EventComposed::Composed,
326 );
327
328 promise.resolve_native(cx, &());
331 }
332}
333
334struct ElementPerformFullscreenExit {
335 element: Trusted<Element>,
336 promise: TrustedPromise,
337}
338
339impl ElementPerformFullscreenExit {
340 fn new(
341 element: Trusted<Element>,
342 promise: TrustedPromise,
343 ) -> Box<ElementPerformFullscreenExit> {
344 Box::new(ElementPerformFullscreenExit { element, promise })
345 }
346}
347
348impl TaskOnce for ElementPerformFullscreenExit {
349 fn run_once(self, cx: &mut js::context::JSContext) {
351 let element = self.element.root();
352 let document = element.owner_document();
353 element.set_fullscreen_state(false);
360 document.set_fullscreen_element(None);
361 document.upcast::<EventTarget>().fire_event_with_params(
362 cx,
363 atom!("fullscreenchange"),
364 EventBubbles::Bubbles,
365 EventCancelable::NotCancelable,
366 EventComposed::Composed,
367 );
368
369 self.promise.root().resolve_native(cx, &());
372 }
373}