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(None));
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>().children().find_map(DomRoot::downcast)
214 }
215
216 fn SetCaption(&self, new_caption: Option<&HTMLTableCaptionElement>) -> Fallible<()> {
218 if let Some(ref caption) = self.GetCaption() {
219 caption.upcast::<Node>().remove_self(CanGc::note());
220 }
221
222 if let Some(caption) = new_caption {
223 let node = self.upcast::<Node>();
224 node.InsertBefore(
225 caption.upcast(),
226 node.GetFirstChild().as_deref(),
227 CanGc::note(),
228 )?;
229 }
230
231 Ok(())
232 }
233
234 fn CreateCaption(&self, can_gc: CanGc) -> DomRoot<HTMLTableCaptionElement> {
236 match self.GetCaption() {
237 Some(caption) => caption,
238 None => {
239 let caption = Element::create(
240 QualName::new(None, ns!(html), local_name!("caption")),
241 None,
242 &self.owner_document(),
243 ElementCreator::ScriptCreated,
244 CustomElementCreationMode::Asynchronous,
245 None,
246 can_gc,
247 );
248 let caption = DomRoot::downcast::<HTMLTableCaptionElement>(caption).unwrap();
249
250 self.SetCaption(Some(&caption))
251 .expect("Generated caption is invalid");
252 caption
253 },
254 }
255 }
256
257 fn DeleteCaption(&self) {
259 if let Some(caption) = self.GetCaption() {
260 caption.upcast::<Node>().remove_self(CanGc::note());
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, thead: Option<&HTMLTableSectionElement>) -> ErrorResult {
271 self.set_first_section_of_type(
272 &local_name!("thead"),
273 thead,
274 |n| !n.is::<HTMLTableCaptionElement>() && !n.is::<HTMLTableColElement>(),
275 CanGc::note(),
276 )
277 }
278
279 fn CreateTHead(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
281 self.create_section_of_type(&local_name!("thead"), can_gc)
282 }
283
284 fn DeleteTHead(&self) {
286 self.delete_first_section_of_type(&local_name!("thead"), CanGc::note())
287 }
288
289 fn GetTFoot(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
291 self.get_first_section_of_type(&local_name!("tfoot"))
292 }
293
294 fn SetTFoot(&self, tfoot: Option<&HTMLTableSectionElement>) -> ErrorResult {
296 self.set_first_section_of_type(
297 &local_name!("tfoot"),
298 tfoot,
299 |n| {
300 if n.is::<HTMLTableCaptionElement>() || n.is::<HTMLTableColElement>() {
301 return false;
302 }
303
304 if n.is::<HTMLTableSectionElement>() {
305 let name = n.local_name();
306 if name == &local_name!("thead") || name == &local_name!("tbody") {
307 return false;
308 }
309 }
310
311 true
312 },
313 CanGc::note(),
314 )
315 }
316
317 fn CreateTFoot(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
319 self.create_section_of_type(&local_name!("tfoot"), can_gc)
320 }
321
322 fn DeleteTFoot(&self) {
324 self.delete_first_section_of_type(&local_name!("tfoot"), CanGc::note())
325 }
326
327 fn TBodies(&self) -> DomRoot<HTMLCollection> {
329 self.tbodies.or_init(|| {
330 HTMLCollection::new_with_filter_fn(
331 &self.owner_window(),
332 self.upcast(),
333 |element, root| {
334 element.is::<HTMLTableSectionElement>() &&
335 element.local_name() == &local_name!("tbody") &&
336 element.upcast::<Node>().GetParentNode().as_deref() == Some(root)
337 },
338 CanGc::note(),
339 )
340 })
341 }
342
343 fn CreateTBody(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
345 let tbody = Element::create(
346 QualName::new(None, ns!(html), local_name!("tbody")),
347 None,
348 &self.owner_document(),
349 ElementCreator::ScriptCreated,
350 CustomElementCreationMode::Asynchronous,
351 None,
352 can_gc,
353 );
354 let tbody = DomRoot::downcast::<HTMLTableSectionElement>(tbody).unwrap();
355 let node = self.upcast::<Node>();
356 let last_tbody = node
357 .rev_children()
358 .filter_map(DomRoot::downcast::<Element>)
359 .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody"));
360 let reference_element = last_tbody.and_then(|t| t.upcast::<Node>().GetNextSibling());
361
362 node.InsertBefore(tbody.upcast(), reference_element.as_deref(), can_gc)
363 .expect("Insertion failed");
364 tbody
365 }
366
367 fn InsertRow(&self, index: i32, can_gc: CanGc) -> Fallible<DomRoot<HTMLTableRowElement>> {
369 let rows = self.Rows();
370 let number_of_row_elements = rows.Length();
371
372 if index < -1 || index > number_of_row_elements as i32 {
373 return Err(Error::IndexSize(None));
374 }
375
376 let new_row = Element::create(
377 QualName::new(None, ns!(html), local_name!("tr")),
378 None,
379 &self.owner_document(),
380 ElementCreator::ScriptCreated,
381 CustomElementCreationMode::Asynchronous,
382 None,
383 can_gc,
384 );
385 let new_row = DomRoot::downcast::<HTMLTableRowElement>(new_row).unwrap();
386 let node = self.upcast::<Node>();
387
388 if number_of_row_elements == 0 {
389 if let Some(last_tbody) = node
391 .rev_children()
392 .filter_map(DomRoot::downcast::<Element>)
393 .find(|n| {
394 n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")
395 })
396 {
397 last_tbody
398 .upcast::<Node>()
399 .AppendChild(new_row.upcast::<Node>(), can_gc)
400 .expect("InsertRow failed to append first row.");
401 } else {
402 let tbody = self.CreateTBody(can_gc);
403 node.AppendChild(tbody.upcast(), can_gc)
404 .expect("InsertRow failed to append new tbody.");
405
406 tbody
407 .upcast::<Node>()
408 .AppendChild(new_row.upcast::<Node>(), can_gc)
409 .expect("InsertRow failed to append first row.");
410 }
411 } else if index == number_of_row_elements as i32 || index == -1 {
412 let last_row = rows
414 .Item(number_of_row_elements - 1)
415 .expect("InsertRow failed to find last row in table.");
416
417 let last_row_parent = last_row
418 .upcast::<Node>()
419 .GetParentNode()
420 .expect("InsertRow failed to find parent of last row in table.");
421
422 last_row_parent
423 .upcast::<Node>()
424 .AppendChild(new_row.upcast::<Node>(), can_gc)
425 .expect("InsertRow failed to append last row.");
426 } else {
427 let ith_row = rows
429 .Item(index as u32)
430 .expect("InsertRow failed to find a row in table.");
431
432 let ith_row_parent = ith_row
433 .upcast::<Node>()
434 .GetParentNode()
435 .expect("InsertRow failed to find parent of a row in table.");
436
437 ith_row_parent
438 .upcast::<Node>()
439 .InsertBefore(
440 new_row.upcast::<Node>(),
441 Some(ith_row.upcast::<Node>()),
442 can_gc,
443 )
444 .expect("InsertRow failed to append row");
445 }
446
447 Ok(new_row)
448 }
449
450 fn DeleteRow(&self, mut index: i32) -> Fallible<()> {
452 let rows = self.Rows();
453 let num_rows = rows.Length() as i32;
454
455 if !(-1..num_rows).contains(&index) {
458 return Err(Error::IndexSize(None));
459 }
460
461 let num_rows = rows.Length() as i32;
462
463 if index == -1 {
466 index = num_rows - 1;
467 }
468
469 if num_rows == 0 {
470 return Ok(());
471 }
472
473 DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(CanGc::note());
475
476 Ok(())
477 }
478
479 make_getter!(BgColor, "bgcolor");
481
482 make_legacy_color_setter!(SetBgColor, "bgcolor");
484
485 make_getter!(Width, "width");
487
488 make_nonzero_dimension_setter!(SetWidth, "width");
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(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
544 self.super_type()
545 .unwrap()
546 .attribute_mutated(attr, mutation, can_gc);
547 match *attr.local_name() {
548 local_name!("border") => {
549 self.border.set(
551 mutation
552 .new_value(attr)
553 .map(|value| parse_unsigned_integer(value.chars()).unwrap_or(1)),
554 );
555 },
556 local_name!("cellpadding") => {
557 self.cellpadding.set(
558 mutation
559 .new_value(attr)
560 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
561 );
562 },
563 local_name!("cellspacing") => {
564 self.cellspacing.set(
565 mutation
566 .new_value(attr)
567 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
568 );
569 },
570 _ => {},
571 }
572 }
573
574 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
575 match attr.local_name() {
576 &local_name!("width") | &local_name!("height") => true,
577 _ => self
578 .super_type()
579 .unwrap()
580 .attribute_affects_presentational_hints(attr),
581 }
582 }
583
584 fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
585 match *local_name {
586 local_name!("border") => AttrValue::from_u32(value.into(), 1),
587 local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
588 local_name!("height") => AttrValue::from_dimension(value.into()),
589 local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
590 _ => self
591 .super_type()
592 .unwrap()
593 .parse_plain_attribute(local_name, value),
594 }
595 }
596}