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::{AttributeMutation, CustomElementCreationMode, Element, ElementCreator};
24use crate::dom::html::htmlcollection::{CollectionFilter, HTMLCollection};
25use crate::dom::html::htmlelement::HTMLElement;
26use crate::dom::html::htmltablecaptionelement::HTMLTableCaptionElement;
27use crate::dom::html::htmltablecolelement::HTMLTableColElement;
28use crate::dom::html::htmltablerowelement::HTMLTableRowElement;
29use crate::dom::html::htmltablesectionelement::HTMLTableSectionElement;
30use crate::dom::node::{Node, NodeTraits};
31use crate::dom::virtualmethods::VirtualMethods;
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, expect(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 pub(crate) fn new(
74 cx: &mut js::context::JSContext,
75 local_name: LocalName,
76 prefix: Option<Prefix>,
77 document: &Document,
78 proto: Option<HandleObject>,
79 ) -> DomRoot<HTMLTableElement> {
80 let n = Node::reflect_node_with_proto(
81 cx,
82 Box::new(HTMLTableElement::new_inherited(
83 local_name, prefix, document,
84 )),
85 document,
86 proto,
87 );
88
89 n.upcast::<Node>().set_weird_parser_insertion_mode();
90 n
91 }
92
93 fn get_first_section_of_type(
96 &self,
97 atom: &LocalName,
98 ) -> Option<DomRoot<HTMLTableSectionElement>> {
99 self.upcast::<Node>()
100 .child_elements()
101 .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == atom)
102 .and_then(|n| n.downcast().map(DomRoot::from_ref))
103 }
104
105 fn set_first_section_of_type<P>(
108 &self,
109 cx: &mut JSContext,
110 atom: &LocalName,
111 section: Option<&HTMLTableSectionElement>,
112 reference_predicate: P,
113 ) -> ErrorResult
114 where
115 P: FnMut(&DomRoot<Element>) -> bool,
116 {
117 if let Some(e) = section {
118 if e.upcast::<Element>().local_name() != atom {
119 return Err(Error::HierarchyRequest(None));
120 }
121 }
122
123 self.delete_first_section_of_type(cx, atom);
124
125 let node = self.upcast::<Node>();
126
127 if let Some(section) = section {
128 let reference_element = node.child_elements().find(reference_predicate);
129 let reference_node = reference_element.as_ref().map(|e| e.upcast());
130
131 node.InsertBefore(cx, section.upcast(), reference_node)?;
132 }
133
134 Ok(())
135 }
136
137 fn create_section_of_type(
140 &self,
141 cx: &mut JSContext,
142 atom: &LocalName,
143 ) -> DomRoot<HTMLTableSectionElement> {
144 if let Some(section) = self.get_first_section_of_type(atom) {
145 return section;
146 }
147
148 let section = Element::create(
149 cx,
150 QualName::new(None, ns!(html), atom.clone()),
151 None,
152 &self.owner_document(),
153 ElementCreator::ScriptCreated,
154 CustomElementCreationMode::Asynchronous,
155 None,
156 );
157
158 let section = DomRoot::downcast::<HTMLTableSectionElement>(section).unwrap();
159
160 match *atom {
161 local_name!("thead") => self.SetTHead(cx, Some(§ion)),
162 local_name!("tfoot") => self.SetTFoot(cx, Some(§ion)),
163 _ => unreachable!("unexpected section type"),
164 }
165 .expect("unexpected section type");
166
167 section
168 }
169
170 fn delete_first_section_of_type(&self, cx: &mut JSContext, atom: &LocalName) {
173 if let Some(thead) = self.get_first_section_of_type(atom) {
174 thead.upcast::<Node>().remove_self(cx);
175 }
176 }
177
178 fn get_rows(&self) -> TableRowFilter {
179 TableRowFilter {
180 sections: self
181 .upcast::<Node>()
182 .children()
183 .filter_map(|ref node| {
184 node.downcast::<HTMLTableSectionElement>()
185 .map(|_| Dom::from_ref(&**node))
186 })
187 .collect(),
188 }
189 }
190}
191
192impl HTMLTableElementMethods<crate::DomTypeHolder> for HTMLTableElement {
193 fn Rows(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
195 let filter = self.get_rows();
196 HTMLCollection::new(cx, &self.owner_window(), self.upcast(), Box::new(filter))
197 }
198
199 fn GetCaption(&self) -> Option<DomRoot<HTMLTableCaptionElement>> {
201 self.upcast::<Node>().children().find_map(DomRoot::downcast)
202 }
203
204 fn SetCaption(
206 &self,
207 cx: &mut JSContext,
208 new_caption: Option<&HTMLTableCaptionElement>,
209 ) -> Fallible<()> {
210 if let Some(ref caption) = self.GetCaption() {
211 caption.upcast::<Node>().remove_self(cx);
212 }
213
214 if let Some(caption) = new_caption {
215 let node = self.upcast::<Node>();
216 node.InsertBefore(cx, caption.upcast(), node.GetFirstChild().as_deref())?;
217 }
218
219 Ok(())
220 }
221
222 fn CreateCaption(&self, cx: &mut JSContext) -> DomRoot<HTMLTableCaptionElement> {
224 match self.GetCaption() {
225 Some(caption) => caption,
226 None => {
227 let caption = Element::create(
228 cx,
229 QualName::new(None, ns!(html), local_name!("caption")),
230 None,
231 &self.owner_document(),
232 ElementCreator::ScriptCreated,
233 CustomElementCreationMode::Asynchronous,
234 None,
235 );
236 let caption = DomRoot::downcast::<HTMLTableCaptionElement>(caption).unwrap();
237
238 self.SetCaption(cx, Some(&caption))
239 .expect("Generated caption is invalid");
240 caption
241 },
242 }
243 }
244
245 fn DeleteCaption(&self, cx: &mut JSContext) {
247 if let Some(caption) = self.GetCaption() {
248 caption.upcast::<Node>().remove_self(cx);
249 }
250 }
251
252 fn GetTHead(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
254 self.get_first_section_of_type(&local_name!("thead"))
255 }
256
257 fn SetTHead(&self, cx: &mut JSContext, thead: Option<&HTMLTableSectionElement>) -> ErrorResult {
259 self.set_first_section_of_type(cx, &local_name!("thead"), thead, |n| {
260 !n.is::<HTMLTableCaptionElement>() && !n.is::<HTMLTableColElement>()
261 })
262 }
263
264 fn CreateTHead(&self, cx: &mut JSContext) -> DomRoot<HTMLTableSectionElement> {
266 self.create_section_of_type(cx, &local_name!("thead"))
267 }
268
269 fn DeleteTHead(&self, cx: &mut JSContext) {
271 self.delete_first_section_of_type(cx, &local_name!("thead"))
272 }
273
274 fn GetTFoot(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
276 self.get_first_section_of_type(&local_name!("tfoot"))
277 }
278
279 fn SetTFoot(&self, cx: &mut JSContext, tfoot: Option<&HTMLTableSectionElement>) -> ErrorResult {
281 self.set_first_section_of_type(cx, &local_name!("tfoot"), tfoot, |n| {
282 if n.is::<HTMLTableCaptionElement>() || n.is::<HTMLTableColElement>() {
283 return false;
284 }
285
286 if n.is::<HTMLTableSectionElement>() {
287 let name = n.local_name();
288 if name == &local_name!("thead") || name == &local_name!("tbody") {
289 return false;
290 }
291 }
292
293 true
294 })
295 }
296
297 fn CreateTFoot(&self, cx: &mut JSContext) -> DomRoot<HTMLTableSectionElement> {
299 self.create_section_of_type(cx, &local_name!("tfoot"))
300 }
301
302 fn DeleteTFoot(&self, cx: &mut JSContext) {
304 self.delete_first_section_of_type(cx, &local_name!("tfoot"))
305 }
306
307 fn TBodies(&self, cx: &mut JSContext) -> DomRoot<HTMLCollection> {
309 self.tbodies.or_init(|| {
310 HTMLCollection::new_with_filter_fn(
311 cx,
312 &self.owner_window(),
313 self.upcast(),
314 |element, root| {
315 element.is::<HTMLTableSectionElement>() &&
316 element.local_name() == &local_name!("tbody") &&
317 element.upcast::<Node>().GetParentNode().as_deref() == Some(root)
318 },
319 )
320 })
321 }
322
323 fn CreateTBody(&self, cx: &mut JSContext) -> DomRoot<HTMLTableSectionElement> {
325 let tbody = Element::create(
326 cx,
327 QualName::new(None, ns!(html), local_name!("tbody")),
328 None,
329 &self.owner_document(),
330 ElementCreator::ScriptCreated,
331 CustomElementCreationMode::Asynchronous,
332 None,
333 );
334 let tbody = DomRoot::downcast::<HTMLTableSectionElement>(tbody).unwrap();
335 let node = self.upcast::<Node>();
336 let last_tbody = node
337 .rev_children()
338 .filter_map(DomRoot::downcast::<Element>)
339 .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody"));
340 let reference_element = last_tbody.and_then(|t| t.upcast::<Node>().GetNextSibling());
341
342 node.InsertBefore(cx, tbody.upcast(), reference_element.as_deref())
343 .expect("Insertion failed");
344 tbody
345 }
346
347 fn InsertRow(&self, cx: &mut JSContext, index: i32) -> Fallible<DomRoot<HTMLTableRowElement>> {
349 let rows = self.Rows(cx);
350 let number_of_row_elements = rows.Length();
351
352 if index < -1 || index > number_of_row_elements as i32 {
353 return Err(Error::IndexSize(None));
354 }
355
356 let new_row = Element::create(
357 cx,
358 QualName::new(None, ns!(html), local_name!("tr")),
359 None,
360 &self.owner_document(),
361 ElementCreator::ScriptCreated,
362 CustomElementCreationMode::Asynchronous,
363 None,
364 );
365 let new_row = DomRoot::downcast::<HTMLTableRowElement>(new_row).unwrap();
366 let node = self.upcast::<Node>();
367
368 if number_of_row_elements == 0 {
369 if let Some(last_tbody) = node
371 .rev_children()
372 .filter_map(DomRoot::downcast::<Element>)
373 .find(|n| {
374 n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")
375 })
376 {
377 last_tbody
378 .upcast::<Node>()
379 .AppendChild(cx, new_row.upcast::<Node>())
380 .expect("InsertRow failed to append first row.");
381 } else {
382 let tbody = self.CreateTBody(cx);
383 node.AppendChild(cx, tbody.upcast())
384 .expect("InsertRow failed to append new tbody.");
385
386 tbody
387 .upcast::<Node>()
388 .AppendChild(cx, new_row.upcast::<Node>())
389 .expect("InsertRow failed to append first row.");
390 }
391 } else if index == number_of_row_elements as i32 || index == -1 {
392 let last_row = rows
394 .Item(number_of_row_elements - 1)
395 .expect("InsertRow failed to find last row in table.");
396
397 let last_row_parent = last_row
398 .upcast::<Node>()
399 .GetParentNode()
400 .expect("InsertRow failed to find parent of last row in table.");
401
402 last_row_parent
403 .upcast::<Node>()
404 .AppendChild(cx, new_row.upcast::<Node>())
405 .expect("InsertRow failed to append last row.");
406 } else {
407 let ith_row = rows
409 .Item(index as u32)
410 .expect("InsertRow failed to find a row in table.");
411
412 let ith_row_parent = ith_row
413 .upcast::<Node>()
414 .GetParentNode()
415 .expect("InsertRow failed to find parent of a row in table.");
416
417 ith_row_parent
418 .upcast::<Node>()
419 .InsertBefore(cx, new_row.upcast::<Node>(), Some(ith_row.upcast::<Node>()))
420 .expect("InsertRow failed to append row");
421 }
422
423 Ok(new_row)
424 }
425
426 fn DeleteRow(&self, cx: &mut JSContext, mut index: i32) -> Fallible<()> {
428 let rows = self.Rows(cx);
429 let num_rows = rows.Length() as i32;
430
431 if !(-1..num_rows).contains(&index) {
434 return Err(Error::IndexSize(None));
435 }
436
437 let num_rows = rows.Length() as i32;
438
439 if index == -1 {
442 index = num_rows - 1;
443 }
444
445 if num_rows == 0 {
446 return Ok(());
447 }
448
449 DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(cx);
451
452 Ok(())
453 }
454
455 make_getter!(BgColor, "bgcolor");
457
458 make_legacy_color_setter!(SetBgColor, "bgcolor");
460
461 make_getter!(Width, "width");
463
464 make_nonzero_dimension_setter!(SetWidth, "width");
466
467 make_setter!(cx, SetAlign, "align");
469 make_getter!(Align, "align");
470
471 make_setter!(cx, SetCellPadding, "cellpadding");
473 make_getter!(CellPadding, "cellpadding");
474
475 make_setter!(cx, SetCellSpacing, "cellspacing");
477 make_getter!(CellSpacing, "cellspacing");
478}
479
480impl LayoutDom<'_, HTMLTableElement> {
481 pub(crate) fn get_background_color(self) -> Option<AbsoluteColor> {
482 self.upcast::<Element>()
483 .get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
484 .and_then(AttrValue::as_color)
485 .cloned()
486 }
487
488 pub(crate) fn get_border(self) -> Option<u32> {
489 (self.unsafe_get()).border.get()
490 }
491
492 pub(crate) fn get_cellpadding(self) -> Option<u32> {
493 (self.unsafe_get()).cellpadding.get()
494 }
495
496 pub(crate) fn get_cellspacing(self) -> Option<u32> {
497 (self.unsafe_get()).cellspacing.get()
498 }
499
500 pub(crate) fn get_width(self) -> LengthOrPercentageOrAuto {
501 self.upcast::<Element>()
502 .get_attr_for_layout(&ns!(), &local_name!("width"))
503 .map(AttrValue::as_dimension)
504 .cloned()
505 .unwrap_or(LengthOrPercentageOrAuto::Auto)
506 }
507
508 pub(crate) fn get_height(self) -> LengthOrPercentageOrAuto {
509 self.upcast::<Element>()
510 .get_attr_for_layout(&ns!(), &local_name!("height"))
511 .map(AttrValue::as_dimension)
512 .cloned()
513 .unwrap_or(LengthOrPercentageOrAuto::Auto)
514 }
515}
516
517impl VirtualMethods for HTMLTableElement {
518 fn super_type(&self) -> Option<&dyn VirtualMethods> {
519 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
520 }
521
522 fn attribute_mutated(
523 &self,
524 cx: &mut js::context::JSContext,
525 attr: &Attr,
526 mutation: AttributeMutation,
527 ) {
528 self.super_type()
529 .unwrap()
530 .attribute_mutated(cx, attr, mutation);
531 match *attr.local_name() {
532 local_name!("border") => {
533 self.border.set(
535 mutation
536 .new_value(attr)
537 .map(|value| parse_unsigned_integer(value.chars()).unwrap_or(1)),
538 );
539 },
540 local_name!("cellpadding") => {
541 self.cellpadding.set(
542 mutation
543 .new_value(attr)
544 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
545 );
546 },
547 local_name!("cellspacing") => {
548 self.cellspacing.set(
549 mutation
550 .new_value(attr)
551 .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
552 );
553 },
554 _ => {},
555 }
556 }
557
558 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
559 match attr.local_name() {
560 &local_name!("width") | &local_name!("height") => true,
561 _ => self
562 .super_type()
563 .unwrap()
564 .attribute_affects_presentational_hints(attr),
565 }
566 }
567
568 fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
569 match *local_name {
570 local_name!("border") => AttrValue::from_u32(value.into(), 1),
571 local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
572 local_name!("height") => AttrValue::from_dimension(value.into()),
573 local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
574 _ => self
575 .super_type()
576 .unwrap()
577 .parse_plain_attribute(local_name, value),
578 }
579 }
580}