mirror of
https://github.com/lexmount/moli.git
synced 2026-10-09 08:01:05 +00:00
fix(fullscreen): expose unsupported request failures
This commit is contained in:
@@ -2718,10 +2718,7 @@ focus/iframe-focus-with-same-as-top-intermediate-frame.html
|
||||
font-access/permissions-policy/local-fonts-supported-by-permissions-policy.html
|
||||
fullscreen/api/document-exit-fullscreen-active-document.html
|
||||
fullscreen/api/document-fullscreen-enabled.html
|
||||
fullscreen/api/document-onfullscreenerror.html
|
||||
fullscreen/api/element-request-fullscreen-active-document.html
|
||||
fullscreen/api/element-request-fullscreen-not-allowed.html
|
||||
fullscreen/api/promises-reject.html
|
||||
gyroscope/Gyroscope-supported-by-permissions-policy.html
|
||||
hr-time/test_cross_frame_start.html
|
||||
html/browsers/browsing-the-web/back-forward-cache/eligibility/dedicated-worker.html
|
||||
|
||||
@@ -5562,7 +5562,10 @@ focus/iframe-focuses-parent-same-site.html
|
||||
focus/scroll-matches-focus.html
|
||||
focus/synthetic-focus-event-crash.html
|
||||
fullscreen/api/document-fullscreen-enabled-active-document.html
|
||||
fullscreen/api/document-onfullscreenerror.html
|
||||
fullscreen/api/element-request-fullscreen-not-allowed.html
|
||||
fullscreen/api/historical.html
|
||||
fullscreen/api/promises-reject.html
|
||||
fullscreen/crashtests/backdrop-list-item.html
|
||||
fullscreen/rendering/fullscreen-pseudo-class-support.html
|
||||
geolocation/non-secure-contexts.http.html
|
||||
|
||||
@@ -120,6 +120,12 @@ struct DocumentPrototypeRuntimeDeclaration {
|
||||
setter = document_fullscreen_enabled_lenient_setter
|
||||
)]
|
||||
fullscreen_enabled: (),
|
||||
#[webapi(
|
||||
method = "exitFullscreen",
|
||||
length = 0,
|
||||
callback = native_bridge::fullscreen::document_exit_fullscreen_callback
|
||||
)]
|
||||
exit_fullscreen: (),
|
||||
#[webapi(
|
||||
accessor_property = "pointerLockElement",
|
||||
getter = native_bridge::pointer_lock::document_pointer_lock_element_getter
|
||||
|
||||
@@ -263,6 +263,7 @@ pub(crate) use details_dialog::{
|
||||
};
|
||||
use event_handlers::install_body_or_frameset_window_event_handler_accessors;
|
||||
use event_handlers::install_global_event_handler_template_bindings as install_global_event_handler_templates_for_owner;
|
||||
use event_handlers::install_node_event_handler_template_bindings;
|
||||
pub(crate) use event_handlers::{
|
||||
EventAttributeHandlerScope, GlobalEventHandlerOwner,
|
||||
body_or_frameset_reflects_window_event_type, canonical_event_handler_event_type,
|
||||
@@ -1481,15 +1482,22 @@ pub(crate) fn install_element_template_bindings<'s>(
|
||||
}
|
||||
|
||||
match interface_name {
|
||||
"Element" => install!(
|
||||
ElementAriaStringReflectionDeclaration,
|
||||
ElementAriaElementReflectionDeclaration,
|
||||
ElementPrototypeReflectionDeclaration,
|
||||
ElementPrototypeQueryAndAttributeMethodsDeclaration,
|
||||
ExtendedElementPrototypeMethodsDeclaration,
|
||||
ElementGeometryPrototypeDeclaration,
|
||||
ElementStylePrototypeDeclaration,
|
||||
),
|
||||
"Element" => {
|
||||
install!(
|
||||
ElementAriaStringReflectionDeclaration,
|
||||
ElementAriaElementReflectionDeclaration,
|
||||
ElementPrototypeReflectionDeclaration,
|
||||
ElementPrototypeQueryAndAttributeMethodsDeclaration,
|
||||
ExtendedElementPrototypeMethodsDeclaration,
|
||||
ElementGeometryPrototypeDeclaration,
|
||||
ElementStylePrototypeDeclaration,
|
||||
);
|
||||
install_node_event_handler_template_bindings(
|
||||
scope,
|
||||
prototype,
|
||||
&["onfullscreenchange", "onfullscreenerror"],
|
||||
);
|
||||
}
|
||||
"Document" => install!(DocumentCustomElementRegistryPrototypeDeclaration),
|
||||
"HTMLElement" => install!(
|
||||
ElementStylePrototypeDeclaration,
|
||||
|
||||
@@ -159,6 +159,13 @@ pub(super) struct ElementPrototypeReflectionDeclaration {
|
||||
callback = super::super::pointer_lock::element_request_pointer_lock_callback
|
||||
)]
|
||||
request_pointer_lock: (),
|
||||
#[webapi(
|
||||
method = "requestFullscreen",
|
||||
length = 0,
|
||||
enumerable,
|
||||
callback = super::fullscreen::element_request_fullscreen_callback
|
||||
)]
|
||||
request_fullscreen: (),
|
||||
#[webapi(accessor_property = "shadowRoot", enumerable, getter = element_shadow_root_getter_function)]
|
||||
shadow_root: (),
|
||||
#[webapi(
|
||||
|
||||
@@ -120,6 +120,8 @@ pub(crate) const GENERIC_EVENT_HANDLER_PROPERTIES: &[&str] = &[
|
||||
|
||||
const DOCUMENT_EVENT_HANDLER_PROPERTIES: &[&str] = &[
|
||||
"onfreeze",
|
||||
"onfullscreenchange",
|
||||
"onfullscreenerror",
|
||||
"onpointerlockchange",
|
||||
"onpointerlockerror",
|
||||
"onreadystatechange",
|
||||
@@ -169,44 +171,68 @@ pub(crate) fn install_global_event_handler_template_bindings<'s>(
|
||||
if matches!(owner, GlobalEventHandlerOwner::Document) && *name == "onstorage" {
|
||||
continue;
|
||||
}
|
||||
let data = v8str(scope, name).into();
|
||||
let (getter, setter) = match owner {
|
||||
GlobalEventHandlerOwner::Document => (
|
||||
v8::FunctionTemplate::builder(document_event_handler_getter_function)
|
||||
.data(data)
|
||||
.length(0)
|
||||
.build(scope),
|
||||
v8::FunctionTemplate::builder(document_event_handler_setter_function)
|
||||
.data(data)
|
||||
.length(1)
|
||||
.build(scope),
|
||||
),
|
||||
GlobalEventHandlerOwner::Element => (
|
||||
v8::FunctionTemplate::builder(node_event_handler_getter_function)
|
||||
.data(data)
|
||||
.length(0)
|
||||
.build(scope),
|
||||
v8::FunctionTemplate::builder(node_event_handler_setter_function)
|
||||
.data(data)
|
||||
.length(1)
|
||||
.build(scope),
|
||||
),
|
||||
};
|
||||
if let Some(function_name) = v8_string(scope, &format!("get {name}")) {
|
||||
getter.set_class_name(function_name);
|
||||
}
|
||||
if let Some(function_name) = v8_string(scope, &format!("set {name}")) {
|
||||
setter.set_class_name(function_name);
|
||||
}
|
||||
prototype.set_accessor_property(
|
||||
v8str(scope, name).into(),
|
||||
Some(getter),
|
||||
Some(setter),
|
||||
v8::PropertyAttribute::NONE,
|
||||
install_event_handler_template_binding(scope, prototype, owner, name);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn install_node_event_handler_template_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
prototype: v8::Local<'s, v8::ObjectTemplate>,
|
||||
names: &[&'static str],
|
||||
) {
|
||||
for name in names {
|
||||
install_event_handler_template_binding(
|
||||
scope,
|
||||
prototype,
|
||||
GlobalEventHandlerOwner::Element,
|
||||
name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn install_event_handler_template_binding<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
prototype: v8::Local<'s, v8::ObjectTemplate>,
|
||||
owner: GlobalEventHandlerOwner,
|
||||
name: &'static str,
|
||||
) {
|
||||
let data = v8str(scope, name).into();
|
||||
let (getter, setter) = match owner {
|
||||
GlobalEventHandlerOwner::Document => (
|
||||
v8::FunctionTemplate::builder(document_event_handler_getter_function)
|
||||
.data(data)
|
||||
.length(0)
|
||||
.build(scope),
|
||||
v8::FunctionTemplate::builder(document_event_handler_setter_function)
|
||||
.data(data)
|
||||
.length(1)
|
||||
.build(scope),
|
||||
),
|
||||
GlobalEventHandlerOwner::Element => (
|
||||
v8::FunctionTemplate::builder(node_event_handler_getter_function)
|
||||
.data(data)
|
||||
.length(0)
|
||||
.build(scope),
|
||||
v8::FunctionTemplate::builder(node_event_handler_setter_function)
|
||||
.data(data)
|
||||
.length(1)
|
||||
.build(scope),
|
||||
),
|
||||
};
|
||||
if let Some(function_name) = v8_string(scope, &format!("get {name}")) {
|
||||
getter.set_class_name(function_name);
|
||||
}
|
||||
if let Some(function_name) = v8_string(scope, &format!("set {name}")) {
|
||||
setter.set_class_name(function_name);
|
||||
}
|
||||
prototype.set_accessor_property(
|
||||
v8str(scope, name).into(),
|
||||
Some(getter),
|
||||
Some(setter),
|
||||
v8::PropertyAttribute::NONE,
|
||||
);
|
||||
}
|
||||
|
||||
fn event_handler_property_value_for_target<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
runtime_ptr: *mut super::super::super::JsContextHost,
|
||||
@@ -328,7 +354,9 @@ pub(crate) fn node_event_handler_getter_function<'s>(
|
||||
rv.set(current);
|
||||
return;
|
||||
}
|
||||
if !handler_name.starts_with("on") {
|
||||
if !handler_name.starts_with("on")
|
||||
|| !is_element_event_handler_content_attribute_name(&handler_name)
|
||||
{
|
||||
rv.set_null();
|
||||
return;
|
||||
}
|
||||
@@ -562,4 +590,3 @@ fn handle_invalid_event_handler_receiver<'s>(
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ pub(super) use generic::is_element_event_handler_content_attribute_name;
|
||||
pub(crate) use generic::{
|
||||
GlobalEventHandlerOwner, canonical_event_handler_event_type,
|
||||
event_handler_content_attribute_name, install_global_event_handler_template_bindings,
|
||||
node_event_handler_getter_function, node_event_handler_setter_function,
|
||||
install_node_event_handler_template_bindings, node_event_handler_getter_function,
|
||||
node_event_handler_setter_function,
|
||||
};
|
||||
pub(crate) use shared::{EventAttributeHandlerScope, compile_event_attribute_handler_for_owner};
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
use super::{
|
||||
JsContextHost,
|
||||
element::{construct_simple_event, dispatch_public_event},
|
||||
node::{
|
||||
node_is_document, node_runtime_and_handle_from_args_or_detached,
|
||||
node_runtime_and_handle_from_object_or_detached, require_element_method_receiver,
|
||||
throw_incompatible_method_receiver,
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
document_runtime::DomHandle,
|
||||
host::HostTimerOwner,
|
||||
util::{context_host_ptr_from_global_bridge, v8_string},
|
||||
webidl,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Default, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "FullscreenKeyboardLock")]
|
||||
enum FullscreenKeyboardLock {
|
||||
#[webidl(token = "browser")]
|
||||
Browser,
|
||||
#[default]
|
||||
#[webidl(token = "none")]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "FullscreenNavigationUI")]
|
||||
enum FullscreenNavigationUi {
|
||||
#[default]
|
||||
#[webidl(token = "auto")]
|
||||
Auto,
|
||||
#[webidl(token = "show")]
|
||||
Show,
|
||||
#[webidl(token = "hide")]
|
||||
Hide,
|
||||
}
|
||||
|
||||
#[derive(Default, webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "FullscreenOptions")]
|
||||
struct FullscreenOptions {
|
||||
#[webidl(
|
||||
name = "keyboardLock",
|
||||
converter = "enum",
|
||||
default = FullscreenKeyboardLock::None
|
||||
)]
|
||||
keyboard_lock: FullscreenKeyboardLock,
|
||||
#[webidl(
|
||||
name = "navigationUI",
|
||||
converter = "enum",
|
||||
default = FullscreenNavigationUi::Auto
|
||||
)]
|
||||
navigation_ui: FullscreenNavigationUi,
|
||||
}
|
||||
|
||||
pub(crate) fn element_request_fullscreen_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Ok((runtime_ptr, element)) = node_runtime_and_handle_from_args_or_detached(scope, &args)
|
||||
else {
|
||||
throw_incompatible_method_receiver(scope, "Element", "requestFullscreen");
|
||||
return;
|
||||
};
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
if !require_element_method_receiver(scope, runtime, element, "requestFullscreen") {
|
||||
return;
|
||||
}
|
||||
let Some(resolver) = v8::PromiseResolver::new(scope) else {
|
||||
return;
|
||||
};
|
||||
rv.set(resolver.get_promise(scope).into());
|
||||
|
||||
let options = match fullscreen_options_for_promise(scope, &args) {
|
||||
Ok(options) => options,
|
||||
Err(reason) => {
|
||||
let _ = resolver.reject(scope, reason);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// Dictionary conversion is observable even though this headless platform
|
||||
// deliberately reports that fullscreen is unsupported.
|
||||
let _ = (options.keyboard_lock, options.navigation_ui);
|
||||
|
||||
let active_document = runtime.document_handle();
|
||||
let Some(owner_document) = runtime.dom_host().owner_document_handle(element) else {
|
||||
reject_fullscreen_promise(scope, resolver, "Element has no node document.");
|
||||
return;
|
||||
};
|
||||
if owner_document != active_document {
|
||||
reject_fullscreen_promise(scope, resolver, "Document is not fully active.");
|
||||
return;
|
||||
}
|
||||
|
||||
queue_fullscreen_error_event(scope, runtime_ptr, element, owner_document);
|
||||
reject_fullscreen_promise(scope, resolver, "Fullscreen is not supported.");
|
||||
}
|
||||
|
||||
pub(crate) fn document_exit_fullscreen_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Ok((runtime_ptr, document)) =
|
||||
node_runtime_and_handle_from_object_or_detached(scope, args.this())
|
||||
else {
|
||||
throw_incompatible_method_receiver(scope, "Document", "exitFullscreen");
|
||||
return;
|
||||
};
|
||||
if !node_is_document(unsafe { &*runtime_ptr }, document) {
|
||||
throw_incompatible_method_receiver(scope, "Document", "exitFullscreen");
|
||||
return;
|
||||
}
|
||||
let Some(resolver) = v8::PromiseResolver::new(scope) else {
|
||||
return;
|
||||
};
|
||||
rv.set(resolver.get_promise(scope).into());
|
||||
reject_fullscreen_promise(scope, resolver, "Document is not in fullscreen.");
|
||||
}
|
||||
|
||||
fn fullscreen_options<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
) -> Result<FullscreenOptions, webidl::WebIdlError> {
|
||||
let context = webidl::Context::argument("Element.requestFullscreen", 1);
|
||||
webidl::dictionary_arg(args, 0, context)?
|
||||
.map(|object| webidl::parse_dictionary_object(scope, object))
|
||||
.transpose()
|
||||
.map(|options| options.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn fullscreen_options_for_promise<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
) -> Result<FullscreenOptions, v8::Local<'s, v8::Value>> {
|
||||
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
||||
let mut scope = try_catch.init();
|
||||
match fullscreen_options(&mut scope, args) {
|
||||
Ok(options) => Ok(options),
|
||||
Err(error) if error.is_pending_exception() => Err(scope
|
||||
.exception()
|
||||
.unwrap_or_else(|| v8::undefined(&scope).into())),
|
||||
Err(error) => {
|
||||
let message =
|
||||
v8_string(&scope, &error.to_string()).unwrap_or_else(|| v8::String::empty(&scope));
|
||||
Err(v8::Exception::type_error(&scope, message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_fullscreen_error_event(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
element: DomHandle,
|
||||
document: DomHandle,
|
||||
) {
|
||||
let data = v8::Array::new(scope, 2);
|
||||
let element = v8::BigInt::new_from_u64(scope, element.index() as u64);
|
||||
let document = v8::BigInt::new_from_u64(scope, document.index() as u64);
|
||||
if data.set_index(scope, 0, element.into()) != Some(true)
|
||||
|| data.set_index(scope, 1, document.into()) != Some(true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(callback) = v8::Function::builder(queued_fullscreen_error_event_callback)
|
||||
.data(data.into())
|
||||
.build(scope)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let _ = unsafe { &mut *runtime_ptr }.queue_timeout(
|
||||
scope,
|
||||
callback,
|
||||
0,
|
||||
HostTimerOwner::Window,
|
||||
Vec::new(),
|
||||
);
|
||||
}
|
||||
|
||||
fn queued_fullscreen_error_event_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(runtime_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
return;
|
||||
};
|
||||
let Some((element, document)) = fullscreen_error_event_task_data(scope, args.data()) else {
|
||||
return;
|
||||
};
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
let target = if runtime.dom_host().owner_document_handle(element) == Some(document)
|
||||
&& runtime.dom_host().is_connected_to_document(element)
|
||||
{
|
||||
element
|
||||
} else {
|
||||
document
|
||||
};
|
||||
if let Some(event) = construct_simple_event(scope, "fullscreenerror", true, false, true) {
|
||||
let _ = dispatch_public_event(scope, runtime_ptr, target, event);
|
||||
}
|
||||
}
|
||||
|
||||
fn fullscreen_error_event_task_data(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
value: v8::Local<'_, v8::Value>,
|
||||
) -> Option<(DomHandle, DomHandle)> {
|
||||
let data = v8::Local::<v8::Array>::try_from(value).ok()?;
|
||||
Some((
|
||||
dom_handle_from_value(data.get_index(scope, 0)?)?,
|
||||
dom_handle_from_value(data.get_index(scope, 1)?)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn reject_fullscreen_promise(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
resolver: v8::Local<'_, v8::PromiseResolver>,
|
||||
message: &str,
|
||||
) {
|
||||
let error = v8_string(scope, message)
|
||||
.map(|message| v8::Exception::type_error(scope, message))
|
||||
.unwrap_or_else(|| v8::undefined(scope).into());
|
||||
let _ = resolver.reject(scope, error);
|
||||
}
|
||||
|
||||
fn dom_handle_from_value(value: v8::Local<'_, v8::Value>) -> Option<DomHandle> {
|
||||
let value = v8::Local::<v8::BigInt>::try_from(value).ok()?;
|
||||
let (index, lossless) = value.u64_value();
|
||||
lossless.then(|| DomHandle::new(index as usize))
|
||||
}
|
||||
@@ -15,6 +15,7 @@ pub(crate) use current_input::{
|
||||
};
|
||||
pub(crate) mod document;
|
||||
pub(crate) mod element;
|
||||
pub(crate) mod fullscreen;
|
||||
mod helpers;
|
||||
mod history_queue;
|
||||
pub(crate) mod history_traversal;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn unsupported_fullscreen_requests_reject_and_queue_spec_shaped_error_events() {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://fullscreen-unsupported.test/",
|
||||
"<!doctype html><html><body><div></div></body></html>",
|
||||
);
|
||||
|
||||
let before = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
globalThis.__fullscreenEvents = [];
|
||||
globalThis.__fullscreenRejections = [];
|
||||
globalThis.__fullscreenConversionRejections = [];
|
||||
globalThis.__fullscreenOptionReads = [];
|
||||
document.body.setAttribute("onfullscreenerror", "globalThis.__fullscreenAttributeRan = true");
|
||||
const contentAttributeIgnored = document.body.onfullscreenerror === null;
|
||||
document.onfullscreenerror = event => {
|
||||
__fullscreenEvents.push([
|
||||
event.type,
|
||||
event.target === document.body,
|
||||
event.bubbles,
|
||||
event.cancelable,
|
||||
event.composed
|
||||
].join(":"));
|
||||
};
|
||||
const options = {
|
||||
get keyboardLock() {
|
||||
__fullscreenOptionReads.push("keyboardLock");
|
||||
return "none";
|
||||
},
|
||||
get navigationUI() {
|
||||
__fullscreenOptionReads.push("navigationUI");
|
||||
return "hide";
|
||||
}
|
||||
};
|
||||
const request = document.body.requestFullscreen(options);
|
||||
const exit = document.exitFullscreen();
|
||||
request.catch(error => __fullscreenRejections.push(`request:${error.name}`));
|
||||
exit.catch(error => __fullscreenRejections.push(`exit:${error.name}`));
|
||||
const getterError = new RangeError("navigationUI getter failed");
|
||||
const conversionInputs = [
|
||||
["enum", { navigationUI: "invalid-value" }, null],
|
||||
["string", "foo", null],
|
||||
["number", 123, null],
|
||||
["getter", {
|
||||
get navigationUI() {
|
||||
throw getterError;
|
||||
}
|
||||
}, getterError]
|
||||
];
|
||||
const conversionPromiseShapes = conversionInputs.map(([label, input, expected]) => {
|
||||
try {
|
||||
const promise = document.body.requestFullscreen(input);
|
||||
promise.catch(error => __fullscreenConversionRejections.push(
|
||||
`${label}:${error.name}:${expected === null || error === expected}`
|
||||
));
|
||||
return promise instanceof Promise;
|
||||
} catch (error) {
|
||||
return `throw:${error.name}`;
|
||||
}
|
||||
});
|
||||
const outcome = callback => {
|
||||
try {
|
||||
callback();
|
||||
return "return";
|
||||
} catch (error) {
|
||||
return error.name;
|
||||
}
|
||||
};
|
||||
return JSON.stringify({
|
||||
requestShape: [
|
||||
typeof Element.prototype.requestFullscreen,
|
||||
Element.prototype.requestFullscreen.length,
|
||||
Object.hasOwn(Element.prototype, "onfullscreenchange"),
|
||||
Object.hasOwn(Element.prototype, "onfullscreenerror")
|
||||
],
|
||||
documentShape: [
|
||||
typeof Document.prototype.exitFullscreen,
|
||||
Document.prototype.exitFullscreen.length,
|
||||
Object.hasOwn(Document.prototype, "onfullscreenchange"),
|
||||
Object.hasOwn(Document.prototype, "onfullscreenerror"),
|
||||
document.fullscreenEnabled
|
||||
],
|
||||
promises: request instanceof Promise && exit instanceof Promise,
|
||||
conversionPromiseShapes,
|
||||
contentAttributeIgnored,
|
||||
optionReads: __fullscreenOptionReads,
|
||||
wrongReceivers: [
|
||||
outcome(() => Element.prototype.requestFullscreen.call({})),
|
||||
outcome(() => Document.prototype.exitFullscreen.call({}))
|
||||
],
|
||||
events: __fullscreenEvents.length
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("fullscreen rejection setup should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
before,
|
||||
r#"{"requestShape":["function",0,true,true],"documentShape":["function",0,true,true,false],"promises":true,"conversionPromiseShapes":[true,true,true,true],"contentAttributeIgnored":true,"optionReads":["keyboardLock","navigationUI"],"wrongReceivers":["TypeError","TypeError"],"events":0}"#,
|
||||
);
|
||||
|
||||
vm.advance_timers_until_deadline_for_test(&loader)
|
||||
.await
|
||||
.expect("fullscreen error task should drain");
|
||||
|
||||
let after = vm
|
||||
.eval(
|
||||
r#"
|
||||
JSON.stringify({
|
||||
events: __fullscreenEvents,
|
||||
rejections: __fullscreenRejections.sort(),
|
||||
conversionRejections: __fullscreenConversionRejections.sort(),
|
||||
contentAttributeRan: globalThis.__fullscreenAttributeRan === true
|
||||
})
|
||||
"#,
|
||||
)
|
||||
.expect("fullscreen rejection result should evaluate");
|
||||
assert_eq!(
|
||||
after,
|
||||
r#"{"events":["fullscreenerror:true:true:false:true"],"rejections":["exit:TypeError","request:TypeError"],"conversionRejections":["enum:TypeError:true","getter:RangeError:true","number:TypeError:true","string:TypeError:true"],"contentAttributeRan":false}"#,
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ mod event_handlers;
|
||||
mod event_listener_options;
|
||||
mod events_selection_storage;
|
||||
mod fontface_descriptors;
|
||||
mod fullscreen;
|
||||
mod gamepad;
|
||||
mod history_worlds;
|
||||
mod ice_candidate;
|
||||
|
||||
Reference in New Issue
Block a user