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, 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    // https://html.spec.whatwg.org/multipage/#dom-table-thead
99    // https://html.spec.whatwg.org/multipage/#dom-table-tfoot
100    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    // https://html.spec.whatwg.org/multipage/#dom-table-thead
111    // https://html.spec.whatwg.org/multipage/#dom-table-tfoot
112    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    // https://html.spec.whatwg.org/multipage/#dom-table-createthead
143    // https://html.spec.whatwg.org/multipage/#dom-table-createtfoot
144    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(&section)),
157            local_name!("tfoot") => self.SetTFoot(Some(&section)),
158            _ => unreachable!("unexpected section type"),
159        }
160        .expect("unexpected section type");
161
162        section
163    }
164
165    // https://html.spec.whatwg.org/multipage/#dom-table-deletethead
166    // https://html.spec.whatwg.org/multipage/#dom-table-deletetfoot
167    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    // https://html.spec.whatwg.org/multipage/#dom-table-rows
189    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    // https://html.spec.whatwg.org/multipage/#dom-table-caption
200    fn GetCaption(&self) -> Option<DomRoot<HTMLTableCaptionElement>> {
201        self.upcast::<Node>()
202            .children()
203            .filter_map(DomRoot::downcast)
204            .next()
205    }
206
207    // https://html.spec.whatwg.org/multipage/#dom-table-caption
208    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    // https://html.spec.whatwg.org/multipage/#dom-table-createcaption
226    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    // https://html.spec.whatwg.org/multipage/#dom-table-deletecaption
245    fn DeleteCaption(&self) {
246        if let Some(caption) = self.GetCaption() {
247            caption.upcast::<Node>().remove_self(CanGc::note());
248        }
249    }
250
251    // https://html.spec.whatwg.org/multipage/#dom-table-thead
252    fn GetTHead(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
253        self.get_first_section_of_type(&local_name!("thead"))
254    }
255
256    // https://html.spec.whatwg.org/multipage/#dom-table-thead
257    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    // https://html.spec.whatwg.org/multipage/#dom-table-createthead
267    fn CreateTHead(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
268        self.create_section_of_type(&local_name!("thead"), can_gc)
269    }
270
271    // https://html.spec.whatwg.org/multipage/#dom-table-deletethead
272    fn DeleteTHead(&self) {
273        self.delete_first_section_of_type(&local_name!("thead"), CanGc::note())
274    }
275
276    // https://html.spec.whatwg.org/multipage/#dom-table-tfoot
277    fn GetTFoot(&self) -> Option<DomRoot<HTMLTableSectionElement>> {
278        self.get_first_section_of_type(&local_name!("tfoot"))
279    }
280
281    // https://html.spec.whatwg.org/multipage/#dom-table-tfoot
282    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    // https://html.spec.whatwg.org/multipage/#dom-table-createtfoot
305    fn CreateTFoot(&self, can_gc: CanGc) -> DomRoot<HTMLTableSectionElement> {
306        self.create_section_of_type(&local_name!("tfoot"), can_gc)
307    }
308
309    // https://html.spec.whatwg.org/multipage/#dom-table-deletetfoot
310    fn DeleteTFoot(&self) {
311        self.delete_first_section_of_type(&local_name!("tfoot"), CanGc::note())
312    }
313
314    // https://html.spec.whatwg.org/multipage/#dom-table-tbodies
315    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    // https://html.spec.whatwg.org/multipage/#dom-table-createtbody
331    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    // https://html.spec.whatwg.org/multipage/#dom-table-insertrow
352    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            // append new row to last or new tbody in table
371            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            // append new row to parent of last row in table
394            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            // insert new row before the index-th row in rows using the same parent
409            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    /// <https://html.spec.whatwg.org/multipage/#dom-table-deleterow>
432    fn DeleteRow(&self, mut index: i32) -> Fallible<()> {
433        let rows = self.Rows();
434        let num_rows = rows.Length() as i32;
435
436        // Step 1: If index is less than −1 or greater than or equal to the number of elements
437        // in the rows collection, then throw an "IndexSizeError".
438        if !(-1..num_rows).contains(&index) {
439            return Err(Error::IndexSize);
440        }
441
442        let num_rows = rows.Length() as i32;
443
444        // Step 2: If index is −1, then remove the last element in the rows collection from its
445        // parent, or do nothing if the rows collection is empty.
446        if index == -1 {
447            index = num_rows - 1;
448        }
449
450        if num_rows == 0 {
451            return Ok(());
452        }
453
454        // Step 3: Otherwise, remove the indexth element in the rows collection from its parent.
455        DomRoot::upcast::<Node>(rows.Item(index as u32).unwrap()).remove_self(CanGc::note());
456
457        Ok(())
458    }
459
460    // https://html.spec.whatwg.org/multipage/#dom-table-bgcolor
461    make_getter!(BgColor, "bgcolor");
462
463    // https://html.spec.whatwg.org/multipage/#dom-table-bgcolor
464    make_legacy_color_setter!(SetBgColor, "bgcolor");
465
466    // https://html.spec.whatwg.org/multipage/#dom-table-width
467    make_getter!(Width, "width");
468
469    // https://html.spec.whatwg.org/multipage/#dom-table-width
470    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                // According to HTML5 § 14.3.9, invalid values map to 1px.
531                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}