servo-script 0.2.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 style::attr::AttrValue;

use crate::dom::attr::Attr;
use crate::dom::bindings::codegen::Bindings::HTMLSourceElementBinding::HTMLSourceElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{Dom, DomRoot, Root};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::Document;
use crate::dom::element::AttributeMutation;
use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlimageelement::HTMLImageElement;
use crate::dom::html::htmlmediaelement::HTMLMediaElement;
use crate::dom::html::htmlpictureelement::HTMLPictureElement;
use crate::dom::node::{BindContext, Node, NodeDamage, UnbindContext};
use crate::dom::virtualmethods::VirtualMethods;

#[dom_struct]
pub(crate) struct HTMLSourceElement {
    htmlelement: HTMLElement,
}

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

    pub(crate) fn new(
        cx: &mut js::context::JSContext,
        local_name: LocalName,
        prefix: Option<Prefix>,
        document: &Document,
        proto: Option<HandleObject>,
    ) -> DomRoot<HTMLSourceElement> {
        Node::reflect_node_with_proto(
            cx,
            Box::new(HTMLSourceElement::new_inherited(
                local_name, prefix, document,
            )),
            document,
            proto,
        )
    }

    fn iterate_next_html_image_element_siblings(
        next_siblings_iterator: impl Iterator<Item = Root<Dom<Node>>>,
        callback: impl Fn(&HTMLImageElement),
    ) {
        for next_sibling in next_siblings_iterator {
            if let Some(html_image_element_sibling) = next_sibling.downcast::<HTMLImageElement>() {
                callback(html_image_element_sibling);
            }
        }
    }

    fn iterate_next_html_image_element_siblings_with_cx(
        cx: &mut JSContext,
        next_siblings_iterator: impl Iterator<Item = Root<Dom<Node>>>,
        callback: impl Fn(&mut JSContext, &HTMLImageElement),
    ) {
        for next_sibling in next_siblings_iterator {
            if let Some(html_image_element_sibling) = next_sibling.downcast::<HTMLImageElement>() {
                callback(cx, html_image_element_sibling);
            }
        }
    }
}

impl VirtualMethods for HTMLSourceElement {
    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);

        match attr.local_name() {
            &local_name!("srcset") |
            &local_name!("sizes") |
            &local_name!("media") |
            &local_name!("type") => {
                // <https://html.spec.whatwg.org/multipage/#reacting-to-dom-mutations>
                // The element's parent is a picture element and a source element that is a previous
                // sibling has its srcset, sizes, media, type attributes set, changed, or removed.
                if let Some(parent) = self.upcast::<Node>().GetParentElement() {
                    if parent.is::<HTMLPictureElement>() {
                        let next_sibling_iterator = self.upcast::<Node>().following_siblings();
                        HTMLSourceElement::iterate_next_html_image_element_siblings_with_cx(
                            cx,
                            next_sibling_iterator,
                            |cx, image| image.update_the_image_data(cx),
                        );
                    }
                }
            },
            &local_name!("width") | &local_name!("height") => {
                // Note: Despite being explicitly stated in the specification that any width or
                // height attributes changes (set, changed, removed) of the source element should be
                // counted as relevant mutation for the sibling image element, these attributes
                // affect only the style presentational hints of the image element.
                if let Some(parent) = self.upcast::<Node>().GetParentElement() {
                    if parent.is::<HTMLPictureElement>() {
                        let next_sibling_iterator = self.upcast::<Node>().following_siblings();
                        HTMLSourceElement::iterate_next_html_image_element_siblings(
                            next_sibling_iterator,
                            |image| image.upcast::<Node>().dirty(NodeDamage::Other),
                        );
                    }
                }
            },
            _ => {},
        }
    }

    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
        match name {
            &local_name!("width") | &local_name!("height") => {
                AttrValue::from_dimension(value.into())
            },
            _ => self
                .super_type()
                .unwrap()
                .parse_plain_attribute(name, value),
        }
    }

    /// <https://html.spec.whatwg.org/multipage/#the-source-element:html-element-insertion-steps>
    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
        self.super_type().unwrap().bind_to_tree(cx, context);

        // Step 1. Let parent be insertedNode's parent.
        let parent = self.upcast::<Node>().GetParentNode().unwrap();

        // Step 2. If parent is a media element that has no src attribute and whose networkState has
        // the value NETWORK_EMPTY, then invoke that media element's resource selection algorithm.
        if parent.is::<HTMLMediaElement>() && std::ptr::eq(&*parent, context.parent) {
            parent
                .downcast::<HTMLMediaElement>()
                .unwrap()
                .handle_source_child_insertion(self, cx);
        }

        // Step 3. If parent is a picture element, then for each child of parent's children, if
        // child is an img element, then count this as a relevant mutation for child.
        if parent.is::<HTMLPictureElement>() && std::ptr::eq(&*parent, context.parent) {
            let next_sibling_iterator = self.upcast::<Node>().following_siblings();
            HTMLSourceElement::iterate_next_html_image_element_siblings_with_cx(
                cx,
                next_sibling_iterator,
                |cx, image| image.update_the_image_data(cx),
            );
        }
    }

    /// <https://html.spec.whatwg.org/multipage/#the-source-element:html-element-removing-steps>
    fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
        self.super_type().unwrap().unbind_from_tree(cx, context);

        // Step 1. If oldParent is a picture element, then for each child of oldParent's children,
        // if child is an img element, then count this as a relevant mutation for child.
        if context.parent.is::<HTMLPictureElement>() && !self.upcast::<Node>().has_parent() {
            if let Some(next_sibling) = context.next_sibling {
                let next_sibling_iterator = next_sibling.inclusively_following_siblings();
                HTMLSourceElement::iterate_next_html_image_element_siblings_with_cx(
                    cx,
                    next_sibling_iterator,
                    |cx, image| image.update_the_image_data(cx),
                );
            }
        }
    }
}

impl HTMLSourceElementMethods<crate::DomTypeHolder> for HTMLSourceElement {
    // https://html.spec.whatwg.org/multipage/#dom-source-src
    make_url_getter!(Src, "src");

    // https://html.spec.whatwg.org/multipage/#dom-source-src
    make_url_setter!(SetSrc, "src");

    // https://html.spec.whatwg.org/multipage/#dom-source-type
    make_getter!(Type, "type");

    // https://html.spec.whatwg.org/multipage/#dom-source-type
    make_setter!(SetType, "type");

    // https://html.spec.whatwg.org/multipage/#dom-source-srcset
    make_url_getter!(Srcset, "srcset");

    // https://html.spec.whatwg.org/multipage/#dom-source-srcset
    make_url_setter!(SetSrcset, "srcset");

    // https://html.spec.whatwg.org/multipage/#dom-source-sizes
    make_getter!(Sizes, "sizes");

    // https://html.spec.whatwg.org/multipage/#dom-source-sizes
    make_setter!(SetSizes, "sizes");

    // https://html.spec.whatwg.org/multipage/#dom-source-media
    make_getter!(Media, "media");

    // https://html.spec.whatwg.org/multipage/#dom-source-media
    make_setter!(SetMedia, "media");

    // <https://html.spec.whatwg.org/multipage/#dom-source-width>
    make_dimension_uint_getter!(Width, "width");

    // <https://html.spec.whatwg.org/multipage/#dom-source-width>
    make_dimension_uint_setter!(SetWidth, "width");

    // <https://html.spec.whatwg.org/multipage/#dom-source-height>
    make_dimension_uint_getter!(Height, "height");

    // <https://html.spec.whatwg.org/multipage/#dom-source-height>
    make_dimension_uint_setter!(SetHeight, "height");
}