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