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, allow(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 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
76 pub(crate) fn new(
77 local_name: LocalName,
78 prefix: Option<Prefix>,
79 document: &Document,
80 proto: Option<HandleObject>,
81 can_gc: CanGc,
82 ) -> DomRoot<HTMLTableElement> {
83 let n = Node::reflect_node_with_proto(
84 Box::new(HTMLTableElement::new_inherited(
85 local_name, prefix, document,
86 )),
87 document,
88 proto,
89 can_gc,
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 atom: &LocalName,
117 section: Option<&HTMLTableSectionElement>,
118 reference_predicate: P,
119 can_gc: CanGc,
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);
127 }
128 }
129
130 self.delete_first_section_of_type(atom, can_gc);
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(section.upcast(), reference_node, can_gc)?;
139 }
140
141 Ok(())
142 }
143
144 fn create_section_of_type(
147 &self,
148 atom: &LocalName,
149 can_gc: CanGc,
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 QualName::new(None, ns!(html), atom.clone()),
157 None,
158 &self.owner_document(),
159 ElementCreator::ScriptCreated,
160 CustomElementCreationMode::Asynchronous,
161 None,
162 can_gc,
163 );
164
165 let section = DomRoot::downcast::<HTMLTableSectionElement>(section).unwrap();
166
167 match *atom {
168 local_name!("thead") => self.SetTHead(Some(§ion)),
169 local_name!("tfoot") => self.SetTFoot(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, atom: &LocalName, can_gc: CanGc) {
180 if let Some(thead) = self.get_first_section_of_type(atom) {
181 thead.upcast::<Node>().remove_self(can_gc);
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>()
214 .children()
215 .filter_map(DomRoot::downcast)
216 .next()
217 }
218
219 fn SetCaption(&self, new_caption: Option<&HTMLTableCaptionElement>) -> Fallible<()> {
221 if let Some(ref caption) = self.GetCaption() {
222 caption.upcast::<Node>().remove_self(CanGc::note());
223 }
224
225 if let Some(caption) = new_caption {
226 let node = self.upcast::<Node>();
227 node.InsertBefore(
228 caption.upcast(),
229 node.GetFirstChild().as_deref(),
230 CanGc::note(),
231 )?;
232 }
233
234 Ok(())
235 }
236
237 fn CreateCaption(&self, can_gc: CanGc) -> DomRoot<HTMLTableCaptionElement> {
239 match self.GetCaption() {
240 Some(caption) => caption,
241 None => {
242 let caption = Element::create(
243 QualName::new(None, ns!(html), local_name!("caption")),
244 None,
245 &self.owner_document(),
246 ElementCreator::ScriptCreated,
247 CustomElementCreationMode::Asynchronous,
248 None,
249 can_gc,
250 );
251 let caption = DomRoot::downcast::<HTMLTableCaptionElement>(caption).unwrap();
252
253 self.SetCaption(Some(&caption))
254 .expect("Generated caption is invalid");
255 caption
256 },
257 }
258 }
259
260 fn DeleteCaption(&self) {
262 if let Some(caption) = self.GetCaption() {
263 caption.upcast::<Node>().remove_self(CanGc::note());
264 }
265 }
266
267 fn GetTHead(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
269 self.get_first_section_of_type(&local_name!("thead"))
270 }
271
272 fn SetTHead(&self, thead: Option<&HTMLTableSectionElement>) -> ErrorResult {
274 self.set_first_section_of_type(
275 &local_name!("thead"),
276 thead,
277 |n| !n.is::<HTMLTableCaptionElement>() && !n.is::<HTMLTableColElement>(),
278 CanGc::note(),
279 )
280 }
281
282 fn CreateTHead(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
284 self.create_section_of_type(&local_name!("thead"), can_gc)
285 }
286
287 fn DeleteTHead(&self) {
289 self.delete_first_section_of_type(&local_name!("thead"), CanGc::note())
290 }
291
292 fn GetTFoot(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
294 self.get_first_section_of_type(&local_name!("tfoot"))
295 }
296
297 fn SetTFoot(&self, tfoot: Option<&HTMLTableSectionElement>) -> ErrorResult {
299 self.set_first_section_of_type(
300 &local_name!("tfoot"),
301 tfoot,
302 |n| {
303 if n.is::<HTMLTableCaptionElement>() || n.is::<HTMLTableColElement>() {
304 return false;
305 }
306
307 if n.is::<HTMLTableSectionElement>() {
308 let name = n.local_name();
309 if name == &local_name!("thead") || name == &local_name!("tbody") {
310 return false;
311 }
312 }
313
314 true
315 },
316 CanGc::note(),
317 )
318 }
319
320 fn CreateTFoot(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
322 self.create_section_of_type(&local_name!("tfoot"), can_gc)
323 }
324
325 fn DeleteTFoot(&self) {
327 self.delete_first_section_of_type(&local_name!("tfoot"), CanGc::note())
328 }
329
330 fn TBodies(&self) -> DomRoot<HTMLCollection> {
332 self.tbodies.or_init(|| {
333 HTMLCollection::new_with_filter_fn(
334 &self.owner_window(),
335 self.upcast(),
336 |element, root| {
337 element.is::<HTMLTableSectionElement>() &&
338 element.local_name() == &local_name!("tbody") &&
339 element.upcast::<Node>().GetParentNode().as_deref() == Some(root)
340 },
341 CanGc::note(),
342 )
343 })
344 }
345
346 fn CreateTBody(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
348 let tbody = Element::create(
349 QualName::new(None, ns!(html), local_name!("tbody")),
350 None,
351 &self.owner_document(),
352 ElementCreator::ScriptCreated,
353 CustomElementCreationMode::Asynchronous,
354 None,
355 can_gc,
356 );
357 let tbody = DomRoot::downcast::<HTMLTableSectionElement>(tbody).unwrap();
358 let node = self.upcast::<Node>();
359 let last_tbody = node
360 .rev_children()
361 .filter_map(DomRoot::downcast::<Element>)
362 .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody"));
363 let reference_element = last_tbody.and_then(|t| t.upcast::<Node>().GetNextSibling());
364
365 node.InsertBefore(tbody.upcast(), reference_element.as_deref(), can_gc)
366 .expect("Insertion failed");
367 tbody
368 }
369
370 fn InsertRow(&self, index: i32, can_gc: CanGc) -> Fallible<DomRoot<HTMLTableRowElement>> {
372 let rows = self.Rows();
373 let number_of_row_elements = rows.Length();
374
375 if index < -1 || index > number_of_row_elements as i32 {
376 return Err(Error::IndexSize);
377 }
378
379 let new_row = Element::create(
380 QualName::new(None, ns!(html), local_name!("tr")),
381 None,
382 &self.owner_document(),
383 ElementCreator::ScriptCreated,
384 CustomElementCreationMode::Asynchronous,
385 None,
386 can_gc,
387 );
388 let new_row = DomRoot::downcast::<HTMLTableRowElement>(new_row).unwrap();
389 let node = self.upcast::<Node>();
390
391 if number_of_row_elements == 0 {
392 if let Some(last_tbody) = node
394 .rev_children()
395 .filter_map(DomRoot::downcast::<Element>)
396 .find(|n| {
397 n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")
398 })
399 {
400 last_tbody
401 .upcast::<Node>()
402 .AppendChild(new_row.upcast::<Node>(), can_gc)
403 .expect("InsertRow failed to append first row.");
404 } else {
405 let tbody = self.CreateTBody(can_gc);
406 node.AppendChild(tbody.upcast(), can_gc)
407 .expect("InsertRow failed to append new tbody.");
408
409 tbody
410 .upcast::<Node>()
411 .AppendChild(new_row.upcast::<Node>(), can_gc)
412 .expect("InsertRow failed to append first row.");
413 }
414 } else if index == number_of_row_elements as i32 || index == -1 {
415 let last_row = rows
417 .Item(number_of_row_elements - 1)
418 .expect("InsertRow failed to find last row in table.");
419
420 let last_row_parent = last_row
421 .upcast::<Node>()
422 .GetParentNode()
423 .expect("InsertRow failed to find parent of last row in table.");
424
425 last_row_parent
426 .upcast::<Node>()
427 .AppendChild(new_row.upcast::<Node>(), can_gc)
428 .expect("InsertRow failed to append last row.");
429 } else {
430 let ith_row = rows
432 .Item(index as u32)
433 .expect("InsertRow failed to find a row in table.");
434
435 let ith_row_parent = ith_row
436 .upcast::<Node>()
437 .GetParentNode()
438 .expect("InsertRow failed to find parent of a row in table.");
439
440 ith_row_parent
441 .upcast::<Node>()
442 .InsertBefore(
443 new_row.upcast::<Node>(),
444 Some(ith_row.upcast::<Node>()),
445 can_gc,
446 )
447 .expect("InsertRow failed to append row");
448 }
449
450 Ok(new_row)
451 }
452
453 fn DeleteRow(&self, mut index: i32) -> Fallible<()> {
455 let rows = self.Rows();
456 let num_rows = rows.Length() as i32;
457
458 if !(-1..num_rows).contains(&index) {
461 return Err(Error::IndexSize);
462 }
463
464 let num_rows = rows.Length() as i32;
465
466 if index == -1 {
469 index = num_rows - 1;
470 }
471
472 if num_rows == 0 {
473 return Ok(());
474 }
475
476 DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(CanGc::note());
478
479 Ok(())
480 }
481
482 make_getter!(BgColor, "bgcolor");
484
485 make_legacy_color_setter!(SetBgColor, "bgcolor");
487
488 make_getter!(Width, "width");
490
491 make_nonzero_dimension_setter!(SetWidth, "width");
493}
494
495pub(crate) trait HTMLTableElementLayoutHelpers {
496 fn get_background_color(self) -> Option<AbsoluteColor>;
497 fn get_border(self) -> Option<u32>;
498 fn get_cellpadding(self) -> Option<u32>;
499 fn get_cellspacing(self) -> Option<u32>;
500 fn get_width(self) -> LengthOrPercentageOrAuto;
501 fn get_height(self) -> LengthOrPercentageOrAuto;
502}
503
504impl HTMLTableElementLayoutHelpers for LayoutDom<'_, HTMLTableElement> {
505 fn get_background_color(self) -> Option<AbsoluteColor> {
506 self.upcast::<Element>()
507 .get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
508 .and_then(AttrValue::as_color)
509 .cloned()
510 }
511
512 fn get_border(self) -> Option<u32> {
513 (self.unsafe_get()).border.get()
514 }
515
516 fn get_cellpadding(self) -> Option<u32> {
517 (self.unsafe_get()).cellpadding.get()
518 }
519
520 fn get_cellspacing(self) -> Option<u32> {
521 (self.unsafe_get()).cellspacing.get()
522 }
523
524 fn get_width(self) -> LengthOrPercentageOrAuto {
525 self.upcast::<Element>()
526 .get_attr_for_layout(&ns!(), &local_name!("width"))
527 .map(AttrValue::as_dimension)
528 .cloned()
529 .unwrap_or(LengthOrPercentageOrAuto::Auto)
530 }
531
532 fn get_height(self) -> LengthOrPercentageOrAuto {
533 self.upcast::<Element>()
534 .get_attr_for_layout(&ns!(), &local_name!("height"))
535 .map(AttrValue::as_dimension)
536 .cloned()
537 .unwrap_or(LengthOrPercentageOrAuto::Auto)
538 }
539}
540
541impl VirtualMethods for HTMLTableElement {
542 fn super_type(&self) -> Option<&dyn VirtualMethods> {
543 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
544 }
545
546 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
547 self.super_type()
548 .unwrap()
549 .attribute_mutated(attr, mutation, can_gc);
550 match *attr.local_name() {
551 local_name!("border") => {
552 self.border.set(
554 mutation
555 .new_value(attr)
556 .map(|value| parse_unsigned_integer(value.chars()).unwrap_or(1)),
557 );
558 },
559 local_name!("cellpadding") => {
560 self.cellpadding.set(
561 mutation
562 .new_value(attr)
563 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
564 );
565 },
566 local_name!("cellspacing") => {
567 self.cellspacing.set(
568 mutation
569 .new_value(attr)
570 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
571 );
572 },
573 _ => {},
574 }
575 }
576
577 fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
578 match *local_name {
579 local_name!("border") => AttrValue::from_u32(value.into(), 1),
580 local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
581 local_name!("height") => AttrValue::from_dimension(value.into()),
582 local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
583 _ => self
584 .super_type()
585 .unwrap()
586 .parse_plain_attribute(local_name, value),
587 }
588 }
589}