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, 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    // https://html.spec.whatwg.org/multipage/#dom-table-thead
101    // https://html.spec.whatwg.org/multipage/#dom-table-tfoot
102    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    // https://html.spec.whatwg.org/multipage/#dom-table-thead
113    // https://html.spec.whatwg.org/multipage/#dom-table-tfoot
114    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);
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    // https://html.spec.whatwg.org/multipage/#dom-table-createthead
145    // https://html.spec.whatwg.org/multipage/#dom-table-createtfoot
146    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(&section)),
169            local_name!("tfoot") => self.SetTFoot(Some(&section)),
170            _ => unreachable!("unexpected section type"),
171        }
172        .expect("unexpected section type");
173
174        section
175    }
176
177    // https://html.spec.whatwg.org/multipage/#dom-table-deletethead
178    // https://html.spec.whatwg.org/multipage/#dom-table-deletetfoot
179    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    // https://html.spec.whatwg.org/multipage/#dom-table-rows
201    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    // https://html.spec.whatwg.org/multipage/#dom-table-caption
212    fn GetCaption(&self) -> Option<DomRoot<HTMLTableCaptionElement>> {
213        self.upcast::<Node>()
214            .children()
215            .filter_map(DomRoot::downcast)
216            .next()
217    }
218
219    // https://html.spec.whatwg.org/multipage/#dom-table-caption
220    fn SetCaption(&self, new_caption: Option<&HTMLTableCaptionElement>) -> Fallible<()> {
221        if let Some(ref caption) = self.GetCaption() {
222            caption.upcast::<Node>().remove_self(CanGc::note());
223        }
224
225        if let Some(caption) = new_caption {
226            let node = self.upcast::<Node>();
227            node.InsertBefore(
228                caption.upcast(),
229                node.GetFirstChild().as_deref(),
230                CanGc::note(),
231            )?;
232        }
233
234        Ok(())
235    }
236
237    // https://html.spec.whatwg.org/multipage/#dom-table-createcaption
238    fn CreateCaption(&self, can_gc: CanGc) -> DomRoot<HTMLTableCaptionElement> {
239        match self.GetCaption() {
240            Some(caption) => caption,
241            None => {
242                let caption = Element::create(
243                    QualName::new(None, ns!(html), local_name!("caption")),
244                    None,
245                    &self.owner_document(),
246                    ElementCreator::ScriptCreated,
247                    CustomElementCreationMode::Asynchronous,
248                    None,
249                    can_gc,
250                );
251                let caption = DomRoot::downcast::<HTMLTableCaptionElement>(caption).unwrap();
252
253                self.SetCaption(Some(&caption))
254                    .expect("Generated caption is invalid");
255                caption
256            },
257        }
258    }
259
260    // https://html.spec.whatwg.org/multipage/#dom-table-deletecaption
261    fn DeleteCaption(&self) {
262        if let Some(caption) = self.GetCaption() {
263            caption.upcast::<Node>().remove_self(CanGc::note());
264        }
265    }
266
267    // https://html.spec.whatwg.org/multipage/#dom-table-thead
268    fn GetTHead(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
269        self.get_first_section_of_type(&local_name!("thead"))
270    }
271
272    // https://html.spec.whatwg.org/multipage/#dom-table-thead
273    fn SetTHead(&self, thead: Option<&HTMLTableSectionElement>) -> ErrorResult {
274        self.set_first_section_of_type(
275            &local_name!("thead"),
276            thead,
277            |n| !n.is::<HTMLTableCaptionElement>() && !n.is::<HTMLTableColElement>(),
278            CanGc::note(),
279        )
280    }
281
282    // https://html.spec.whatwg.org/multipage/#dom-table-createthead
283    fn CreateTHead(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
284        self.create_section_of_type(&local_name!("thead"), can_gc)
285    }
286
287    // https://html.spec.whatwg.org/multipage/#dom-table-deletethead
288    fn DeleteTHead(&self) {
289        self.delete_first_section_of_type(&local_name!("thead"), CanGc::note())
290    }
291
292    // https://html.spec.whatwg.org/multipage/#dom-table-tfoot
293    fn GetTFoot(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
294        self.get_first_section_of_type(&local_name!("tfoot"))
295    }
296
297    // https://html.spec.whatwg.org/multipage/#dom-table-tfoot
298    fn SetTFoot(&self, tfoot: Option<&HTMLTableSectionElement>) -> ErrorResult {
299        self.set_first_section_of_type(
300            &local_name!("tfoot"),
301            tfoot,
302            |n| {
303                if n.is::<HTMLTableCaptionElement>() || n.is::<HTMLTableColElement>() {
304                    return false;
305                }
306
307                if n.is::<HTMLTableSectionElement>() {
308                    let name = n.local_name();
309                    if name == &local_name!("thead") || name == &local_name!("tbody") {
310                        return false;
311                    }
312                }
313
314                true
315            },
316            CanGc::note(),
317        )
318    }
319
320    // https://html.spec.whatwg.org/multipage/#dom-table-createtfoot
321    fn CreateTFoot(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
322        self.create_section_of_type(&local_name!("tfoot"), can_gc)
323    }
324
325    // https://html.spec.whatwg.org/multipage/#dom-table-deletetfoot
326    fn DeleteTFoot(&self) {
327        self.delete_first_section_of_type(&local_name!("tfoot"), CanGc::note())
328    }
329
330    // https://html.spec.whatwg.org/multipage/#dom-table-tbodies
331    fn TBodies(&self) -> DomRoot<HTMLCollection> {
332        self.tbodies.or_init(|| {
333            HTMLCollection::new_with_filter_fn(
334                &self.owner_window(),
335                self.upcast(),
336                |element, root| {
337                    element.is::<HTMLTableSectionElement>() &&
338                        element.local_name() == &local_name!("tbody") &&
339                        element.upcast::<Node>().GetParentNode().as_deref() == Some(root)
340                },
341                CanGc::note(),
342            )
343        })
344    }
345
346    // https://html.spec.whatwg.org/multipage/#dom-table-createtbody
347    fn CreateTBody(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
348        let tbody = Element::create(
349            QualName::new(None, ns!(html), local_name!("tbody")),
350            None,
351            &self.owner_document(),
352            ElementCreator::ScriptCreated,
353            CustomElementCreationMode::Asynchronous,
354            None,
355            can_gc,
356        );
357        let tbody = DomRoot::downcast::<HTMLTableSectionElement>(tbody).unwrap();
358        let node = self.upcast::<Node>();
359        let last_tbody = node
360            .rev_children()
361            .filter_map(DomRoot::downcast::<Element>)
362            .find(|n| n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody"));
363        let reference_element = last_tbody.and_then(|t| t.upcast::<Node>().GetNextSibling());
364
365        node.InsertBefore(tbody.upcast(), reference_element.as_deref(), can_gc)
366            .expect("Insertion failed");
367        tbody
368    }
369
370    // https://html.spec.whatwg.org/multipage/#dom-table-insertrow
371    fn InsertRow(&self, index: i32, can_gc: CanGc) -> Fallible<DomRoot<HTMLTableRowElement>> {
372        let rows = self.Rows();
373        let number_of_row_elements = rows.Length();
374
375        if index < -1 || index > number_of_row_elements as i32 {
376            return Err(Error::IndexSize);
377        }
378
379        let new_row = Element::create(
380            QualName::new(None, ns!(html), local_name!("tr")),
381            None,
382            &self.owner_document(),
383            ElementCreator::ScriptCreated,
384            CustomElementCreationMode::Asynchronous,
385            None,
386            can_gc,
387        );
388        let new_row = DomRoot::downcast::<HTMLTableRowElement>(new_row).unwrap();
389        let node = self.upcast::<Node>();
390
391        if number_of_row_elements == 0 {
392            // append new row to last or new tbody in table
393            if let Some(last_tbody) = node
394                .rev_children()
395                .filter_map(DomRoot::downcast::<Element>)
396                .find(|n| {
397                    n.is::<HTMLTableSectionElement>() && n.local_name() == &local_name!("tbody")
398                })
399            {
400                last_tbody
401                    .upcast::<Node>()
402                    .AppendChild(new_row.upcast::<Node>(), can_gc)
403                    .expect("InsertRow failed to append first row.");
404            } else {
405                let tbody = self.CreateTBody(can_gc);
406                node.AppendChild(tbody.upcast(), can_gc)
407                    .expect("InsertRow failed to append new tbody.");
408
409                tbody
410                    .upcast::<Node>()
411                    .AppendChild(new_row.upcast::<Node>(), can_gc)
412                    .expect("InsertRow failed to append first row.");
413            }
414        } else if index == number_of_row_elements as i32 || index == -1 {
415            // append new row to parent of last row in table
416            let last_row = rows
417                .Item(number_of_row_elements - 1)
418                .expect("InsertRow failed to find last row in table.");
419
420            let last_row_parent = last_row
421                .upcast::<Node>()
422                .GetParentNode()
423                .expect("InsertRow failed to find parent of last row in table.");
424
425            last_row_parent
426                .upcast::<Node>()
427                .AppendChild(new_row.upcast::<Node>(), can_gc)
428                .expect("InsertRow failed to append last row.");
429        } else {
430            // insert new row before the index-th row in rows using the same parent
431            let ith_row = rows
432                .Item(index as u32)
433                .expect("InsertRow failed to find a row in table.");
434
435            let ith_row_parent = ith_row
436                .upcast::<Node>()
437                .GetParentNode()
438                .expect("InsertRow failed to find parent of a row in table.");
439
440            ith_row_parent
441                .upcast::<Node>()
442                .InsertBefore(
443                    new_row.upcast::<Node>(),
444                    Some(ith_row.upcast::<Node>()),
445                    can_gc,
446                )
447                .expect("InsertRow failed to append row");
448        }
449
450        Ok(new_row)
451    }
452
453    /// <https://html.spec.whatwg.org/multipage/#dom-table-deleterow>
454    fn DeleteRow(&self, mut index: i32) -> Fallible<()> {
455        let rows = self.Rows();
456        let num_rows = rows.Length() as i32;
457
458        // Step 1: If index is less than −1 or greater than or equal to the number of elements
459        // in the rows collection, then throw an "IndexSizeError".
460        if !(-1..num_rows).contains(&index) {
461            return Err(Error::IndexSize);
462        }
463
464        let num_rows = rows.Length() as i32;
465
466        // Step 2: If index is −1, then remove the last element in the rows collection from its
467        // parent, or do nothing if the rows collection is empty.
468        if index == -1 {
469            index = num_rows - 1;
470        }
471
472        if num_rows == 0 {
473            return Ok(());
474        }
475
476        // Step 3: Otherwise, remove the indexth element in the rows collection from its parent.
477        DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(CanGc::note());
478
479        Ok(())
480    }
481
482    // https://html.spec.whatwg.org/multipage/#dom-table-bgcolor
483    make_getter!(BgColor, "bgcolor");
484
485    // https://html.spec.whatwg.org/multipage/#dom-table-bgcolor
486    make_legacy_color_setter!(SetBgColor, "bgcolor");
487
488    // https://html.spec.whatwg.org/multipage/#dom-table-width
489    make_getter!(Width, "width");
490
491    // https://html.spec.whatwg.org/multipage/#dom-table-width
492    make_nonzero_dimension_setter!(SetWidth, "width");
493}
494
495pub(crate) trait HTMLTableElementLayoutHelpers {
496    fn get_background_color(self) -> Option<AbsoluteColor>;
497    fn get_border(self) -> Option<u32>;
498    fn get_cellpadding(self) -> Option<u32>;
499    fn get_cellspacing(self) -> Option<u32>;
500    fn get_width(self) -> LengthOrPercentageOrAuto;
501    fn get_height(self) -> LengthOrPercentageOrAuto;
502}
503
504impl HTMLTableElementLayoutHelpers for LayoutDom<'_, HTMLTableElement> {
505    fn get_background_color(self) -> Option<AbsoluteColor> {
506        self.upcast::<Element>()
507            .get_attr_for_layout(&ns!(), &local_name!("bgcolor"))
508            .and_then(AttrValue::as_color)
509            .cloned()
510    }
511
512    fn get_border(self) -> Option<u32> {
513        (self.unsafe_get()).border.get()
514    }
515
516    fn get_cellpadding(self) -> Option<u32> {
517        (self.unsafe_get()).cellpadding.get()
518    }
519
520    fn get_cellspacing(self) -> Option<u32> {
521        (self.unsafe_get()).cellspacing.get()
522    }
523
524    fn get_width(self) -> LengthOrPercentageOrAuto {
525        self.upcast::<Element>()
526            .get_attr_for_layout(&ns!(), &local_name!("width"))
527            .map(AttrValue::as_dimension)
528            .cloned()
529            .unwrap_or(LengthOrPercentageOrAuto::Auto)
530    }
531
532    fn get_height(self) -> LengthOrPercentageOrAuto {
533        self.upcast::<Element>()
534            .get_attr_for_layout(&ns!(), &local_name!("height"))
535            .map(AttrValue::as_dimension)
536            .cloned()
537            .unwrap_or(LengthOrPercentageOrAuto::Auto)
538    }
539}
540
541impl VirtualMethods for HTMLTableElement {
542    fn super_type(&self) -> Option<&dyn VirtualMethods> {
543        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
544    }
545
546    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
547        self.super_type()
548            .unwrap()
549            .attribute_mutated(attr, mutation, can_gc);
550        match *attr.local_name() {
551            local_name!("border") => {
552                // According to HTML5 § 14.3.9, invalid values map to 1px.
553                self.border.set(
554                    mutation
555                        .new_value(attr)
556                        .map(|value| parse_unsigned_integer(value.chars()).unwrap_or(1)),
557                );
558            },
559            local_name!("cellpadding") => {
560                self.cellpadding.set(
561                    mutation
562                        .new_value(attr)
563                        .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
564                );
565            },
566            local_name!("cellspacing") => {
567                self.cellspacing.set(
568                    mutation
569                        .new_value(attr)
570                        .and_then(|value| parse_unsigned_integer(value.chars()).ok()),
571                );
572            },
573            _ => {},
574        }
575    }
576
577    fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
578        match *local_name {
579            local_name!("border") => AttrValue::from_u32(value.into(), 1),
580            local_name!("width") => AttrValue::from_nonzero_dimension(value.into()),
581            local_name!("height") => AttrValue::from_dimension(value.into()),
582            local_name!("bgcolor") => AttrValue::from_legacy_color(value.into()),
583            _ => self
584                .super_type()
585                .unwrap()
586                .parse_plain_attribute(local_name, value),
587        }
588    }
589}