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