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
52 .reject_error_with_cx(cx, Error::Type(c"Document is not fully active".to_owned()));
53 return promise;
54 }
55
56 let mut error = false;
59
60 {
63 match *pending.namespace() {
65 ns!(mathml) => {
66 if pending.local_name().as_ref() != "math" {
67 error = true;
68 }
69 },
70 ns!(svg) => {
71 if pending.local_name().as_ref() != "svg" {
72 error = true;
73 }
74 },
75 ns!(html) => (),
76 _ => error = true,
77 }
78
79 if pending.is::<HTMLDialogElement>() {
81 error = true;
82 }
83
84 if !pending.fullscreen_element_ready_check() {
86 error = true;
87 }
88
89 if !pending.owner_window().has_transient_activation() {
97 error = true;
98 }
99 }
100
101 if pref!(dom_fullscreen_test) {
102 info!("Tests don't really enter fullscreen.");
105 } else {
106 warn!("Fullscreen not supported yet");
109 }
110
111 if !error {
114 pending.owner_window().consume_user_activation();
115 }
116
117 if !error {
123 let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), true);
124 self.send_to_embedder(event);
125 }
126
127 let pipeline_id = self.window().pipeline_id();
130
131 let trusted_pending = Trusted::new(pending);
132 let trusted_pending_doc = Trusted::new(self);
133 let trusted_promise = TrustedPromise::new(promise.clone());
134 let handler = ElementPerformFullscreenEnter::new(
135 trusted_pending,
136 trusted_pending_doc,
137 trusted_promise,
138 error,
139 );
140 let script_msg = CommonScriptMsg::Task(
141 ScriptThreadEventCategory::EnterFullscreen,
142 handler,
143 Some(pipeline_id),
144 TaskSourceName::DOMManipulation,
145 );
146 let msg = MainThreadScriptMsg::Common(script_msg);
147 self.window().main_thread_script_chan().send(msg).unwrap();
148
149 promise
150 }
151
152 pub(crate) fn exit_fullscreen(&self, cx: &mut JSContext) -> Rc<Promise> {
154 let global = self.global();
155
156 let mut realm = CurrentRealm::assert(cx);
159 let promise = Promise::new_in_realm(&mut realm);
160
161 if !self.is_fully_active() || self.fullscreen_element().is_none() {
164 promise.reject_error_with_cx(
165 cx,
166 Error::Type(
167 c"No fullscreen element to exit or document is not fully active".to_owned(),
168 ),
169 );
170 return promise;
171 }
172
173 let element = self.fullscreen_element().unwrap();
176 let window = self.window();
177
178 let event = EmbedderMsg::NotifyFullscreenStateChanged(self.webview_id(), false);
182 self.send_to_embedder(event);
183
184 let trusted_element = Trusted::new(&*element);
187 let trusted_promise = TrustedPromise::new(promise.clone());
188 let handler = ElementPerformFullscreenExit::new(trusted_element, trusted_promise);
189 let pipeline_id = Some(global.pipeline_id());
190 let script_msg = CommonScriptMsg::Task(
191 ScriptThreadEventCategory::ExitFullscreen,
192 handler,
193 pipeline_id,
194 TaskSourceName::DOMManipulation,
195 );
196 let msg = MainThreadScriptMsg::Common(script_msg);
197 window.main_thread_script_chan().send(msg).unwrap();
198
199 promise
200 }
201
202 pub(crate) fn get_allow_fullscreen(&self) -> bool {
203 match self.browsing_context() {
205 None => false,
207 Some(_) => {
208 let window = self.window();
210 if window.is_top_level() {
211 true
212 } else {
213 window
215 .GetFrameElement()
216 .is_some_and(|el| el.has_attribute(&local_name!("allowfullscreen")))
217 }
218 },
219 }
220 }
221}
222
223impl DocumentOrShadowRoot {
224 pub(crate) fn get_fullscreen_element(
226 node: &Node,
227 fullscreen_element: Option<DomRoot<Element>>,
228 ) -> Option<DomRoot<Element>> {
229 if let Some(shadow_root) = node.downcast::<ShadowRoot>() &&
231 !shadow_root.Host().is_connected()
232 {
233 return None;
234 }
235
236 let retargeted = fullscreen_element?
238 .upcast::<EventTarget>()
239 .retarget(node.upcast());
240 let candidate = DomRoot::downcast::<Element>(retargeted).unwrap();
242
243 if *candidate
245 .upcast::<Node>()
246 .GetRootNode(&GetRootNodeOptions::empty()) ==
247 *node
248 {
249 return Some(candidate);
250 }
251
252 None
254 }
255}
256
257impl Element {
258 pub(crate) fn fullscreen_element_ready_check(&self) -> bool {
260 if !self.is_connected() {
261 return false;
262 }
263 self.owner_document().get_allow_fullscreen()
264 }
265}
266
267struct ElementPerformFullscreenEnter {
268 element: Trusted<Element>,
269 document: Trusted<Document>,
270 promise: TrustedPromise,
271 error: bool,
272}
273
274impl ElementPerformFullscreenEnter {
275 fn new(
276 element: Trusted<Element>,
277 document: Trusted<Document>,
278 promise: TrustedPromise,
279 error: bool,
280 ) -> Box<ElementPerformFullscreenEnter> {
281 Box::new(ElementPerformFullscreenEnter {
282 element,
283 document,
284 promise,
285 error,
286 })
287 }
288}
289
290impl TaskOnce for ElementPerformFullscreenEnter {
291 fn run_once(self, cx: &mut js::context::JSContext) {
293 let element = self.element.root();
294 let promise = self.promise.root();
295 let document = element.owner_document();
296
297 if self.document.root() != document ||
306 !element.fullscreen_element_ready_check() ||
307 self.error
308 {
309 document
311 .upcast::<EventTarget>()
312 .fire_event(cx, atom!("fullscreenerror"));
313 promise
314 .reject_error_with_cx(cx, Error::Type(c"fullscreen is not connected".to_owned()));
315 return;
316 }
317
318 element.set_fullscreen_state(true);
321 document.set_fullscreen_element(Some(&element));
322 document.upcast::<EventTarget>().fire_event_with_params(
323 cx,
324 atom!("fullscreenchange"),
325 EventBubbles::Bubbles,
326 EventCancelable::NotCancelable,
327 EventComposed::Composed,
328 );
329
330 promise.resolve_native_with_cx(cx, &());
333 }
334}
335
336struct ElementPerformFullscreenExit {
337 element: Trusted<Element>,
338 promise: TrustedPromise,
339}
340
341impl ElementPerformFullscreenExit {
342 fn new(
343 element: Trusted<Element>,
344 promise: TrustedPromise,
345 ) -> Box<ElementPerformFullscreenExit> {
346 Box::new(ElementPerformFullscreenExit { element, promise })
347 }
348}
349
350impl TaskOnce for ElementPerformFullscreenExit {
351 fn run_once(self, cx: &mut js::context::JSContext) {
353 let element = self.element.root();
354 let document = element.owner_document();
355 element.set_fullscreen_state(false);
362 document.set_fullscreen_element(None);
363 document.upcast::<EventTarget>().fire_event_with_params(
364 cx,
365 atom!("fullscreenchange"),
366 EventBubbles::Bubbles,
367 EventCancelable::NotCancelable,
368 EventComposed::Composed,
369 );
370
371 self.promise.root().resolve_native_with_cx(cx, &());
374 }
375}