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

//! A collection of invalidations due to changes in which stylesheets affect a
//! document.

#![deny(unsafe_code)]

use Atom;
use dom::{TElement, TNode};
use fnv::FnvHashSet;
use invalidation::element::restyle_hints::RestyleHint;
use media_queries::Device;
use selector_parser::SelectorImpl;
use selectors::attr::CaseSensitivity;
use selectors::parser::{Component, Selector};
use shared_lock::SharedRwLockReadGuard;
use stylesheets::{CssRule, StylesheetInDocument};

/// An invalidation scope represents a kind of subtree that may need to be
/// restyled.
#[derive(Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
enum InvalidationScope {
    /// All the descendants of an element with a given id.
    ID(Atom),
    /// All the descendants of an element with a given class name.
    Class(Atom),
}

impl InvalidationScope {
    fn is_id(&self) -> bool {
        matches!(*self, InvalidationScope::ID(..))
    }

    fn matches<E>(&self, element: E) -> bool
        where E: TElement,
    {
        match *self {
            InvalidationScope::Class(ref class) => {
                element.has_class(class, CaseSensitivity::CaseSensitive)
            }
            InvalidationScope::ID(ref id) => {
                match element.get_id() {
                    Some(element_id) => element_id == *id,
                    None => false,
                }
            }
        }
    }
}

/// A set of invalidations due to stylesheet additions.
///
/// TODO(emilio): We might be able to do the same analysis for media query
/// changes too (or even selector changes?).
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct StylesheetInvalidationSet {
    /// The style scopes we know we have to restyle so far.
    invalid_scopes: FnvHashSet<InvalidationScope>,
    /// Whether the whole document should be invalid.
    fully_invalid: bool,
}

impl StylesheetInvalidationSet {
    /// Create an empty `StylesheetInvalidationSet`.
    pub fn new() -> Self {
        Self {
            invalid_scopes: FnvHashSet::default(),
            fully_invalid: false,
        }
    }

    /// Mark the DOM tree styles' as fully invalid.
    pub fn invalidate_fully(&mut self) {
        debug!("StylesheetInvalidationSet::invalidate_fully");
        self.invalid_scopes.clear();
        self.fully_invalid = true;
    }

    /// Analyze the given stylesheet, and collect invalidations from their
    /// rules, in order to avoid doing a full restyle when we style the document
    /// next time.
    pub fn collect_invalidations_for<S>(
        &mut self,
        device: &Device,
        stylesheet: &S,
        guard: &SharedRwLockReadGuard
    )
    where
        S: StylesheetInDocument,
    {
        debug!("StylesheetInvalidationSet::collect_invalidations_for");
        if self.fully_invalid {
            debug!(" > Fully invalid already");
            return;
        }

        if !stylesheet.enabled() ||
           !stylesheet.is_effective_for_device(device, guard) {
            debug!(" > Stylesheet was not effective");
            return; // Nothing to do here.
        }

        for rule in stylesheet.effective_rules(device, guard) {
            self.collect_invalidations_for_rule(rule, guard);
            if self.fully_invalid {
                self.invalid_scopes.clear();
                break;
            }
        }

        debug!(" > resulting invalidations: {:?}", self.invalid_scopes);
        debug!(" > fully_invalid: {}", self.fully_invalid);
    }

    /// Clears the invalidation set, invalidating elements as needed if
    /// `document_element` is provided.
    ///
    /// Returns true if any invalidations ocurred.
    pub fn flush<E>(&mut self, document_element: Option<E>) -> bool
        where E: TElement,
    {
        let have_invalidations = match document_element {
            Some(e) => self.process_invalidations(e),
            None => false,
        };
        self.clear();
        have_invalidations
    }

    /// Clears the invalidation set without processing.
    pub fn clear(&mut self) {
        self.invalid_scopes.clear();
        self.fully_invalid = false;
    }

    fn process_invalidations<E>(&self, element: E) -> bool
        where E: TElement,
    {
        {
            let mut data = match element.mutate_data() {
                Some(data) => data,
                None => return false,
            };

            if self.fully_invalid {
                debug!("process_invalidations: fully_invalid({:?})",
                       element);
                data.hint.insert(RestyleHint::restyle_subtree());
                return true;
            }
        }

        if self.invalid_scopes.is_empty() {
            debug!("process_invalidations: empty invalidation set");
            return false;
        }

        self.process_invalidations_in_subtree(element)
    }

    /// Process style invalidations in a given subtree, that is, look for all
    /// the relevant scopes in the subtree, and mark as dirty only the relevant
    /// ones.
    ///
    /// Returns whether it invalidated at least one element's style.
    #[allow(unsafe_code)]
    fn process_invalidations_in_subtree<E>(&self, element: E) -> bool
        where E: TElement,
    {
        let mut data = match element.mutate_data() {
            Some(data) => data,
            None => return false,
        };

        if !data.has_styles() {
            return false;
        }

        if data.hint.contains_subtree() {
            debug!("process_invalidations_in_subtree: {:?} was already invalid",
                   element);
            return false;
        }

        for scope in &self.invalid_scopes {
            if scope.matches(element) {
                debug!("process_invalidations_in_subtree: {:?} matched {:?}",
                       element, scope);
                data.hint.insert(RestyleHint::restyle_subtree());
                return true;
            }
        }

        let mut any_children_invalid = false;

        for child in element.as_node().traversal_children() {
            let child = match child.as_element() {
                Some(e) => e,
                None => continue,
            };

            any_children_invalid |= self.process_invalidations_in_subtree(child);
        }

        if any_children_invalid {
            debug!("Children of {:?} changed, setting dirty descendants",
                   element);
            unsafe { element.set_dirty_descendants() }
        }

        return any_children_invalid
    }

    fn scan_component(
        component: &Component<SelectorImpl>,
        scope: &mut Option<InvalidationScope>)
    {
        match *component {
            Component::Class(ref class) => {
                if scope.as_ref().map_or(true, |s| !s.is_id()) {
                    *scope = Some(InvalidationScope::Class(class.clone()));
                }
            }
            Component::ID(ref id) => {
                if scope.is_none() {
                    *scope = Some(InvalidationScope::ID(id.clone()));
                }
            }
            _ => {
                // Ignore everything else, at least for now.
            }
        }
    }

    /// Collect a style scopes for a given selector.
    ///
    /// We look at the outermost class or id selector to the left of an ancestor
    /// combinator, in order to restyle only a given subtree.
    ///
    /// We prefer id scopes to class scopes, and outermost scopes to innermost
    /// scopes (to reduce the amount of traversal we need to do).
    fn collect_scopes(&mut self, selector: &Selector<SelectorImpl>) {
        debug!("StylesheetInvalidationSet::collect_scopes({:?})", selector);

        let mut scope: Option<InvalidationScope> = None;

        let mut scan = true;
        let mut iter = selector.iter();

        loop {
            for component in &mut iter {
                if scan {
                    Self::scan_component(component, &mut scope);
                }
            }
            match iter.next_sequence() {
                None => break,
                Some(combinator) => {
                    scan = combinator.is_ancestor();
                }
            }
        }

        match scope {
            Some(s) => {
                debug!(" > Found scope: {:?}", s);
                self.invalid_scopes.insert(s);
            }
            None => {
                debug!(" > Scope not found");

                // If we didn't find a scope, any element could match this, so
                // let's just bail out.
                self.fully_invalid = true;
            }
        }
    }

    /// Collects invalidations for a given CSS rule.
    fn collect_invalidations_for_rule(
        &mut self,
        rule: &CssRule,
        guard: &SharedRwLockReadGuard)
    {
        use stylesheets::CssRule::*;
        debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
        debug_assert!(!self.fully_invalid, "Not worth to be here!");

        match *rule {
            Style(ref lock) => {
                let style_rule = lock.read_with(guard);
                for selector in &style_rule.selectors.0 {
                    self.collect_scopes(selector);
                    if self.fully_invalid {
                        return;
                    }
                }
            }
            Document(..) |
            Namespace(..) |
            Import(..) |
            Media(..) |
            Supports(..) => {
                // Do nothing, relevant nested rules are visited as part of the
                // iteration.
            }
            FontFace(..) |
            CounterStyle(..) |
            Keyframes(..) |
            Page(..) |
            Viewport(..) |
            FontFeatureValues(..) => {
                debug!(" > Found unsupported rule, marking the whole subtree \
                       invalid.");

                // TODO(emilio): Can we do better here?
                //
                // At least in `@page`, we could check the relevant media, I
                // guess.
                self.fully_invalid = true;
            }
        }
    }
}
<commit_msg>Auto merge of #18827 - heycam:invalidate-less, r=emilio<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/. */

//! A collection of invalidations due to changes in which stylesheets affect a
//! document.

#![deny(unsafe_code)]

use Atom;
use LocalName as SelectorLocalName;
use dom::{TElement, TNode};
use fnv::FnvHashSet;
use invalidation::element::restyle_hints::{RESTYLE_SELF, RestyleHint};
use media_queries::Device;
use selector_parser::SelectorImpl;
use selectors::attr::CaseSensitivity;
use selectors::parser::{Component, LocalName, Selector};
use shared_lock::SharedRwLockReadGuard;
use stylesheets::{CssRule, StylesheetInDocument};

/// A style sheet invalidation represents a kind of element or subtree that may
/// need to be restyled. Whether it represents a whole subtree or just a single
/// element is determined by whether the invalidation is stored in the
/// StylesheetInvalidationSet's invalid_scopes or invalid_elements table.
#[derive(Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
enum Invalidation {
    /// An element with a given id.
    ID(Atom),
    /// An element with a given class name.
    Class(Atom),
    /// An element with a given local name.
    LocalName { name: SelectorLocalName, lower_name: SelectorLocalName },
}

impl Invalidation {
    fn is_id(&self) -> bool {
        matches!(*self, Invalidation::ID(..))
    }

    fn is_id_or_class(&self) -> bool {
        matches!(*self, Invalidation::ID(..) | Invalidation::Class(..))
    }

    fn matches<E>(&self, element: E) -> bool
        where E: TElement,
    {
        match *self {
            Invalidation::Class(ref class) => {
                // FIXME This should look at the quirks mode of the document to
                // determine case sensitivity.
                element.has_class(class, CaseSensitivity::CaseSensitive)
            }
            Invalidation::ID(ref id) => {
                match element.get_id() {
                    // FIXME This should look at the quirks mode of the document
                    // to determine case sensitivity.
                    Some(element_id) => element_id == *id,
                    None => false,
                }
            }
            Invalidation::LocalName { ref name, ref lower_name } => {
                // This could look at the quirks mode of the document, instead
                // of testing against both names, but it's probably not worth
                // it.
                let local_name = element.get_local_name();
                *local_name == **name || *local_name == **lower_name
            }
        }
    }
}

/// A set of invalidations due to stylesheet additions.
///
/// TODO(emilio): We might be able to do the same analysis for media query
/// changes too (or even selector changes?).
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct StylesheetInvalidationSet {
    /// The subtrees we know we have to restyle so far.
    invalid_scopes: FnvHashSet<Invalidation>,
    /// The elements we know we have to restyle so far.
    invalid_elements: FnvHashSet<Invalidation>,
    /// Whether the whole document should be restyled.
    fully_invalid: bool,
}

impl StylesheetInvalidationSet {
    /// Create an empty `StylesheetInvalidationSet`.
    pub fn new() -> Self {
        Self {
            invalid_scopes: FnvHashSet::default(),
            invalid_elements: FnvHashSet::default(),
            fully_invalid: false,
        }
    }

    /// Mark the DOM tree styles' as fully invalid.
    pub fn invalidate_fully(&mut self) {
        debug!("StylesheetInvalidationSet::invalidate_fully");
        self.invalid_scopes.clear();
        self.invalid_elements.clear();
        self.fully_invalid = true;
    }

    /// Analyze the given stylesheet, and collect invalidations from their
    /// rules, in order to avoid doing a full restyle when we style the document
    /// next time.
    pub fn collect_invalidations_for<S>(
        &mut self,
        device: &Device,
        stylesheet: &S,
        guard: &SharedRwLockReadGuard
    )
    where
        S: StylesheetInDocument,
    {
        debug!("StylesheetInvalidationSet::collect_invalidations_for");
        if self.fully_invalid {
            debug!(" > Fully invalid already");
            return;
        }

        if !stylesheet.enabled() ||
           !stylesheet.is_effective_for_device(device, guard) {
            debug!(" > Stylesheet was not effective");
            return; // Nothing to do here.
        }

        for rule in stylesheet.effective_rules(device, guard) {
            self.collect_invalidations_for_rule(rule, guard);
            if self.fully_invalid {
                self.invalid_scopes.clear();
                self.invalid_elements.clear();
                break;
            }
        }

        debug!(" > resulting subtree invalidations: {:?}", self.invalid_scopes);
        debug!(" > resulting self invalidations: {:?}", self.invalid_elements);
        debug!(" > fully_invalid: {}", self.fully_invalid);
    }

    /// Clears the invalidation set, invalidating elements as needed if
    /// `document_element` is provided.
    ///
    /// Returns true if any invalidations ocurred.
    pub fn flush<E>(&mut self, document_element: Option<E>) -> bool
        where E: TElement,
    {
        let have_invalidations = match document_element {
            Some(e) => self.process_invalidations(e),
            None => false,
        };
        self.clear();
        have_invalidations
    }

    /// Clears the invalidation set without processing.
    pub fn clear(&mut self) {
        self.invalid_scopes.clear();
        self.invalid_elements.clear();
        self.fully_invalid = false;
    }

    fn process_invalidations<E>(&self, element: E) -> bool
        where E: TElement,
    {
        {
            let mut data = match element.mutate_data() {
                Some(data) => data,
                None => return false,
            };

            if self.fully_invalid {
                debug!("process_invalidations: fully_invalid({:?})",
                       element);
                data.hint.insert(RestyleHint::restyle_subtree());
                return true;
            }
        }

        if self.invalid_scopes.is_empty() && self.invalid_elements.is_empty() {
            debug!("process_invalidations: empty invalidation set");
            return false;
        }

        self.process_invalidations_in_subtree(element)
    }

    /// Process style invalidations in a given subtree. This traverses the
    /// subtree looking for elements that match the invalidations in
    /// invalid_scopes and invalid_elements.
    ///
    /// Returns whether it invalidated at least one element's style.
    #[allow(unsafe_code)]
    fn process_invalidations_in_subtree<E>(&self, element: E) -> bool
        where E: TElement,
    {
        let mut data = match element.mutate_data() {
            Some(data) => data,
            None => return false,
        };

        if !data.has_styles() {
            return false;
        }

        if data.hint.contains_subtree() {
            debug!("process_invalidations_in_subtree: {:?} was already invalid",
                   element);
            return false;
        }

        for invalidation in &self.invalid_scopes {
            if invalidation.matches(element) {
                debug!("process_invalidations_in_subtree: {:?} matched subtree {:?}",
                       element, invalidation);
                data.hint.insert(RestyleHint::restyle_subtree());
                return true;
            }
        }

        let mut self_invalid = false;

        if !data.hint.contains(RESTYLE_SELF) {
            for invalidation in &self.invalid_elements {
                if invalidation.matches(element) {
                    debug!("process_invalidations_in_subtree: {:?} matched self {:?}",
                           element, invalidation);
                    data.hint.insert(RESTYLE_SELF);
                    self_invalid = true;
                    break;
                }
            }
        }

        let mut any_children_invalid = false;

        for child in element.as_node().traversal_children() {
            let child = match child.as_element() {
                Some(e) => e,
                None => continue,
            };

            any_children_invalid |= self.process_invalidations_in_subtree(child);
        }

        if any_children_invalid {
            debug!("Children of {:?} changed, setting dirty descendants",
                   element);
            unsafe { element.set_dirty_descendants() }
        }

        return self_invalid || any_children_invalid
    }

    fn scan_component(
        component: &Component<SelectorImpl>,
        invalidation: &mut Option<Invalidation>)
    {
        match *component {
            Component::LocalName(LocalName { ref name, ref lower_name }) => {
                if invalidation.as_ref().map_or(true, |s| !s.is_id_or_class()) {
                    *invalidation = Some(Invalidation::LocalName {
                        name: name.clone(),
                        lower_name: lower_name.clone(),
                    });
                }
            }
            Component::Class(ref class) => {
                if invalidation.as_ref().map_or(true, |s| !s.is_id()) {
                    *invalidation = Some(Invalidation::Class(class.clone()));
                }
            }
            Component::ID(ref id) => {
                if invalidation.is_none() {
                    *invalidation = Some(Invalidation::ID(id.clone()));
                }
            }
            _ => {
                // Ignore everything else, at least for now.
            }
        }
    }

    /// Collect invalidations for a given selector.
    ///
    /// We look at the outermost local name, class, or ID selector to the left
    /// of an ancestor combinator, in order to restyle only a given subtree.
    ///
    /// If the selector has no ancestor combinator, then we do the same for
    /// the only sequence it has, but record it as an element invalidation
    /// instead of a subtree invalidation.
    ///
    /// We prefer IDs to classs, and classes to local names, on the basis
    /// that the former should be more specific than the latter. We also
    /// prefer to generate subtree invalidations for the outermost part
    /// of the selector, to reduce the amount of traversal we need to do
    /// when flushing invalidations.
    fn collect_invalidations(&mut self, selector: &Selector<SelectorImpl>) {
        debug!("StylesheetInvalidationSet::collect_invalidations({:?})", selector);

        let mut element_invalidation: Option<Invalidation> = None;
        let mut subtree_invalidation: Option<Invalidation> = None;

        let mut scan_for_element_invalidation = true;
        let mut scan_for_subtree_invalidation = false;

        let mut iter = selector.iter();

        loop {
            for component in &mut iter {
                if scan_for_element_invalidation {
                    Self::scan_component(component, &mut element_invalidation);
                } else if scan_for_subtree_invalidation {
                    Self::scan_component(component, &mut subtree_invalidation);
                }
            }
            match iter.next_sequence() {
                None => break,
                Some(combinator) => {
                    scan_for_subtree_invalidation = combinator.is_ancestor();
                }
            }
            scan_for_element_invalidation = false;
        }

        if let Some(s) = subtree_invalidation {
            debug!(" > Found subtree invalidation: {:?}", s);
            self.invalid_scopes.insert(s);
        } else if let Some(s) = element_invalidation {
            debug!(" > Found element invalidation: {:?}", s);
            self.invalid_elements.insert(s);
        } else {
            // The selector was of a form that we can't handle. Any element
            // could match it, so let's just bail out.
            debug!(" > Can't handle selector, marking fully invalid");
            self.fully_invalid = true;
        }
    }

    /// Collects invalidations for a given CSS rule.
    fn collect_invalidations_for_rule(
        &mut self,
        rule: &CssRule,
        guard: &SharedRwLockReadGuard)
    {
        use stylesheets::CssRule::*;
        debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
        debug_assert!(!self.fully_invalid, "Not worth to be here!");

        match *rule {
            Style(ref lock) => {
                let style_rule = lock.read_with(guard);
                for selector in &style_rule.selectors.0 {
                    self.collect_invalidations(selector);
                    if self.fully_invalid {
                        return;
                    }
                }
            }
            Document(..) |
            Namespace(..) |
            Import(..) |
            Media(..) |
            Supports(..) => {
                // Do nothing, relevant nested rules are visited as part of the
                // iteration.
            }
            FontFace(..) |
            CounterStyle(..) |
            Keyframes(..) |
            Page(..) |
            Viewport(..) |
            FontFeatureValues(..) => {
                debug!(" > Found unsupported rule, marking the whole subtree \
                       invalid.");

                // TODO(emilio): Can we do better here?
                //
                // At least in `@page`, we could check the relevant media, I
                // guess.
                self.fully_invalid = true;
            }
        }
    }
}
<|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/. */

use app_units::Au;
use gfx::font::{
    fallback_font_families, FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontSearchScope,
};
use gfx::font_cache_thread::{FontTemplateInfo, FontTemplates};
use gfx::font_context::{FontContext, FontContextHandle, FontSource};
use gfx::font_template::FontTemplateDescriptor;
use servo_arc::Arc;
use servo_atoms::Atom;
use std::cell::Cell;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::rc::Rc;
use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{
    FamilyName, FamilyNameSyntax, FontFamily, FontFamilyList, FontSize,
};
use style::values::computed::font::{FontStretch, FontWeight, SingleFontFamily};
use style::values::generics::font::FontStyle;

struct TestFontSource {
    handle: FontContextHandle,
    families: HashMap<String, FontTemplates>,
    find_font_count: Rc<Cell<isize>>,
}

impl TestFontSource {
    fn new() -> TestFontSource {
        let mut csstest_ascii = FontTemplates::new();
        Self::add_face(&mut csstest_ascii, "csstest-ascii", None);

        let mut csstest_basic = FontTemplates::new();
        Self::add_face(&mut csstest_basic, "csstest-basic-regular", None);

        let mut fallback = FontTemplates::new();
        Self::add_face(&mut fallback, "csstest-basic-regular", Some("fallback"));

        let mut families = HashMap::new();
        families.insert("CSSTest ASCII".to_owned(), csstest_ascii);
        families.insert("CSSTest Basic".to_owned(), csstest_basic);
        families.insert(fallback_font_families(None)[0].to_owned(), fallback);

        TestFontSource {
            handle: FontContextHandle::new(),
            families,
            find_font_count: Rc::new(Cell::new(0)),
        }
    }

    fn add_face(family: &mut FontTemplates, name: &str, identifier: Option<&str>) {
        let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
            .iter()
            .collect();
        path.push(format!("{}.ttf", name));

        let file = File::open(path).unwrap();
        let identifier = Atom::from(identifier.unwrap_or(name));

        family.add_template(identifier, Some(file.bytes().map(|b| b.unwrap()).collect()))
    }
}

impl FontSource for TestFontSource {
    fn get_font_instance(
        &mut self,
        _key: webrender_api::FontKey,
        _size: Au,
    ) -> webrender_api::FontInstanceKey {
        webrender_api::FontInstanceKey(webrender_api::IdNamespace(0), 0)
    }

    fn font_template(
        &mut self,
        template_descriptor: FontTemplateDescriptor,
        family_descriptor: FontFamilyDescriptor,
    ) -> Option<FontTemplateInfo> {
        let handle = &self.handle;

        self.find_font_count.set(self.find_font_count.get() + 1);
        self.families
            .get_mut(family_descriptor.name())
            .and_then(|family| family.find_font_for_style(&template_descriptor, handle))
            .map(|template| FontTemplateInfo {
                font_template: template,
                font_key: webrender_api::FontKey(webrender_api::IdNamespace(0), 0),
            })
    }
}

fn style() -> FontStyleStruct {
    let mut style = FontStyleStruct {
        font_family: FontFamily::serif(),
        font_style: FontStyle::Normal,
        font_variant_caps: FontVariantCaps::Normal,
        font_weight: FontWeight::normal(),
        font_size: FontSize::medium(),
        font_stretch: FontStretch::hundred(),
        hash: 0,
    };
    style.compute_font_hash();
    style
}

fn font_family(names: Vec<&str>) -> FontFamily {
    let names: Vec<SingleFontFamily> = names
        .into_iter()
        .map(|name| {
            SingleFontFamily::FamilyName(FamilyName {
                name: Atom::from(name),
                syntax: FamilyNameSyntax::Quoted,
            })
        })
        .collect();

    FontFamily(FontFamilyList::new(names.into_boxed_slice()))
}

#[test]
fn test_font_group_is_cached_by_style() {
    let source = TestFontSource::new();
    let mut context = FontContext::new(source);

    let style1 = style();

    let mut style2 = style();
    style2.set_font_style(FontStyle::Italic);

    assert_eq!(
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        "the same font group should be returned for two styles with the same hash"
    );

    assert_ne!(
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        context.font_group(Arc::new(style2.clone())).as_ptr(),
        "different font groups should be returned for two styles with different hashes"
    )
}

#[test]
fn test_font_group_find_by_codepoint() {
    let source = TestFontSource::new();
    let count = source.find_font_count.clone();
    let mut context = FontContext::new(source);

    let mut style = style();
    style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"]));

    let group = context.font_group(Arc::new(style));

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-ascii");
    assert_eq!(
        count.get(),
        1,
        "only the first font in the list should have been loaded"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-ascii");
    assert_eq!(
        count.get(),
        1,
        "we shouldn't load the same font a second time"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'á')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-basic-regular");
    assert_eq!(count.get(), 2, "both fonts should now have been loaded");
}

#[test]
fn test_font_fallback() {
    let source = TestFontSource::new();
    let mut context = FontContext::new(source);

    let mut style = style();
    style.set_font_family(font_family(vec!["CSSTest ASCII"]));

    let group = context.font_group(Arc::new(style));

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(
        &*font.borrow().identifier(),
        "csstest-ascii",
        "a family in the group should be used if there is a matching glyph"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'á')
        .unwrap();
    assert_eq!(
        &*font.borrow().identifier(),
        "fallback",
        "a fallback font should be used if there is no matching glyph in the group"
    );
}

#[test]
fn test_font_template_is_cached() {
    let source = TestFontSource::new();
    let count = source.find_font_count.clone();
    let mut context = FontContext::new(source);

    let mut font_descriptor = FontDescriptor {
        template_descriptor: FontTemplateDescriptor {
            weight: FontWeight::normal(),
            stretch: FontStretch::hundred(),
            style: FontStyle::Normal,
        },
        variant: FontVariantCaps::Normal,
        pt_size: Au(10),
    };

    let family_descriptor =
        FontFamilyDescriptor::new(FontFamilyName::from("CSSTest Basic"), FontSearchScope::Any);

    let font1 = context.font(&font_descriptor, &family_descriptor).unwrap();

    font_descriptor.pt_size = Au(20);
    let font2 = context.font(&font_descriptor, &family_descriptor).unwrap();

    assert_ne!(
        font1.borrow().actual_pt_size,
        font2.borrow().actual_pt_size,
        "the same font should not have been returned"
    );

    assert_eq!(
        count.get(),
        1,
        "we should only have fetched the template data from the cache thread once"
    );
}
<commit_msg>gfx: Fix unit tests.<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/. */

use app_units::Au;
use gfx::font::{
    fallback_font_families, FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontSearchScope,
};
use gfx::font_cache_thread::{FontTemplateInfo, FontTemplates};
use gfx::font_context::{FontContext, FontContextHandle, FontSource};
use gfx::font_template::FontTemplateDescriptor;
use servo_arc::Arc;
use servo_atoms::Atom;
use std::cell::Cell;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::rc::Rc;
use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{
    FamilyName, FamilyNameSyntax, FontFamily, FontFamilyList, FontSize,
};
use style::values::computed::font::{FontStretch, FontWeight, SingleFontFamily};
use style::values::generics::font::FontStyle;

struct TestFontSource {
    handle: FontContextHandle,
    families: HashMap<String, FontTemplates>,
    find_font_count: Rc<Cell<isize>>,
}

impl TestFontSource {
    fn new() -> TestFontSource {
        let mut csstest_ascii = FontTemplates::new();
        Self::add_face(&mut csstest_ascii, "csstest-ascii", None);

        let mut csstest_basic = FontTemplates::new();
        Self::add_face(&mut csstest_basic, "csstest-basic-regular", None);

        let mut fallback = FontTemplates::new();
        Self::add_face(&mut fallback, "csstest-basic-regular", Some("fallback"));

        let mut families = HashMap::new();
        families.insert("CSSTest ASCII".to_owned(), csstest_ascii);
        families.insert("CSSTest Basic".to_owned(), csstest_basic);
        families.insert(fallback_font_families(None)[0].to_owned(), fallback);

        TestFontSource {
            handle: FontContextHandle::new(),
            families,
            find_font_count: Rc::new(Cell::new(0)),
        }
    }

    fn add_face(family: &mut FontTemplates, name: &str, identifier: Option<&str>) {
        let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"]
            .iter()
            .collect();
        path.push(format!("{}.ttf", name));

        let file = File::open(path).unwrap();
        let identifier = Atom::from(identifier.unwrap_or(name));

        family.add_template(identifier, Some(file.bytes().map(|b| b.unwrap()).collect()))
    }
}

impl FontSource for TestFontSource {
    fn get_font_instance(
        &mut self,
        _key: webrender_api::FontKey,
        _size: Au,
    ) -> webrender_api::FontInstanceKey {
        webrender_api::FontInstanceKey(webrender_api::IdNamespace(0), 0)
    }

    fn font_template(
        &mut self,
        template_descriptor: FontTemplateDescriptor,
        family_descriptor: FontFamilyDescriptor,
    ) -> Option<FontTemplateInfo> {
        let handle = &self.handle;

        self.find_font_count.set(self.find_font_count.get() + 1);
        self.families
            .get_mut(family_descriptor.name())
            .and_then(|family| family.find_font_for_style(&template_descriptor, handle))
            .map(|template| FontTemplateInfo {
                font_template: template,
                font_key: webrender_api::FontKey(webrender_api::IdNamespace(0), 0),
            })
    }
}

fn style() -> FontStyleStruct {
    let mut style = FontStyleStruct {
        font_family: FontFamily::serif(),
        font_style: FontStyle::Normal,
        font_variant_caps: FontVariantCaps::Normal,
        font_weight: FontWeight::normal(),
        font_size: FontSize::medium(),
        font_stretch: FontStretch::hundred(),
        hash: 0,
    };
    style.compute_font_hash();
    style
}

fn font_family(names: Vec<&str>) -> FontFamily {
    let names: Vec<SingleFontFamily> = names
        .into_iter()
        .map(|name| {
            SingleFontFamily::FamilyName(FamilyName {
                name: Atom::from(name),
                syntax: FamilyNameSyntax::Quoted,
            })
        })
        .collect();

    FontFamily {
        families: FontFamilyList::new(names.into_boxed_slice()),
        is_system_font: false,
    }
}

#[test]
fn test_font_group_is_cached_by_style() {
    let source = TestFontSource::new();
    let mut context = FontContext::new(source);

    let style1 = style();

    let mut style2 = style();
    style2.set_font_style(FontStyle::Italic);

    assert_eq!(
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        "the same font group should be returned for two styles with the same hash"
    );

    assert_ne!(
        context.font_group(Arc::new(style1.clone())).as_ptr(),
        context.font_group(Arc::new(style2.clone())).as_ptr(),
        "different font groups should be returned for two styles with different hashes"
    )
}

#[test]
fn test_font_group_find_by_codepoint() {
    let source = TestFontSource::new();
    let count = source.find_font_count.clone();
    let mut context = FontContext::new(source);

    let mut style = style();
    style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"]));

    let group = context.font_group(Arc::new(style));

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-ascii");
    assert_eq!(
        count.get(),
        1,
        "only the first font in the list should have been loaded"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-ascii");
    assert_eq!(
        count.get(),
        1,
        "we shouldn't load the same font a second time"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'á')
        .unwrap();
    assert_eq!(&*font.borrow().identifier(), "csstest-basic-regular");
    assert_eq!(count.get(), 2, "both fonts should now have been loaded");
}

#[test]
fn test_font_fallback() {
    let source = TestFontSource::new();
    let mut context = FontContext::new(source);

    let mut style = style();
    style.set_font_family(font_family(vec!["CSSTest ASCII"]));

    let group = context.font_group(Arc::new(style));

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'a')
        .unwrap();
    assert_eq!(
        &*font.borrow().identifier(),
        "csstest-ascii",
        "a family in the group should be used if there is a matching glyph"
    );

    let font = group
        .borrow_mut()
        .find_by_codepoint(&mut context, 'á')
        .unwrap();
    assert_eq!(
        &*font.borrow().identifier(),
        "fallback",
        "a fallback font should be used if there is no matching glyph in the group"
    );
}

#[test]
fn test_font_template_is_cached() {
    let source = TestFontSource::new();
    let count = source.find_font_count.clone();
    let mut context = FontContext::new(source);

    let mut font_descriptor = FontDescriptor {
        template_descriptor: FontTemplateDescriptor {
            weight: FontWeight::normal(),
            stretch: FontStretch::hundred(),
            style: FontStyle::Normal,
        },
        variant: FontVariantCaps::Normal,
        pt_size: Au(10),
    };

    let family_descriptor =
        FontFamilyDescriptor::new(FontFamilyName::from("CSSTest Basic"), FontSearchScope::Any);

    let font1 = context.font(&font_descriptor, &family_descriptor).unwrap();

    font_descriptor.pt_size = Au(20);
    let font2 = context.font(&font_descriptor, &family_descriptor).unwrap();

    assert_ne!(
        font1.borrow().actual_pt_size,
        font2.borrow().actual_pt_size,
        "the same font should not have been returned"
    );

    assert_eq!(
        count.get(),
        1,
        "we should only have fetched the template data from the cache thread once"
    );
}
<|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 http://mozilla.org/MPL/2.0/. */

use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::EventSourceBinding::{EventSourceInit, EventSourceMethods, Wrap};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::Root;
use dom::bindings::reflector::reflect_dom_object;
use dom::eventtarget::EventTarget;
use std::cell::Cell;
use url::Url;
use util::str::DOMString;

#[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)]
enum EventSourceReadyState {
    Connecting = 0,
    Open = 1,
    Closed = 2
}

#[dom_struct]
pub struct EventSource {
    eventtarget: EventTarget,
    url: Url,
    ready_state: Cell<EventSourceReadyState>,
    with_credentials: bool,
    last_event_id: DOMRefCell<DOMString>
}

impl EventSource {
    fn new_inherited(url: Url, with_credentials: bool) -> EventSource {
        EventSource {
            eventtarget: EventTarget::new_inherited(),
            url: url,
            ready_state: Cell::new(EventSourceReadyState::Connecting),
            with_credentials: with_credentials,
            last_event_id: DOMRefCell::new(DOMString::from(""))
        }
    }

    fn new(global: GlobalRef, url: Url, with_credentials: bool) -> Root<EventSource> {
        reflect_dom_object(box EventSource::new_inherited(url, with_credentials), global, Wrap)
    }

    pub fn Constructor(global: GlobalRef,
                       url_str: DOMString,
                       event_source_init: &EventSourceInit) -> Fallible<Root<EventSource>> {
        // Steps 1-2
        let base_url = global.get_url();
        let url = match base_url.join(&*url_str) {
            Ok(u) => u,
            Err(_) => return Err(Error::Syntax)
        };
        // Step 3
        let event_source = EventSource::new(global, url, event_source_init.withCredentials);
        // Step 4
        // Step 5
        // Step 6
        // Step 7
        // Step 8
        // Step 9
        // Step 10
        // Step 11
        Ok(event_source)
        // Step 12
    }
}

impl EventSourceMethods for EventSource {
    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onopen
    event_handler!(open, GetOnopen, SetOnopen);

    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onmessage
    event_handler!(message, GetOnmessage, SetOnmessage);

    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onerror
    event_handler!(error, GetOnerror, SetOnerror);

    // https://html.spec.whatwg.org/multipage/#dom-eventsource-url
    fn Url(&self) -> DOMString {
        DOMString::from(self.url.serialize())
    }

    // https://html.spec.whatwg.org/multipage/#dom-eventsource-withcredentials
    fn WithCredentials(&self) -> bool {
        self.with_credentials
    }

    // https://html.spec.whatwg.org/multipage/#dom-eventsource-readystate
    fn ReadyState(&self) -> u16 {
        self.ready_state.get() as u16
    }

    // https://html.spec.whatwg.org/multipage/#dom-eventsource-close
    fn Close(&self) {
        self.ready_state.set(EventSourceReadyState::Closed);
        // TODO: Terminate ongoing fetch
    }
}
<commit_msg>Silence a warning in EventSourceReadyState.<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/. */

use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::EventSourceBinding::{EventSourceInit, EventSourceMethods, Wrap};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::Root;
use dom::bindings::reflector::reflect_dom_object;
use dom::eventtarget::EventTarget;
use std::cell::Cell;
use url::Url;
use util::str::DOMString;

#[derive(JSTraceable, PartialEq, Copy, Clone, Debug, HeapSizeOf)]
enum EventSourceReadyState {
    Connecting = 0,
    #[allow(dead_code)]
    Open = 1,
    Closed = 2
}

#[dom_struct]
pub struct EventSource {
    eventtarget: EventTarget,
    url: Url,
    ready_state: Cell<EventSourceReadyState>,
    with_credentials: bool,
    last_event_id: DOMRefCell<DOMString>
}

impl EventSource {
    fn new_inherited(url: Url, with_credentials: bool) -> EventSource {
        EventSource {
            eventtarget: EventTarget::new_inherited(),
            url: url,
            ready_state: Cell::new(EventSourceReadyState::Connecting),
            with_credentials: with_credentials,
            last_event_id: DOMRefCell::new(DOMString::from(""))
        }
    }

    fn new(global: GlobalRef, url: Url, with_credentials: bool) -> Root<EventSource> {
        reflect_dom_object(box EventSource::new_inherited(url, with_credentials), global, Wrap)
    }

    pub fn Constructor(global: GlobalRef,
                       url_str: DOMString,
                       event_source_init: &EventSourceInit) -> Fallible<Root<EventSource>> {
        // Steps 1-2
        let base_url = global.get_url();
        let url = match base_url.join(&*url_str) {
            Ok(u) => u,
            Err(_) => return Err(Error::Syntax)
        };
        // Step 3
        let event_source = EventSource::new(global, url, event_source_init.withCredentials);
        // Step 4
        // Step 5
        // Step 6
        // Step 7
        // Step 8
        // Step 9
        // Step 10
        // Step 11
        Ok(event_source)
        // Step 12
    }
}

impl EventSourceMethods for EventSource {
    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onopen
    event_handler!(open, GetOnopen, SetOnopen);

    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onmessage
    event_handler!(message, GetOnmessage, SetOnmessage);

    // https://html.spec.whatwg.org/multipage/#handler-eventsource-onerror
    event_handler!(error, GetOnerror, SetOnerror);

    // https://html.spec.whatwg.org/multipage/#dom-eventsource-url
    fn Url(&self) -> DOMString {
        DOMString::from(self.url.serialize())
    }

    // https://html.spec.whatwg.org/multipage/#dom-eventsource-withcredentials
    fn WithCredentials(&self) -> bool {
        self.with_credentials
    }

    // https://html.spec.whatwg.org/multipage/#dom-eventsource-readystate
    fn ReadyState(&self) -> u16 {
        self.ready_state.get() as u16
    }

    // https://html.spec.whatwg.org/multipage/#dom-eventsource-close
    fn Close(&self) {
        self.ready_state.set(EventSourceReadyState::Closed);
        // TODO: Terminate ongoing fetch
    }
}
<|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/. */

use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::MediaStreamBinding::MediaStreamMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::mediastreamtrack::MediaStreamTrack;
use crate::dom::window::Window;
use dom_struct::dom_struct;
use servo_media::streams::MediaStreamType;

#[dom_struct]
pub struct MediaStream {
    eventtarget: EventTarget,
    tracks: DomRefCell<Vec<Dom<MediaStreamTrack>>>,
}

#[allow(non_snake_case)]
impl MediaStream {
    pub fn new_inherited() -> MediaStream {
        MediaStream {
            eventtarget: EventTarget::new_inherited(),
            tracks: DomRefCell::new(vec![]),
        }
    }

    pub fn new(global: &GlobalScope) -> DomRoot<MediaStream> {
        reflect_dom_object(Box::new(MediaStream::new_inherited()), global)
    }

    pub fn Constructor(global: &Window) -> Fallible<DomRoot<MediaStream>> {
        Ok(MediaStream::new(&global.global()))
    }

    pub fn Constructor_(_: &Window, stream: &MediaStream) -> Fallible<DomRoot<MediaStream>> {
        Ok(stream.Clone())
    }

    pub fn Constructor__(
        global: &Window,
        tracks: Vec<DomRoot<MediaStreamTrack>>,
    ) -> Fallible<DomRoot<MediaStream>> {
        let new = MediaStream::new(&global.global());
        for track in tracks {
            // this is quadratic, but shouldn't matter much
            // if this becomes a problem we can use a hash map
            new.AddTrack(&track)
        }
        Ok(new)
    }

    pub fn get_tracks(&self) -> Ref<[Dom<MediaStreamTrack>]> {
        Ref::map(self.tracks.borrow(), |tracks| &**tracks)
    }

    pub fn add_track(&self, track: &MediaStreamTrack) {
        self.tracks.borrow_mut().push(Dom::from_ref(track))
    }
}

impl MediaStreamMethods for MediaStream {
    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks
    fn GetTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> {
        self.tracks
            .borrow()
            .iter()
            .map(|x| DomRoot::from_ref(&**x))
            .collect()
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks
    fn GetAudioTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> {
        self.tracks
            .borrow()
            .iter()
            .filter(|x| x.ty() == MediaStreamType::Audio)
            .map(|x| DomRoot::from_ref(&**x))
            .collect()
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks
    fn GetVideoTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> {
        self.tracks
            .borrow()
            .iter()
            .filter(|x| x.ty() == MediaStreamType::Video)
            .map(|x| DomRoot::from_ref(&**x))
            .collect()
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-gettrackbyid
    fn GetTrackById(&self, id: DOMString) -> Option<DomRoot<MediaStreamTrack>> {
        self.tracks
            .borrow()
            .iter()
            .find(|x| x.id().id().to_string() == &*id)
            .map(|x| DomRoot::from_ref(&**x))
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack
    fn AddTrack(&self, track: &MediaStreamTrack) {
        let existing = self.tracks.borrow().iter().find(|x| *x == &track).is_some();

        if existing {
            return;
        }
        self.add_track(track)
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack
    fn RemoveTrack(&self, track: &MediaStreamTrack) {
        self.tracks.borrow_mut().retain(|x| *x != track);
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-clone
    fn Clone(&self) -> DomRoot<MediaStream> {
        let new = MediaStream::new(&self.global());
        for track in &*self.tracks.borrow() {
            new.add_track(&track)
        }
        new
    }
}
<commit_msg>Add MediaStream::new_single()<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/. */

use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::MediaStreamBinding::MediaStreamMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::mediastreamtrack::MediaStreamTrack;
use crate::dom::window::Window;
use dom_struct::dom_struct;
use servo_media::streams::registry::MediaStreamId;
use servo_media::streams::MediaStreamType;

#[dom_struct]
pub struct MediaStream {
    eventtarget: EventTarget,
    tracks: DomRefCell<Vec<Dom<MediaStreamTrack>>>,
}

#[allow(non_snake_case)]
impl MediaStream {
    pub fn new_inherited() -> MediaStream {
        MediaStream {
            eventtarget: EventTarget::new_inherited(),
            tracks: DomRefCell::new(vec![]),
        }
    }

    pub fn new(global: &GlobalScope) -> DomRoot<MediaStream> {
        reflect_dom_object(Box::new(MediaStream::new_inherited()), global)
    }

    pub fn new_single(
        global: &GlobalScope,
        id: MediaStreamId,
        ty: MediaStreamType,
    ) -> DomRoot<MediaStream> {
        let this = Self::new(global);
        let track = MediaStreamTrack::new(global, id, ty);
        this.AddTrack(&track);
        this
    }

    pub fn Constructor(global: &Window) -> Fallible<DomRoot<MediaStream>> {
        Ok(MediaStream::new(&global.global()))
    }

    pub fn Constructor_(_: &Window, stream: &MediaStream) -> Fallible<DomRoot<MediaStream>> {
        Ok(stream.Clone())
    }

    pub fn Constructor__(
        global: &Window,
        tracks: Vec<DomRoot<MediaStreamTrack>>,
    ) -> Fallible<DomRoot<MediaStream>> {
        let new = MediaStream::new(&global.global());
        for track in tracks {
            // this is quadratic, but shouldn't matter much
            // if this becomes a problem we can use a hash map
            new.AddTrack(&track)
        }
        Ok(new)
    }

    pub fn get_tracks(&self) -> Ref<[Dom<MediaStreamTrack>]> {
        Ref::map(self.tracks.borrow(), |tracks| &**tracks)
    }

    pub fn add_track(&self, track: &MediaStreamTrack) {
        self.tracks.borrow_mut().push(Dom::from_ref(track))
    }
}

impl MediaStreamMethods for MediaStream {
    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-gettracks
    fn GetTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> {
        self.tracks
            .borrow()
            .iter()
            .map(|x| DomRoot::from_ref(&**x))
            .collect()
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-getaudiotracks
    fn GetAudioTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> {
        self.tracks
            .borrow()
            .iter()
            .filter(|x| x.ty() == MediaStreamType::Audio)
            .map(|x| DomRoot::from_ref(&**x))
            .collect()
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-getvideotracks
    fn GetVideoTracks(&self) -> Vec<DomRoot<MediaStreamTrack>> {
        self.tracks
            .borrow()
            .iter()
            .filter(|x| x.ty() == MediaStreamType::Video)
            .map(|x| DomRoot::from_ref(&**x))
            .collect()
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-gettrackbyid
    fn GetTrackById(&self, id: DOMString) -> Option<DomRoot<MediaStreamTrack>> {
        self.tracks
            .borrow()
            .iter()
            .find(|x| x.id().id().to_string() == &*id)
            .map(|x| DomRoot::from_ref(&**x))
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-addtrack
    fn AddTrack(&self, track: &MediaStreamTrack) {
        let existing = self.tracks.borrow().iter().find(|x| *x == &track).is_some();

        if existing {
            return;
        }
        self.add_track(track)
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-removetrack
    fn RemoveTrack(&self, track: &MediaStreamTrack) {
        self.tracks.borrow_mut().retain(|x| *x != track);
    }

    /// https://w3c.github.io/mediacapture-main/#dom-mediastream-clone
    fn Clone(&self) -> DomRoot<MediaStream> {
        let new = MediaStream::new(&self.global());
        for track in &*self.tracks.borrow() {
            new.add_track(&track)
        }
        new
    }
}
<|endoftext|>


<commit_before>pub trait RdbParseFormatter {
    fn start_rdb(&mut self);
    fn end_rdb(&mut self);
    fn checksum(&mut self, Vec<u8>);

    fn start_database(&mut self, u32);
    fn end_database(&mut self, u32) {}

    fn set(&mut self, Vec<u8>, Vec<u8>);

    fn aux_field(&mut self, _key: Vec<u8>, _value: Vec<u8>) {}
}
<commit_msg>List all trait methods<commit_after>#[allow(unused_variables)]
pub trait RdbParseFormatter {
    fn start_rdb(&mut self) {}
    fn end_rdb(&mut self) {}
    fn checksum(&mut self, checksum: Vec<u8>) {}

    fn start_database(&mut self, db_index: u32) {}
    fn end_database(&mut self, db_index: u32) {}

    fn aux_field(&mut self, _key: Vec<u8>, _value: Vec<u8>) {}

    fn set(&mut self, key: Vec<u8>, value: Vec<u8>, expiry: Option<u32>) {}

    fn start_hash(&mut self, key: Vec<u8>, length: u32, expiry: Option<u32>, info: Option<()>) {}
    fn end_hash(&mut self, key: Vec<u8>) {}
    fn hash_element(&mut self, key: Vec<u8>, field: Vec<u8>, value: Vec<u8>) {}

    fn start_set(&mut self, key: Vec<u8>, cardinality: u32, expiry: Option<u32>, info: Option<()>) {}
    fn end_set(&mut self, key: Vec<u8>) {}
    fn set_element(&mut self, key: Vec<u8>, member: Vec<u8>) {}

    fn start_list(&mut self, key: Vec<u8>, length: u32, expiry: Option<u32>, info: Option<()>) {}
    fn end_list(&mut self, key: Vec<u8>) {}
    fn list_element(&mut self, key: Vec<u8>, value: Vec<u8>) {}

    fn start_sorted_set(&mut self, key: Vec<u8>, length: u32, expiry: Option<u32>, info: Option<()>) {}
    fn end_sorted_set(&mut self, key: Vec<u8>) {}
    fn sorted_set_element(&mut self, key: Vec<u8>, score: f64, member: Vec<u8>) {}
}
<|endoftext|>


<commit_before>use glutin::Window;

use event::{Event, PollEvents, Reactor};
use render::{Context, GlutinContext, MetaContext};

pub struct Harness<A, C>
    where A: Application<C>,
          C: MetaContext
{
    application: A,
    context: Context<C>,
}

impl<A> Harness<A, GlutinContext>
    where A: Application<GlutinContext>
{
    pub fn from_glutin_window(application: A, window: Window) -> Self {
        Harness {
            application: application,
            context: Context::from_glutin_window(window),
        }
    }
}

impl<A, C> Harness<A, C>
    where A: Application<C>,
          C: MetaContext
{
    pub fn start(&mut self) {
        'main: loop {
            for event in self.context.window.poll_events() {
                match event {
                    Event::Closed => {
                        break 'main;
                    }
                    Event::Resized(..) => {
                        self.context.update_frame_buffer_view();
                    }
                    _ => {}
                }
                self.application.react(&event);
            }
            self.context.clear();
            // TODO: Do not lend the entire `Context`. Maybe `Context` can emit
            //       a more limited type that can be used for rendering.
            self.application.render(&mut self.context);
            self.context.flush().unwrap();
        }
    }
}

pub trait Application<C>: Reactor
    where C: MetaContext
{
    fn render(&mut self, context: &mut Context<C>);
}
<commit_msg>Add a `stop` function to `Application`.<commit_after>use glutin::Window;

use event::{Event, PollEvents, Reactor};
use render::{Context, GlutinContext, MetaContext};

pub struct Harness<A, C>
    where A: Application<C>,
          C: MetaContext
{
    application: A,
    context: Context<C>,
}

impl<A> Harness<A, GlutinContext>
    where A: Application<GlutinContext>
{
    pub fn from_glutin_window(application: A, window: Window) -> Self {
        Harness {
            application: application,
            context: Context::from_glutin_window(window),
        }
    }
}

impl<A, C> Harness<A, C>
    where A: Application<C>,
          C: MetaContext
{
    pub fn start(&mut self) {
        'main: loop {
            for event in self.context.window.poll_events() {
                match event {
                    Event::Closed => {
                        break 'main;
                    }
                    Event::Resized(..) => {
                        self.context.update_frame_buffer_view();
                    }
                    _ => {}
                }
                self.application.react(&event);
            }
            self.context.clear();
            self.application.render(&mut self.context);
            self.context.flush().unwrap();
        }
        self.application.stop();
    }
}

pub trait Application<C>: Reactor
    where C: MetaContext
{
    // TODO: Do not accept the entire `Context`. Maybe `Context` can emit a
    //       more limited type that can be used for rendering.
    fn render(&mut self, context: &mut Context<C>);
    fn stop(&mut self) {}
}
<|endoftext|>


<commit_before>//! System and Hardware Clocks

/// Internal Oscillator
pub const HSI: u32 = 8_000_000;
/// External clock
pub const HSE: u32 = 8_000_000;

use stm32f103xx::{Rcc, Flash, rcc};

/// Board clock speeds
pub struct ClockSpeeds{
    /// System Clock
    pub sysclk: u32,
    /// AHB peripheral clock
    pub hclk: u32,
    /// Low speed bus
    pub apb1: u32,
    /// high speed bus
    pub apb2: u32,
}

impl ClockSpeeds {
    /// Get clock speeds
    pub fn get(rcc: &Rcc) -> ClockSpeeds {
        let sysclk = match rcc.cfgr.read().sws(){
            rcc::cfgr::SwsR::Hse => HSE,
            rcc::cfgr::SwsR::Pll => Self::get_pll_speed(rcc),
            _ => HSI,
        };

        let hclk = match rcc.cfgr.read().hpre() {
            rcc::cfgr::HpreR::Div2 => sysclk / 2,
            rcc::cfgr::HpreR::Div4 => sysclk / 4,
            rcc::cfgr::HpreR::Div8 => sysclk / 8,
            rcc::cfgr::HpreR::Div16 => sysclk / 16,
            rcc::cfgr::HpreR::Div64 => sysclk / 64,
            rcc::cfgr::HpreR::Div128 => sysclk / 128,
            rcc::cfgr::HpreR::Div256 => sysclk / 256,
            rcc::cfgr::HpreR::Div512 => sysclk / 512,
            _ => sysclk,
        };

        let apb1 = match rcc.cfgr.read().ppre1() {
            rcc::cfgr::Ppre1R::Div2 => hclk / 2,
            rcc::cfgr::Ppre1R::Div4 => hclk / 4,
            rcc::cfgr::Ppre1R::Div8 => hclk / 8,
            rcc::cfgr::Ppre1R::Div16 => hclk / 16,
            _ => hclk,
        };

        let apb2 = match rcc.cfgr.read().ppre2() {
            rcc::cfgr::Ppre2R::Div2 => hclk / 2,
            rcc::cfgr::Ppre2R::Div4 => hclk / 4,
            rcc::cfgr::Ppre2R::Div8 => hclk / 8,
            rcc::cfgr::Ppre2R::Div16 => hclk / 16,
            _ => hclk,
        };

        ClockSpeeds{
            sysclk: sysclk,
            hclk: hclk,
            apb1: apb1,
            apb2: apb2,
        }
    }

    fn get_pll_speed(rcc: &Rcc) -> u32 {
        let hse_div = match rcc.cfgr.read().pllxtpre(){
            rcc::cfgr::PllxtpreR::Nodiv => 1,
            rcc::cfgr::PllxtpreR::Div2 => 2,
        };

        let src_spd = match rcc.cfgr.read().pllsrc(){
            rcc::cfgr::PllsrcR::Internal => HSI / 2,
            rcc::cfgr::PllsrcR::External => HSE / hse_div,
        };

        match rcc.cfgr.read().pllmul() {
            rcc::cfgr::PllmulR::Mul2 => src_spd * 2,
            rcc::cfgr::PllmulR::Mul3 => src_spd * 3,
            rcc::cfgr::PllmulR::Mul4 => src_spd * 4,
            rcc::cfgr::PllmulR::Mul5 => src_spd * 5,
            rcc::cfgr::PllmulR::Mul6 => src_spd * 6,
            rcc::cfgr::PllmulR::Mul7 => src_spd * 7,
            rcc::cfgr::PllmulR::Mul8 => src_spd * 8,
            rcc::cfgr::PllmulR::Mul9 => src_spd * 9,
            rcc::cfgr::PllmulR::Mul10 => src_spd * 10,
            rcc::cfgr::PllmulR::Mul11 => src_spd * 11,
            rcc::cfgr::PllmulR::Mul12 => src_spd * 12,
            rcc::cfgr::PllmulR::Mul13 => src_spd * 13,
            rcc::cfgr::PllmulR::Mul14 => src_spd * 14,
            rcc::cfgr::PllmulR::Mul15 => src_spd * 15,
            rcc::cfgr::PllmulR::Mul16 => src_spd * 16,
            _ => src_spd,
        }
    }
}

/// Preconfigured clock speed options
/// enum fields can't start with a number makes the names not look as good
pub enum Speed {
    /// 8Mhz
    S8Mhz,
    /// 16 Mhz
    S16Mhz,
    /// 32Mhz
    S32Mhz,
    /// 72 Mhz
    S72Mhz,}

/// Initializes SYSCLK to 72Mhz
pub fn init(rcc: &Rcc, flash: &Flash, speed: Speed) {
    // enable external clock
    rcc.cr.modify(|_,w| w.hseon().enabled());
    while rcc.cr.read().hserdy().is_notready() {}

    // configure pll to external clock
    rcc.cfgr.modify(|_,w| w.pllsrc().external());

    // enable flash prefetch buffer
    flash.acr.modify(|_,w| w.prftbe().enabled());

    match speed {
        Speed::S8Mhz => rcc.cfgr.modify(|_,w| w.sw().hse()),
        Speed::S16Mhz => {
            rcc.cfgr.modify(|_,w| w.pllmul().mul2());
            use_pll(rcc);
        }
        Speed::S32Mhz => {
            rcc.cfgr.modify(|_,w| w.pllmul().mul4());

            // set flash latency to one
            flash.acr.modify(|_,w| w.latency().one());
            use_pll(rcc);
        }
        Speed::S72Mhz => {
            rcc.cfgr.modify(|_,w| w.pllmul().mul9());

            // set apb1 to hclk / 2
            rcc.cfgr.modify(|_,w| w.ppre1().div2());

            // set flash latency to two
            flash.acr.modify(|_,w| w.latency().two());
            use_pll(rcc);
        }
    }
}

fn use_pll(rcc: &Rcc) {
    // enable pll
    rcc.cr.modify(|_,w| w.pllon().enabled());

    while rcc.cr.read().pllrdy().is_unlocked() {}

    // set system clock to pll
    rcc.cfgr.modify(|_,w| w.sw().pll());
}<commit_msg>Fix enum name.<commit_after>//! System and Hardware Clocks

/// Internal Oscillator
pub const HSI: u32 = 8_000_000;
/// External clock
pub const HSE: u32 = 8_000_000;

use stm32f103xx::{Rcc, Flash, rcc};

/// Board clock speeds
pub struct ClockSpeeds{
    /// System Clock
    pub sysclk: u32,
    /// AHB peripheral clock
    pub hclk: u32,
    /// Low speed bus
    pub apb1: u32,
    /// high speed bus
    pub apb2: u32,
}

impl ClockSpeeds {
    /// Get clock speeds
    pub fn get(rcc: &Rcc) -> ClockSpeeds {
        let sysclk = match rcc.cfgr.read().sws(){
            rcc::cfgr::SwsR::Hse => HSE,
            rcc::cfgr::SwsR::Pll => Self::get_pll_speed(rcc),
            _ => HSI,
        };

        let hclk = match rcc.cfgr.read().hpre() {
            rcc::cfgr::HpreR::Div2 => sysclk / 2,
            rcc::cfgr::HpreR::Div4 => sysclk / 4,
            rcc::cfgr::HpreR::Div8 => sysclk / 8,
            rcc::cfgr::HpreR::Div16 => sysclk / 16,
            rcc::cfgr::HpreR::Div64 => sysclk / 64,
            rcc::cfgr::HpreR::Div128 => sysclk / 128,
            rcc::cfgr::HpreR::Div256 => sysclk / 256,
            rcc::cfgr::HpreR::Div512 => sysclk / 512,
            _ => sysclk,
        };

        let apb1 = match rcc.cfgr.read().ppre1() {
            rcc::cfgr::Ppre1R::Div2 => hclk / 2,
            rcc::cfgr::Ppre1R::Div4 => hclk / 4,
            rcc::cfgr::Ppre1R::Div8 => hclk / 8,
            rcc::cfgr::Ppre1R::Div16 => hclk / 16,
            _ => hclk,
        };

        let apb2 = match rcc.cfgr.read().ppre2() {
            rcc::cfgr::Ppre2R::Div2 => hclk / 2,
            rcc::cfgr::Ppre2R::Div4 => hclk / 4,
            rcc::cfgr::Ppre2R::Div8 => hclk / 8,
            rcc::cfgr::Ppre2R::Div16 => hclk / 16,
            _ => hclk,
        };

        ClockSpeeds{
            sysclk: sysclk,
            hclk: hclk,
            apb1: apb1,
            apb2: apb2,
        }
    }

    fn get_pll_speed(rcc: &Rcc) -> u32 {
        let hse_div = match rcc.cfgr.read().pllxtpre(){
            rcc::cfgr::PllxtpreR::Div1 => 1,
            rcc::cfgr::PllxtpreR::Div2 => 2,
        };

        let src_spd = match rcc.cfgr.read().pllsrc(){
            rcc::cfgr::PllsrcR::Internal => HSI / 2,
            rcc::cfgr::PllsrcR::External => HSE / hse_div,
        };

        match rcc.cfgr.read().pllmul() {
            rcc::cfgr::PllmulR::Mul2 => src_spd * 2,
            rcc::cfgr::PllmulR::Mul3 => src_spd * 3,
            rcc::cfgr::PllmulR::Mul4 => src_spd * 4,
            rcc::cfgr::PllmulR::Mul5 => src_spd * 5,
            rcc::cfgr::PllmulR::Mul6 => src_spd * 6,
            rcc::cfgr::PllmulR::Mul7 => src_spd * 7,
            rcc::cfgr::PllmulR::Mul8 => src_spd * 8,
            rcc::cfgr::PllmulR::Mul9 => src_spd * 9,
            rcc::cfgr::PllmulR::Mul10 => src_spd * 10,
            rcc::cfgr::PllmulR::Mul11 => src_spd * 11,
            rcc::cfgr::PllmulR::Mul12 => src_spd * 12,
            rcc::cfgr::PllmulR::Mul13 => src_spd * 13,
            rcc::cfgr::PllmulR::Mul14 => src_spd * 14,
            rcc::cfgr::PllmulR::Mul15 => src_spd * 15,
            rcc::cfgr::PllmulR::Mul16 => src_spd * 16,
            _ => src_spd,
        }
    }
}

/// Preconfigured clock speed options
/// enum fields can't start with a number makes the names not look as good
pub enum Speed {
    /// 8Mhz
    S8Mhz,
    /// 16 Mhz
    S16Mhz,
    /// 32Mhz
    S32Mhz,
    /// 72 Mhz
    S72Mhz,}

/// Initializes SYSCLK to 72Mhz
pub fn init(rcc: &Rcc, flash: &Flash, speed: Speed) {
    // enable external clock
    rcc.cr.modify(|_,w| w.hseon().enabled());
    while rcc.cr.read().hserdy().is_notready() {}

    // configure pll to external clock
    rcc.cfgr.modify(|_,w| w.pllsrc().external());

    // enable flash prefetch buffer
    flash.acr.modify(|_,w| w.prftbe().enabled());

    match speed {
        Speed::S8Mhz => rcc.cfgr.modify(|_,w| w.sw().hse()),
        Speed::S16Mhz => {
            rcc.cfgr.modify(|_,w| w.pllmul().mul2());
            use_pll(rcc);
        }
        Speed::S32Mhz => {
            rcc.cfgr.modify(|_,w| w.pllmul().mul4());

            // set flash latency to one
            flash.acr.modify(|_,w| w.latency().one());
            use_pll(rcc);
        }
        Speed::S72Mhz => {
            rcc.cfgr.modify(|_,w| w.pllmul().mul9());

            // set apb1 to hclk / 2
            rcc.cfgr.modify(|_,w| w.ppre1().div2());

            // set flash latency to two
            flash.acr.modify(|_,w| w.latency().two());
            use_pll(rcc);
        }
    }
}

fn use_pll(rcc: &Rcc) {
    // enable pll
    rcc.cr.modify(|_,w| w.pllon().enabled());

    while rcc.cr.read().pllrdy().is_unlocked() {}

    // set system clock to pll
    rcc.cfgr.modify(|_,w| w.sw().pll());
}<|endoftext|>


<commit_before>/// this analyzer parses the Control Flow Guard function table.
/// it populates functions in the workspace.
/// the purpose of the table is to enumerate the functions that may be called indirectly.
///
/// references:
///   - https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#load-configuration-directory
///   - https://lucasg.github.io/2017/02/05/Control-Flow-Guard/
use log::{debug};
use goblin::{Object};
use failure::{Error};

use super::super::super::arch::{RVA, VA};
use super::super::super::loader::{Permissions};
use super::super::super::workspace::Workspace;
use super::super::{Analyzer};

pub struct CFGuardTableAnalyzer {}

impl CFGuardTableAnalyzer {
    pub fn new() -> CFGuardTableAnalyzer {
        CFGuardTableAnalyzer {}
    }
}

const IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK: u32 = 0xF0000000;
const IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT: u32 = 28;

impl Analyzer for CFGuardTableAnalyzer {
    fn get_name(&self) -> String {
        "CF Guard Table analyzer".to_string()
    }

    /// ```
    /// use lancelot::rsrc::*;
    /// use lancelot::arch::*;
    /// use lancelot::analysis::Analyzer;
    /// use lancelot::workspace::Workspace;
    /// use lancelot::analysis::pe::CFGuardTableAnalyzer;
    ///
    /// let mut ws = Workspace::from_bytes("k32.dll", &get_buf(Rsrc::K32))
    ///    .disable_analysis()
    ///    .load().unwrap();
    /// CFGuardTableAnalyzer::new().analyze(&mut ws);
    ///
    /// // export: RtlVirtualUnwind
    /// assert!(ws.get_functions().find(|&&rva| rva == RVA(0x1010)).is_some());
    ///
    /// // __guard_check_icall
    /// assert!(ws.get_functions().find(|&&rva| rva == RVA(0x21960)).is_some());
    ///
    /// // __guard_dispatch_icall
    /// assert!(ws.get_functions().find(|&&rva| rva == RVA(0x21B40)).is_some());
    /// ```
    fn analyze(&self, ws: &mut Workspace) -> Result<(), Error> {
        let (load_config_directory, is_64) = {
            let pe = match Object::parse(&ws.buf) {
                Ok(Object::PE(pe)) => pe,
                _ => panic!("can't analyze unexpected format"),
            };

            let opt_header = match pe.header.optional_header {
                Some(opt_header) => opt_header,
                _ => return Ok(()),
            };

            let load_config_directory = match opt_header.data_directories.get_load_config_table() {
                Some(load_config_directory) => load_config_directory,
                _ => return Ok(()),
            };

            (RVA::from(load_config_directory.virtual_address as i64), pe.is_64)
        };

        debug!("load config directory: {}", load_config_directory);

        // offsets defined here:
        // https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#load-configuration-directory

        let cfg_flags = match is_64 {
            true => ws.read_u32(load_config_directory + 144)?,
            false => ws.read_u32(load_config_directory + 88)?,
        };

        let stride = (cfg_flags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT;

        let cfg_table_va = match is_64 {
            true => ws.read_va(load_config_directory + 128)?,
            false => ws.read_va(load_config_directory + 80)?,
        };

        let cfg_table_count = match is_64 {
            true => ws.read_u32(load_config_directory + 136)?,
            false => ws.read_u32(load_config_directory + 84)?,
        };

        if cfg_table_va == VA(0x0) {
            debug!("CF guard table empty");
            return Ok(())
        };

        let cfg_table_rva = ws.rva(cfg_table_va).unwrap();
        debug!("CF guard table: {}", cfg_table_rva);

        let mut offset = cfg_table_rva;
        for _ in 0..cfg_table_count {
            let function = RVA::from(ws.read_i32(offset)?);

            debug!("CF guard function: {}", function);
            ws.make_function(function)?;
            ws.analyze()?;

            // 4 == sizeof(32-bit RVA)
            offset = offset + (4 + stride as usize);
        }

        // add function pointed to by GuardCFCheckFunctionPointer
        let guard_check_icall_fptr = match is_64 {
            true => ws.rva(ws.read_va(load_config_directory + 112)?).unwrap(),
            false => ws.rva(ws.read_va(load_config_directory + 72)?).unwrap(),
        };
        if ws.probe(guard_check_icall_fptr, 8, Permissions::R) {
            let guard_check_icall = ws.rva(ws.read_va(guard_check_icall_fptr)?).unwrap();
            if ws.probe(guard_check_icall, 1, Permissions::X) {
                debug!("CF guard check function: {:#x}", guard_check_icall);
                ws.make_function(guard_check_icall)?;
                ws.analyze()?;
            }
        };

        // add function pointed to by GuardCFDispatchFunctionPointer
        let guard_dispatch_icall_fptr = match is_64 {
            true => ws.rva(ws.read_va(load_config_directory + 120)?).unwrap(),
            false => ws.rva(ws.read_va(load_config_directory + 76)?).unwrap(),
        };
        if ws.probe(guard_dispatch_icall_fptr, 8, Permissions::R) {
            let guard_dispatch_icall = ws.rva(ws.read_va(guard_dispatch_icall_fptr)?).unwrap();
            if ws.probe(guard_dispatch_icall, 1, Permissions::X) {
                debug!("CF guard dispatch function: {:#x}", guard_dispatch_icall);
                ws.make_function(guard_dispatch_icall)?;
                ws.analyze()?;
            }
        };

        Ok(())
    }
}
<commit_msg>cfguard: use hex offsets<commit_after>/// this analyzer parses the Control Flow Guard function table.
/// it populates functions in the workspace.
/// the purpose of the table is to enumerate the functions that may be called indirectly.
///
/// references:
///   - https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#load-configuration-directory
///   - https://lucasg.github.io/2017/02/05/Control-Flow-Guard/
use log::{debug};
use goblin::{Object};
use failure::{Error};

use super::super::super::arch::{RVA, VA};
use super::super::super::loader::{Permissions};
use super::super::super::workspace::Workspace;
use super::super::{Analyzer};

pub struct CFGuardTableAnalyzer {}

impl CFGuardTableAnalyzer {
    pub fn new() -> CFGuardTableAnalyzer {
        CFGuardTableAnalyzer {}
    }
}

const IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK: u32 = 0xF0000000;
const IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT: u32 = 28;

impl Analyzer for CFGuardTableAnalyzer {
    fn get_name(&self) -> String {
        "CF Guard Table analyzer".to_string()
    }

    /// ```
    /// use lancelot::rsrc::*;
    /// use lancelot::arch::*;
    /// use lancelot::analysis::Analyzer;
    /// use lancelot::workspace::Workspace;
    /// use lancelot::analysis::pe::CFGuardTableAnalyzer;
    ///
    /// let mut ws = Workspace::from_bytes("k32.dll", &get_buf(Rsrc::K32))
    ///    .disable_analysis()
    ///    .load().unwrap();
    /// CFGuardTableAnalyzer::new().analyze(&mut ws);
    ///
    /// // export: RtlVirtualUnwind
    /// assert!(ws.get_functions().find(|&&rva| rva == RVA(0x1010)).is_some());
    ///
    /// // __guard_check_icall
    /// assert!(ws.get_functions().find(|&&rva| rva == RVA(0x21960)).is_some());
    ///
    /// // __guard_dispatch_icall
    /// assert!(ws.get_functions().find(|&&rva| rva == RVA(0x21B40)).is_some());
    /// ```
    fn analyze(&self, ws: &mut Workspace) -> Result<(), Error> {
        let (load_config_directory, is_64) = {
            let pe = match Object::parse(&ws.buf) {
                Ok(Object::PE(pe)) => pe,
                _ => panic!("can't analyze unexpected format"),
            };

            let opt_header = match pe.header.optional_header {
                Some(opt_header) => opt_header,
                _ => return Ok(()),
            };

            let load_config_directory = match opt_header.data_directories.get_load_config_table() {
                Some(load_config_directory) => load_config_directory,
                _ => return Ok(()),
            };

            (RVA::from(load_config_directory.virtual_address as i64), pe.is_64)
        };

        debug!("load config directory: {}", load_config_directory);

        // offsets defined here:
        // https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format#load-configuration-directory

        let cfg_flags = match is_64 {
            true => ws.read_u32(load_config_directory + 0x90)?,
            false => ws.read_u32(load_config_directory + 0x58)?,
        };

        let stride = (cfg_flags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT;

        let cfg_table_va = match is_64 {
            true => ws.read_va(load_config_directory + 0x80)?,
            false => ws.read_va(load_config_directory + 0x50)?,
        };
        debug!("CF Guard table: {}", cfg_table_va);

        let cfg_table_count = match is_64 {
            true => ws.read_u32(load_config_directory + 0x88)?,
            false => ws.read_u32(load_config_directory + 0x54)?,
        };
        debug!("CF Guard table count: {}", cfg_table_count);

        if cfg_table_va == VA(0x0) {
            debug!("CF guard table empty");
            return Ok(())
        };

        let cfg_table_rva = ws.rva(cfg_table_va).unwrap();
        debug!("CF guard table: {}", cfg_table_rva);

        let mut offset = cfg_table_rva;
        for _ in 0..cfg_table_count {
            let function = RVA::from(ws.read_i32(offset)?);

            debug!("CF guard function: {}", function);
            ws.make_function(function)?;
            ws.analyze()?;

            // 4 == sizeof(32-bit RVA)
            offset = offset + (4 + stride as usize);
        }

        // add function pointed to by GuardCFCheckFunctionPointer
        let guard_check_icall_fptr = match is_64 {
            true => ws.rva(ws.read_va(load_config_directory + 112)?).unwrap(),
            false => ws.rva(ws.read_va(load_config_directory + 72)?).unwrap(),
        };
        if ws.probe(guard_check_icall_fptr, 8, Permissions::R) {
            let guard_check_icall = ws.rva(ws.read_va(guard_check_icall_fptr)?).unwrap();
            if ws.probe(guard_check_icall, 1, Permissions::X) {
                debug!("CF guard check function: {:#x}", guard_check_icall);
                ws.make_function(guard_check_icall)?;
                ws.analyze()?;
            }
        };

        // add function pointed to by GuardCFDispatchFunctionPointer
        let guard_dispatch_icall_fptr = match is_64 {
            true => ws.rva(ws.read_va(load_config_directory + 120)?).unwrap(),
            false => ws.rva(ws.read_va(load_config_directory + 76)?).unwrap(),
        };
        if ws.probe(guard_dispatch_icall_fptr, 8, Permissions::R) {
            let guard_dispatch_icall = ws.rva(ws.read_va(guard_dispatch_icall_fptr)?).unwrap();
            if ws.probe(guard_dispatch_icall, 1, Permissions::X) {
                debug!("CF guard dispatch function: {:#x}", guard_dispatch_icall);
                ws.make_function(guard_dispatch_icall)?;
                ws.analyze()?;
            }
        };

        Ok(())
    }
}
<|endoftext|>


<commit_before>// (C)opyleft 2013 Frank Denis

/*!
 * Bindings for the GeoIP library
 */
#![desc = "Bindings for the GeoIP library."]
#![license = "BSD"]
#![crate_id = "geoip#0.1"]
#![crate_type = "rlib"]

#![warn(non_camel_case_types,
        non_uppercase_statics,
        unnecessary_qualification,
        managed_heap_memory)]

extern crate libc;

use libc::{c_void, c_char, c_int, c_ulong};
use std::c_str::CString;
use std::fmt;
use std::io::net::ip::{IpAddr,Ipv4Addr,Ipv6Addr};

type GeoIP_ = *c_void;
type In6Addr = [u8, ..16];

struct GeoIPLookup {
    netmask: c_int
}

impl GeoIPLookup {
    fn new() -> GeoIPLookup {
        GeoIPLookup {
            netmask: 0
        }
    }
}

#[link(name = "GeoIP")]
extern {
    fn GeoIP_open(dbtype: *c_char, flags: c_int) -> GeoIP_;
    fn GeoIP_delete(db: GeoIP_);
    fn GeoIP_name_by_ipnum_gl(db: GeoIP_, ipnum: c_ulong, gl: &GeoIPLookup) -> *c_char;
    fn GeoIP_name_by_ipnum_v6_gl(db: GeoIP_, ipnum: In6Addr, gl: &GeoIPLookup) -> *c_char;
}

pub enum Options {
    Standard = 0,
    MemoryCache = 1,
    CheckCache = 2,
    IndexCache = 4,
    MmapCache = 8
}

pub enum DBType {
    CountryEdition = 1,
    RegionEditionRev0 = 7,
    CityEditionRev0 = 6,
    ORGEdition = 5,
    ISPEdition = 4,
    CityEditionRev1 = 2,
    RegionEditionRev1 = 3,
    ProxyEdition = 8,
    ASNUMEdition = 9,
    NetSpeedEdition = 10,
    DomainEdition = 11,
    CountryEditionV6 = 12,
    LocationAEdition = 13,
    AccuracyRadiusEdition = 14,
    LargeCountryEdition = 17,
    LargeCountryEditionV6 = 18,
    ASNumEditionV6 = 21,
    ISPEditionV6 = 22,
    ORGEditionV6 = 23,
    DomainEditionV6 = 24,
    LoctionAEditionV6 = 25,
    RegistrarEdition = 26,
    RegistrarEditionV6 = 27,
    UserTypeEdition = 28,
    UserTypeEditionV6 = 29,
    CityEditionRev1V6 = 30,
    CityEditionRev0V6 = 31,
    NetSpeedEditionRev1 = 32,
    NetSpeedEditionRev1V6 = 33,
    CountryConfEdition = 34,
    CityConfEdition = 35,
    RegionConfEdition = 36,
    PostalConfEdition = 37,
    AccuracyRadiusEditionV6 = 38
}

pub struct GeoIP {
    db: GeoIP_
}

pub struct ASInfo {
    pub asn: uint,
    pub name: StrBuf,
    pub netmask: uint
}

impl fmt::Show for ASInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}\t{}", self.asn, self.name)
    }
}

impl GeoIP {
    pub fn open(path: &Path, options: Options) -> Result<GeoIP, StrBuf> {
        let file = match path.as_str() {
            None => return Err(format!("Invalid path {}", path.display())),
            Some(file) => file
        };
        let db = unsafe {
            GeoIP_open(file.to_c_str().unwrap(), options as c_int)
        };
        match db.is_null() {
            true => Err(format!("Can't open {}", file)),
            false => Ok(GeoIP {
                    db: db
                })
        }
    }

    pub fn as_info_by_ip(&self, ip: IpAddr) -> Option<ASInfo> {
        let gl = GeoIPLookup::new();
        let cres = match ip {
            Ipv4Addr(a, b, c, d) => {
                let ipnum: c_ulong =
                    (a as c_ulong << 24) | (b as c_ulong << 16) |
                    (c as c_ulong << 8)  | (d as c_ulong);
                unsafe {
                    GeoIP_name_by_ipnum_gl(self.db, ipnum, &gl)
                }
            },
            Ipv6Addr(a, b, c, d, e, f, g, h) => {
                let in6_addr: In6Addr = [(a >> 8) as u8, a as u8,
                                         (b >> 8) as u8, b as u8,
                                         (c >> 8) as u8, c as u8,
                                         (d >> 8) as u8, d as u8,
                                         (e >> 8) as u8, e as u8,
                                         (f >> 8) as u8, f as u8,
                                         (g >> 8) as u8, g as u8,
                                         (h >> 8) as u8, h as u8];
                unsafe {
                    GeoIP_name_by_ipnum_v6_gl(self.db, in6_addr, &gl)
                }
            }
        };
        if cres.is_null() {
            return None;
        }
        let description_cstr = unsafe { CString::new(cres, true) };
        let description = match description_cstr.as_str() {
            None => return None,
            Some(description) => description
        };
        let mut di = description.splitn(' ', 1);
        let asn = match di.next() {
            None => return None,
            Some(asn) => {
                if ! asn.starts_with("AS") {
                    return None
                } else {
                    from_str::<uint>(asn.slice_from(2)).unwrap()
                }
            }
        };
        let name = di.next().unwrap_or("(none)");
        let asinfo = ASInfo {
            asn: asn,
            name: name.to_owned(),
            netmask: gl.netmask as uint
        };
        Some(asinfo)
    }
}

impl Drop for GeoIP {
    fn drop(&mut self) {
        unsafe {
            GeoIP_delete(self.db);
        }
    }
}

#[test]
fn geoip_test_basic() {
    let geoip = match GeoIP::open(&from_str("/opt/geoip/GeoIPASNum.dat").unwrap(), MemoryCache) {
        Err(err) => fail!(err),
        Ok(geoip) => geoip
    };
    let ip = from_str("91.203.184.192").unwrap();
    let res = geoip.as_info_by_ip(ip).unwrap();
    assert!(res.asn == 41064);
    assert!(res.name.as_slice().contains("Telefun"));
    assert!(res.netmask == 22);
}
<commit_msg>StrBuf -> String<commit_after>// (C)opyleft 2013 Frank Denis

/*!
 * Bindings for the GeoIP library
 */
#![desc = "Bindings for the GeoIP library."]
#![license = "BSD"]
#![crate_id = "geoip#0.1"]
#![crate_type = "rlib"]

#![warn(non_camel_case_types,
        non_uppercase_statics,
        unnecessary_qualification,
        managed_heap_memory)]

extern crate libc;

use libc::{c_void, c_char, c_int, c_ulong};
use std::c_str::CString;
use std::fmt;
use std::io::net::ip::{IpAddr,Ipv4Addr,Ipv6Addr};

type GeoIP_ = *c_void;
type In6Addr = [u8, ..16];

struct GeoIPLookup {
    netmask: c_int
}

impl GeoIPLookup {
    fn new() -> GeoIPLookup {
        GeoIPLookup {
            netmask: 0
        }
    }
}

#[link(name = "GeoIP")]
extern {
    fn GeoIP_open(dbtype: *c_char, flags: c_int) -> GeoIP_;
    fn GeoIP_delete(db: GeoIP_);
    fn GeoIP_name_by_ipnum_gl(db: GeoIP_, ipnum: c_ulong, gl: &GeoIPLookup) -> *c_char;
    fn GeoIP_name_by_ipnum_v6_gl(db: GeoIP_, ipnum: In6Addr, gl: &GeoIPLookup) -> *c_char;
}

pub enum Options {
    Standard = 0,
    MemoryCache = 1,
    CheckCache = 2,
    IndexCache = 4,
    MmapCache = 8
}

pub enum DBType {
    CountryEdition = 1,
    RegionEditionRev0 = 7,
    CityEditionRev0 = 6,
    ORGEdition = 5,
    ISPEdition = 4,
    CityEditionRev1 = 2,
    RegionEditionRev1 = 3,
    ProxyEdition = 8,
    ASNUMEdition = 9,
    NetSpeedEdition = 10,
    DomainEdition = 11,
    CountryEditionV6 = 12,
    LocationAEdition = 13,
    AccuracyRadiusEdition = 14,
    LargeCountryEdition = 17,
    LargeCountryEditionV6 = 18,
    ASNumEditionV6 = 21,
    ISPEditionV6 = 22,
    ORGEditionV6 = 23,
    DomainEditionV6 = 24,
    LoctionAEditionV6 = 25,
    RegistrarEdition = 26,
    RegistrarEditionV6 = 27,
    UserTypeEdition = 28,
    UserTypeEditionV6 = 29,
    CityEditionRev1V6 = 30,
    CityEditionRev0V6 = 31,
    NetSpeedEditionRev1 = 32,
    NetSpeedEditionRev1V6 = 33,
    CountryConfEdition = 34,
    CityConfEdition = 35,
    RegionConfEdition = 36,
    PostalConfEdition = 37,
    AccuracyRadiusEditionV6 = 38
}

pub struct GeoIP {
    db: GeoIP_
}

pub struct ASInfo {
    pub asn: uint,
    pub name: String,
    pub netmask: uint
}

impl fmt::Show for ASInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}\t{}", self.asn, self.name)
    }
}

impl GeoIP {
    pub fn open(path: &Path, options: Options) -> Result<GeoIP, String> {
        let file = match path.as_str() {
            None => return Err(format!("Invalid path {}", path.display())),
            Some(file) => file
        };
        let db = unsafe {
            GeoIP_open(file.to_c_str().unwrap(), options as c_int)
        };
        match db.is_null() {
            true => Err(format!("Can't open {}", file)),
            false => Ok(GeoIP {
                    db: db
                })
        }
    }

    pub fn as_info_by_ip(&self, ip: IpAddr) -> Option<ASInfo> {
        let gl = GeoIPLookup::new();
        let cres = match ip {
            Ipv4Addr(a, b, c, d) => {
                let ipnum: c_ulong =
                    (a as c_ulong << 24) | (b as c_ulong << 16) |
                    (c as c_ulong << 8)  | (d as c_ulong);
                unsafe {
                    GeoIP_name_by_ipnum_gl(self.db, ipnum, &gl)
                }
            },
            Ipv6Addr(a, b, c, d, e, f, g, h) => {
                let in6_addr: In6Addr = [(a >> 8) as u8, a as u8,
                                         (b >> 8) as u8, b as u8,
                                         (c >> 8) as u8, c as u8,
                                         (d >> 8) as u8, d as u8,
                                         (e >> 8) as u8, e as u8,
                                         (f >> 8) as u8, f as u8,
                                         (g >> 8) as u8, g as u8,
                                         (h >> 8) as u8, h as u8];
                unsafe {
                    GeoIP_name_by_ipnum_v6_gl(self.db, in6_addr, &gl)
                }
            }
        };
        if cres.is_null() {
            return None;
        }
        let description_cstr = unsafe { CString::new(cres, true) };
        let description = match description_cstr.as_str() {
            None => return None,
            Some(description) => description
        };
        let mut di = description.splitn(' ', 1);
        let asn = match di.next() {
            None => return None,
            Some(asn) => {
                if ! asn.starts_with("AS") {
                    return None
                } else {
                    from_str::<uint>(asn.slice_from(2)).unwrap()
                }
            }
        };
        let name = di.next().unwrap_or("(none)");
        let asinfo = ASInfo {
            asn: asn,
            name: name.to_owned(),
            netmask: gl.netmask as uint
        };
        Some(asinfo)
    }
}

impl Drop for GeoIP {
    fn drop(&mut self) {
        unsafe {
            GeoIP_delete(self.db);
        }
    }
}

#[test]
fn geoip_test_basic() {
    let geoip = match GeoIP::open(&from_str("/opt/geoip/GeoIPASNum.dat").unwrap(), MemoryCache) {
        Err(err) => fail!(err),
        Ok(geoip) => geoip
    };
    let ip = from_str("91.203.184.192").unwrap();
    let res = geoip.as_info_by_ip(ip).unwrap();
    assert!(res.asn == 41064);
    assert!(res.name.as_slice().contains("Telefun"));
    assert!(res.netmask == 22);
}
<|endoftext|>