<commit_before>/* 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 http://mozilla.org/MPL/2.0/. */

use {Atom, Prefix, Namespace, LocalName};
use attr::{AttrIdentifier, AttrValue};
use cssparser::ToCss;
use element_state::ElementState;
use restyle_hints::ElementSnapshot;
use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
use selector_parser::{attr_equals_selector_is_shareable, attr_exists_selector_is_shareable};
use selectors::{Element, MatchAttrGeneric};
use selectors::parser::AttrSelector;
use std::borrow::Cow;
use std::fmt;
use std::fmt::Debug;

/// NB: If you add to this list, be sure to update `each_pseudo_element` too.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum PseudoElement {
    Before,
    After,
    Selection,
    DetailsSummary,
    DetailsContent,
    ServoInputText,
    ServoTableWrapper,
    ServoAnonymousTableWrapper,
    ServoAnonymousTable,
    ServoAnonymousTableRow,
    ServoAnonymousTableCell,
    ServoAnonymousBlock,
}

impl ToCss for PseudoElement {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        use self::PseudoElement::*;
        dest.write_str(match *self {
            Before => "::before",
            After => "::after",
            Selection => "::selection",
            DetailsSummary => "::-servo-details-summary",
            DetailsContent => "::-servo-details-content",
            ServoInputText => "::-servo-input-text",
            ServoTableWrapper => "::-servo-table-wrapper",
            ServoAnonymousTableWrapper => "::-servo-anonymous-table-wrapper",
            ServoAnonymousTable => "::-servo-anonymous-table",
            ServoAnonymousTableRow => "::-servo-anonymous-table-row",
            ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
            ServoAnonymousBlock => "::-servo-anonymous-block",
        })
    }
}

impl PseudoElement {
    #[inline]
    pub fn is_before_or_after(&self) -> bool {
        match *self {
            PseudoElement::Before |
            PseudoElement::After => true,
            _ => false,
        }
    }

    #[inline]
    pub fn cascade_type(&self) -> PseudoElementCascadeType {
        match *self {
            PseudoElement::Before |
            PseudoElement::After |
            PseudoElement::Selection => PseudoElementCascadeType::Eager,
            PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
            PseudoElement::DetailsContent |
            PseudoElement::ServoInputText |
            PseudoElement::ServoTableWrapper |
            PseudoElement::ServoAnonymousTableWrapper |
            PseudoElement::ServoAnonymousTable |
            PseudoElement::ServoAnonymousTableRow |
            PseudoElement::ServoAnonymousTableCell |
            PseudoElement::ServoAnonymousBlock => PseudoElementCascadeType::Precomputed,
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum NonTSPseudoClass {
    AnyLink,
    Link,
    Visited,
    Active,
    Focus,
    Fullscreen,
    Hover,
    Enabled,
    Disabled,
    Checked,
    Indeterminate,
    ServoNonZeroBorder,
    ReadWrite,
    ReadOnly,
    PlaceholderShown,
    Target,
}

impl ToCss for NonTSPseudoClass {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        use self::NonTSPseudoClass::*;
        dest.write_str(match *self {
            AnyLink => ":any-link",
            Link => ":link",
            Visited => ":visited",
            Active => ":active",
            Focus => ":focus",
            Fullscreen => ":fullscreen",
            Hover => ":hover",
            Enabled => ":enabled",
            Disabled => ":disabled",
            Checked => ":checked",
            Indeterminate => ":indeterminate",
            ReadWrite => ":read-write",
            ReadOnly => ":read-only",
            PlaceholderShown => ":placeholder-shown",
            Target => ":target",
            ServoNonZeroBorder => ":-servo-nonzero-border",
        })
    }
}

impl NonTSPseudoClass {
    pub fn state_flag(&self) -> ElementState {
        use element_state::*;
        use self::NonTSPseudoClass::*;
        match *self {
            Active => IN_ACTIVE_STATE,
            Focus => IN_FOCUS_STATE,
            Fullscreen => IN_FULLSCREEN_STATE,
            Hover => IN_HOVER_STATE,
            Enabled => IN_ENABLED_STATE,
            Disabled => IN_DISABLED_STATE,
            Checked => IN_CHECKED_STATE,
            Indeterminate => IN_INDETERMINATE_STATE,
            ReadOnly | ReadWrite => IN_READ_WRITE_STATE,
            PlaceholderShown => IN_PLACEHOLDER_SHOWN_STATE,
            Target => IN_TARGET_STATE,

            AnyLink |
            Link |
            Visited |
            ServoNonZeroBorder => ElementState::empty(),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct SelectorImpl;

impl ::selectors::SelectorImpl for SelectorImpl {
    type PseudoElement = PseudoElement;
    type NonTSPseudoClass = NonTSPseudoClass;

    type AttrValue = String;
    type Identifier = Atom;
    type ClassName = Atom;
    type LocalName = LocalName;
    type NamespacePrefix = Prefix;
    type NamespaceUrl = Namespace;
    type BorrowedLocalName = LocalName;
    type BorrowedNamespaceUrl = Namespace;

    fn attr_exists_selector_is_shareable(attr_selector: &AttrSelector<Self>) -> bool {
        attr_exists_selector_is_shareable(attr_selector)
    }

    fn attr_equals_selector_is_shareable(attr_selector: &AttrSelector<Self>,
                                         value: &Self::AttrValue) -> bool {
        attr_equals_selector_is_shareable(attr_selector, value)
    }
}

impl<'a> ::selectors::Parser for SelectorParser<'a> {
    type Impl = SelectorImpl;

    fn parse_non_ts_pseudo_class(&self, name: Cow<str>) -> Result<NonTSPseudoClass, ()> {
        use self::NonTSPseudoClass::*;
        let pseudo_class = match_ignore_ascii_case! { &name,
            "any-link" => AnyLink,
            "link" => Link,
            "visited" => Visited,
            "active" => Active,
            "focus" => Focus,
            "fullscreen" => Fullscreen,
            "hover" => Hover,
            "enabled" => Enabled,
            "disabled" => Disabled,
            "checked" => Checked,
            "indeterminate" => Indeterminate,
            "read-write" => ReadWrite,
            "read-only" => ReadOnly,
            "placeholder-shown" => PlaceholderShown,
            "target" => Target,
            "-servo-nonzero-border" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(());
                }
                ServoNonZeroBorder
            },
            _ => return Err(())
        };

        Ok(pseudo_class)
    }

    fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
        use self::PseudoElement::*;
        let pseudo_element = match_ignore_ascii_case! { &name,
            "before" => Before,
            "after" => After,
            "selection" => Selection,
            "-servo-details-summary" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                DetailsSummary
            },
            "-servo-details-content" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                DetailsContent
            },
            "-servo-input-text" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoInputText
            },
            "-servo-table-wrapper" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoTableWrapper
            },
            "-servo-anonymous-table-wrapper" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousTableWrapper
            },
            "-servo-anonymous-table" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousTable
            },
            "-servo-anonymous-table-row" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousTableRow
            },
            "-servo-anonymous-table-cell" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousTableCell
            },
            "-servo-anonymous-block" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousBlock
            },
            _ => return Err(())
        };

        Ok(pseudo_element)
    }

    fn default_namespace(&self) -> Option<Namespace> {
        self.namespaces.default.clone()
    }

    fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> {
        self.namespaces.prefixes.get(prefix).cloned()
    }
}

impl SelectorImpl {
    #[inline]
    pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
        pseudo.cascade_type()
    }

    #[inline]
    pub fn each_pseudo_element<F>(mut fun: F)
        where F: FnMut(PseudoElement) {
        fun(PseudoElement::Before);
        fun(PseudoElement::After);
        fun(PseudoElement::DetailsContent);
        fun(PseudoElement::DetailsSummary);
        fun(PseudoElement::Selection);
        fun(PseudoElement::ServoInputText);
        fun(PseudoElement::ServoTableWrapper);
        fun(PseudoElement::ServoAnonymousTableWrapper);
        fun(PseudoElement::ServoAnonymousTable);
        fun(PseudoElement::ServoAnonymousTableRow);
        fun(PseudoElement::ServoAnonymousTableCell);
        fun(PseudoElement::ServoAnonymousBlock);
    }

    #[inline]
    pub fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
        pc.state_flag()
    }

    #[inline]
    pub fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool {
        pseudo.is_before_or_after()
    }
}

/// Servo's version of an element snapshot.
#[derive(Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct ServoElementSnapshot {
    pub state: Option<ElementState>,
    pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
    pub is_html_element_in_html_document: bool,
}

impl ServoElementSnapshot {
    pub fn new(is_html_element_in_html_document: bool) -> Self {
        ServoElementSnapshot {
            state: None,
            attrs: None,
            is_html_element_in_html_document: is_html_element_in_html_document,
        }
    }

    fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
        self.attrs.as_ref().unwrap().iter()
            .find(|&&(ref ident, _)| ident.local_name == *name &&
                                     ident.namespace == *namespace)
            .map(|&(_, ref v)| v)
    }

    fn get_attr_ignore_ns(&self, name: &LocalName) -> Option<&AttrValue> {
        self.attrs.as_ref().unwrap().iter()
                  .find(|&&(ref ident, _)| ident.local_name == *name)
                  .map(|&(_, ref v)| v)
    }
}

impl ElementSnapshot for ServoElementSnapshot {
    fn state(&self) -> Option<ElementState> {
        self.state.clone()
    }

    fn has_attrs(&self) -> bool {
        self.attrs.is_some()
    }

    fn id_attr(&self) -> Option<Atom> {
        self.get_attr(&ns!(), &local_name!("id")).map(|v| v.as_atom().clone())
    }

    fn has_class(&self, name: &Atom) -> bool {
        self.get_attr(&ns!(), &local_name!("class"))
            .map_or(false, |v| v.as_tokens().iter().any(|atom| atom == name))
    }

    fn each_class<F>(&self, mut callback: F)
        where F: FnMut(&Atom)
    {
        if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
            for class in v.as_tokens() {
                callback(class);
            }
        }
    }
}

impl MatchAttrGeneric for ServoElementSnapshot {
    type Impl = SelectorImpl;

    fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool
        where F: Fn(&str) -> bool
    {
        use selectors::parser::NamespaceConstraint;
        let html = self.is_html_element_in_html_document;
        let local_name = if html { &attr.lower_name } else { &attr.name };
        match attr.namespace {
            NamespaceConstraint::Specific(ref ns) => self.get_attr(&ns.url, local_name),
            NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name),
        }.map_or(false, |v| test(v))
    }
}

impl<E: Element<Impl=SelectorImpl> + Debug> ElementExt for E {
    fn is_link(&self) -> bool {
        self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink)
    }

    #[inline]
    fn matches_user_and_author_rules(&self) -> bool {
        true
    }
}
<commit_msg>style: Document Servo's SelectorImpl and deny missing docs.<commit_after>/* 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 http://mozilla.org/MPL/2.0/. */

#![deny(missing_docs)]

//! Servo's selector parser.

use {Atom, Prefix, Namespace, LocalName};
use attr::{AttrIdentifier, AttrValue};
use cssparser::ToCss;
use element_state::ElementState;
use restyle_hints::ElementSnapshot;
use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
use selector_parser::{attr_equals_selector_is_shareable, attr_exists_selector_is_shareable};
use selectors::{Element, MatchAttrGeneric};
use selectors::parser::AttrSelector;
use std::borrow::Cow;
use std::fmt;
use std::fmt::Debug;

/// A pseudo-element, both public and private.
///
/// NB: If you add to this list, be sure to update `each_pseudo_element` too.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum PseudoElement {
    Before,
    After,
    Selection,
    DetailsSummary,
    DetailsContent,
    ServoInputText,
    ServoTableWrapper,
    ServoAnonymousTableWrapper,
    ServoAnonymousTable,
    ServoAnonymousTableRow,
    ServoAnonymousTableCell,
    ServoAnonymousBlock,
}

impl ToCss for PseudoElement {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        use self::PseudoElement::*;
        dest.write_str(match *self {
            Before => "::before",
            After => "::after",
            Selection => "::selection",
            DetailsSummary => "::-servo-details-summary",
            DetailsContent => "::-servo-details-content",
            ServoInputText => "::-servo-input-text",
            ServoTableWrapper => "::-servo-table-wrapper",
            ServoAnonymousTableWrapper => "::-servo-anonymous-table-wrapper",
            ServoAnonymousTable => "::-servo-anonymous-table",
            ServoAnonymousTableRow => "::-servo-anonymous-table-row",
            ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
            ServoAnonymousBlock => "::-servo-anonymous-block",
        })
    }
}

impl PseudoElement {
    /// Whether the current pseudo element is :before or :after.
    #[inline]
    pub fn is_before_or_after(&self) -> bool {
        match *self {
            PseudoElement::Before |
            PseudoElement::After => true,
            _ => false,
        }
    }

    /// Returns which kind of cascade type has this pseudo.
    ///
    /// For more info on cascade types, see docs/components/style.md
    #[inline]
    pub fn cascade_type(&self) -> PseudoElementCascadeType {
        match *self {
            PseudoElement::Before |
            PseudoElement::After |
            PseudoElement::Selection => PseudoElementCascadeType::Eager,
            PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
            PseudoElement::DetailsContent |
            PseudoElement::ServoInputText |
            PseudoElement::ServoTableWrapper |
            PseudoElement::ServoAnonymousTableWrapper |
            PseudoElement::ServoAnonymousTable |
            PseudoElement::ServoAnonymousTableRow |
            PseudoElement::ServoAnonymousTableCell |
            PseudoElement::ServoAnonymousBlock => PseudoElementCascadeType::Precomputed,
        }
    }
}

/// A non tree-structural pseudo-class.
/// See https://drafts.csswg.org/selectors-4/#structural-pseudos
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[allow(missing_docs)]
pub enum NonTSPseudoClass {
    AnyLink,
    Link,
    Visited,
    Active,
    Focus,
    Fullscreen,
    Hover,
    Enabled,
    Disabled,
    Checked,
    Indeterminate,
    ServoNonZeroBorder,
    ReadWrite,
    ReadOnly,
    PlaceholderShown,
    Target,
}

impl ToCss for NonTSPseudoClass {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        use self::NonTSPseudoClass::*;
        dest.write_str(match *self {
            AnyLink => ":any-link",
            Link => ":link",
            Visited => ":visited",
            Active => ":active",
            Focus => ":focus",
            Fullscreen => ":fullscreen",
            Hover => ":hover",
            Enabled => ":enabled",
            Disabled => ":disabled",
            Checked => ":checked",
            Indeterminate => ":indeterminate",
            ReadWrite => ":read-write",
            ReadOnly => ":read-only",
            PlaceholderShown => ":placeholder-shown",
            Target => ":target",
            ServoNonZeroBorder => ":-servo-nonzero-border",
        })
    }
}

impl NonTSPseudoClass {
    /// Gets a given state flag for this pseudo-class. This is used to do
    /// selector matching, and it's set from the DOM.
    pub fn state_flag(&self) -> ElementState {
        use element_state::*;
        use self::NonTSPseudoClass::*;
        match *self {
            Active => IN_ACTIVE_STATE,
            Focus => IN_FOCUS_STATE,
            Fullscreen => IN_FULLSCREEN_STATE,
            Hover => IN_HOVER_STATE,
            Enabled => IN_ENABLED_STATE,
            Disabled => IN_DISABLED_STATE,
            Checked => IN_CHECKED_STATE,
            Indeterminate => IN_INDETERMINATE_STATE,
            ReadOnly | ReadWrite => IN_READ_WRITE_STATE,
            PlaceholderShown => IN_PLACEHOLDER_SHOWN_STATE,
            Target => IN_TARGET_STATE,

            AnyLink |
            Link |
            Visited |
            ServoNonZeroBorder => ElementState::empty(),
        }
    }
}

/// The abstract struct we implement the selector parser implementation on top
/// of.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct SelectorImpl;

impl ::selectors::SelectorImpl for SelectorImpl {
    type PseudoElement = PseudoElement;
    type NonTSPseudoClass = NonTSPseudoClass;

    type AttrValue = String;
    type Identifier = Atom;
    type ClassName = Atom;
    type LocalName = LocalName;
    type NamespacePrefix = Prefix;
    type NamespaceUrl = Namespace;
    type BorrowedLocalName = LocalName;
    type BorrowedNamespaceUrl = Namespace;

    fn attr_exists_selector_is_shareable(attr_selector: &AttrSelector<Self>) -> bool {
        attr_exists_selector_is_shareable(attr_selector)
    }

    fn attr_equals_selector_is_shareable(attr_selector: &AttrSelector<Self>,
                                         value: &Self::AttrValue) -> bool {
        attr_equals_selector_is_shareable(attr_selector, value)
    }
}

impl<'a> ::selectors::Parser for SelectorParser<'a> {
    type Impl = SelectorImpl;

    fn parse_non_ts_pseudo_class(&self, name: Cow<str>) -> Result<NonTSPseudoClass, ()> {
        use self::NonTSPseudoClass::*;
        let pseudo_class = match_ignore_ascii_case! { &name,
            "any-link" => AnyLink,
            "link" => Link,
            "visited" => Visited,
            "active" => Active,
            "focus" => Focus,
            "fullscreen" => Fullscreen,
            "hover" => Hover,
            "enabled" => Enabled,
            "disabled" => Disabled,
            "checked" => Checked,
            "indeterminate" => Indeterminate,
            "read-write" => ReadWrite,
            "read-only" => ReadOnly,
            "placeholder-shown" => PlaceholderShown,
            "target" => Target,
            "-servo-nonzero-border" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(());
                }
                ServoNonZeroBorder
            },
            _ => return Err(())
        };

        Ok(pseudo_class)
    }

    fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
        use self::PseudoElement::*;
        let pseudo_element = match_ignore_ascii_case! { &name,
            "before" => Before,
            "after" => After,
            "selection" => Selection,
            "-servo-details-summary" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                DetailsSummary
            },
            "-servo-details-content" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                DetailsContent
            },
            "-servo-input-text" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoInputText
            },
            "-servo-table-wrapper" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoTableWrapper
            },
            "-servo-anonymous-table-wrapper" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousTableWrapper
            },
            "-servo-anonymous-table" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousTable
            },
            "-servo-anonymous-table-row" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousTableRow
            },
            "-servo-anonymous-table-cell" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousTableCell
            },
            "-servo-anonymous-block" => {
                if !self.in_user_agent_stylesheet() {
                    return Err(())
                }
                ServoAnonymousBlock
            },
            _ => return Err(())
        };

        Ok(pseudo_element)
    }

    fn default_namespace(&self) -> Option<Namespace> {
        self.namespaces.default.clone()
    }

    fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> {
        self.namespaces.prefixes.get(prefix).cloned()
    }
}

impl SelectorImpl {
    /// Returns the pseudo-element cascade type of the given `pseudo`.
    #[inline]
    pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
        pseudo.cascade_type()
    }

    /// Executes `fun` for each pseudo-element.
    #[inline]
    pub fn each_pseudo_element<F>(mut fun: F)
        where F: FnMut(PseudoElement),
    {
        fun(PseudoElement::Before);
        fun(PseudoElement::After);
        fun(PseudoElement::DetailsContent);
        fun(PseudoElement::DetailsSummary);
        fun(PseudoElement::Selection);
        fun(PseudoElement::ServoInputText);
        fun(PseudoElement::ServoTableWrapper);
        fun(PseudoElement::ServoAnonymousTableWrapper);
        fun(PseudoElement::ServoAnonymousTable);
        fun(PseudoElement::ServoAnonymousTableRow);
        fun(PseudoElement::ServoAnonymousTableCell);
        fun(PseudoElement::ServoAnonymousBlock);
    }

    /// Returns the pseudo-class state flag for selector matching.
    #[inline]
    pub fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
        pc.state_flag()
    }

    /// Returns whether this pseudo is either :before or :after.
    #[inline]
    pub fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool {
        pseudo.is_before_or_after()
    }
}

/// Servo's version of an element snapshot.
#[derive(Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct ServoElementSnapshot {
    /// The stored state of the element.
    pub state: Option<ElementState>,
    /// The set of stored attributes and its values.
    pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
    /// Whether this element is an HTML element in an HTML document.
    pub is_html_element_in_html_document: bool,
}

impl ServoElementSnapshot {
    /// Create an empty element snapshot.
    pub fn new(is_html_element_in_html_document: bool) -> Self {
        ServoElementSnapshot {
            state: None,
            attrs: None,
            is_html_element_in_html_document: is_html_element_in_html_document,
        }
    }

    fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
        self.attrs.as_ref().unwrap().iter()
            .find(|&&(ref ident, _)| ident.local_name == *name &&
                                     ident.namespace == *namespace)
            .map(|&(_, ref v)| v)
    }

    fn get_attr_ignore_ns(&self, name: &LocalName) -> Option<&AttrValue> {
        self.attrs.as_ref().unwrap().iter()
                  .find(|&&(ref ident, _)| ident.local_name == *name)
                  .map(|&(_, ref v)| v)
    }
}

impl ElementSnapshot for ServoElementSnapshot {
    fn state(&self) -> Option<ElementState> {
        self.state.clone()
    }

    fn has_attrs(&self) -> bool {
        self.attrs.is_some()
    }

    fn id_attr(&self) -> Option<Atom> {
        self.get_attr(&ns!(), &local_name!("id")).map(|v| v.as_atom().clone())
    }

    fn has_class(&self, name: &Atom) -> bool {
        self.get_attr(&ns!(), &local_name!("class"))
            .map_or(false, |v| v.as_tokens().iter().any(|atom| atom == name))
    }

    fn each_class<F>(&self, mut callback: F)
        where F: FnMut(&Atom),
    {
        if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
            for class in v.as_tokens() {
                callback(class);
            }
        }
    }
}

impl MatchAttrGeneric for ServoElementSnapshot {
    type Impl = SelectorImpl;

    fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool
        where F: Fn(&str) -> bool
    {
        use selectors::parser::NamespaceConstraint;
        let html = self.is_html_element_in_html_document;
        let local_name = if html { &attr.lower_name } else { &attr.name };
        match attr.namespace {
            NamespaceConstraint::Specific(ref ns) => self.get_attr(&ns.url, local_name),
            NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name),
        }.map_or(false, |v| test(v))
    }
}

impl<E: Element<Impl=SelectorImpl> + Debug> ElementExt for E {
    fn is_link(&self) -> bool {
        self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink)
    }

    #[inline]
    fn matches_user_and_author_rules(&self) -> bool {
        true
    }
}
<|endoftext|>


<commit_before>//! Workspace information we get from cargo consists of two pieces. The first is
//! the output of `cargo metadata`. The second is the output of running
//! `build.rs` files (`OUT_DIR` env var, extra cfg flags) and compiling proc
//! macro.
//!
//! This module implements this second part. We use "build script" terminology
//! here, but it covers procedural macros as well.

use std::{cell::RefCell, io, path::PathBuf, process::Command};

use cargo_metadata::{camino::Utf8Path, Message};
use la_arena::ArenaMap;
use paths::AbsPathBuf;
use rustc_hash::FxHashMap;
use serde::Deserialize;

use crate::{cfg_flag::CfgFlag, CargoConfig, CargoWorkspace, Package};

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct WorkspaceBuildScripts {
    outputs: ArenaMap<Package, Option<BuildScriptOutput>>,
    error: Option<String>,
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct BuildScriptOutput {
    /// List of config flags defined by this package's build script.
    pub(crate) cfgs: Vec<CfgFlag>,
    /// List of cargo-related environment variables with their value.
    ///
    /// If the package has a build script which defines environment variables,
    /// they can also be found here.
    pub(crate) envs: Vec<(String, String)>,
    /// Directory where a build script might place its output.
    pub(crate) out_dir: Option<AbsPathBuf>,
    /// Path to the proc-macro library file if this package exposes proc-macros.
    pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
}

impl WorkspaceBuildScripts {
    fn build_command(config: &CargoConfig) -> Command {
        if let Some([program, args @ ..]) = config.run_build_script_command.as_deref() {
            let mut cmd = Command::new(program);
            cmd.args(args);
            return cmd;
        }

        let mut cmd = Command::new(toolchain::cargo());

        cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);

        // --all-targets includes tests, benches and examples in addition to the
        // default lib and bins. This is an independent concept from the --targets
        // flag below.
        cmd.arg("--all-targets");

        if let Some(target) = &config.target {
            cmd.args(&["--target", target]);
        }

        if config.all_features {
            cmd.arg("--all-features");
        } else {
            if config.no_default_features {
                cmd.arg("--no-default-features");
            }
            if !config.features.is_empty() {
                cmd.arg("--features");
                cmd.arg(config.features.join(" "));
            }
        }

        cmd
    }

    pub(crate) fn run(
        config: &CargoConfig,
        workspace: &CargoWorkspace,
        progress: &dyn Fn(String),
    ) -> io::Result<WorkspaceBuildScripts> {
        let mut cmd = Self::build_command(config);

        if config.wrap_rustc_in_build_scripts {
            // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
            // that to compile only proc macros and build scripts during the initial
            // `cargo check`.
            let myself = std::env::current_exe()?;
            cmd.env("RUSTC_WRAPPER", myself);
            cmd.env("RA_RUSTC_WRAPPER", "1");
        }

        cmd.current_dir(workspace.workspace_root());

        let mut res = WorkspaceBuildScripts::default();
        let outputs = &mut res.outputs;
        // NB: Cargo.toml could have been modified between `cargo metadata` and
        // `cargo check`. We shouldn't assume that package ids we see here are
        // exactly those from `config`.
        let mut by_id: FxHashMap<String, Package> = FxHashMap::default();
        for package in workspace.packages() {
            outputs.insert(package, None);
            by_id.insert(workspace[package].id.clone(), package);
        }

        let errors = RefCell::new(String::new());
        let push_err = |err: &str| {
            let mut e = errors.borrow_mut();
            e.push_str(err);
            e.push('\n');
        };

        tracing::info!("Running build scripts: {:?}", cmd);
        let output = stdx::process::streaming_output(
            cmd,
            &mut |line| {
                // Copy-pasted from existing cargo_metadata. It seems like we
                // should be using serde_stacker here?
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                let message = Message::deserialize(&mut deserializer)
                    .unwrap_or_else(|_| Message::TextLine(line.to_string()));

                match message {
                    Message::BuildScriptExecuted(message) => {
                        let package = match by_id.get(&message.package_id.repr) {
                            Some(&it) => it,
                            None => return,
                        };
                        let cfgs = {
                            let mut acc = Vec::new();
                            for cfg in message.cfgs {
                                match cfg.parse::<CfgFlag>() {
                                    Ok(it) => acc.push(it),
                                    Err(err) => {
                                        push_err(&format!(
                                            "invalid cfg from cargo-metadata: {}",
                                            err
                                        ));
                                        return;
                                    }
                                };
                            }
                            acc
                        };
                        let package_build_data =
                            outputs[package].get_or_insert_with(Default::default);
                        // cargo_metadata crate returns default (empty) path for
                        // older cargos, which is not absolute, so work around that.
                        if !message.out_dir.as_str().is_empty() {
                            let out_dir =
                                AbsPathBuf::assert(PathBuf::from(message.out_dir.into_os_string()));
                            package_build_data.out_dir = Some(out_dir);
                            package_build_data.cfgs = cfgs;
                        }

                        package_build_data.envs = message.env;
                    }
                    Message::CompilerArtifact(message) => {
                        let package = match by_id.get(&message.package_id.repr) {
                            Some(it) => *it,
                            None => return,
                        };

                        progress(format!("metadata {}", message.target.name));

                        if message.target.kind.iter().any(|k| k == "proc-macro") {
                            // Skip rmeta file
                            if let Some(filename) =
                                message.filenames.iter().find(|name| is_dylib(name))
                            {
                                let filename = AbsPathBuf::assert(PathBuf::from(&filename));
                                outputs[package]
                                    .get_or_insert_with(Default::default)
                                    .proc_macro_dylib_path = Some(filename);
                            }
                        }
                    }
                    Message::CompilerMessage(message) => {
                        progress(message.target.name);

                        if let Some(diag) = message.message.rendered.as_deref() {
                            push_err(diag);
                        }
                    }
                    Message::BuildFinished(_) => {}
                    Message::TextLine(_) => {}
                    _ => {}
                }
            },
            &mut |line| {
                push_err(line);
            },
        )?;

        for package in workspace.packages() {
            if let Some(package_build_data) = &mut outputs[package] {
                tracing::info!(
                    "{} BuildScriptOutput: {:?}",
                    workspace[package].manifest.parent().display(),
                    package_build_data,
                );
                // inject_cargo_env(package, package_build_data);
                if let Some(out_dir) = &package_build_data.out_dir {
                    // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
                    if let Some(out_dir) = out_dir.as_os_str().to_str().map(|s| s.to_owned()) {
                        package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
                    }
                }
            }
        }

        let mut errors = errors.into_inner();
        if !output.status.success() {
            if errors.is_empty() {
                errors = "cargo check failed".to_string();
            }
            res.error = Some(errors);
        }

        Ok(res)
    }

    pub fn error(&self) -> Option<&str> {
        self.error.as_deref()
    }

    pub(crate) fn get_output(&self, idx: Package) -> Option<&BuildScriptOutput> {
        self.outputs.get(idx)?.as_ref()
    }
}

// FIXME: File a better way to know if it is a dylib.
fn is_dylib(path: &Utf8Path) -> bool {
    match path.extension().map(|e| e.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}
<commit_msg>Auto merge of #12465 - Veykril:buildscripts, r=Veykril<commit_after>//! Workspace information we get from cargo consists of two pieces. The first is
//! the output of `cargo metadata`. The second is the output of running
//! `build.rs` files (`OUT_DIR` env var, extra cfg flags) and compiling proc
//! macro.
//!
//! This module implements this second part. We use "build script" terminology
//! here, but it covers procedural macros as well.

use std::{cell::RefCell, io, path::PathBuf, process::Command};

use cargo_metadata::{camino::Utf8Path, Message};
use la_arena::ArenaMap;
use paths::AbsPathBuf;
use rustc_hash::FxHashMap;
use serde::Deserialize;

use crate::{cfg_flag::CfgFlag, CargoConfig, CargoWorkspace, Package};

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct WorkspaceBuildScripts {
    outputs: ArenaMap<Package, Option<BuildScriptOutput>>,
    error: Option<String>,
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct BuildScriptOutput {
    /// List of config flags defined by this package's build script.
    pub(crate) cfgs: Vec<CfgFlag>,
    /// List of cargo-related environment variables with their value.
    ///
    /// If the package has a build script which defines environment variables,
    /// they can also be found here.
    pub(crate) envs: Vec<(String, String)>,
    /// Directory where a build script might place its output.
    pub(crate) out_dir: Option<AbsPathBuf>,
    /// Path to the proc-macro library file if this package exposes proc-macros.
    pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
}

impl WorkspaceBuildScripts {
    fn build_command(config: &CargoConfig) -> Command {
        if let Some([program, args @ ..]) = config.run_build_script_command.as_deref() {
            let mut cmd = Command::new(program);
            cmd.args(args);
            return cmd;
        }

        let mut cmd = Command::new(toolchain::cargo());

        cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);

        // --all-targets includes tests, benches and examples in addition to the
        // default lib and bins. This is an independent concept from the --targets
        // flag below.
        cmd.arg("--all-targets");

        if let Some(target) = &config.target {
            cmd.args(&["--target", target]);
        }

        if config.all_features {
            cmd.arg("--all-features");
        } else {
            if config.no_default_features {
                cmd.arg("--no-default-features");
            }
            if !config.features.is_empty() {
                cmd.arg("--features");
                cmd.arg(config.features.join(" "));
            }
        }

        cmd
    }

    pub(crate) fn run(
        config: &CargoConfig,
        workspace: &CargoWorkspace,
        progress: &dyn Fn(String),
    ) -> io::Result<WorkspaceBuildScripts> {
        let mut cmd = Self::build_command(config);

        if config.wrap_rustc_in_build_scripts {
            // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
            // that to compile only proc macros and build scripts during the initial
            // `cargo check`.
            let myself = std::env::current_exe()?;
            cmd.env("RUSTC_WRAPPER", myself);
            cmd.env("RA_RUSTC_WRAPPER", "1");
        }

        cmd.current_dir(workspace.workspace_root());

        let mut res = WorkspaceBuildScripts::default();
        let outputs = &mut res.outputs;
        // NB: Cargo.toml could have been modified between `cargo metadata` and
        // `cargo check`. We shouldn't assume that package ids we see here are
        // exactly those from `config`.
        let mut by_id: FxHashMap<String, Package> = FxHashMap::default();
        for package in workspace.packages() {
            outputs.insert(package, None);
            by_id.insert(workspace[package].id.clone(), package);
        }

        let errors = RefCell::new(String::new());
        let push_err = |err: &str| {
            let mut e = errors.borrow_mut();
            e.push_str(err);
            e.push('\n');
        };

        tracing::info!("Running build scripts: {:?}", cmd);
        let output = stdx::process::streaming_output(
            cmd,
            &mut |line| {
                // Copy-pasted from existing cargo_metadata. It seems like we
                // should be using serde_stacker here?
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                let message = Message::deserialize(&mut deserializer)
                    .unwrap_or_else(|_| Message::TextLine(line.to_string()));

                match message {
                    Message::BuildScriptExecuted(message) => {
                        let package = match by_id.get(&message.package_id.repr) {
                            Some(&it) => it,
                            None => return,
                        };
                        let cfgs = {
                            let mut acc = Vec::new();
                            for cfg in message.cfgs {
                                match cfg.parse::<CfgFlag>() {
                                    Ok(it) => acc.push(it),
                                    Err(err) => {
                                        push_err(&format!(
                                            "invalid cfg from cargo-metadata: {}",
                                            err
                                        ));
                                        return;
                                    }
                                };
                            }
                            acc
                        };
                        // cargo_metadata crate returns default (empty) path for
                        // older cargos, which is not absolute, so work around that.
                        let out_dir = message.out_dir.into_os_string();
                        if !out_dir.is_empty() {
                            let data = outputs[package].get_or_insert_with(Default::default);
                            let out_dir = Some(AbsPathBuf::assert(PathBuf::from(out_dir)));
                            (data.out_dir, data.cfgs) = (out_dir, cfgs);
                        }
                        if !message.env.is_empty() {
                            outputs[package].get_or_insert_with(Default::default).envs =
                                message.env;
                        }
                    }
                    Message::CompilerArtifact(message) => {
                        let package = match by_id.get(&message.package_id.repr) {
                            Some(it) => *it,
                            None => return,
                        };

                        progress(format!("metadata {}", message.target.name));

                        if message.target.kind.iter().any(|k| k == "proc-macro") {
                            // Skip rmeta file
                            if let Some(filename) =
                                message.filenames.iter().find(|name| is_dylib(name))
                            {
                                let filename = AbsPathBuf::assert(PathBuf::from(&filename));
                                outputs[package]
                                    .get_or_insert_with(Default::default)
                                    .proc_macro_dylib_path = Some(filename);
                            }
                        }
                    }
                    Message::CompilerMessage(message) => {
                        progress(message.target.name);

                        if let Some(diag) = message.message.rendered.as_deref() {
                            push_err(diag);
                        }
                    }
                    Message::BuildFinished(_) => {}
                    Message::TextLine(_) => {}
                    _ => {}
                }
            },
            &mut |line| {
                push_err(line);
            },
        )?;

        for package in workspace.packages() {
            if let Some(package_build_data) = &mut outputs[package] {
                tracing::info!(
                    "{}: {:?}",
                    workspace[package].manifest.parent().display(),
                    package_build_data,
                );
                // inject_cargo_env(package, package_build_data);
                if let Some(out_dir) = &package_build_data.out_dir {
                    // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
                    if let Some(out_dir) = out_dir.as_os_str().to_str().map(|s| s.to_owned()) {
                        package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
                    }
                }
            }
        }

        let mut errors = errors.into_inner();
        if !output.status.success() {
            if errors.is_empty() {
                errors = "cargo check failed".to_string();
            }
            res.error = Some(errors);
        }

        Ok(res)
    }

    pub fn error(&self) -> Option<&str> {
        self.error.as_deref()
    }

    pub(crate) fn get_output(&self, idx: Package) -> Option<&BuildScriptOutput> {
        self.outputs.get(idx)?.as_ref()
    }
}

// FIXME: File a better way to know if it is a dylib.
fn is_dylib(path: &Utf8Path) -> bool {
    match path.extension().map(|e| e.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}
<|endoftext|>


<commit_before>//! Types describing event relations after MSC 2674, 2675, 2676, 2677.

use std::fmt::Debug;

use js_int::UInt;
use serde::{Deserialize, Serialize};

use crate::MilliSecondsSinceUnixEpoch;

/// Summary of all reactions with the given key to an event.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[cfg(feature = "unstable-msc2677")]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct BundledReaction {
    /// The key (emoji) used for reaction.
    pub key: String,

    /// Time of the bundled reaction being compiled on the server.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub origin_server_ts: Option<MilliSecondsSinceUnixEpoch>,

    /// Number of reactions.
    pub count: UInt,
}

#[cfg(feature = "unstable-msc2677")]
impl BundledReaction {
    /// Creates a new `BundledReaction`.
    pub fn new(
        key: String,
        origin_server_ts: Option<MilliSecondsSinceUnixEpoch>,
        count: UInt,
    ) -> Self {
        Self { key, origin_server_ts, count }
    }
}

/// Type of bundled annotation.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg(feature = "unstable-msc2677")]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "type")]
pub enum BundledAnnotation {
    /// An emoji reaction and its count.
    #[serde(rename = "m.reaction")]
    Reaction(BundledReaction),
}

/// The first chunk of annotations with a token for loading more.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg(feature = "unstable-msc2677")]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct AnnotationChunk {
    /// The first batch of bundled annotations.
    pub chunk: Vec<BundledAnnotation>,

    /// Token to receive the next annotation batch.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next_batch: Option<String>,
}

#[cfg(feature = "unstable-msc2677")]
impl AnnotationChunk {
    /// Creates a new `AnnotationChunk` with the given chunk and next batch token.
    pub fn new(chunk: Vec<BundledAnnotation>, next_batch: Option<String>) -> Self {
        Self { chunk, next_batch }
    }
}

/// Precompiled list of relations to this event grouped by relation type.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct Relations {
    /// Annotation relations.
    #[cfg(feature = "unstable-msc2677")]
    #[serde(rename = "m.annotation")]
    pub annotation: Option<AnnotationChunk>,
}

impl Relations {
    #[cfg(feature = "unstable-msc2677")]
    /// Creates a new `Relations` with the given annotation.
    ///
    /// Without the `unstable-msc-2677` feature, this method doesn't have any
    /// parameters.
    pub fn new(annotation: Option<AnnotationChunk>) -> Self {
        Self { annotation }
    }

    #[cfg(not(feature = "unstable-msc2677"))]
    /// Creates a new empty `Relations`.
    ///
    /// With the `unstable-msc-2677` feature, this method takes an annotation.
    pub fn new() -> Self {
        Self {}
    }
}
<commit_msg>common: Remove unstable Relations constructor<commit_after>//! Types describing event relations after MSC 2674, 2675, 2676, 2677.

use std::fmt::Debug;

use js_int::UInt;
use serde::{Deserialize, Serialize};

use crate::MilliSecondsSinceUnixEpoch;

/// Summary of all reactions with the given key to an event.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[cfg(feature = "unstable-msc2677")]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct BundledReaction {
    /// The key (emoji) used for reaction.
    pub key: String,

    /// Time of the bundled reaction being compiled on the server.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub origin_server_ts: Option<MilliSecondsSinceUnixEpoch>,

    /// Number of reactions.
    pub count: UInt,
}

#[cfg(feature = "unstable-msc2677")]
impl BundledReaction {
    /// Creates a new `BundledReaction`.
    pub fn new(
        key: String,
        origin_server_ts: Option<MilliSecondsSinceUnixEpoch>,
        count: UInt,
    ) -> Self {
        Self { key, origin_server_ts, count }
    }
}

/// Type of bundled annotation.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg(feature = "unstable-msc2677")]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[serde(tag = "type")]
pub enum BundledAnnotation {
    /// An emoji reaction and its count.
    #[serde(rename = "m.reaction")]
    Reaction(BundledReaction),
}

/// The first chunk of annotations with a token for loading more.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg(feature = "unstable-msc2677")]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct AnnotationChunk {
    /// The first batch of bundled annotations.
    pub chunk: Vec<BundledAnnotation>,

    /// Token to receive the next annotation batch.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub next_batch: Option<String>,
}

#[cfg(feature = "unstable-msc2677")]
impl AnnotationChunk {
    /// Creates a new `AnnotationChunk` with the given chunk and next batch token.
    pub fn new(chunk: Vec<BundledAnnotation>, next_batch: Option<String>) -> Self {
        Self { chunk, next_batch }
    }
}

/// Precompiled list of relations to this event grouped by relation type.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct Relations {
    /// Annotation relations.
    #[cfg(feature = "unstable-msc2677")]
    #[serde(rename = "m.annotation")]
    pub annotation: Option<AnnotationChunk>,
}

impl Relations {
    /// Creates a new empty `Relations`.
    pub fn new() -> Self {
        Self::default()
    }
}
<|endoftext|>


<commit_before>use crate::config::MoMMIConfig;
use crate::github::data::{PullRequestAction, PullRequestEvent, PushEvent};
use crate::mommi::commloop;
use lazy_static::lazy_static;
use regex::{Regex, RegexBuilder};
use serde::de::{Error, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::fs::{read_dir, File};
use std::io::Write;
use std::path::Path;
use std::process::Command;
use std::sync::Arc;
use std::sync::{Mutex, MutexGuard};
use std::thread;
use std::time::{Duration, Instant};

pub fn try_handle_changelog_pr(event: &PullRequestEvent, config: &Arc<MoMMIConfig>) {
    if event.action != PullRequestAction::Closed
        || !event.pull_request.merged
        || !config.has_changelog_repo_path()
    {
        // Not a merge
        return;
    }

    let additions = parse_body_changelog(&event.pull_request.body);

    if additions.len() == 0 {
        return;
    }

    let changelog = Changelog {
        author: event.pull_request.user.login.clone(),
        changes: additions,
        delete_after: Some(true),
    };

    let mut changelog_path = config.get_changelog_repo_path().unwrap().to_path_buf();
    changelog_path.push(&format!("html/changelogs/PR-{}-temp.yml", event.number));

    match write_temp_changelog(&changelog_path, changelog) {
        Err(e) => eprintln!("Error writing changelog temp file: {:?}", e),
        _ => {}
    };

    process_changelogs(config);
}

pub fn try_handle_changelog_push(event: &PushEvent, config: &Arc<MoMMIConfig>) {
    lazy_static! {
        static ref is_changelog_re: Regex = Regex::new(r#"^html/changelogs/[^.].*\.yml$"#).unwrap();
    }

    for filename in event
        .commits
        .iter()
        .flat_map(|c| c.added.iter().chain(c.modified.iter()))
    {
        println!("{}", filename);
        if is_changelog_re.is_match(filename) {
            process_changelogs(config);
            return;
        }
    }
}

fn parse_body_changelog(body: &str) -> Vec<ChangelogEntry> {
    lazy_static! {
        static ref header_re: Regex = RegexBuilder::new(r#"(?::cl:|🆑) *\r?\n(.+)$"#).dot_matches_new_line(true).build().unwrap();
        static ref entry_re: Regex = RegexBuilder::new(r#"^ *[*-]? *(bugfix|wip|tweak|soundadd|sounddel|rscdel|rscadd|imageadd|imagedel|spellcheck|experiment|tgs): *(\S[^\n\r]+)$"#).multi_line(true).build().unwrap();
    }

    let content = match header_re.captures(body) {
        Some(capture) => capture.get(1).unwrap().as_str(),
        _ => return Vec::new(),
    };

    entry_re
        .captures_iter(content)
        .map(|m| {
            let entry_type = match m.get(1).unwrap().as_str() {
                "bugfix" => ChangelogEntryType::Bugfix,
                "wip" => ChangelogEntryType::Wip,
                "tweak" => ChangelogEntryType::Tweak,
                "soundadd" => ChangelogEntryType::Soundadd,
                "sounddel" => ChangelogEntryType::Sounddel,
                "rscdel" => ChangelogEntryType::Rscdel,
                "rscadd" => ChangelogEntryType::Rscadd,
                "imageadd" => ChangelogEntryType::Imageadd,
                "imagedel" => ChangelogEntryType::Imagedel,
                "spellcheck" => ChangelogEntryType::Spellcheck,
                "experiment" => ChangelogEntryType::Experiment,
                "tgs" => ChangelogEntryType::Tgs,
                _ => unreachable!(),
            };

            ChangelogEntry(entry_type, m.get(2).unwrap().as_str().to_owned())
        })
        .collect()
}

fn write_temp_changelog(path: &Path, changelog: Changelog) -> std::io::Result<()> {
    let mut file = File::create(path)?;
    serde_yaml::to_writer(&file, &changelog).unwrap(); // TODO: Remove unwrap.
    file.flush()?;
    Ok(())
}

lazy_static! {
    pub static ref CHANGELOG_MANAGER: Mutex<ChangelogManager> =
        { Mutex::new(ChangelogManager { last_time: None }) };
}

pub struct ChangelogManager {
    // If None, no thread is currently on it.
    last_time: Option<Instant>,
}

/// Represents a new changelog entry.
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Changelog {
    pub author: String,
    pub changes: Vec<ChangelogEntry>,
    pub delete_after: Option<bool>,
}

#[derive(Debug, Clone)]
pub struct ChangelogEntry(ChangelogEntryType, String);

impl Serialize for ChangelogEntry {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(1))?;
        map.serialize_entry(&self.0, &self.1)?;
        map.end()
    }
}

impl<'de> Deserialize<'de> for ChangelogEntry {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_map(ChangelogEntryVisitor)
    }
}

struct ChangelogEntryVisitor;

impl<'de> Visitor<'de> for ChangelogEntryVisitor {
    type Value = ChangelogEntry;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("A single-element map")
    }

    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
    where
        M: MapAccess<'de>,
    {
        match access.next_entry()? {
            Some((key, value)) => {
                let value = ChangelogEntry(key, value);
                match access.next_key::<ChangelogEntryType>()? {
                    Some(_) => Err(M::Error::invalid_length(2, &"A single-element map.")),
                    _ => Ok(value),
                }
            }
            None => Err(M::Error::invalid_length(0, &"A single-element map.")),
        }
    }
}

#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ChangelogEntryType {
    Bugfix,
    Wip,
    Tweak,
    Soundadd,
    Sounddel,
    Rscadd,
    Rscdel,
    Imageadd,
    Imagedel,
    Spellcheck,
    Experiment,
    Tgs,
}

pub fn process_changelogs(config: &Arc<MoMMIConfig>) {
    let mut lock = CHANGELOG_MANAGER.lock().unwrap();
    let should_spawn_thread = lock.last_time.is_none();
    lock.last_time = Some(Instant::now());

    if should_spawn_thread {
        // Nobody currently processing.
        lock.last_time = Some(Instant::now());
        let config = config.clone();
        thread::Builder::new()
            .name("Changelog thread".into())
            .spawn(move || {
                handle_changelog_thread(config);
            })
            .unwrap();
    }
}

fn handle_changelog_thread(config: Arc<MoMMIConfig>) {
    let delay = config.get_changelog_delay();

    loop {
        let time = {
            let lock = CHANGELOG_MANAGER.lock().unwrap();
            let elapsed = lock.last_time.as_ref().unwrap().elapsed();
            if elapsed.as_secs() > delay {
                return do_changelog(lock, config);
            }

            match Duration::from_secs(delay).checked_sub(elapsed) {
                Some(t) => t,
                None => return do_changelog(lock, config),
            }
        };
        thread::sleep(time);
    }
}

// Pass the lock directly so we don't risk race conditions.
fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>) {
    println!("Running changelogs!");
    // Get what we need and drop the lock.
    // so we don't hang everything for the time it takes for the git commands and stuff.
    lock.last_time = None;
    drop(lock);

    let path = config.get_changelog_repo_path().unwrap();
    let ssh_config = config
        .get_ssh_key()
        .map(|p| format!("ssh -i {}", p.to_string_lossy()));

    // Git pull the repo.
    let mut command = Command::new("git");
    command
        .arg("pull")
        .arg("origin")
        .arg("--ff-only")
        .current_dir(&path);
    if let Some(ref ssh_command) = ssh_config {
        command.env("GIT_SSH_COMMAND", &ssh_command);
    }
    let status = command.status().unwrap();

    assert!(status.success());

    let mut changelog_dir_path = path.to_owned();
    changelog_dir_path.push("html/changelogs");

    // Send changelog files over to MoMMI maybe.
    if let Some((addr, pass)) = config.get_commloop() {
        for entry in read_dir(&changelog_dir_path).unwrap() {
            let entry = entry.unwrap();
            let os_file_name = entry.file_name();
            let file_name = os_file_name.to_str().unwrap();
            if file_name.starts_with(".")
                || !file_name.ends_with(".yml")
                || file_name == "example.yml"
            {
                continue;
            }

            println!("{}", file_name);

            let file = File::open(entry.path()).unwrap();
            let data: Changelog = serde_yaml::from_reader(&file).unwrap();

            if data.changes.len() == 0 {
                continue;
            }

            commloop(addr, pass, "changelog", "", data).unwrap();
        }
    }

    // Run changelog script.
    let status = Command::new("python2")
        .arg("tools/changelog/ss13_genchangelog.py")
        .arg("html/changelog.html")
        .arg("html/changelogs")
        .current_dir(&path)
        .status()
        .unwrap();

    assert!(status.success());

    Command::new("git")
        .arg("update-index")
        .arg("--refresh")
        .current_dir(&path)
        .status()
        .unwrap();

    // See if repo is dirty.
    let status = Command::new("git")
        .arg("diff-index")
        .arg("--exit-code")
        .arg("HEAD")
        .current_dir(&path)
        .status()
        .unwrap();

    if status.code().unwrap_or(0) == 0 {
        // No changes, nothing to commit.
        return;
    }

    let status = Command::new("git")
        .arg("add")
        .arg(".")
        .arg("-A")
        .current_dir(&path)
        .status()
        .unwrap();

    assert!(status.success());

    let status = Command::new("git")
        .arg("commit")
        .arg("-m")
        .arg("[ci skip] Automatic changelog update.")
        .current_dir(&path)
        .status()
        .unwrap();

    assert!(status.success());

    // Git push the repo.
    let mut command = Command::new("git");
    command.arg("push").arg("origin").current_dir(&path);
    if let Some(ref ssh_command) = ssh_config {
        command.env("GIT_SSH_COMMAND", &ssh_command);
    }
    let status = command.status().unwrap();

    assert!(status.success());

    println!("done");
}
<commit_msg>Fix parsing, add a unit test.<commit_after>use crate::config::MoMMIConfig;
use crate::github::data::{PullRequestAction, PullRequestEvent, PushEvent};
use crate::mommi::commloop;
use lazy_static::lazy_static;
use regex::{Regex, RegexBuilder};
use serde::de::{Error, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::fs::{read_dir, File};
use std::io::Write;
use std::path::Path;
use std::process::Command;
use std::sync::Arc;
use std::sync::{Mutex, MutexGuard};
use std::thread;
use std::time::{Duration, Instant};

pub fn try_handle_changelog_pr(event: &PullRequestEvent, config: &Arc<MoMMIConfig>) {
    if event.action != PullRequestAction::Closed
        || !event.pull_request.merged
        || !config.has_changelog_repo_path()
    {
        // Not a merge
        return;
    }

    let additions = parse_body_changelog(&event.pull_request.body);

    if additions.len() == 0 {
        return;
    }

    let changelog = Changelog {
        author: event.pull_request.user.login.clone(),
        changes: additions,
        delete_after: Some(true),
    };

    let mut changelog_path = config.get_changelog_repo_path().unwrap().to_path_buf();
    changelog_path.push(&format!("html/changelogs/PR-{}-temp.yml", event.number));

    match write_temp_changelog(&changelog_path, changelog) {
        Err(e) => eprintln!("Error writing changelog temp file: {:?}", e),
        _ => {}
    };

    process_changelogs(config);
}

pub fn try_handle_changelog_push(event: &PushEvent, config: &Arc<MoMMIConfig>) {
    lazy_static! {
        static ref is_changelog_re: Regex = Regex::new(r#"^html/changelogs/[^.].*\.yml$"#).unwrap();
    }

    for filename in event
        .commits
        .iter()
        .flat_map(|c| c.added.iter().chain(c.modified.iter()))
    {
        println!("{}", filename);
        if is_changelog_re.is_match(filename) {
            process_changelogs(config);
            return;
        }
    }
}

fn parse_body_changelog(body: &str) -> Vec<ChangelogEntry> {
    lazy_static! {
        static ref header_re: Regex = RegexBuilder::new(r#"(?::cl:|🆑) *\r?\n(.+)$"#).dot_matches_new_line(true).build().unwrap();
        static ref entry_re: Regex = RegexBuilder::new(r#"^ *[*-]? *(bugfix|wip|tweak|soundadd|sounddel|rscdel|rscadd|imageadd|imagedel|spellcheck|experiment|tgs): *(\S[^\n\r]+)\r?$"#).multi_line(true).build().unwrap();
    }

    let content = match header_re.captures(body) {
        Some(capture) => capture.get(1).unwrap().as_str(),
        _ => return Vec::new(),
    };

    entry_re
        .captures_iter(content)
        .map(|m| {
            let entry_type = match m.get(1).unwrap().as_str() {
                "bugfix" => ChangelogEntryType::Bugfix,
                "wip" => ChangelogEntryType::Wip,
                "tweak" => ChangelogEntryType::Tweak,
                "soundadd" => ChangelogEntryType::Soundadd,
                "sounddel" => ChangelogEntryType::Sounddel,
                "rscdel" => ChangelogEntryType::Rscdel,
                "rscadd" => ChangelogEntryType::Rscadd,
                "imageadd" => ChangelogEntryType::Imageadd,
                "imagedel" => ChangelogEntryType::Imagedel,
                "spellcheck" => ChangelogEntryType::Spellcheck,
                "experiment" => ChangelogEntryType::Experiment,
                "tgs" => ChangelogEntryType::Tgs,
                _ => unreachable!(),
            };

            ChangelogEntry(entry_type, m.get(2).unwrap().as_str().trim().to_owned())
        })
        .collect()
}

fn write_temp_changelog(path: &Path, changelog: Changelog) -> std::io::Result<()> {
    let mut file = File::create(path)?;
    serde_yaml::to_writer(&file, &changelog).unwrap(); // TODO: Remove unwrap.
    file.flush()?;
    Ok(())
}

lazy_static! {
    pub static ref CHANGELOG_MANAGER: Mutex<ChangelogManager> =
        { Mutex::new(ChangelogManager { last_time: None }) };
}

pub struct ChangelogManager {
    // If None, no thread is currently on it.
    last_time: Option<Instant>,
}

/// Represents a new changelog entry.
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Changelog {
    pub author: String,
    pub changes: Vec<ChangelogEntry>,
    pub delete_after: Option<bool>,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ChangelogEntry(ChangelogEntryType, String);

impl Serialize for ChangelogEntry {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(1))?;
        map.serialize_entry(&self.0, &self.1)?;
        map.end()
    }
}

impl<'de> Deserialize<'de> for ChangelogEntry {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_map(ChangelogEntryVisitor)
    }
}

struct ChangelogEntryVisitor;

impl<'de> Visitor<'de> for ChangelogEntryVisitor {
    type Value = ChangelogEntry;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("A single-element map")
    }

    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
    where
        M: MapAccess<'de>,
    {
        match access.next_entry()? {
            Some((key, value)) => {
                let value = ChangelogEntry(key, value);
                match access.next_key::<ChangelogEntryType>()? {
                    Some(_) => Err(M::Error::invalid_length(2, &"A single-element map.")),
                    _ => Ok(value),
                }
            }
            None => Err(M::Error::invalid_length(0, &"A single-element map.")),
        }
    }
}

#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ChangelogEntryType {
    Bugfix,
    Wip,
    Tweak,
    Soundadd,
    Sounddel,
    Rscadd,
    Rscdel,
    Imageadd,
    Imagedel,
    Spellcheck,
    Experiment,
    Tgs,
}

pub fn process_changelogs(config: &Arc<MoMMIConfig>) {
    let mut lock = CHANGELOG_MANAGER.lock().unwrap();
    let should_spawn_thread = lock.last_time.is_none();
    lock.last_time = Some(Instant::now());

    if should_spawn_thread {
        // Nobody currently processing.
        lock.last_time = Some(Instant::now());
        let config = config.clone();
        thread::Builder::new()
            .name("Changelog thread".into())
            .spawn(move || {
                handle_changelog_thread(config);
            })
            .unwrap();
    }
}

fn handle_changelog_thread(config: Arc<MoMMIConfig>) {
    let delay = config.get_changelog_delay();

    loop {
        let time = {
            let lock = CHANGELOG_MANAGER.lock().unwrap();
            let elapsed = lock.last_time.as_ref().unwrap().elapsed();
            if elapsed.as_secs() > delay {
                return do_changelog(lock, config);
            }

            match Duration::from_secs(delay).checked_sub(elapsed) {
                Some(t) => t,
                None => return do_changelog(lock, config),
            }
        };
        thread::sleep(time);
    }
}

// Pass the lock directly so we don't risk race conditions.
fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>) {
    println!("Running changelogs!");
    // Get what we need and drop the lock.
    // so we don't hang everything for the time it takes for the git commands and stuff.
    lock.last_time = None;
    drop(lock);

    let path = config.get_changelog_repo_path().unwrap();
    let ssh_config = config
        .get_ssh_key()
        .map(|p| format!("ssh -i {}", p.to_string_lossy()));

    // Git pull the repo.
    let mut command = Command::new("git");
    command
        .arg("pull")
        .arg("origin")
        .arg("--ff-only")
        .current_dir(&path);
    if let Some(ref ssh_command) = ssh_config {
        command.env("GIT_SSH_COMMAND", &ssh_command);
    }
    let status = command.status().unwrap();

    assert!(status.success());

    let mut changelog_dir_path = path.to_owned();
    changelog_dir_path.push("html/changelogs");

    // Send changelog files over to MoMMI maybe.
    if let Some((addr, pass)) = config.get_commloop() {
        for entry in read_dir(&changelog_dir_path).unwrap() {
            let entry = entry.unwrap();
            let os_file_name = entry.file_name();
            let file_name = os_file_name.to_str().unwrap();
            if file_name.starts_with(".")
                || !file_name.ends_with(".yml")
                || file_name == "example.yml"
            {
                continue;
            }

            println!("{}", file_name);

            let file = File::open(entry.path()).unwrap();
            let data: Changelog = serde_yaml::from_reader(&file).unwrap();

            if data.changes.len() == 0 {
                continue;
            }

            commloop(addr, pass, "changelog", "", data).unwrap();
        }
    }

    // Run changelog script.
    let status = Command::new("python2")
        .arg("tools/changelog/ss13_genchangelog.py")
        .arg("html/changelog.html")
        .arg("html/changelogs")
        .current_dir(&path)
        .status()
        .unwrap();

    assert!(status.success());

    Command::new("git")
        .arg("update-index")
        .arg("--refresh")
        .current_dir(&path)
        .status()
        .unwrap();

    // See if repo is dirty.
    let status = Command::new("git")
        .arg("diff-index")
        .arg("--exit-code")
        .arg("HEAD")
        .current_dir(&path)
        .status()
        .unwrap();

    if status.code().unwrap_or(0) == 0 {
        // No changes, nothing to commit.
        return;
    }

    let status = Command::new("git")
        .arg("add")
        .arg(".")
        .arg("-A")
        .current_dir(&path)
        .status()
        .unwrap();

    assert!(status.success());

    let status = Command::new("git")
        .arg("commit")
        .arg("-m")
        .arg("[ci skip] Automatic changelog update.")
        .current_dir(&path)
        .status()
        .unwrap();

    assert!(status.success());

    // Git push the repo.
    let mut command = Command::new("git");
    command.arg("push").arg("origin").current_dir(&path);
    if let Some(ref ssh_command) = ssh_config {
        command.env("GIT_SSH_COMMAND", &ssh_command);
    }
    let status = command.status().unwrap();

    assert!(status.success());

    println!("done");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn body_parse_test() {
        // vgstation-coders/vgstation13#21025
        const TEST: &str = "Ghosts can now possess dionae and mushmonkeys via clicking on them, should they have no client controlling them.\r\n\r\n:cl:\r\n * rscadd: Ghosts can now possess inactive diona nymphs and mushrum monkeys by clicking on them.  \r\n * rscadd: Dionae now don't expire after harvesting, should they not be possessed in the given time.  ";

        let entries = parse_body_changelog(TEST);
        assert_eq!(entries.len(), 2);
        assert_eq!(entries[0], ChangelogEntry(ChangelogEntryType::Rscadd, "Ghosts can now possess inactive diona nymphs and mushrum monkeys by clicking on them.".into()));
        assert_eq!(entries[1], ChangelogEntry(ChangelogEntryType::Rscadd, "Dionae now don't expire after harvesting, should they not be possessed in the given time.".into()));
    }
}
<|endoftext|>


<commit_before>/* 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/. */

//! Helpers for different FFI pointer kinds that Gecko's FFI layer uses.

use servo_arc::{Arc, RawOffsetArc};
use std::marker::PhantomData;
use std::mem::{forget, transmute};
use std::ops::{Deref, DerefMut};
use std::ptr;

/// Indicates that a given Servo type has a corresponding Gecko FFI type.
pub unsafe trait HasFFI: Sized + 'static {
    /// The corresponding Gecko type that this rust type represents.
    ///
    /// See the examples in `components/style/gecko/conversions.rs`.
    type FFIType: Sized;
}

/// Indicates that a given Servo type has the same layout as the corresponding
/// `HasFFI::FFIType` type.
pub unsafe trait HasSimpleFFI: HasFFI {
    #[inline]
    /// Given a Servo-side reference, converts it to an FFI-safe reference which
    /// can be passed to Gecko.
    ///
    /// &ServoType -> &GeckoType
    fn as_ffi(&self) -> &Self::FFIType {
        unsafe { transmute(self) }
    }
    #[inline]
    /// Given a Servo-side mutable reference, converts it to an FFI-safe mutable
    /// reference which can be passed to Gecko.
    ///
    /// &mut ServoType -> &mut GeckoType
    fn as_ffi_mut(&mut self) -> &mut Self::FFIType {
        unsafe { transmute(self) }
    }
    #[inline]
    /// Given an FFI-safe reference obtained from Gecko converts it to a
    /// Servo-side reference.
    ///
    /// &GeckoType -> &ServoType
    fn from_ffi(ffi: &Self::FFIType) -> &Self {
        unsafe { transmute(ffi) }
    }
    #[inline]
    /// Given an FFI-safe mutable reference obtained from Gecko converts it to a
    /// Servo-side mutable reference.
    ///
    /// &mut GeckoType -> &mut ServoType
    fn from_ffi_mut(ffi: &mut Self::FFIType) -> &mut Self {
        unsafe { transmute(ffi) }
    }
}

/// Indicates that the given Servo type is passed over FFI
/// as a Box
pub unsafe trait HasBoxFFI: HasSimpleFFI {
    #[inline]
    /// Converts a borrowed Arc to a borrowed FFI reference.
    ///
    /// &Arc<ServoType> -> &GeckoType
    fn into_ffi(self: Box<Self>) -> Owned<Self::FFIType> {
        unsafe { transmute(self) }
    }

    /// Drops an owned FFI pointer. This conceptually takes the
    /// Owned<Self::FFIType>, except it's a bit of a paint to do that without
    /// much benefit.
    #[inline]
    unsafe fn drop_ffi(ptr: *mut Self::FFIType) {
        let _ = Box::from_raw(ptr as *mut Self);
    }
}

/// Helper trait for conversions between FFI Strong/Borrowed types and Arcs
///
/// Should be implemented by types which are passed over FFI as Arcs via Strong
/// and Borrowed.
///
/// In this case, the FFIType is the rough equivalent of ArcInner<Self>.
pub unsafe trait HasArcFFI: HasFFI {
    // these methods can't be on Borrowed because it leads to an unspecified
    // impl parameter
    /// Artificially increments the refcount of a (possibly null) borrowed Arc
    /// over FFI.
    unsafe fn addref_opt(ptr: Option<&Self::FFIType>) {
        forget(Self::arc_from_borrowed(&ptr).clone())
    }

    /// Given a (possibly null) borrowed FFI reference, decrements the refcount.
    /// Unsafe since it doesn't consume the backing Arc. Run it only when you
    /// know that a strong reference to the backing Arc is disappearing
    /// (usually on the C++ side) without running the Arc destructor.
    unsafe fn release_opt(ptr: Option<&Self::FFIType>) {
        if let Some(arc) = Self::arc_from_borrowed(&ptr) {
            let _: RawOffsetArc<_> = ptr::read(arc as *const RawOffsetArc<_>);
        }
    }

    /// Artificially increments the refcount of a borrowed Arc over FFI.
    unsafe fn addref(ptr: &Self::FFIType) {
        forget(Self::as_arc(&ptr).clone())
    }

    /// Given a non-null borrowed FFI reference, decrements the refcount.
    /// Unsafe since it doesn't consume the backing Arc. Run it only when you
    /// know that a strong reference to the backing Arc is disappearing
    /// (usually on the C++ side) without running the Arc destructor.
    unsafe fn release(ptr: &Self::FFIType) {
        let _: RawOffsetArc<_> = ptr::read(Self::as_arc(&ptr) as *const RawOffsetArc<_>);
    }
    #[inline]
    /// Converts a borrowed FFI reference to a borrowed Arc.
    ///
    /// &GeckoType -> &Arc<ServoType>
    fn as_arc<'a>(ptr: &'a &Self::FFIType) -> &'a RawOffsetArc<Self> {
        unsafe { transmute::<&&Self::FFIType, &RawOffsetArc<Self>>(ptr) }
    }

    #[inline]
    /// Converts a borrowed Arc to a borrowed FFI reference.
    ///
    /// &Arc<ServoType> -> &GeckoType
    fn arc_as_borrowed<'a>(arc: &'a RawOffsetArc<Self>) -> &'a &Self::FFIType {
        unsafe { transmute::<&RawOffsetArc<Self>, &&Self::FFIType>(arc) }
    }

    #[inline]
    /// Converts a borrowed nullable FFI reference to a borrowed Arc.
    ///
    /// &GeckoType -> &Arc<ServoType>
    fn arc_from_borrowed<'a>(ptr: &'a Option<&Self::FFIType>) -> Option<&'a RawOffsetArc<Self>> {
        unsafe {
            if let Some(ref reference) = *ptr {
                Some(transmute::<&&Self::FFIType, &RawOffsetArc<_>>(reference))
            } else {
                None
            }
        }
    }
}

/// Gecko-FFI-safe Arc (T is an ArcInner).
///
/// This can be null.
///
/// Leaks on drop. Please don't drop this.
#[repr(C)]
pub struct Strong<GeckoType> {
    ptr: *const GeckoType,
    _marker: PhantomData<GeckoType>,
}

impl<GeckoType> Strong<GeckoType> {
    #[inline]
    /// Returns whether this reference is null.
    pub fn is_null(&self) -> bool {
        self.ptr.is_null()
    }

    #[inline]
    /// Given a non-null strong FFI reference, converts it into a servo-side
    /// Arc.
    ///
    /// Panics on null.
    ///
    /// Strong<GeckoType> -> Arc<ServoType>
    pub fn into_arc<ServoType>(self) -> RawOffsetArc<ServoType>
    where
        ServoType: HasArcFFI<FFIType = GeckoType>,
    {
        self.into_arc_opt().unwrap()
    }

    #[inline]
    /// Given a strong FFI reference,
    /// converts it into a servo-side Arc
    /// Returns None on null.
    ///
    /// Strong<GeckoType> -> Arc<ServoType>
    pub fn into_arc_opt<ServoType>(self) -> Option<RawOffsetArc<ServoType>>
    where
        ServoType: HasArcFFI<FFIType = GeckoType>,
    {
        if self.is_null() {
            None
        } else {
            unsafe { Some(transmute(self)) }
        }
    }

    #[inline]
    /// Given a reference to a strong FFI reference, converts it to a reference
    /// to a servo-side Arc.
    ///
    /// Returns None on null.
    ///
    /// Strong<GeckoType> -> Arc<ServoType>
    pub fn as_arc_opt<ServoType>(&self) -> Option<&RawOffsetArc<ServoType>>
    where
        ServoType: HasArcFFI<FFIType = GeckoType>,
    {
        if self.is_null() {
            None
        } else {
            unsafe { Some(transmute(self)) }
        }
    }

    #[inline]
    /// Produces a null strong FFI reference.
    pub fn null() -> Self {
        unsafe { transmute(ptr::null::<GeckoType>()) }
    }
}

/// A few helpers implemented on top of Arc<ServoType> to make it more
/// comfortable to use and write safe code with.
pub unsafe trait FFIArcHelpers {
    /// The Rust FFI type that we're implementing methods for.
    type Inner: HasArcFFI;

    /// Converts an Arc into a strong FFI reference.
    ///
    /// Arc<ServoType> -> Strong<GeckoType>
    fn into_strong(self) -> Strong<<Self::Inner as HasFFI>::FFIType>;

    /// Produces a borrowed FFI reference by borrowing an Arc.
    ///
    /// &Arc<ServoType> -> &GeckoType
    ///
    /// Then the `arc_as_borrowed` method can go away.
    fn as_borrowed(&self) -> &<Self::Inner as HasFFI>::FFIType;
}

unsafe impl<T: HasArcFFI> FFIArcHelpers for RawOffsetArc<T> {
    type Inner = T;

    #[inline]
    fn into_strong(self) -> Strong<T::FFIType> {
        unsafe { transmute(self) }
    }

    #[inline]
    fn as_borrowed(&self) -> &T::FFIType {
        unsafe { &*(&**self as *const T as *const T::FFIType) }
    }
}

unsafe impl<T: HasArcFFI> FFIArcHelpers for Arc<T> {
    type Inner = T;

    #[inline]
    fn into_strong(self) -> Strong<T::FFIType> {
        Arc::into_raw_offset(self).into_strong()
    }

    #[inline]
    fn as_borrowed(&self) -> &T::FFIType {
        unsafe { &*(&**self as *const T as *const T::FFIType) }
    }
}

#[repr(C)]
#[derive(Debug)]
/// Gecko-FFI-safe owned pointer.
///
/// Cannot be null, and leaks on drop, so needs to be converted into a rust-side
/// `Box` before.
pub struct Owned<GeckoType> {
    ptr: *mut GeckoType,
    _marker: PhantomData<GeckoType>,
}

impl<GeckoType> Owned<GeckoType> {
    /// Converts this instance to a (non-null) instance of `OwnedOrNull`.
    pub fn maybe(self) -> OwnedOrNull<GeckoType> {
        unsafe { transmute(self) }
    }
}

impl<GeckoType> Deref for Owned<GeckoType> {
    type Target = GeckoType;
    fn deref(&self) -> &GeckoType {
        unsafe { &*self.ptr }
    }
}

impl<GeckoType> DerefMut for Owned<GeckoType> {
    fn deref_mut(&mut self) -> &mut GeckoType {
        unsafe { &mut *self.ptr }
    }
}

#[repr(C)]
/// Gecko-FFI-safe owned pointer.
///
/// Can be null, and just as `Owned` leaks on `Drop`.
pub struct OwnedOrNull<GeckoType> {
    ptr: *mut GeckoType,
    _marker: PhantomData<GeckoType>,
}

impl<GeckoType> OwnedOrNull<GeckoType> {
    /// Returns a null pointer.
    #[inline]
    pub fn null() -> Self {
        Self {
            ptr: ptr::null_mut(),
            _marker: PhantomData,
        }
    }

    /// Returns whether this pointer is null.
    #[inline]
    pub fn is_null(&self) -> bool {
        self.ptr.is_null()
    }

    /// Gets a immutable reference to the underlying Gecko type, or `None` if
    /// null.
    pub fn borrow(&self) -> Option<&GeckoType> {
        unsafe { transmute(self) }
    }

    /// Gets a mutable reference to the underlying Gecko type, or `None` if
    /// null.
    pub fn borrow_mut(&self) -> Option<&mut GeckoType> {
        unsafe { transmute(self) }
    }
}
<commit_msg>style: Make NotNull move its member pointer where possible.<commit_after>/* 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/. */

//! Helpers for different FFI pointer kinds that Gecko's FFI layer uses.

use servo_arc::{Arc, RawOffsetArc};
use std::marker::PhantomData;
use std::mem::{forget, transmute};
use std::ops::{Deref, DerefMut};
use std::ptr;
use gecko_bindings::structs::root::mozilla::detail::CopyablePtr;

/// Indicates that a given Servo type has a corresponding Gecko FFI type.
pub unsafe trait HasFFI: Sized + 'static {
    /// The corresponding Gecko type that this rust type represents.
    ///
    /// See the examples in `components/style/gecko/conversions.rs`.
    type FFIType: Sized;
}

/// Indicates that a given Servo type has the same layout as the corresponding
/// `HasFFI::FFIType` type.
pub unsafe trait HasSimpleFFI: HasFFI {
    #[inline]
    /// Given a Servo-side reference, converts it to an FFI-safe reference which
    /// can be passed to Gecko.
    ///
    /// &ServoType -> &GeckoType
    fn as_ffi(&self) -> &Self::FFIType {
        unsafe { transmute(self) }
    }
    #[inline]
    /// Given a Servo-side mutable reference, converts it to an FFI-safe mutable
    /// reference which can be passed to Gecko.
    ///
    /// &mut ServoType -> &mut GeckoType
    fn as_ffi_mut(&mut self) -> &mut Self::FFIType {
        unsafe { transmute(self) }
    }
    #[inline]
    /// Given an FFI-safe reference obtained from Gecko converts it to a
    /// Servo-side reference.
    ///
    /// &GeckoType -> &ServoType
    fn from_ffi(ffi: &Self::FFIType) -> &Self {
        unsafe { transmute(ffi) }
    }
    #[inline]
    /// Given an FFI-safe mutable reference obtained from Gecko converts it to a
    /// Servo-side mutable reference.
    ///
    /// &mut GeckoType -> &mut ServoType
    fn from_ffi_mut(ffi: &mut Self::FFIType) -> &mut Self {
        unsafe { transmute(ffi) }
    }
}

/// Indicates that the given Servo type is passed over FFI
/// as a Box
pub unsafe trait HasBoxFFI: HasSimpleFFI {
    #[inline]
    /// Converts a borrowed Arc to a borrowed FFI reference.
    ///
    /// &Arc<ServoType> -> &GeckoType
    fn into_ffi(self: Box<Self>) -> Owned<Self::FFIType> {
        unsafe { transmute(self) }
    }

    /// Drops an owned FFI pointer. This conceptually takes the
    /// Owned<Self::FFIType>, except it's a bit of a paint to do that without
    /// much benefit.
    #[inline]
    unsafe fn drop_ffi(ptr: *mut Self::FFIType) {
        let _ = Box::from_raw(ptr as *mut Self);
    }
}

/// Helper trait for conversions between FFI Strong/Borrowed types and Arcs
///
/// Should be implemented by types which are passed over FFI as Arcs via Strong
/// and Borrowed.
///
/// In this case, the FFIType is the rough equivalent of ArcInner<Self>.
pub unsafe trait HasArcFFI: HasFFI {
    // these methods can't be on Borrowed because it leads to an unspecified
    // impl parameter
    /// Artificially increments the refcount of a (possibly null) borrowed Arc
    /// over FFI.
    unsafe fn addref_opt(ptr: Option<&Self::FFIType>) {
        forget(Self::arc_from_borrowed(&ptr).clone())
    }

    /// Given a (possibly null) borrowed FFI reference, decrements the refcount.
    /// Unsafe since it doesn't consume the backing Arc. Run it only when you
    /// know that a strong reference to the backing Arc is disappearing
    /// (usually on the C++ side) without running the Arc destructor.
    unsafe fn release_opt(ptr: Option<&Self::FFIType>) {
        if let Some(arc) = Self::arc_from_borrowed(&ptr) {
            let _: RawOffsetArc<_> = ptr::read(arc as *const RawOffsetArc<_>);
        }
    }

    /// Artificially increments the refcount of a borrowed Arc over FFI.
    unsafe fn addref(ptr: &Self::FFIType) {
        forget(Self::as_arc(&ptr).clone())
    }

    /// Given a non-null borrowed FFI reference, decrements the refcount.
    /// Unsafe since it doesn't consume the backing Arc. Run it only when you
    /// know that a strong reference to the backing Arc is disappearing
    /// (usually on the C++ side) without running the Arc destructor.
    unsafe fn release(ptr: &Self::FFIType) {
        let _: RawOffsetArc<_> = ptr::read(Self::as_arc(&ptr) as *const RawOffsetArc<_>);
    }
    #[inline]
    /// Converts a borrowed FFI reference to a borrowed Arc.
    ///
    /// &GeckoType -> &Arc<ServoType>
    fn as_arc<'a>(ptr: &'a &Self::FFIType) -> &'a RawOffsetArc<Self> {
        unsafe { transmute::<&&Self::FFIType, &RawOffsetArc<Self>>(ptr) }
    }

    #[inline]
    /// Converts a borrowed Arc to a borrowed FFI reference.
    ///
    /// &Arc<ServoType> -> &GeckoType
    fn arc_as_borrowed<'a>(arc: &'a RawOffsetArc<Self>) -> &'a &Self::FFIType {
        unsafe { transmute::<&RawOffsetArc<Self>, &&Self::FFIType>(arc) }
    }

    #[inline]
    /// Converts a borrowed nullable FFI reference to a borrowed Arc.
    ///
    /// &GeckoType -> &Arc<ServoType>
    fn arc_from_borrowed<'a>(ptr: &'a Option<&Self::FFIType>) -> Option<&'a RawOffsetArc<Self>> {
        unsafe {
            if let Some(ref reference) = *ptr {
                Some(transmute::<&&Self::FFIType, &RawOffsetArc<_>>(reference))
            } else {
                None
            }
        }
    }
}

/// Gecko-FFI-safe Arc (T is an ArcInner).
///
/// This can be null.
///
/// Leaks on drop. Please don't drop this.
#[repr(C)]
pub struct Strong<GeckoType> {
    ptr: *const GeckoType,
    _marker: PhantomData<GeckoType>,
}

impl<GeckoType> Strong<GeckoType> {
    #[inline]
    /// Returns whether this reference is null.
    pub fn is_null(&self) -> bool {
        self.ptr.is_null()
    }

    #[inline]
    /// Given a non-null strong FFI reference, converts it into a servo-side
    /// Arc.
    ///
    /// Panics on null.
    ///
    /// Strong<GeckoType> -> Arc<ServoType>
    pub fn into_arc<ServoType>(self) -> RawOffsetArc<ServoType>
    where
        ServoType: HasArcFFI<FFIType = GeckoType>,
    {
        self.into_arc_opt().unwrap()
    }

    #[inline]
    /// Given a strong FFI reference,
    /// converts it into a servo-side Arc
    /// Returns None on null.
    ///
    /// Strong<GeckoType> -> Arc<ServoType>
    pub fn into_arc_opt<ServoType>(self) -> Option<RawOffsetArc<ServoType>>
    where
        ServoType: HasArcFFI<FFIType = GeckoType>,
    {
        if self.is_null() {
            None
        } else {
            unsafe { Some(transmute(self)) }
        }
    }

    #[inline]
    /// Given a reference to a strong FFI reference, converts it to a reference
    /// to a servo-side Arc.
    ///
    /// Returns None on null.
    ///
    /// Strong<GeckoType> -> Arc<ServoType>
    pub fn as_arc_opt<ServoType>(&self) -> Option<&RawOffsetArc<ServoType>>
    where
        ServoType: HasArcFFI<FFIType = GeckoType>,
    {
        if self.is_null() {
            None
        } else {
            unsafe { Some(transmute(self)) }
        }
    }

    #[inline]
    /// Produces a null strong FFI reference.
    pub fn null() -> Self {
        unsafe { transmute(ptr::null::<GeckoType>()) }
    }
}

/// A few helpers implemented on top of Arc<ServoType> to make it more
/// comfortable to use and write safe code with.
pub unsafe trait FFIArcHelpers {
    /// The Rust FFI type that we're implementing methods for.
    type Inner: HasArcFFI;

    /// Converts an Arc into a strong FFI reference.
    ///
    /// Arc<ServoType> -> Strong<GeckoType>
    fn into_strong(self) -> Strong<<Self::Inner as HasFFI>::FFIType>;

    /// Produces a borrowed FFI reference by borrowing an Arc.
    ///
    /// &Arc<ServoType> -> &GeckoType
    ///
    /// Then the `arc_as_borrowed` method can go away.
    fn as_borrowed(&self) -> &<Self::Inner as HasFFI>::FFIType;
}

unsafe impl<T: HasArcFFI> FFIArcHelpers for RawOffsetArc<T> {
    type Inner = T;

    #[inline]
    fn into_strong(self) -> Strong<T::FFIType> {
        unsafe { transmute(self) }
    }

    #[inline]
    fn as_borrowed(&self) -> &T::FFIType {
        unsafe { &*(&**self as *const T as *const T::FFIType) }
    }
}

unsafe impl<T: HasArcFFI> FFIArcHelpers for Arc<T> {
    type Inner = T;

    #[inline]
    fn into_strong(self) -> Strong<T::FFIType> {
        Arc::into_raw_offset(self).into_strong()
    }

    #[inline]
    fn as_borrowed(&self) -> &T::FFIType {
        unsafe { &*(&**self as *const T as *const T::FFIType) }
    }
}

#[repr(C)]
#[derive(Debug)]
/// Gecko-FFI-safe owned pointer.
///
/// Cannot be null, and leaks on drop, so needs to be converted into a rust-side
/// `Box` before.
pub struct Owned<GeckoType> {
    ptr: *mut GeckoType,
    _marker: PhantomData<GeckoType>,
}

impl<GeckoType> Owned<GeckoType> {
    /// Converts this instance to a (non-null) instance of `OwnedOrNull`.
    pub fn maybe(self) -> OwnedOrNull<GeckoType> {
        unsafe { transmute(self) }
    }
}

impl<GeckoType> Deref for Owned<GeckoType> {
    type Target = GeckoType;
    fn deref(&self) -> &GeckoType {
        unsafe { &*self.ptr }
    }
}

impl<GeckoType> DerefMut for Owned<GeckoType> {
    fn deref_mut(&mut self) -> &mut GeckoType {
        unsafe { &mut *self.ptr }
    }
}

#[repr(C)]
/// Gecko-FFI-safe owned pointer.
///
/// Can be null, and just as `Owned` leaks on `Drop`.
pub struct OwnedOrNull<GeckoType> {
    ptr: *mut GeckoType,
    _marker: PhantomData<GeckoType>,
}

impl<GeckoType> OwnedOrNull<GeckoType> {
    /// Returns a null pointer.
    #[inline]
    pub fn null() -> Self {
        Self {
            ptr: ptr::null_mut(),
            _marker: PhantomData,
        }
    }

    /// Returns whether this pointer is null.
    #[inline]
    pub fn is_null(&self) -> bool {
        self.ptr.is_null()
    }

    /// Gets a immutable reference to the underlying Gecko type, or `None` if
    /// null.
    pub fn borrow(&self) -> Option<&GeckoType> {
        unsafe { transmute(self) }
    }

    /// Gets a mutable reference to the underlying Gecko type, or `None` if
    /// null.
    pub fn borrow_mut(&self) -> Option<&mut GeckoType> {
        unsafe { transmute(self) }
    }
}

impl<T> Deref for CopyablePtr<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.mPtr
    }
}

impl<T> DerefMut for CopyablePtr<T> {
    fn deref_mut<'a>(&'a mut self) -> &'a mut T {
        &mut self.mPtr
    }
}<|endoftext|>