afia-component 0.0.2

A high-level Rust wrapper for `libafia_component`.
Documentation
//! Types related to the DOM elements.

use std::slice;
use std::sync::Arc;

use crate::dom::node::AsDomNode;
use crate::dom::node_list::NodeList;
use crate::ComponentImports;

mod child;

/// A DOM element.
#[derive(Clone)]
pub struct DomElement {
    inner: Arc<DomElementInner>,
}
struct DomElementInner {
    element: i64,
    component_imports: ComponentImports,
}
impl DomElement {
    /// If the handle is `0`, returns `None`.
    /// Otherwise, returns a `DomElement`.
    pub fn maybe_new(handle: i64, component_imports: ComponentImports) -> Option<Self> {
        if handle == 0 {
            return None;
        }

        Some(Self {
            inner: Arc::new(DomElementInner {
                element: handle,
                component_imports,
            }),
        })
    }

    /// Will be deleted once we stop using it.
    pub fn temporary_way_to_get_i64(&self) -> i64 {
        self.to_i64()
    }

    pub(crate) fn component_imports(&self) -> &ComponentImports {
        &self.inner.component_imports
    }

    pub(crate) fn component_imports_ptr(&self) -> *const std::ffi::c_void {
        self.inner.component_imports.component_imports_ptr
    }

    pub(crate) fn to_i64(&self) -> i64 {
        self.inner.element
    }

    /// Adding this because some of our older components are using older patterns and this will help
    ///  with migrating them towards using this `afia-component` crate.
    ///  DO NOT use this. It was introduced in the middle of a huge PR to help avoid needing to
    ///  refctor everything at once.
    pub fn do_not_use_this_for_new_stuff_temporary_public_way_to_create_using_existing_i64_handle(
        &self,
        element_handle: i64,
    ) -> Option<Self> {
        Self::maybe_new(element_handle, self.inner.component_imports.clone())
    }

    /// Set the text content of this element.
    pub fn set_text_content(&self, text: &str) {
        unsafe {
            afia_component_sys::dom_element_set_text_content(
                self.inner.component_imports.pointer(),
                self.inner.element,
                text.as_ptr(),
                text.len(),
            )
        }
    }

    /// Get this element's previous element sibling.
    ///
    /// ## MDN Documentation
    /// https://developer.mozilla.org/en-US/docs/Web/API/Element/previousElementSibling
    pub fn previous_element_sibling(&self) -> Option<DomElement> {
        let sibling = unsafe {
            afia_component_sys::dom_element_previous_element_sibling(
                self.imports_ptr(),
                self.handle(),
            )
        };

        DomElement::maybe_new(sibling, self.inner.component_imports.clone())
    }

    /// Get this element's next element sibling.
    ///
    /// ## MDN Documentation
    /// https://developer.mozilla.org/en-US/docs/Web/API/Element/nextElementSibling
    pub fn next_element_sibling(&self) -> Option<DomElement> {
        let sibling = unsafe {
            afia_component_sys::dom_element_next_element_sibling(self.imports_ptr(), self.handle())
        };

        DomElement::maybe_new(sibling, self.inner.component_imports.clone())
    }

    /// Get the element's tag name, such as "DIV" or "SPAN".
    ///
    /// ## MDN Documentation
    /// https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName
    pub fn tag_name(&self) -> String {
        // TODO: `20` was chosen arbitrarily. This will fail if the tag is more than 20 chars.
        let mut buf = [0; 20];

        let tag_len = unsafe {
            afia_component_sys::element_tag_name(
                self.imports_ptr(),
                self.handle(),
                buf.as_mut_ptr(),
                buf.len(),
            )
        };
        // TODO: If the buffer is was small try again with a larger buffer.
        assert!(buf.len() as i32 >= tag_len);

        let tag_slice = unsafe { slice::from_raw_parts(buf.as_ptr(), tag_len as usize) };
        let tag = str::from_utf8(tag_slice).unwrap();
        tag.to_string()
    }

    /// Simulate a click on the element.
    ///
    /// ## MDN Documentation
    /// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click
    pub fn click(&self) {
        unsafe { afia_component_sys::element_click(self.imports_ptr(), self.handle()) }
    }

    /// Get the element's child nodes.
    ///
    /// ## MDN Documentation
    /// https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes
    pub fn child_nodes(&self) -> NodeList {
        let child_nodes =
            unsafe { afia_component_sys::element_child_nodes(self.imports_ptr(), self.handle()) };
        NodeList::new(child_nodes, self.component_imports().clone())
    }

    /// Get one of the element's attributes.
    ///
    /// ## MDN Documentation
    /// https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute
    pub fn get_attribute(&self, key: &str) -> Option<String> {
        let mut out = [0; 255];
        let result = unsafe {
            afia_component_sys::element_get_attribute(
                self.imports_ptr(),
                self.handle(),
                key.as_ptr(),
                key.len(),
                out.as_mut_ptr(),
                out.len(),
            )
        };
        match result {
            ..-1 => {
                todo!("error handling")
            }
            -1 => None,
            0 => None,
            1.. => {
                let result = result as usize;
                if out.len() < result {
                    todo!("handle scenario where buffer was smaller than the value")
                };
                let value = str::from_utf8(&out[0..result]).unwrap();
                Some(value.to_string())
            }
        }
    }

    /// Get the DOM element's inner text.
    ///
    /// ## MDN Documentation
    /// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText
    pub fn inner_text(&self) -> String {
        let mut out = [0u8; 1000];
        let result = unsafe {
            afia_component_sys::element_inner_text(
                self.imports_ptr(),
                self.handle(),
                out.as_mut_ptr(),
                out.len(),
            )
        };
        match result {
            ..=-1 => {
                todo!("error handling")
            }
            0.. => {
                let result = result as usize;
                if out.len() < result {
                    todo!("handle scenario where buffer was smaller than the value")
                };
                let value = str::from_utf8(&out[0..result]).unwrap();
                value.to_string()
            }
        }
    }
}
impl Drop for DomElementInner {
    fn drop(&mut self) {
        // TODO: Call a function to free the `self.element`
    }
}

impl ComponentImports {
    /// Create a new [`DomElement`].
    pub fn create_element(&self, tag: &str) -> Option<DomElement> {
        let element = unsafe {
            afia_component_sys::create_element(self.component_imports_ptr, tag.as_ptr(), tag.len())
        };

        DomElement::maybe_new(element, self.clone())
    }
}