1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, QualName, local_name, ns};
9use js::context::JSContext;
10use js::rust::HandleObject;
11use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_unsigned_integer};
12use style::color::AbsoluteColor;
13
14use crate::dom::attr::Attr;
15use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
16use crate::dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods;
17use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
18use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
19use crate::dom::bindings::inheritance::Castable;
20use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
21use crate::dom::bindings::str::DOMString;
22use crate::dom::document::Document;
23use crate::dom::element::{
24 AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers,
25};
26use crate::dom::html::htmlcollection::{CollectionFilter, HTMLCollection};
27use crate::dom::html::htmlelement::HTMLElement;
28use crate::dom::html::htmltablecaptionelement::HTMLTableCaptionElement;
29use crate::dom::html::htmltablecolelement::HTMLTableColElement;
30use crate::dom::html::htmltablerowelement::HTMLTableRowElement;
31use crate::dom::html::htmltablesectionelement::HTMLTableSectionElement;
32use crate::dom::node::{Node, NodeTraits};
33use crate::dom::virtualmethods::VirtualMethods;
34use crate::script_runtime::CanGc;
35
36#[dom_struct]
37pub(crate) struct HTMLTableElement {
38 htmlelement: HTMLElement,
39 border: Cell<Option<u32>>,
40 cellpadding: Cell<Option<u32>>,
41 cellspacing: Cell<Option<u32>>,
42 tbodies: MutNullableDom<HTMLCollection>,
43}
44
45#[cfg_attr(crown, expect(crown::unrooted_must_root))]
46#[derive(JSTraceable, MallocSizeOf)]
47struct TableRowFilter {
48 sections: Vec<Dom<Node>>,
49}
50
51impl CollectionFilter for TableRowFilter {
52 fn filter(&self, elem: &Element, root: &Node) -> bool {
53 elem.is::<HTMLTableRowElement>() &&
54 (root.is_parent_of(elem.upcast()) ||
55 self.sections
56 .iter()
57 .any(|section| section.is_parent_of(elem.upcast())))
58 }
59}
60
61impl HTMLTableElement {
62 fn new_inherited(
63 local_name: LocalName,
64 prefix: Option<Prefix>,
65 document: &Document,
66 ) -> HTMLTableElement {
67 HTMLTableElement {
68 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
69 border: Cell::new(None),
70 cellpadding: Cell::new(None),
71 cellspacing: Cell::new(None),
72 tbodies: Default::default(),
73 }
74 }
75
76 pub(crate) fn new(
77 cx: &mut js::context::JSContext,
78 local_name: LocalName,
79 prefix: Option<Prefix>,
80 document: &Document,
81 proto: Option<HandleObject>,
82 ) -> DomRoot<HTMLTableElement> {
83 let n = Node::reflect_node_with_proto(
84 cx,
85 Box::new(HTMLTableElement::new_inherited(
86 local_name, prefix, document,
87 )),
88 document,
89 proto,
90 );
91
92 n.upcast::<Node>().set_weird_parser_insertion_mode();
93 n
94 }
95
96 pub(crate) fn get_border(&self) -> Option<u32> {
97 self.border.get()
98 }
99
100 fn get_first_section_of_type(
103 &self,
104 atom: &LocalName,
105 ) -> Option<DomRoot<HTMLTableSectionElement>> {
106 self.upcast::<Node>()
107 .child_elements()
108 .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == atom)
109 .and_then(|n| n.downcast().map(DomRoot::from_ref))
110 }
111
112 fn set_first_section_of_type<P>(
115 &self,
116 cx: &mut JSContext,
117 atom: &LocalName,
118 section: Option<&HTMLTableSectionElement>,
119 reference_predicate: P,
120 ) -> ErrorResult
121 where
122 P: FnMut(&DomRoot<Element>) -> bool,
123 {
124 if let Some(e) = section {
125 if e.upcast::<Element>().local_name() != atom {
126 return Err(Error::HierarchyRequest(None));
127 }
128 }
129
130 self.delete_first_section_of_type(cx, atom);
131
132 let node = self.upcast::<Node>();
133
134 if let Some(section) = section {
135 let reference_element = node.child_elements().find(reference_predicate);
136 let reference_node = reference_element.as_ref().map(|e| e.upcast());
137
138 node.InsertBefore(cx, section.upcast(), reference_node)?;
139 }
140
141 Ok(())
142 }
143
144 fn create_section_of_type(
147 &self,
148 cx: &mut JSContext,
149 atom: &LocalName,
150 ) -> DomRoot<HTMLTableSectionElement> {
151 if let Some(section) = self.get_first_section_of_type(atom) {
152 return section;
153 }
154
155 let section = Element::create(
156 cx,
157 QualName::new(None, ns!(html), atom.clone()),
158 None,
159 &self.owner_document(),
160 ElementCreator::ScriptCreated,
161 CustomElementCreationMode::Asynchronous,
162 None,
163 );
164
165 let section = DomRoot::downcast::<HTMLTableSectionElement>(section).unwrap();
166
167 match *atom {
168 local_name!("thead") => self.SetTHead(cx, Some(§ion)),
169 local_name!("tfoot") => self.SetTFoot(cx, Some(§ion)),
170 _ => unreachable!("unexpected section type"),
171 }
172 .expect("unexpected section type");
173
174 section
175 }
176
177 fn delete_first_section_of_type(&self, cx: &mut JSContext, atom: &LocalName) {
180 if let Some(thead) = self.get_first_section_of_type(atom) {
181 thead.upcast::<Node>().remove_self(cx);
182 }
183 }
184
185 fn get_rows(&self) -> TableRowFilter {
186 TableRowFilter {
187 sections: self
188 .upcast::<Node>()
189 .children()
190 .filter_map(|ref node| {
191 node.downcast::<HTMLTableSectionElement>()
192 .map(|_| Dom::from_ref(&**node))
193 })
194 .collect(),
195 }
196 }
197}
198
199impl HTMLTableElementMethods<crate::DomTypeHolder> for HTMLTableElement {
200 fn Rows(&self) -> DomRoot<HTMLCollection> {
202 let filter = self.get_rows();
203 HTMLCollection::new(
204 &self.owner_window(),
205 self.upcast(),
206 Box::new(filter),
207 CanGc::note(),
208 )
209 }
210
211 fn GetCaption(&self) -> Option<DomRoot<HTMLTableCaptionElement>> {
213 self.upcast::<Node>().children().find_map(DomRoot::downcast)
214 }
215
216 fn SetCaption(
218 &self,
219 cx: &mut JSContext,
220 new_caption: Option<&HTMLTableCaptionElement>,
221 ) -> Fallible<()> {
222 if let Some(ref caption) = self.GetCaption() {
223 caption.upcast::<Node>().remove_self(cx);
224 }
225
226 if let Some(caption) = new_caption {
227 let node = self.upcast::<Node>();
228 node.InsertBefore(cx, caption.upcast(), node.GetFirstChild().as_deref())?;
229 }
230
231 Ok(())
232 }
233
234 fn CreateCaption(&self, cx: &mut JSContext) -> DomRoot<HTMLTableCaptionElement> {
236 match self.GetCaption() {
237 Some(caption) => caption,
238 None => {
239 let caption = Element::create(
240 cx,
241 QualName::new(None, ns!(html), local_name!("caption")),
242 None,
243 &self.owner_document(),
244 ElementCreator::ScriptCreated,
245 CustomElementCreationMode::Asynchronous,
246 None,
247 );
248 let caption = DomRoot::downcast::<HTMLTableCaptionElement>(caption).unwrap();
249
250 self.SetCaption(cx, Some(&caption))
251 .expect("Generated caption is invalid");
252 caption
253 },
254 }
255 }
256
257 fn DeleteCaption(&self, cx: &mut JSContext) {
259 if let Some(caption) = self.GetCaption() {
260 caption.upcast::<Node>().remove_self(cx);
261 }
262 }
263
264 fn GetTHead(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
266 self.get_first_section_of_type(&local_name!("thead"))
267 }
268
269 fn SetTHead(&self, cx: &mut JSContext, thead: Option<&HTMLTableSectionElement>) -> ErrorResult {
271 self.set_first_section_of_type(cx, &local_name!("thead"), thead, |n| {
272 !n.is::<HTMLTableCaptionElement>() && !n.is::<HTMLTableColElement>()
273 })
274 }
275
276 fn CreateTHead(&self, cx: &mut JSContext) -> DomRoot<HTMLTableSectionElement> {
278 self.create_section_of_type(cx, &local_name!("thead"))
279 }
280
281 fn DeleteTHead(&self, cx: &mut JSContext) {
283 self.delete_first_section_of_type(cx, &local_name!("thead"))
284 }
285
286 fn GetTFoot(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
288 self.get_first_section_of_type(&local_name!("tfoot"))
289 }
290
291 fn SetTFoot(&self, cx: &mut JSContext, tfoot: Option<&HTMLTableSectionElement>) -> ErrorResult {
293 self.set_first_section_of_type(cx, &local_name!("tfoot"), tfoot, |n| {
294 if n.is::<HTMLTableCaptionElement>() || n.is::<HTMLTableColElement>() {
295 return false;
296 }
297
298 if n.is::<HTMLTableSectionElement>() {
299 let name = n.local_name();
300 if name == &local_name!("thead") || name == &local_name!("tbody") {
301 return false;
302 }
303 }
304
305 true
306 })
307 }
308
309 fn CreateTFoot(&self, cx: &mut JSContext) -> DomRoot<HTMLTableSectionElement> {
311 self.create_section_of_type(cx, &local_name!("tfoot"))
312 }
313
314 fn DeleteTFoot(&self, cx: &mut JSContext) {
316 self.delete_first_section_of_type(cx, &local_name!("tfoot"))
317 }
318
319 fn TBodies(&self) -> DomRoot<HTMLCollection> {
321 self.tbodies.or_init(|| {
322 HTMLCollection::new_with_filter_fn(
323 &self.owner_window(),
324 self.upcast(),
325 |element, root| {
326 element.is::<HTMLTableSectionElement>() &&
327 element.local_name() == &local_name!("tbody") &&
328 element.upcast::<Node>().GetParentNode().as_deref() == Some(root)
329 },
330 CanGc::note(),
331 )
332 })
333 }
334
335 fn CreateTBody(&self, cx: &mut JSContext) -> DomRoot<HTMLTableSectionElement> {
337 let tbody = Element::create(
338 cx,
339 QualName::new(None, ns!(html), local_name!("tbody")),
340 None,
341 &self.owner_document(),
342 ElementCreator::ScriptCreated,
343 CustomElementCreationMode::Asynchronous,
344 None,
345 );
346 let tbody = DomRoot::downcast::<HTMLTableSectionElement>(tbody).unwrap();
347 let node = self.upcast::<Node>();
348 let last_tbody = node
349 .rev_children()
350 .filter_map(DomRoot::downcast::<Element>)
351 .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody"));
352 let reference_element = last_tbody.and_then(|t| t.upcast::<Node>().GetNextSibling());
353
354 node.InsertBefore(cx, tbody.upcast(), reference_element.as_deref())
355 .expect("Insertion failed");
356 tbody
357 }
358
359 fn InsertRow(&self, cx: &mut JSContext, index: i32) -> Fallible<DomRoot<HTMLTableRowElement>> {
361 let rows = self.Rows();
362 let number_of_row_elements = rows.Length();
363
364 if index < -1 || index > number_of_row_elements as i32 {
365 return Err(Error::IndexSize(None));
366 }
367
368 let new_row = Element::create(
369 cx,
370 QualName::new(None, ns!(html), local_name!("tr")),
371 None,
372 &self.owner_document(),
373 ElementCreator::ScriptCreated,
374 CustomElementCreationMode::Asynchronous,
375 None,
376 );
377 let new_row = DomRoot::downcast::<HTMLTableRowElement>(new_row).unwrap();
378 let node = self.upcast::<Node>();
379
380 if number_of_row_elements == 0 {
381 if let Some(last_tbody) = node
383 .rev_children()
384 .filter_map(DomRoot::downcast::<Element>)
385 .find(|n| {
386 n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")
387 })
388 {
389 last_tbody
390 .upcast::<Node>()
391 .AppendChild(cx, new_row.upcast::<Node>())
392 .expect("InsertRow failed to append first row.");
393 } else {
394 let tbody = self.CreateTBody(cx);
395 node.AppendChild(cx, tbody.upcast())
396 .expect("InsertRow failed to append new tbody.");
397
398 tbody
399 .upcast::<Node>()
400 .AppendChild(cx, new_row.upcast::<Node>())
401 .expect("InsertRow failed to append first row.");
402 }
403 } else if index == number_of_row_elements as i32 || index == -1 {
404 let last_row = rows
406 .Item(number_of_row_elements - 1)
407 .expect("InsertRow failed to find last row in table.");
408
409 let last_row_parent = last_row
410 .upcast::<Node>()
411 .GetParentNode()
412 .expect("InsertRow failed to find parent of last row in table.");
413
414 last_row_parent
415 .upcast::<Node>()
416 .AppendChild(cx, new_row.upcast::<Node>())
417 .expect("InsertRow failed to append last row.");
418 } else {
419 let ith_row = rows
421 .Item(index as u32)
422 .expect("InsertRow failed to find a row in table.");
423
424 let ith_row_parent = ith_row
425 .upcast::<Node>()
426 .GetParentNode()
427 .expect("InsertRow failed to find parent of a row in table.");
428
429 ith_row_parent
430 .upcast::<Node>()
431 .InsertBefore(cx, new_row.upcast::<Node>(), Some(ith_row.upcast::<Node>()))
432 .expect("InsertRow failed to append row");
433 }
434
435 Ok(new_row)
436 }
437
438 fn DeleteRow(&self, cx: &mut JSContext, mut index: i32) -> Fallible<()> {
440 let rows = self.Rows();
441 let num_rows = rows.Length() as i32;
442
443 if !(-1..num_rows).contains(&index) {
446 return Err(Error::IndexSize(None));
447 }
448
449 let num_rows = rows.Length() as i32;
450
451 if index == -1 {
454 index = num_rows - 1;
455 }
456
457 if num_rows == 0 {
458 return Ok(());
459 }
460
461 DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(cx);
463
464 Ok(())
465 }
466
467 make_getter!(BgColor, "bgcolor");
469
470 make_legacy_color_setter!(SetBgColor, "bgcolor");
472
473 make_getter!(Width, "width");
475
476 make_nonzero_dimension_setter!(SetWidth, "width");
478
479 make_setter!(SetAlign, "align");
481 make_getter!(Align, "align");
482
483 make_setter!(SetCellPadding, "cellpadding");
485 make_getter!(CellPadding, "cellpadding");
486
487 make_setter!(SetCellSpacing, "cellspacing");
489 make_getter!(CellSpacing, "cellspacing");
490}
491
492pub(crate) trait HTMLTableElementLayoutHelpers {
493 fn get_background_color(self) -> Option<AbsoluteColor>;
494 fn get_border(self) -> Option<u32>;
495 fn get_cellpadding(self) -> Option<u32>;
496 fn get_cellspacing(self) -> Option<u32>;
497 fn get_width(self) -> LengthOrPercentageOrAuto;
498 fn get_height(self) -> LengthOrPercentageOrAuto;
499}
500
501impl HTMLTableElementLayoutHelpers for LayoutDom<'_, HTMLTableElement> {
502 fn get_background_color(self) -> Option<AbsoluteColor> {
503 self.upcast::<Element>()
504 .get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
505 .and_then(AttrValue::as_color)
506 .cloned()
507 }
508
509 fn get_border(self) -> Option<u32> {
510 (self.unsafe_get()).border.get()
511 }
512
513 fn get_cellpadding(self) -> Option<u32> {
514 (self.unsafe_get()).cellpadding.get()
515 }
516
517 fn get_cellspacing(self) -> Option<u32> {
518 (self.unsafe_get()).cellspacing.get()
519 }
520
521 fn get_width(self) -> LengthOrPercentageOrAuto {
522 self.upcast::<Element>()
523 .get_attr_for_layout(&ns!(), &local_name!("width"))
524 .map(AttrValue::as_dimension)
525 .cloned()
526 .unwrap_or(LengthOrPercentageOrAuto::Auto)
527 }
528
529 fn get_height(self) -> LengthOrPercentageOrAuto {
530 self.upcast::<Element>()
531 .get_attr_for_layout(&ns!(), &local_name!("height"))
532 .map(AttrValue::as_dimension)
533 .cloned()
534 .unwrap_or(LengthOrPercentageOrAuto::Auto)
535 }
536}
537
538impl VirtualMethods for HTMLTableElement {
539 fn super_type(&self) -> Option<&dyn VirtualMethods> {
540 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
541 }
542
543 fn attribute_mutated(
544 &self,
545 cx: &mut js::context::JSContext,
546 attr: &Attr,
547 mutation: AttributeMutation,
548 ) {
549 self.super_type()
550 .unwrap()
551 .attribute_mutated(cx, attr, mutation);
552 match *attr.local_name() {
553 local_name!("border") => {
554 self.border.set(
556 mutation
557 .new_value(attr)
558 .map(|value| parse_unsigned_integer(value.chars()).unwrap_or(1)),
559 );
560 },
561 local_name!("cellpadding") => {
562 self.cellpadding.set(
563 mutation
564 .new_value(attr)
565 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
566 );
567 },
568 local_name!("cellspacing") => {
569 self.cellspacing.set(
570 mutation
571 .new_value(attr)
572 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
573 );
574 },
575 _ => {},
576 }
577 }
578
579 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
580 match attr.local_name() {
581 &local_name!("width") | &local_name!("height") => true,
582 _ => self
583 .super_type()
584 .unwrap()
585 .attribute_affects_presentational_hints(attr),
586 }
587 }
588
589 fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
590 match *local_name {
591 local_name!("border") => AttrValue::from_u32(value.into(), 1),
592 local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
593 local_name!("height") => AttrValue::from_dimension(value.into()),
594 local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
595 _ => self
596 .super_type()
597 .unwrap()
598 .parse_plain_attribute(local_name, value),
599 }
600 }
601}