mirror of
https://github.com/lexmount/moli.git
synced 2026-10-03 08:00:49 +00:00
fix(renderer): complete tabular element reflection
This commit is contained in:
@@ -353,7 +353,7 @@ async fn html_element_reflected_accessors_cover_simple_tag_specific_surface() ->
|
||||
page.serialize_html_async()
|
||||
.await
|
||||
.unwrap()
|
||||
.contains("data-table-cell=\"1:1:1000:1000:0:0:65534:65534\"")
|
||||
.contains("data-table-cell=\"1:1:1000:2000:0:0:65534:70000\"")
|
||||
);
|
||||
assert!(
|
||||
page.serialize_html_async()
|
||||
|
||||
@@ -453,6 +453,7 @@ pub(super) use global_attributes::{
|
||||
node_tab_index_setter_function, node_title_getter_function, node_title_setter_function,
|
||||
node_translate_getter_function, node_translate_setter_function,
|
||||
node_writing_suggestions_getter_function, node_writing_suggestions_setter_function,
|
||||
null_to_empty_dom_string_reflection_getter_function,
|
||||
null_to_empty_dom_string_reflection_setter_function, object_archive_getter_function,
|
||||
object_code_base_getter_function, object_code_getter_function,
|
||||
object_code_type_getter_function, object_data_getter_function, object_declare_getter_function,
|
||||
@@ -462,8 +463,8 @@ pub(super) use global_attributes::{
|
||||
table_cell_axis_getter_function, table_cell_headers_getter_function,
|
||||
table_cell_no_wrap_getter_function, table_cell_no_wrap_setter_function,
|
||||
table_cell_scope_getter_function, table_ch_getter_function, table_ch_off_getter_function,
|
||||
table_col_span_getter_function, table_col_span_setter_function, table_v_align_getter_function,
|
||||
unsigned_long_reflection_setter_function, usv_string_reflection_setter_function,
|
||||
table_v_align_getter_function, unsigned_long_reflection_setter_function,
|
||||
usv_string_reflection_setter_function,
|
||||
};
|
||||
pub(in crate::native_bridge) const BODY_LEGACY_PROTOTYPE_ACCESSORS: &[&str] = &[
|
||||
"onload",
|
||||
@@ -487,19 +488,19 @@ use html_elements::{
|
||||
table_caption_getter_function, table_caption_setter_function,
|
||||
table_cell_col_span_getter_function, table_cell_col_span_setter_function,
|
||||
table_cell_index_getter_function, table_cell_row_span_getter_function,
|
||||
table_cell_row_span_setter_function, table_create_caption_callback,
|
||||
table_create_t_body_callback, table_create_t_foot_callback, table_create_t_head_callback,
|
||||
table_delete_caption_callback, table_delete_row_callback, table_delete_t_foot_callback,
|
||||
table_delete_t_head_callback, table_insert_row_callback, table_row_cells_getter_function,
|
||||
table_row_delete_cell_callback, table_row_index_getter_function,
|
||||
table_row_insert_cell_callback, table_rows_getter_function, table_section_delete_row_callback,
|
||||
table_section_insert_row_callback, table_section_row_index_getter_function,
|
||||
table_section_rows_getter_function, table_t_bodies_getter_function,
|
||||
table_t_foot_getter_function, table_t_foot_setter_function, table_t_head_getter_function,
|
||||
table_t_head_setter_function, track_default_getter_function, track_default_setter_function,
|
||||
track_kind_getter_function, track_kind_setter_function, track_ready_state_getter_function,
|
||||
track_src_getter_function, track_src_setter_function, track_srclang_getter_function,
|
||||
track_srclang_setter_function,
|
||||
table_cell_row_span_setter_function, table_col_span_getter_function,
|
||||
table_col_span_setter_function, table_create_caption_callback, table_create_t_body_callback,
|
||||
table_create_t_foot_callback, table_create_t_head_callback, table_delete_caption_callback,
|
||||
table_delete_row_callback, table_delete_t_foot_callback, table_delete_t_head_callback,
|
||||
table_insert_row_callback, table_row_cells_getter_function, table_row_delete_cell_callback,
|
||||
table_row_index_getter_function, table_row_insert_cell_callback, table_rows_getter_function,
|
||||
table_section_delete_row_callback, table_section_insert_row_callback,
|
||||
table_section_row_index_getter_function, table_section_rows_getter_function,
|
||||
table_t_bodies_getter_function, table_t_foot_getter_function, table_t_foot_setter_function,
|
||||
table_t_head_getter_function, table_t_head_setter_function, track_default_getter_function,
|
||||
track_default_setter_function, track_kind_getter_function, track_kind_setter_function,
|
||||
track_ready_state_getter_function, track_src_getter_function, track_src_setter_function,
|
||||
track_srclang_getter_function, track_srclang_setter_function,
|
||||
};
|
||||
use html_elements::{
|
||||
marquee_loop_getter_function, marquee_loop_setter_function,
|
||||
@@ -1940,6 +1941,46 @@ struct HtmlTableElementPrototypeDeclaration {
|
||||
setter_data = DomStringReflection::TableBorder
|
||||
)]
|
||||
border: (),
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
enumerable,
|
||||
getter = dom_string_reflection_getter_function,
|
||||
setter = dom_string_reflection_setter_function,
|
||||
data = DomStringReflection::TableFrame
|
||||
)]
|
||||
frame: (),
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
enumerable,
|
||||
getter = dom_string_reflection_getter_function,
|
||||
setter = dom_string_reflection_setter_function,
|
||||
data = DomStringReflection::TableRules
|
||||
)]
|
||||
rules: (),
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
enumerable,
|
||||
getter = dom_string_reflection_getter_function,
|
||||
setter = dom_string_reflection_setter_function,
|
||||
data = DomStringReflection::TableSummary
|
||||
)]
|
||||
summary: (),
|
||||
#[webapi(
|
||||
accessor_property = "cellPadding",
|
||||
enumerable,
|
||||
getter = null_to_empty_dom_string_reflection_getter_function,
|
||||
setter = null_to_empty_dom_string_reflection_setter_function,
|
||||
data = NullToEmptyDomStringReflection::TableCellPadding
|
||||
)]
|
||||
cell_padding: (),
|
||||
#[webapi(
|
||||
accessor_property = "cellSpacing",
|
||||
enumerable,
|
||||
getter = null_to_empty_dom_string_reflection_getter_function,
|
||||
setter = null_to_empty_dom_string_reflection_setter_function,
|
||||
data = NullToEmptyDomStringReflection::TableCellSpacing
|
||||
)]
|
||||
cell_spacing: (),
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
enumerable,
|
||||
|
||||
@@ -687,32 +687,6 @@ fn set_html_unsigned_long_attribute_for_receiver<'s>(
|
||||
set_reflected_attribute(scope, runtime_ptr, handle, content_attr, &value.to_string());
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn table_col_span_getter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
html_unsigned_long_attribute_getter_for_receiver(scope, args.this(), rv, "span", 1, |value| {
|
||||
if value == 0 { 1 } else { value.min(1000) }
|
||||
});
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn table_col_span_setter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
set_html_unsigned_long_attribute_for_receiver(
|
||||
scope,
|
||||
args.this(),
|
||||
"span",
|
||||
args.get(0),
|
||||
"HTMLTableColElement",
|
||||
"span",
|
||||
);
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn html_download_getter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -1110,6 +1084,31 @@ fn set_dom_string_treat_null_as_empty_on_object<'s>(
|
||||
set_reflected_attribute(scope, runtime_ptr, handle, attribute, &value);
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn null_to_empty_dom_string_reflection_getter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Some(descriptor) =
|
||||
NullToEmptyDomStringReflection::descriptor_from_callback_data(scope, args.data())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if let Some(local_name) = descriptor.local_name
|
||||
&& html_element_getter_receiver(
|
||||
scope,
|
||||
args.this(),
|
||||
descriptor.interface,
|
||||
descriptor.member,
|
||||
local_name,
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
attribute_property_getter_from_object_or_detached(scope, args.this(), descriptor.attribute, rv);
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge) fn null_to_empty_dom_string_reflection_setter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -1118,6 +1117,18 @@ pub(in crate::native_bridge) fn null_to_empty_dom_string_reflection_setter_funct
|
||||
if let Some(descriptor) =
|
||||
NullToEmptyDomStringReflection::descriptor_from_callback_data(scope, args.data())
|
||||
{
|
||||
if let Some(local_name) = descriptor.local_name
|
||||
&& html_element_setter_receiver(
|
||||
scope,
|
||||
args.this(),
|
||||
descriptor.interface,
|
||||
descriptor.member,
|
||||
local_name,
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
set_dom_string_treat_null_as_empty_on_object(
|
||||
scope,
|
||||
args.this(),
|
||||
@@ -1521,20 +1532,27 @@ fn canonical_scope_value(raw: &str) -> &'static str {
|
||||
// ---------- Unsigned-long content attribute reflections ----------
|
||||
//
|
||||
// Each parses the content attribute via the HTML "non-negative integer
|
||||
// parsing rules" (leading whitespace skipped, leading digits consumed) and
|
||||
// returns the parsed value (clamped to [0, u32::MAX] then narrowed to i32 for
|
||||
// the IDL `unsigned long`). When the attribute is missing or unparseable the
|
||||
// attribute-specific default applies.
|
||||
// parsing rules" (leading whitespace skipped, leading digits consumed).
|
||||
// Attribute-specific reflection code decides whether to apply the signed
|
||||
// 31-bit boundary or a narrower getter clamp.
|
||||
|
||||
fn parse_non_negative_integer(value: &str) -> Option<u32> {
|
||||
pub(in crate::native_bridge::element) fn parse_non_negative_integer(value: &str) -> Option<u32> {
|
||||
// Per HTML "rules for parsing non-negative integers": skip leading ASCII
|
||||
// whitespace, then consume leading ASCII digits. Out-of-range results
|
||||
// saturate at u32::MAX rather than falling back to the attribute default
|
||||
// — the spec's reflection algorithm clamps to the unsigned-long range.
|
||||
let mut chars = value.chars().skip_while(|ch| ch.is_ascii_whitespace());
|
||||
if matches!(chars.clone().next(), Some('+')) {
|
||||
chars.next();
|
||||
}
|
||||
let negative = match chars.clone().next() {
|
||||
Some('+') => {
|
||||
chars.next();
|
||||
false
|
||||
}
|
||||
Some('-') => {
|
||||
chars.next();
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let mut acc: u64 = 0;
|
||||
let mut had_digit = false;
|
||||
for ch in chars.by_ref() {
|
||||
@@ -1548,7 +1566,11 @@ fn parse_non_negative_integer(value: &str) -> Option<u32> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if had_digit { Some(acc as u32) } else { None }
|
||||
if !had_digit || (negative && acc != 0) {
|
||||
None
|
||||
} else {
|
||||
Some(acc as u32)
|
||||
}
|
||||
}
|
||||
|
||||
fn unsigned_long_attribute_getter_from_object_or_detached<'s, F>(
|
||||
@@ -2553,7 +2575,18 @@ fn parse_tab_index_attribute(value: &str) -> Option<i32> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{canonical_dir_value, parse_tab_index_attribute};
|
||||
use super::{canonical_dir_value, parse_non_negative_integer, parse_tab_index_attribute};
|
||||
|
||||
#[test]
|
||||
fn parses_html_non_negative_integer_including_minus_zero() {
|
||||
assert_eq!(parse_non_negative_integer(""), None);
|
||||
assert_eq!(parse_non_negative_integer("\u{b}7"), None);
|
||||
assert_eq!(parse_non_negative_integer(" +7tail"), Some(7));
|
||||
assert_eq!(parse_non_negative_integer("-0"), Some(0));
|
||||
assert_eq!(parse_non_negative_integer("-00tail"), Some(0));
|
||||
assert_eq!(parse_non_negative_integer("-1"), None);
|
||||
assert_eq!(parse_non_negative_integer("4294967296"), Some(u32::MAX));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_tab_index_attribute_like_html_signed_integer() {
|
||||
|
||||
@@ -19,7 +19,8 @@ use super::super::super::{
|
||||
throw_dom_exception,
|
||||
};
|
||||
use super::super::set_reflected_attribute;
|
||||
use super::{DomHandle, JsContextHost, parse_i32_attribute_or};
|
||||
use super::super::{element_attribute, global_attributes::parse_non_negative_integer};
|
||||
use super::{DomHandle, JsContextHost};
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "HTMLTableElement.insertRow")]
|
||||
@@ -85,6 +86,7 @@ enum TableReceiverKind {
|
||||
Table,
|
||||
Section,
|
||||
Row,
|
||||
Col,
|
||||
Cell,
|
||||
}
|
||||
|
||||
@@ -97,6 +99,9 @@ fn table_receiver_matches(
|
||||
TableReceiverKind::Table => runtime.dom_host().is_html_element_named(handle, "table"),
|
||||
TableReceiverKind::Section => is_html_table_section(runtime, handle),
|
||||
TableReceiverKind::Row => runtime.dom_host().is_html_element_named(handle, "tr"),
|
||||
TableReceiverKind::Col => ["col", "colgroup"]
|
||||
.into_iter()
|
||||
.any(|name| runtime.dom_host().is_html_element_named(handle, name)),
|
||||
TableReceiverKind::Cell => is_html_table_cell(runtime, handle),
|
||||
}
|
||||
}
|
||||
@@ -273,20 +278,83 @@ fn html_table_cell_setter_receiver<'s>(
|
||||
)
|
||||
}
|
||||
|
||||
fn clamp_col_span(value: i32) -> i32 {
|
||||
value.clamp(1, 1000)
|
||||
fn html_table_col_getter_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
member: &'static str,
|
||||
) -> Option<(*mut JsContextHost, DomHandle)> {
|
||||
table_getter_receiver(
|
||||
scope,
|
||||
receiver,
|
||||
"HTMLTableColElement",
|
||||
member,
|
||||
TableReceiverKind::Col,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_col_span(runtime: &JsContextHost, handle: DomHandle) -> i32 {
|
||||
clamp_col_span(parse_i32_attribute_or(runtime, handle, "colspan", 1))
|
||||
fn html_table_col_setter_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
member: &'static str,
|
||||
) -> Option<(*mut JsContextHost, DomHandle)> {
|
||||
table_setter_receiver(
|
||||
scope,
|
||||
receiver,
|
||||
"HTMLTableColElement",
|
||||
member,
|
||||
TableReceiverKind::Col,
|
||||
)
|
||||
}
|
||||
|
||||
fn clamp_row_span(value: i32) -> i32 {
|
||||
if value == 0 { 0 } else { value.clamp(1, 65534) }
|
||||
fn parse_clamped_table_span(
|
||||
runtime: &JsContextHost,
|
||||
handle: DomHandle,
|
||||
attribute: &str,
|
||||
default: u32,
|
||||
min: u32,
|
||||
max: u32,
|
||||
) -> u32 {
|
||||
let Some(raw) = element_attribute(runtime, handle, attribute) else {
|
||||
return default;
|
||||
};
|
||||
parse_non_negative_integer(&raw)
|
||||
.map(|value| value.clamp(min, max))
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn parse_row_span(runtime: &JsContextHost, handle: DomHandle) -> i32 {
|
||||
clamp_row_span(parse_i32_attribute_or(runtime, handle, "rowspan", 1))
|
||||
fn set_table_unsigned_long_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
runtime_ptr: *mut JsContextHost,
|
||||
handle: DomHandle,
|
||||
attribute: &str,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
default: u32,
|
||||
interface: &'static str,
|
||||
member: &'static str,
|
||||
) {
|
||||
let value = match webidl::convert::<webidl::UnsignedLong>(
|
||||
scope,
|
||||
value,
|
||||
webidl::Context::member(interface, member),
|
||||
) {
|
||||
Ok(value) => value.0,
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let reflected = if value <= i32::MAX as u32 {
|
||||
value
|
||||
} else {
|
||||
default
|
||||
};
|
||||
set_reflected_attribute(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
attribute,
|
||||
&reflected.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::element) fn table_cell_col_span_getter_function<'s>(
|
||||
@@ -297,18 +365,40 @@ pub(in crate::native_bridge::element) fn table_cell_col_span_getter_function<'s>
|
||||
let Some((runtime_ptr, handle)) =
|
||||
html_table_cell_getter_receiver(scope, args.this(), "colSpan")
|
||||
else {
|
||||
rv.set_int32(1);
|
||||
rv.set_uint32(1);
|
||||
return;
|
||||
};
|
||||
rv.set_int32(parse_col_span(unsafe { &*runtime_ptr }, handle));
|
||||
rv.set_uint32(parse_clamped_table_span(
|
||||
unsafe { &*runtime_ptr },
|
||||
handle,
|
||||
"colspan",
|
||||
1,
|
||||
1,
|
||||
1000,
|
||||
));
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::element) fn table_cell_col_span_setter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'s, v8::Value>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
table_cell_i32_attribute_setter(scope, args.this(), "colspan", args.get(0), 1, "colSpan", rv);
|
||||
let Some((runtime_ptr, handle)) =
|
||||
html_table_cell_setter_receiver(scope, args.this(), "colSpan")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
set_table_unsigned_long_attribute(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
"colspan",
|
||||
args.get(0),
|
||||
1,
|
||||
"HTMLTableCellElement",
|
||||
"colSpan",
|
||||
);
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::element) fn table_cell_row_span_getter_function<'s>(
|
||||
@@ -319,18 +409,82 @@ pub(in crate::native_bridge::element) fn table_cell_row_span_getter_function<'s>
|
||||
let Some((runtime_ptr, handle)) =
|
||||
html_table_cell_getter_receiver(scope, args.this(), "rowSpan")
|
||||
else {
|
||||
rv.set_int32(1);
|
||||
rv.set_uint32(1);
|
||||
return;
|
||||
};
|
||||
rv.set_int32(parse_row_span(unsafe { &*runtime_ptr }, handle));
|
||||
rv.set_uint32(parse_clamped_table_span(
|
||||
unsafe { &*runtime_ptr },
|
||||
handle,
|
||||
"rowspan",
|
||||
1,
|
||||
0,
|
||||
65534,
|
||||
));
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::element) fn table_cell_row_span_setter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'s, v8::Value>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
table_cell_i32_attribute_setter(scope, args.this(), "rowspan", args.get(0), 1, "rowSpan", rv);
|
||||
let Some((runtime_ptr, handle)) =
|
||||
html_table_cell_setter_receiver(scope, args.this(), "rowSpan")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
set_table_unsigned_long_attribute(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
"rowspan",
|
||||
args.get(0),
|
||||
1,
|
||||
"HTMLTableCellElement",
|
||||
"rowSpan",
|
||||
);
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::element) fn table_col_span_getter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Some((runtime_ptr, handle)) = html_table_col_getter_receiver(scope, args.this(), "span")
|
||||
else {
|
||||
rv.set_uint32(1);
|
||||
return;
|
||||
};
|
||||
rv.set_uint32(parse_clamped_table_span(
|
||||
unsafe { &*runtime_ptr },
|
||||
handle,
|
||||
"span",
|
||||
1,
|
||||
1,
|
||||
1000,
|
||||
));
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::element) fn table_col_span_setter_function<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Some((runtime_ptr, handle)) = html_table_col_setter_receiver(scope, args.this(), "span")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
set_table_unsigned_long_attribute(
|
||||
scope,
|
||||
runtime_ptr,
|
||||
handle,
|
||||
"span",
|
||||
args.get(0),
|
||||
1,
|
||||
"HTMLTableColElement",
|
||||
"span",
|
||||
);
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::element) fn table_caption_getter_function<'s>(
|
||||
@@ -875,28 +1029,6 @@ enum TableSlotPlacement {
|
||||
LastChild,
|
||||
}
|
||||
|
||||
fn table_cell_i32_attribute_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
attribute: &str,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
default: i32,
|
||||
member: &'static str,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Some((runtime_ptr, handle)) = html_table_cell_setter_receiver(scope, object, member) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let number = match attribute {
|
||||
"colspan" => clamp_col_span(value.int32_value(scope).unwrap_or(default)),
|
||||
"rowspan" => clamp_row_span(value.int32_value(scope).unwrap_or(default)),
|
||||
_ => value.int32_value(scope).unwrap_or(default),
|
||||
};
|
||||
set_reflected_attribute(scope, runtime_ptr, handle, attribute, &number.to_string());
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
fn set_table_slot_for_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -241,6 +241,9 @@ pub(super) enum DomStringReflection {
|
||||
StyleMedia,
|
||||
StyleType,
|
||||
TableBorder,
|
||||
TableFrame,
|
||||
TableRules,
|
||||
TableSummary,
|
||||
TableCellAbbr,
|
||||
TableCellAxis,
|
||||
TableCellCh,
|
||||
@@ -692,6 +695,33 @@ const DOM_STRING_REFLECTION_DESCRIPTORS: &[(DomStringReflection, DomStringReflec
|
||||
DomStringReflection::TableBorder,
|
||||
DomStringReflectionDescriptor::new("HTMLTableElement", "border", "border"),
|
||||
),
|
||||
(
|
||||
DomStringReflection::TableFrame,
|
||||
DomStringReflectionDescriptor::new_html_element(
|
||||
"HTMLTableElement",
|
||||
"table",
|
||||
"frame",
|
||||
"frame",
|
||||
),
|
||||
),
|
||||
(
|
||||
DomStringReflection::TableRules,
|
||||
DomStringReflectionDescriptor::new_html_element(
|
||||
"HTMLTableElement",
|
||||
"table",
|
||||
"rules",
|
||||
"rules",
|
||||
),
|
||||
),
|
||||
(
|
||||
DomStringReflection::TableSummary,
|
||||
DomStringReflectionDescriptor::new_html_element(
|
||||
"HTMLTableElement",
|
||||
"table",
|
||||
"summary",
|
||||
"summary",
|
||||
),
|
||||
),
|
||||
(
|
||||
DomStringReflection::TableCellAbbr,
|
||||
DomStringReflectionDescriptor::new("HTMLTableCellElement", "abbr", "abbr"),
|
||||
@@ -887,6 +917,8 @@ pub(super) enum NullToEmptyDomStringReflection {
|
||||
ImageBorder,
|
||||
ObjectBorder,
|
||||
TableBgColor,
|
||||
TableCellPadding,
|
||||
TableCellSpacing,
|
||||
TableCellBgColor,
|
||||
TableRowBgColor,
|
||||
Count,
|
||||
@@ -932,6 +964,24 @@ const NULL_TO_EMPTY_DOM_STRING_REFLECTION_DESCRIPTORS: &[(
|
||||
NullToEmptyDomStringReflection::TableBgColor,
|
||||
ReflectedAttributeDescriptor::new("HTMLTableElement", "bgcolor", "bgColor"),
|
||||
),
|
||||
(
|
||||
NullToEmptyDomStringReflection::TableCellPadding,
|
||||
ReflectedAttributeDescriptor::new_html_element(
|
||||
"HTMLTableElement",
|
||||
"table",
|
||||
"cellpadding",
|
||||
"cellPadding",
|
||||
),
|
||||
),
|
||||
(
|
||||
NullToEmptyDomStringReflection::TableCellSpacing,
|
||||
ReflectedAttributeDescriptor::new_html_element(
|
||||
"HTMLTableElement",
|
||||
"table",
|
||||
"cellspacing",
|
||||
"cellSpacing",
|
||||
),
|
||||
),
|
||||
(
|
||||
NullToEmptyDomStringReflection::TableCellBgColor,
|
||||
ReflectedAttributeDescriptor::new("HTMLTableCellElement", "bgcolor", "bgColor"),
|
||||
|
||||
@@ -1797,6 +1797,75 @@ fn table_cell_legacy_accessors_use_owner_prototype() {
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_legacy_dom_string_reflectors_use_owner_prototype() {
|
||||
let mut vm = new_storage_test_vm("https://table-legacy-dom-string-reflectors.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const own = (object, name) => Object.prototype.hasOwnProperty.call(object, name);
|
||||
const throwsTypeError = callback => {
|
||||
try {
|
||||
callback();
|
||||
return false;
|
||||
} catch (error) {
|
||||
return error instanceof TypeError;
|
||||
}
|
||||
};
|
||||
const cases = [
|
||||
["frame", "frame", false],
|
||||
["rules", "rules", false],
|
||||
["summary", "summary", false],
|
||||
["cellPadding", "cellpadding", true],
|
||||
["cellSpacing", "cellspacing", true]
|
||||
];
|
||||
const detachedDocument = document.implementation.createHTMLDocument("");
|
||||
|
||||
for (const [name, attribute, nullAsEmpty] of cases) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(HTMLTableElement.prototype, name);
|
||||
assert(!!descriptor, `${name} descriptor missing`);
|
||||
assert(typeof descriptor.get === "function", `${name} getter`);
|
||||
assert(typeof descriptor.set === "function", `${name} setter`);
|
||||
assert(descriptor.enumerable === true, `${name} enumerable`);
|
||||
assert(descriptor.configurable === true, `${name} configurable`);
|
||||
assert(!own(HTMLElement.prototype, name), `${name} should not be on HTMLElement.prototype`);
|
||||
|
||||
for (const [doc, label] of [[document, "live"], [detachedDocument, "detached"]]) {
|
||||
const table = doc.createElement("table");
|
||||
assert(!own(table, name), `${label}.${name} should not be own before set`);
|
||||
assert(table[name] === "", `${label}.${name} missing-value default`);
|
||||
table[name] = { toString: () => `${name}-value` };
|
||||
assert(table[name] === `${name}-value`, `${label}.${name} getter`);
|
||||
assert(table.getAttribute(attribute) === `${name}-value`, `${label}.${name} attribute`);
|
||||
table[name] = null;
|
||||
const expectedNull = nullAsEmpty ? "" : "null";
|
||||
assert(table[name] === expectedNull, `${label}.${name} null getter`);
|
||||
assert(table.getAttribute(attribute) === expectedNull, `${label}.${name} null attribute`);
|
||||
assert(!own(table, name), `${label}.${name} should stay inherited after set`);
|
||||
assert(delete table[name], `${label}.${name} delete`);
|
||||
assert(!own(table, name), `${label}.${name} should stay inherited after delete`);
|
||||
assert(table[name] === expectedNull, `${label}.${name} after delete`);
|
||||
}
|
||||
|
||||
for (const receiver of [document.createElement("div"), {}]) {
|
||||
assert(throwsTypeError(() => descriptor.get.call(receiver)), `${name} getter receiver`);
|
||||
assert(throwsTypeError(() => descriptor.set.call(receiver, "wrong")), `${name} setter receiver`);
|
||||
}
|
||||
}
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("table legacy DOMString reflectors should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detached_table_structural_accessors_use_owner_prototypes() {
|
||||
let mut vm = new_storage_test_vm("https://detached-table-structural-prototypes.test/");
|
||||
@@ -1872,9 +1941,28 @@ fn detached_table_structural_accessors_use_owner_prototypes() {
|
||||
secondCell.rowSpan = -5;
|
||||
assert(firstCell.colSpan === 7 && firstCell.getAttribute("colspan") === "7", `${label}.colSpan`);
|
||||
assert(firstCell.rowSpan === 0 && firstCell.getAttribute("rowspan") === "0", `${label}.rowSpan zero`);
|
||||
assert(secondCell.colSpan === 1000 && secondCell.getAttribute("colspan") === "1000", `${label}.colSpan clamp`);
|
||||
assert(secondCell.colSpan === 1000 && secondCell.getAttribute("colspan") === "2000", `${label}.colSpan clamp`);
|
||||
assert(secondCell.rowSpan === 1 && secondCell.getAttribute("rowspan") === "1", `${label}.rowSpan clamp`);
|
||||
|
||||
firstCell.setAttribute("colspan", "4294967296");
|
||||
firstCell.setAttribute("rowspan", "2147483648");
|
||||
assert(firstCell.colSpan === 1000, `${label}.colSpan large content clamp`);
|
||||
assert(firstCell.rowSpan === 65534, `${label}.rowSpan large content clamp`);
|
||||
firstCell.setAttribute("rowspan", "-0");
|
||||
assert(firstCell.rowSpan === 0, `${label}.rowSpan minus-zero content`);
|
||||
firstCell.colSpan = "-0";
|
||||
assert(firstCell.getAttribute("colspan") === "0" && firstCell.colSpan === 1, `${label}.colSpan zero setter`);
|
||||
firstCell.colSpan = 1001;
|
||||
assert(firstCell.getAttribute("colspan") === "1001" && firstCell.colSpan === 1000, `${label}.colSpan setter clamp`);
|
||||
firstCell.rowSpan = 65535;
|
||||
assert(firstCell.getAttribute("rowspan") === "65535" && firstCell.rowSpan === 65534, `${label}.rowSpan setter clamp`);
|
||||
firstCell.colSpan = 2147483648;
|
||||
firstCell.rowSpan = 4294967295;
|
||||
assert(firstCell.getAttribute("colspan") === "1" && firstCell.colSpan === 1, `${label}.colSpan setter default`);
|
||||
assert(firstCell.getAttribute("rowspan") === "1" && firstCell.rowSpan === 1, `${label}.rowSpan setter default`);
|
||||
firstCell.colSpan = 7;
|
||||
firstCell.rowSpan = 0;
|
||||
|
||||
for (const [element, names, elementLabel] of [
|
||||
[tbody, ["rows"], "tbody"],
|
||||
[firstRow, ["rowIndex", "sectionRowIndex", "cells"], "firstRow"],
|
||||
@@ -2043,6 +2131,14 @@ fn table_legacy_alignment_accessors_use_owner_prototypes() {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const own = (object, name) => Object.prototype.hasOwnProperty.call(object, name);
|
||||
const throwsTypeError = callback => {
|
||||
try {
|
||||
callback();
|
||||
return false;
|
||||
} catch (error) {
|
||||
return error instanceof TypeError;
|
||||
}
|
||||
};
|
||||
const accessor = (prototype, name) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
||||
assert(!!descriptor, `${name} descriptor missing`);
|
||||
@@ -2138,11 +2234,22 @@ fn table_legacy_alignment_accessors_use_owner_prototypes() {
|
||||
element.span = 1002;
|
||||
assert(element.getAttribute("span") === "1002", `${label}.span large attr`);
|
||||
assert(element.span === 1000, `${label}.span large canonical`);
|
||||
element.setAttribute("span", "4294967296");
|
||||
assert(element.span === 1000, `${label}.span large content clamp`);
|
||||
element.span = 2147483648;
|
||||
assert(element.getAttribute("span") === "1", `${label}.span setter default attr`);
|
||||
assert(element.span === 1, `${label}.span setter default`);
|
||||
element.setAttribute("span", "invalid");
|
||||
assert(element.span === 1, `${label}.span invalid canonical`);
|
||||
assert(delete element.span, `${label}.span delete`);
|
||||
assert(!own(element, "span"), `${label}.span should stay inherited`);
|
||||
}
|
||||
|
||||
const spanDescriptor = Object.getOwnPropertyDescriptor(HTMLTableColElement.prototype, "span");
|
||||
for (const receiver of [document.createElement("div"), document.createElement("td"), {}]) {
|
||||
assert(throwsTypeError(() => spanDescriptor.get.call(receiver)), "span getter receiver");
|
||||
assert(throwsTypeError(() => spanDescriptor.set.call(receiver, 2)), "span setter receiver");
|
||||
}
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
|
||||
Reference in New Issue
Block a user