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 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 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 QualName::new(None, ns!(html), atom.clone()),
157 None,
158 &self.owner_document(),
159 ElementCreator::ScriptCreated,
160 CustomElementCreationMode::Asynchronous,
161 None,
162 CanGc::from_cx(cx),
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, cx: &mut JSContext) -> 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 CanGc::from_cx(cx),
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, cx: &mut JSContext) -> DomRoot<HTMLTableSectionElement> {
281 self.create_section_of_type(cx, &local_name!("thead"))
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, cx: &mut JSContext) -> DomRoot<HTMLTableSectionElement> {
319 self.create_section_of_type(cx, &local_name!("tfoot"))
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, cx: &mut JSContext) -> 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 CanGc::from_cx(cx),
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(
363 tbody.upcast(),
364 reference_element.as_deref(),
365 CanGc::from_cx(cx),
366 )
367 .expect("Insertion failed");
368 tbody
369 }
370
371 fn InsertRow(&self, cx: &mut JSContext, index: i32) -> Fallible<DomRoot<HTMLTableRowElement>> {
373 let rows = self.Rows();
374 let number_of_row_elements = rows.Length();
375
376 if index < -1 || index > number_of_row_elements as i32 {
377 return Err(Error::IndexSize(None));
378 }
379
380 let new_row = Element::create(
381 QualName::new(None, ns!(html), local_name!("tr")),
382 None,
383 &self.owner_document(),
384 ElementCreator::ScriptCreated,
385 CustomElementCreationMode::Asynchronous,
386 None,
387 CanGc::from_cx(cx),
388 );
389 let new_row = DomRoot::downcast::<HTMLTableRowElement>(new_row).unwrap();
390 let node = self.upcast::<Node>();
391
392 if number_of_row_elements == 0 {
393 if let Some(last_tbody) = node
395 .rev_children()
396 .filter_map(DomRoot::downcast::<Element>)
397 .find(|n| {
398 n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")
399 })
400 {
401 last_tbody
402 .upcast::<Node>()
403 .AppendChild(new_row.upcast::<Node>(), CanGc::from_cx(cx))
404 .expect("InsertRow failed to append first row.");
405 } else {
406 let tbody = self.CreateTBody(cx);
407 node.AppendChild(tbody.upcast(), CanGc::from_cx(cx))
408 .expect("InsertRow failed to append new tbody.");
409
410 tbody
411 .upcast::<Node>()
412 .AppendChild(new_row.upcast::<Node>(), CanGc::from_cx(cx))
413 .expect("InsertRow failed to append first row.");
414 }
415 } else if index == number_of_row_elements as i32 || index == -1 {
416 let last_row = rows
418 .Item(number_of_row_elements - 1)
419 .expect("InsertRow failed to find last row in table.");
420
421 let last_row_parent = last_row
422 .upcast::<Node>()
423 .GetParentNode()
424 .expect("InsertRow failed to find parent of last row in table.");
425
426 last_row_parent
427 .upcast::<Node>()
428 .AppendChild(new_row.upcast::<Node>(), CanGc::from_cx(cx))
429 .expect("InsertRow failed to append last row.");
430 } else {
431 let ith_row = rows
433 .Item(index as u32)
434 .expect("InsertRow failed to find a row in table.");
435
436 let ith_row_parent = ith_row
437 .upcast::<Node>()
438 .GetParentNode()
439 .expect("InsertRow failed to find parent of a row in table.");
440
441 ith_row_parent
442 .upcast::<Node>()
443 .InsertBefore(
444 new_row.upcast::<Node>(),
445 Some(ith_row.upcast::<Node>()),
446 CanGc::from_cx(cx),
447 )
448 .expect("InsertRow failed to append row");
449 }
450
451 Ok(new_row)
452 }
453
454 fn DeleteRow(&self, mut index: i32) -> Fallible<()> {
456 let rows = self.Rows();
457 let num_rows = rows.Length() as i32;
458
459 if !(-1..num_rows).contains(&index) {
462 return Err(Error::IndexSize(None));
463 }
464
465 let num_rows = rows.Length() as i32;
466
467 if index == -1 {
470 index = num_rows - 1;
471 }
472
473 if num_rows == 0 {
474 return Ok(());
475 }
476
477 DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(CanGc::note());
479
480 Ok(())
481 }
482
483 make_getter!(BgColor, "bgcolor");
485
486 make_legacy_color_setter!(SetBgColor, "bgcolor");
488
489 make_getter!(Width, "width");
491
492 make_nonzero_dimension_setter!(SetWidth, "width");
494}
495
496pub(crate) trait HTMLTableElementLayoutHelpers {
497 fn get_background_color(self) -> Option<AbsoluteColor>;
498 fn get_border(self) -> Option<u32>;
499 fn get_cellpadding(self) -> Option<u32>;
500 fn get_cellspacing(self) -> Option<u32>;
501 fn get_width(self) -> LengthOrPercentageOrAuto;
502 fn get_height(self) -> LengthOrPercentageOrAuto;
503}
504
505impl HTMLTableElementLayoutHelpers for LayoutDom<'_, HTMLTableElement> {
506 fn get_background_color(self) -> Option<AbsoluteColor> {
507 self.upcast::<Element>()
508 .get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
509 .and_then(AttrValue::as_color)
510 .cloned()
511 }
512
513 fn get_border(self) -> Option<u32> {
514 (self.unsafe_get()).border.get()
515 }
516
517 fn get_cellpadding(self) -> Option<u32> {
518 (self.unsafe_get()).cellpadding.get()
519 }
520
521 fn get_cellspacing(self) -> Option<u32> {
522 (self.unsafe_get()).cellspacing.get()
523 }
524
525 fn get_width(self) -> LengthOrPercentageOrAuto {
526 self.upcast::<Element>()
527 .get_attr_for_layout(&ns!(), &local_name!("width"))
528 .map(AttrValue::as_dimension)
529 .cloned()
530 .unwrap_or(LengthOrPercentageOrAuto::Auto)
531 }
532
533 fn get_height(self) -> LengthOrPercentageOrAuto {
534 self.upcast::<Element>()
535 .get_attr_for_layout(&ns!(), &local_name!("height"))
536 .map(AttrValue::as_dimension)
537 .cloned()
538 .unwrap_or(LengthOrPercentageOrAuto::Auto)
539 }
540}
541
542impl VirtualMethods for HTMLTableElement {
543 fn super_type(&self) -> Option<&dyn VirtualMethods> {
544 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
545 }
546
547 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
548 self.super_type()
549 .unwrap()
550 .attribute_mutated(attr, mutation, can_gc);
551 match *attr.local_name() {
552 local_name!("border") => {
553 self.border.set(
555 mutation
556 .new_value(attr)
557 .map(|value| parse_unsigned_integer(value.chars()).unwrap_or(1)),
558 );
559 },
560 local_name!("cellpadding") => {
561 self.cellpadding.set(
562 mutation
563 .new_value(attr)
564 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
565 );
566 },
567 local_name!("cellspacing") => {
568 self.cellspacing.set(
569 mutation
570 .new_value(attr)
571 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
572 );
573 },
574 _ => {},
575 }
576 }
577
578 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
579 match attr.local_name() {
580 &local_name!("width") | &local_name!("height") => true,
581 _ => self
582 .super_type()
583 .unwrap()
584 .attribute_affects_presentational_hints(attr),
585 }
586 }
587
588 fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
589 match *local_name {
590 local_name!("border") => AttrValue::from_u32(value.into(), 1),
591 local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
592 local_name!("height") => AttrValue::from_dimension(value.into()),
593 local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
594 _ => self
595 .super_type()
596 .unwrap()
597 .parse_plain_attribute(local_name, value),
598 }
599 }
600}