servo-script 0.1.0

A component of the servo web-engine.
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name};
use js::context::JSContext;
use js::rust::HandleObject;
use script_bindings::str::DOMString;
use stylo_dom::ElementState;

use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding::HTMLOptGroupElementMethods;
use crate::dom::bindings::codegen::GenericBindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmloptionelement::HTMLOptionElement;
use crate::dom::html::htmlselectelement::HTMLSelectElement;
use crate::dom::node::{BindContext, Node, UnbindContext};
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidationFlags;
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;

/// <https://html.spec.whatwg.org/multipage/#htmloptgroupelement>
#[dom_struct]
pub(crate) struct HTMLOptGroupElement {
    htmlelement: HTMLElement,
}

impl HTMLOptGroupElement {
    fn new_inherited(
        local_name: LocalName,
        prefix: Option<Prefix>,
        document: &Document,
    ) -> HTMLOptGroupElement {
        HTMLOptGroupElement {
            htmlelement: HTMLElement::new_inherited_with_state(
                ElementState::ENABLED,
                local_name,
                prefix,
                document,
            ),
        }
    }

    pub(crate) fn new(
        local_name: LocalName,
        prefix: Option<Prefix>,
        document: &Document,
        proto: Option<HandleObject>,
        can_gc: CanGc,
    ) -> DomRoot<HTMLOptGroupElement> {
        Node::reflect_node_with_proto(
            Box::new(HTMLOptGroupElement::new_inherited(
                local_name, prefix, document,
            )),
            document,
            proto,
            can_gc,
        )
    }

    fn update_select_validity(&self, can_gc: CanGc) {
        if let Some(select) = self.owner_select_element() {
            select
                .validity_state(can_gc)
                .perform_validation_and_update(ValidationFlags::all(), can_gc);
        }
    }

    fn owner_select_element(&self) -> Option<DomRoot<HTMLSelectElement>> {
        self.upcast::<Node>()
            .GetParentNode()
            .and_then(DomRoot::downcast)
    }
}

impl HTMLOptGroupElementMethods<crate::DomTypeHolder> for HTMLOptGroupElement {
    // https://html.spec.whatwg.org/multipage/#dom-optgroup-disabled
    make_bool_getter!(Disabled, "disabled");

    // https://html.spec.whatwg.org/multipage/#dom-optgroup-disabled
    make_bool_setter!(SetDisabled, "disabled");

    // https://html.spec.whatwg.org/multipage/#dom-optgroup-label
    make_getter!(Label, "label");

    // https://html.spec.whatwg.org/multipage/#dom-optgroup-label
    make_setter!(SetLabel, "label");
}

impl VirtualMethods for HTMLOptGroupElement {
    fn super_type(&self) -> Option<&dyn VirtualMethods> {
        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
    }

    fn attribute_mutated(
        &self,
        cx: &mut js::context::JSContext,
        attr: &Attr,
        mutation: AttributeMutation,
    ) {
        self.super_type()
            .unwrap()
            .attribute_mutated(cx, attr, mutation);
        if attr.local_name() == &local_name!("disabled") {
            let disabled_state = match mutation {
                AttributeMutation::Set(None, _) => true,
                AttributeMutation::Set(Some(_), _) => {
                    // Option group was already disabled.
                    return;
                },
                AttributeMutation::Removed => false,
            };
            let el = self.upcast::<Element>();
            el.set_disabled_state(disabled_state);
            el.set_enabled_state(!disabled_state);
            let options = el
                .upcast::<Node>()
                .children()
                .filter(|child| child.is::<HTMLOptionElement>())
                .map(|child| DomRoot::from_ref(child.downcast::<HTMLOptionElement>().unwrap()));
            if disabled_state {
                for option in options {
                    let el = option.upcast::<Element>();
                    el.set_disabled_state(true);
                    el.set_enabled_state(false);
                }
            } else {
                for option in options {
                    let el = option.upcast::<Element>();
                    el.check_disabled_attribute();
                }
            }
        }
    }

    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
        if let Some(super_type) = self.super_type() {
            super_type.bind_to_tree(cx, context);
        }

        self.update_select_validity(CanGc::from_cx(cx));
    }

    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
        self.super_type().unwrap().unbind_from_tree(context, can_gc);

        if let Some(select) = context.parent.downcast::<HTMLSelectElement>() {
            select
                .validity_state(can_gc)
                .perform_validation_and_update(ValidationFlags::all(), can_gc);
        }
    }
}