fix(renderer): complete tabular element reflection

This commit is contained in:
ldm0
2026-09-22 21:43:32 +08:00
parent 6f79e40d5c
commit f29e6aeb87
6 changed files with 455 additions and 92 deletions
+1 -1
View File
@@ -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()
+56 -15
View File
@@ -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";
})()
"#,