script/dom/html/
htmltableelement.rs

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