<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 data::StoredRestyleHint;
use dom::{TElement, TNode};
use fnv::FnvHashSet;
use selector_parser::SelectorImpl;
use selectors::attr::CaseSensitivity;
use selectors::parser::{Component, Selector};
use shared_lock::SharedRwLockReadGuard;
use stylesheets::{CssRule, Stylesheet};
use stylist::Stylist;

/// An invalidation scope represents a kind of subtree that may need to be
/// restyled.
#[derive(Debug, Hash, Eq, PartialEq)]
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 removals and
/// media query changes too?
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(
        &mut self,
        stylist: &Stylist,
        stylesheet: &Stylesheet,
        guard: &SharedRwLockReadGuard)
    {
        debug!("StylesheetInvalidationSet::collect_invalidations_for");
        if self.fully_invalid {
            debug!(" > Fully invalid already");
            return;
        }

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

        for rule in stylesheet.effective_rules(stylist.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.
    pub fn flush<E>(&mut self, document_element: Option<E>)
        where E: TElement,
    {
        if let Some(e) = document_element {
            self.process_invalidations_in_subtree(e);
        }
        self.invalid_scopes.clear();
        self.fully_invalid = false;
    }

    /// 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 let Some(ref r) = data.get_restyle() {
            if r.hint.contains_subtree() {
                debug!("process_invalidations_in_subtree: {:?} was already invalid",
                       element);
                return false;
            }
        }

        if self.fully_invalid {
            debug!("process_invalidations_in_subtree: fully_invalid({:?})",
                   element);
            data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
            return true;
        }

        for scope in &self.invalid_scopes {
            if scope.matches(element) {
                debug!("process_invalidations_in_subtree: {:?} matched {:?}",
                       element, scope);
                data.ensure_restyle().hint.insert(StoredRestyleHint::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_and_hashes in &style_rule.selectors.0 {
                    self.collect_scopes(&selector_and_hashes.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(..) => {
                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>style: Don't try to go down the tree if there aren't invalidations.<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 data::StoredRestyleHint;
use dom::{TElement, TNode};
use fnv::FnvHashSet;
use selector_parser::SelectorImpl;
use selectors::attr::CaseSensitivity;
use selectors::parser::{Component, Selector};
use shared_lock::SharedRwLockReadGuard;
use stylesheets::{CssRule, Stylesheet};
use stylist::Stylist;

/// An invalidation scope represents a kind of subtree that may need to be
/// restyled.
#[derive(Debug, Hash, Eq, PartialEq)]
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 removals and
/// media query changes too?
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(
        &mut self,
        stylist: &Stylist,
        stylesheet: &Stylesheet,
        guard: &SharedRwLockReadGuard)
    {
        debug!("StylesheetInvalidationSet::collect_invalidations_for");
        if self.fully_invalid {
            debug!(" > Fully invalid already");
            return;
        }

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

        for rule in stylesheet.effective_rules(stylist.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.
    pub fn flush<E>(&mut self, document_element: Option<E>)
        where E: TElement,
    {
        if let Some(e) = document_element {
            self.process_invalidations_in_subtree(e);
        }
        self.invalid_scopes.clear();
        self.fully_invalid = false;
    }

    /// 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 let Some(ref r) = data.get_restyle() {
            if r.hint.contains_subtree() {
                debug!("process_invalidations_in_subtree: {:?} was already invalid",
                       element);
                return false;
            }
        }

        if self.fully_invalid {
            debug!("process_invalidations_in_subtree: fully_invalid({:?})",
                   element);
            data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
            return true;
        }

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

        for scope in &self.invalid_scopes {
            if scope.matches(element) {
                debug!("process_invalidations_in_subtree: {:?} matched {:?}",
                       element, scope);
                data.ensure_restyle().hint.insert(StoredRestyleHint::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_and_hashes in &style_rule.selectors.0 {
                    self.collect_scopes(&selector_and_hashes.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(..) => {
                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>use std::ffi::CString;
use libc::c_int;

use super::ffi;

use {SqliteResult, SqliteError, SqliteConnection, SqliteStatement, SqliteRows, SqliteRow};
use types::ToSql;

impl SqliteConnection {
    /// Convenience method to prepare and execute a single SQL statement with named parameter(s).
    ///
    /// On success, returns the number of rows that were changed or inserted or deleted (via
    /// `sqlite3_changes`).
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// # use rusqlite::{SqliteConnection, SqliteResult};
    /// fn insert(conn: &SqliteConnection) -> SqliteResult<i32> {
    ///     conn.execute_named("INSERT INTO test (name) VALUES (:name)", &[(":name", &"one")])
    /// }
    /// ```
    ///
    /// # Failure
    ///
    /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
    /// underlying SQLite call fails.
    pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> SqliteResult<c_int> {
        self.prepare(sql).and_then(|mut stmt| stmt.execute_named(params))
    }

    /// Convenience method to execute a query with named parameter(s) that is expected to return
    /// a single row.
    ///
    /// If the query returns more than one row, all rows except the first are ignored.
    ///
    /// # Failure
    ///
    /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
    /// underlying SQLite call fails.
    pub fn query_row_named<T, F>(&self,
                                 sql: &str,
                                 params: &[(&str, &ToSql)],
                                 f: F)
                                 -> SqliteResult<T>
        where F: FnOnce(SqliteRow) -> T
    {
        let mut stmt = try!(self.prepare(sql));
        let mut rows = try!(stmt.query_named(params));

        match rows.next() {
            Some(row) => row.map(f),
            None => {
                Err(SqliteError {
                    code: ffi::SQLITE_NOTICE,
                    message: "Query did not return a row".to_string(),
                })
            }
        }
    }
}

impl<'conn> SqliteStatement<'conn> {
    /// Return the index of an SQL parameter given its name.
    ///
    /// # Failure
    ///
    /// Return None if `name` is invalid or if no matching parameter is found.
    pub fn parameter_index(&self, name: &str) -> Option<i32> {
        unsafe {
            CString::new(name).ok().and_then(|c_name| {
                match ffi::sqlite3_bind_parameter_index(self.stmt, c_name.as_ptr()) {
                    0 => None, // A zero is returned if no matching parameter is found.
                    n => Some(n),
                }
            })

        }
    }

    /// Execute the prepared statement with named parameter(s).
    ///
    /// On success, returns the number of rows that were changed or inserted or deleted (via
    /// `sqlite3_changes`).
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// # use rusqlite::{SqliteConnection, SqliteResult};
    /// fn insert(conn: &SqliteConnection) -> SqliteResult<i32> {
    ///     let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)"));
    ///     stmt.execute_named(&[(":name", &"one")])
    /// }
    /// ```
    ///
    /// # Failure
    ///
    /// Will return `Err` if binding parameters fails, the executed statement returns rows (in
    /// which case `query` should be used instead), or the underling SQLite call fails.
    pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> SqliteResult<c_int> {
        unsafe {
            try!(self.bind_named_parameters(params));
            self.execute_()
        }
    }

    /// Execute the prepared statement with named parameter(s), returning an iterator over the
    /// resulting rows.
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// # use rusqlite::{SqliteConnection, SqliteResult, SqliteRows};
    /// fn query(conn: &SqliteConnection) -> SqliteResult<()> {
    ///     let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name"));
    ///     let mut rows = try!(stmt.query_named(&[(":name", &"one")]));
    ///     for row in rows {
    ///         // ...
    ///     }
    ///     Ok(())
    /// }
    /// ```
    ///
    /// # Failure
    ///
    /// Will return `Err` if binding parameters fails.
    pub fn query_named<'a>(&'a mut self,
                           params: &[(&str, &ToSql)])
                           -> SqliteResult<SqliteRows<'a>> {
        self.reset_if_needed();

        unsafe {
            try!(self.bind_named_parameters(params));
        }

        self.needs_reset = true;
        Ok(SqliteRows::new(self))
    }

    unsafe fn bind_named_parameters(&mut self, params: &[(&str, &ToSql)]) -> SqliteResult<()> {
        // Always check that the number of parameters is correct.
        assert!(params.len() as c_int == ffi::sqlite3_bind_parameter_count(self.stmt),
                "incorrect number of parameters to query(): expected {}, got {}",
                ffi::sqlite3_bind_parameter_count(self.stmt),
                params.len());

        // In debug, also sanity check that we got distinct parameter names.
        debug_assert!({
                          use std::collections::HashSet;

                          let mut s = HashSet::with_capacity(params.len());
                          for &(name, _) in params {
                              s.insert(name);
                          }

                          s.len() == params.len()
                      },
                      "named parameters must be unique");

        for &(name, value) in params {
            let i = try!(self.parameter_index(name).ok_or(SqliteError {
                code: ffi::SQLITE_MISUSE,
                message: format!("Invalid parameter name: {}", name),
            }));
            try!(self.conn.decode_result(value.bind_parameter(self.stmt, i)));
        }
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use SqliteConnection;

    #[test]
    fn test_execute_named() {
        let db = SqliteConnection::open_in_memory().unwrap();
        db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();

        assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)]).unwrap(),
                   1);
        assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]).unwrap(),
                   1);

        assert_eq!(3i32,
                   db.query_row_named("SELECT SUM(x) FROM foo WHERE x > :x",
                                      &[(":x", &0i32)],
                                      |r| r.get(0))
                     .unwrap());
    }

    #[test]
    fn test_stmt_execute_named() {
        let db = SqliteConnection::open_in_memory().unwrap();
        let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
                   INTEGER)";
        db.execute_batch(sql).unwrap();

        let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)").unwrap();
        stmt.execute_named(&[(":name", &"one")]).unwrap();

        assert_eq!(1i32,
                   db.query_row_named("SELECT COUNT(*) FROM test WHERE name = :name",
                                      &[(":name", &"one")],
                                      |r| r.get(0))
                     .unwrap());
    }

    #[test]
    fn test_query_named() {
        let db = SqliteConnection::open_in_memory().unwrap();
        let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
                   INTEGER)";
        db.execute_batch(sql).unwrap();

        let mut stmt = db.prepare("SELECT * FROM test where name = :name").unwrap();
        stmt.query_named(&[(":name", &"one")]).unwrap();
    }

    #[test]
    #[should_panic]
    fn test_panic_on_incorrect_number_of_parameters() {
        let db = SqliteConnection::open_in_memory().unwrap();

        let mut stmt = db.prepare("SELECT 1 WHERE 1 = :one AND 2 = :two").unwrap();
        let _ = stmt.query_named(&[(":one", &1i32)]);
    }

    #[test]
    #[cfg(debug_assertions)]
    #[should_panic]
    fn test_debug_panic_on_incorrect_parameter_names() {
        let db = SqliteConnection::open_in_memory().unwrap();

        let mut stmt = db.prepare("SELECT 1 WHERE 1 = :one AND 2 = :two").unwrap();
        let _ = stmt.query_named(&[(":one", &1i32), (":one", &2i32)]);
    }
}
<commit_msg>Make parameter_index return a Result<Option<_>> instead of squashing string conversion errors into None.<commit_after>use libc::c_int;

use super::ffi;

use {SqliteResult, SqliteError, SqliteConnection, SqliteStatement, SqliteRows, SqliteRow,
     str_to_cstring};
use types::ToSql;

impl SqliteConnection {
    /// Convenience method to prepare and execute a single SQL statement with named parameter(s).
    ///
    /// On success, returns the number of rows that were changed or inserted or deleted (via
    /// `sqlite3_changes`).
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// # use rusqlite::{SqliteConnection, SqliteResult};
    /// fn insert(conn: &SqliteConnection) -> SqliteResult<i32> {
    ///     conn.execute_named("INSERT INTO test (name) VALUES (:name)", &[(":name", &"one")])
    /// }
    /// ```
    ///
    /// # Failure
    ///
    /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
    /// underlying SQLite call fails.
    pub fn execute_named(&self, sql: &str, params: &[(&str, &ToSql)]) -> SqliteResult<c_int> {
        self.prepare(sql).and_then(|mut stmt| stmt.execute_named(params))
    }

    /// Convenience method to execute a query with named parameter(s) that is expected to return
    /// a single row.
    ///
    /// If the query returns more than one row, all rows except the first are ignored.
    ///
    /// # Failure
    ///
    /// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
    /// underlying SQLite call fails.
    pub fn query_row_named<T, F>(&self,
                                 sql: &str,
                                 params: &[(&str, &ToSql)],
                                 f: F)
                                 -> SqliteResult<T>
        where F: FnOnce(SqliteRow) -> T
    {
        let mut stmt = try!(self.prepare(sql));
        let mut rows = try!(stmt.query_named(params));

        match rows.next() {
            Some(row) => row.map(f),
            None => {
                Err(SqliteError {
                    code: ffi::SQLITE_NOTICE,
                    message: "Query did not return a row".to_string(),
                })
            }
        }
    }
}

impl<'conn> SqliteStatement<'conn> {
    /// Return the index of an SQL parameter given its name.
    ///
    /// # Failure
    ///
    /// Will return Err if `name` is invalid. Will return Ok(None) if the name
    /// is valid but not a bound parameter of this statement.
    pub fn parameter_index(&self, name: &str) -> SqliteResult<Option<i32>> {
        let c_name = try!(str_to_cstring(name));
        let c_index = unsafe { ffi::sqlite3_bind_parameter_index(self.stmt, c_name.as_ptr()) };
        Ok(match c_index {
            0 => None, // A zero is returned if no matching parameter is found.
            n => Some(n),
        })
    }

    /// Execute the prepared statement with named parameter(s).
    ///
    /// On success, returns the number of rows that were changed or inserted or deleted (via
    /// `sqlite3_changes`).
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// # use rusqlite::{SqliteConnection, SqliteResult};
    /// fn insert(conn: &SqliteConnection) -> SqliteResult<i32> {
    ///     let mut stmt = try!(conn.prepare("INSERT INTO test (name) VALUES (:name)"));
    ///     stmt.execute_named(&[(":name", &"one")])
    /// }
    /// ```
    ///
    /// # Failure
    ///
    /// Will return `Err` if binding parameters fails, the executed statement returns rows (in
    /// which case `query` should be used instead), or the underling SQLite call fails.
    pub fn execute_named(&mut self, params: &[(&str, &ToSql)]) -> SqliteResult<c_int> {
        try!(self.bind_named_parameters(params));
        unsafe {
            self.execute_()
        }
    }

    /// Execute the prepared statement with named parameter(s), returning an iterator over the
    /// resulting rows.
    ///
    /// ## Example
    ///
    /// ```rust,no_run
    /// # use rusqlite::{SqliteConnection, SqliteResult, SqliteRows};
    /// fn query(conn: &SqliteConnection) -> SqliteResult<()> {
    ///     let mut stmt = try!(conn.prepare("SELECT * FROM test where name = :name"));
    ///     let mut rows = try!(stmt.query_named(&[(":name", &"one")]));
    ///     for row in rows {
    ///         // ...
    ///     }
    ///     Ok(())
    /// }
    /// ```
    ///
    /// # Failure
    ///
    /// Will return `Err` if binding parameters fails.
    pub fn query_named<'a>(&'a mut self,
                           params: &[(&str, &ToSql)])
                           -> SqliteResult<SqliteRows<'a>> {
        self.reset_if_needed();
        try!(self.bind_named_parameters(params));

        self.needs_reset = true;
        Ok(SqliteRows::new(self))
    }

    fn bind_named_parameters(&mut self, params: &[(&str, &ToSql)]) -> SqliteResult<()> {
        // Always check that the number of parameters is correct.
        assert!(params.len() as c_int == unsafe { ffi::sqlite3_bind_parameter_count(self.stmt) },
                "incorrect number of parameters to query(): expected {}, got {}",
                unsafe { ffi::sqlite3_bind_parameter_count(self.stmt) },
                params.len());

        // In debug, also sanity check that we got distinct parameter names.
        debug_assert!({
                          use std::collections::HashSet;

                          let mut s = HashSet::with_capacity(params.len());
                          for &(name, _) in params {
                              s.insert(name);
                          }

                          s.len() == params.len()
                      },
                      "named parameters must be unique");

        for &(name, value) in params {
            if let Some(i) = try!(self.parameter_index(name)) {
                try!(self.conn.decode_result(unsafe { value.bind_parameter(self.stmt, i) }));
            } else {
                return Err(SqliteError {
                    code: ffi::SQLITE_MISUSE,
                    message: format!("Invalid parameter name: {}", name),
                });
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use SqliteConnection;

    #[test]
    fn test_execute_named() {
        let db = SqliteConnection::open_in_memory().unwrap();
        db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();

        assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &1i32)]).unwrap(),
                   1);
        assert_eq!(db.execute_named("INSERT INTO foo(x) VALUES (:x)", &[(":x", &2i32)]).unwrap(),
                   1);

        assert_eq!(3i32,
                   db.query_row_named("SELECT SUM(x) FROM foo WHERE x > :x",
                                      &[(":x", &0i32)],
                                      |r| r.get(0))
                     .unwrap());
    }

    #[test]
    fn test_stmt_execute_named() {
        let db = SqliteConnection::open_in_memory().unwrap();
        let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
                   INTEGER)";
        db.execute_batch(sql).unwrap();

        let mut stmt = db.prepare("INSERT INTO test (name) VALUES (:name)").unwrap();
        stmt.execute_named(&[(":name", &"one")]).unwrap();

        assert_eq!(1i32,
                   db.query_row_named("SELECT COUNT(*) FROM test WHERE name = :name",
                                      &[(":name", &"one")],
                                      |r| r.get(0))
                     .unwrap());
    }

    #[test]
    fn test_query_named() {
        let db = SqliteConnection::open_in_memory().unwrap();
        let sql = "CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, flag \
                   INTEGER)";
        db.execute_batch(sql).unwrap();

        let mut stmt = db.prepare("SELECT * FROM test where name = :name").unwrap();
        stmt.query_named(&[(":name", &"one")]).unwrap();
    }

    #[test]
    #[should_panic]
    fn test_panic_on_incorrect_number_of_parameters() {
        let db = SqliteConnection::open_in_memory().unwrap();

        let mut stmt = db.prepare("SELECT 1 WHERE 1 = :one AND 2 = :two").unwrap();
        let _ = stmt.query_named(&[(":one", &1i32)]);
    }

    #[test]
    #[cfg(debug_assertions)]
    #[should_panic]
    fn test_debug_panic_on_incorrect_parameter_names() {
        let db = SqliteConnection::open_in_memory().unwrap();

        let mut stmt = db.prepare("SELECT 1 WHERE 1 = :one AND 2 = :two").unwrap();
        let _ = stmt.query_named(&[(":one", &1i32), (":one", &2i32)]);
    }
}
<|endoftext|>


<commit_before>use std::process;
use std::process::Command;
use std::{env, fs};

fn main() {
    let usage = r#"tutorial-maker <username> <program-name> <language> <program-type>

    <language> can only be the following:

    - rust

    <program-type> can only be the following:

    - library
"#;
    let args: Vec<String> = env::args().collect();

    if args.len() < 5 {
        println!("{usage}");

        process::exit(1);
    }

    println!("{:?}", args);

    let username = args[1].to_lowercase();
    let program_name = args[2].to_lowercase();
    let args3 = args[3].to_lowercase();
    let language = if args3 == "rust" {
        "rust"
    } else {
        println!("{usage}");

        process::exit(1);
    };

    let args4 = args[4].to_lowercase();
    let program_type = if args4 == "library" {
        "library"
    } else {
        println!("{usage}");

        process::exit(1);
    };

    let path_name = format!("repos/github/{username}");

    println!("mkdir -p path_name: {path_name}");

    fs::create_dir_all(&path_name).expect("unable to create path name");

    println!("cd {path_name}");

    env::set_current_dir(&path_name).expect("changing directory failed");

    let app_name = format!("{program_name}-{program_type}");
    let folder_name = format!("{program_name}-{language}-{program_type}");
    let repo_name = format!("{username}/{folder_name}");

    println!("rm -rf {folder_name}");

    let removing_folder_output = Command::new("rm")
        .arg("-rf")
        .arg(&folder_name)
        .output()
        .expect("removing folder failed");

    if removing_folder_output.status.success() {
        println!(
            "cloning stdout: {}",
            String::from_utf8_lossy(&removing_folder_output.stdout)
        );
    } else {
        println!(
            "cloning stderr: {}",
            String::from_utf8_lossy(&removing_folder_output.stderr)
        );

        process::exit(1);
    }

    println!("gh repo clone {repo_name}");

    let cloning_output = Command::new("gh")
        .arg("repo")
        .arg("clone")
        .arg(&repo_name)
        .output()
        .expect("cloning failed");

    if cloning_output.status.success() {
        println!(
            "cloning stdout: {}",
            String::from_utf8_lossy(&cloning_output.stdout)
        );
    } else {
        println!(
            "cloning stderr: {}",
            String::from_utf8_lossy(&cloning_output.stderr)
        );

        process::exit(1);
    }

    println!("cd {folder_name}");

    env::set_current_dir(&folder_name).expect("changing directory failed");

    println!("git rev-list --all --reverse");

    let rev_list_output = Command::new("git")
        .arg("rev-list")
        .arg("--all")
        .arg("--reverse")
        .output()
        .expect("rev-list failed");

    if rev_list_output.status.success() {
        let rev_list_output_cow = String::from_utf8_lossy(&rev_list_output.stdout).to_string();

        for (_, commit_hash) in rev_list_output_cow.split("\n").enumerate() {
            if !commit_hash.is_empty() {
                println!("git checkout {commit_hash}");

                let checkout_output = Command::new("git")
                    .arg("checkout")
                    .arg(commit_hash)
                    .output()
                    .expect("checkout failed");

                if checkout_output.status.success() {
                    println!(
                        "checkout stdout: {}",
                        String::from_utf8_lossy(&checkout_output.stdout)
                    );
                } else {
                    println!(
                        "checkout stderr: {}",
                        String::from_utf8_lossy(&checkout_output.stderr)
                    );

                    process::exit(1);
                }

                println!("git show --no-patch --format='%s'");

                let show_s_output = Command::new("git")
                    .arg("show")
                    .arg("--no-patch")
                    .arg("--format='%s'")
                    .output()
                    .expect("show failed");

                if show_s_output.status.success() {
                    println!(
                        "show stdout: {}",
                        String::from_utf8_lossy(&show_s_output.stdout)
                    );
                } else {
                    println!(
                        "show stderr: {}",
                        String::from_utf8_lossy(&show_s_output.stderr)
                    );

                    process::exit(1);
                }

                println!("git show --no-patch --format='%b'");

                let show_b_output = Command::new("git")
                    .arg("show")
                    .arg("--no-patch")
                    .arg("--format='%b'")
                    .output()
                    .expect("show failed");

                if show_b_output.status.success() {
                    println!(
                        "show stdout: {}",
                        String::from_utf8_lossy(&show_b_output.stdout)
                    );
                } else {
                    println!(
                        "show stderr: {}",
                        String::from_utf8_lossy(&show_b_output.stderr)
                    );

                    process::exit(1);
                }

                let mut path = env::current_dir().expect("getting current directory failed");
                path.push(&app_name);
                let metadata = fs::metadata(path);
                match metadata {
                  Ok(_) => {},
                  Err(_) => {
                      continue;
                  }
                };

                println!("cd {app_name}");

                env::set_current_dir(&app_name).expect("changing directory failed");

                println!("cargo test");

                let test_output = Command::new("cargo")
                    .arg("test")
                    .output()
                    .expect("test failed");

                if test_output.status.success() {
                    println!(
                        "test stdout: {}",
                        String::from_utf8_lossy(&test_output.stdout)
                    );
                } else {
                    println!(
                        "test stderr: {}",
                        String::from_utf8_lossy(&test_output.stderr)
                    );

                    process::exit(1);
                }
            }
        }

        println!(
            "rev-list stdout: {}",
            String::from_utf8_lossy(&rev_list_output.stdout)
        );
    } else {
        println!(
            "rev-list stderr: {}",
            String::from_utf8_lossy(&rev_list_output.stderr)
        );

        process::exit(1);
    }
}
<commit_msg>showing index<commit_after>use std::process;
use std::process::Command;
use std::{env, fs};

fn main() {
    let usage = r#"tutorial-maker <username> <program-name> <language> <program-type>

    <language> can only be the following:

    - rust

    <program-type> can only be the following:

    - library
"#;
    let args: Vec<String> = env::args().collect();

    if args.len() < 5 {
        println!("{usage}");

        process::exit(1);
    }

    println!("{:?}", args);

    let username = args[1].to_lowercase();
    let program_name = args[2].to_lowercase();
    let args3 = args[3].to_lowercase();
    let language = if args3 == "rust" {
        "rust"
    } else {
        println!("{usage}");

        process::exit(1);
    };

    let args4 = args[4].to_lowercase();
    let program_type = if args4 == "library" {
        "library"
    } else {
        println!("{usage}");

        process::exit(1);
    };

    let path_name = format!("repos/github/{username}");

    println!("mkdir -p path_name: {path_name}");

    fs::create_dir_all(&path_name).expect("unable to create path name");

    println!("cd {path_name}");

    env::set_current_dir(&path_name).expect("changing directory failed");

    let app_name = format!("{program_name}-{program_type}");
    let folder_name = format!("{program_name}-{language}-{program_type}");
    let repo_name = format!("{username}/{folder_name}");

    println!("rm -rf {folder_name}");

    let removing_folder_output = Command::new("rm")
        .arg("-rf")
        .arg(&folder_name)
        .output()
        .expect("removing folder failed");

    if removing_folder_output.status.success() {
        println!(
            "cloning stdout: {}",
            String::from_utf8_lossy(&removing_folder_output.stdout)
        );
    } else {
        println!(
            "cloning stderr: {}",
            String::from_utf8_lossy(&removing_folder_output.stderr)
        );

        process::exit(1);
    }

    println!("gh repo clone {repo_name}");

    let cloning_output = Command::new("gh")
        .arg("repo")
        .arg("clone")
        .arg(&repo_name)
        .output()
        .expect("cloning failed");

    if cloning_output.status.success() {
        println!(
            "cloning stdout: {}",
            String::from_utf8_lossy(&cloning_output.stdout)
        );
    } else {
        println!(
            "cloning stderr: {}",
            String::from_utf8_lossy(&cloning_output.stderr)
        );

        process::exit(1);
    }

    println!("cd {folder_name}");

    env::set_current_dir(&folder_name).expect("changing directory failed");

    println!("git rev-list --all --reverse");

    let rev_list_output = Command::new("git")
        .arg("rev-list")
        .arg("--all")
        .arg("--reverse")
        .output()
        .expect("rev-list failed");

    if rev_list_output.status.success() {
        let rev_list_output_cow = String::from_utf8_lossy(&rev_list_output.stdout).to_string();

        for (i, commit_hash) in rev_list_output_cow.split("\n").enumerate() {
            println!("i: {i}");
            if !commit_hash.is_empty() {
                println!("git checkout {commit_hash}");

                let checkout_output = Command::new("git")
                    .arg("checkout")
                    .arg(commit_hash)
                    .output()
                    .expect("checkout failed");

                if checkout_output.status.success() {
                    println!(
                        "checkout stdout: {}",
                        String::from_utf8_lossy(&checkout_output.stdout)
                    );
                } else {
                    println!(
                        "checkout stderr: {}",
                        String::from_utf8_lossy(&checkout_output.stderr)
                    );

                    process::exit(1);
                }

                println!("git show --no-patch --format='%s'");

                let show_s_output = Command::new("git")
                    .arg("show")
                    .arg("--no-patch")
                    .arg("--format='%s'")
                    .output()
                    .expect("show failed");

                if show_s_output.status.success() {
                    println!(
                        "show stdout: {}",
                        String::from_utf8_lossy(&show_s_output.stdout)
                    );
                } else {
                    println!(
                        "show stderr: {}",
                        String::from_utf8_lossy(&show_s_output.stderr)
                    );

                    process::exit(1);
                }

                println!("git show --no-patch --format='%b'");

                let show_b_output = Command::new("git")
                    .arg("show")
                    .arg("--no-patch")
                    .arg("--format='%b'")
                    .output()
                    .expect("show failed");

                if show_b_output.status.success() {
                    println!(
                        "show stdout: {}",
                        String::from_utf8_lossy(&show_b_output.stdout)
                    );
                } else {
                    println!(
                        "show stderr: {}",
                        String::from_utf8_lossy(&show_b_output.stderr)
                    );

                    process::exit(1);
                }

                let mut path = env::current_dir().expect("getting current directory failed");
                path.push(&app_name);
                let metadata = fs::metadata(path);
                match metadata {
                  Ok(_) => {},
                  Err(_) => {
                      continue;
                  }
                };

                println!("cd {app_name}");

                env::set_current_dir(&app_name).expect("changing directory failed");

                println!("cargo test");

                let test_output = Command::new("cargo")
                    .arg("test")
                    .output()
                    .expect("test failed");

                if test_output.status.success() {
                    println!(
                        "test stdout: {}",
                        String::from_utf8_lossy(&test_output.stdout)
                    );
                } else {
                    println!(
                        "test stderr: {}",
                        String::from_utf8_lossy(&test_output.stderr)
                    );

                    process::exit(1);
                }
            }
        }

        println!(
            "rev-list stdout: {}",
            String::from_utf8_lossy(&rev_list_output.stdout)
        );
    } else {
        println!(
            "rev-list stderr: {}",
            String::from_utf8_lossy(&rev_list_output.stderr)
        );

        process::exit(1);
    }
}
<|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::codegen::Bindings::ImageDataBinding;
use crate::dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::JSContext;
use dom_struct::dom_struct;
use euclid::default::{Rect, Size2D};
use ipc_channel::ipc::IpcSharedMemory;
use js::jsapi::{Heap, JSObject};
use js::rust::Runtime;
use js::typedarray::{CreateWith, Uint8ClampedArray};
use std::borrow::Cow;
use std::default::Default;
use std::ptr;
use std::ptr::NonNull;
use std::vec::Vec;

#[dom_struct]
pub struct ImageData {
    reflector_: Reflector,
    width: u32,
    height: u32,
    #[ignore_malloc_size_of = "mozjs"]
    data: Heap<*mut JSObject>,
}

impl ImageData {
    #[allow(unsafe_code)]
    pub fn new(
        global: &GlobalScope,
        width: u32,
        height: u32,
        mut data: Option<Vec<u8>>,
    ) -> Fallible<DomRoot<ImageData>> {
        let len = width * height * 4;
        unsafe {
            let cx = global.get_cx();
            rooted!(in (*cx) let mut js_object = ptr::null_mut::<JSObject>());
            let data = match data {
                Some(ref mut d) => {
                    d.resize(len as usize, 0);
                    CreateWith::Slice(&d[..])
                },
                None => CreateWith::Length(len),
            };
            Uint8ClampedArray::create(*cx, data, js_object.handle_mut()).unwrap();
            Self::new_with_jsobject(global, width, Some(height), Some(js_object.get()))
        }
    }

    #[allow(unsafe_code)]
    unsafe fn new_with_jsobject(
        global: &GlobalScope,
        width: u32,
        mut opt_height: Option<u32>,
        opt_jsobject: Option<*mut JSObject>,
    ) -> Fallible<DomRoot<ImageData>> {
        assert!(opt_jsobject.is_some() || opt_height.is_some());

        if width == 0 {
            return Err(Error::IndexSize);
        }

        // checking jsobject type and verifying (height * width * 4 == jsobject.byte_len())
        if let Some(jsobject) = opt_jsobject {
            let cx = global.get_cx();
            typedarray!(in(*cx) let array_res: Uint8ClampedArray = jsobject);
            let array = array_res.map_err(|_| {
                Error::Type("Argument to Image data is not an Uint8ClampedArray".to_owned())
            })?;

            let byte_len = array.as_slice().len() as u32;
            if byte_len % 4 != 0 || byte_len == 0 {
                return Err(Error::InvalidState);
            }

            let len = byte_len / 4;
            if width == 0 || len % width != 0 {
                return Err(Error::IndexSize);
            }

            let height = len / width;
            if opt_height.map_or(false, |x| height != x) {
                return Err(Error::IndexSize);
            } else {
                opt_height = Some(height);
            }
        }

        let height = opt_height.unwrap();
        if height == 0 {
            return Err(Error::IndexSize);
        }

        let imagedata = Box::new(ImageData {
            reflector_: Reflector::new(),
            width: width,
            height: height,
            data: Heap::default(),
        });

        if let Some(jsobject) = opt_jsobject {
            (*imagedata).data.set(jsobject);
        } else {
            let len = width * height * 4;
            let cx = global.get_cx();
            rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
            Uint8ClampedArray::create(*cx, CreateWith::Length(len), array.handle_mut()).unwrap();
            (*imagedata).data.set(array.get());
        }

        Ok(reflect_dom_object(
            imagedata,
            global,
            ImageDataBinding::Wrap,
        ))
    }

    // https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-3
    #[allow(unsafe_code)]
    pub fn Constructor(global: &GlobalScope, width: u32, height: u32) -> Fallible<DomRoot<Self>> {
        unsafe { Self::new_with_jsobject(global, width, Some(height), None) }
    }

    // https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-4
    #[allow(unsafe_code)]
    #[allow(unused_variables)]
    pub unsafe fn Constructor_(
        cx: JSContext,
        global: &GlobalScope,
        jsobject: *mut JSObject,
        width: u32,
        opt_height: Option<u32>,
    ) -> Fallible<DomRoot<Self>> {
        Self::new_with_jsobject(global, width, opt_height, Some(jsobject))
    }

    /// Nothing must change the array on the JS side while the slice is live.
    #[allow(unsafe_code)]
    pub unsafe fn as_slice(&self) -> &[u8] {
        assert!(!self.data.get().is_null());
        let cx = Runtime::get();
        assert!(!cx.is_null());
        typedarray!(in(cx) let array: Uint8ClampedArray = self.data.get());
        let array = array.as_ref().unwrap();
        // NOTE(nox): This is just as unsafe as `as_slice` itself even though we
        // are extending the lifetime of the slice, because the data in
        // this ImageData instance will never change. The method is thus unsafe
        // because the array may be manipulated from JS while the reference
        // is live.
        let ptr = array.as_slice() as *const _;
        &*ptr
    }

    #[allow(unsafe_code)]
    pub fn to_shared_memory(&self) -> IpcSharedMemory {
        IpcSharedMemory::from_bytes(unsafe { self.as_slice() })
    }

    #[allow(unsafe_code)]
    pub unsafe fn get_rect(&self, rect: Rect<u32>) -> Cow<[u8]> {
        pixels::rgba8_get_rect(self.as_slice(), self.get_size(), rect)
    }

    pub fn get_size(&self) -> Size2D<u32> {
        Size2D::new(self.Width(), self.Height())
    }
}

impl ImageDataMethods for ImageData {
    // https://html.spec.whatwg.org/multipage/#dom-imagedata-width
    fn Width(&self) -> u32 {
        self.width
    }

    // https://html.spec.whatwg.org/multipage/#dom-imagedata-height
    fn Height(&self) -> u32 {
        self.height
    }

    // https://html.spec.whatwg.org/multipage/#dom-imagedata-data
    fn Data(&self, _: JSContext) -> NonNull<JSObject> {
        NonNull::new(self.data.get()).expect("got a null pointer")
    }
}
<commit_msg>Refactor constructor code to align with spec.<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::codegen::Bindings::ImageDataBinding;
use crate::dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::JSContext;
use dom_struct::dom_struct;
use euclid::default::{Rect, Size2D};
use ipc_channel::ipc::IpcSharedMemory;
use js::jsapi::{Heap, JSObject};
use js::rust::Runtime;
use js::typedarray::{CreateWith, Uint8ClampedArray};
use std::borrow::Cow;
use std::default::Default;
use std::ptr;
use std::ptr::NonNull;
use std::vec::Vec;

#[dom_struct]
pub struct ImageData {
    reflector_: Reflector,
    width: u32,
    height: u32,
    #[ignore_malloc_size_of = "mozjs"]
    data: Heap<*mut JSObject>,
}

impl ImageData {
    #[allow(unsafe_code)]
    pub fn new(
        global: &GlobalScope,
        width: u32,
        height: u32,
        mut data: Option<Vec<u8>>,
    ) -> Fallible<DomRoot<ImageData>> {
        let len = width * height * 4;
        unsafe {
            let cx = global.get_cx();
            rooted!(in (*cx) let mut js_object = ptr::null_mut::<JSObject>());
            if let Some(ref mut d) = data {
                d.resize(len as usize, 0);
                let data = CreateWith::Slice(&d[..]);
                Uint8ClampedArray::create(*cx, data, js_object.handle_mut()).unwrap();
                Self::new_with_jsobject(global, width, Some(height), js_object.get())
            } else {
                Self::new_without_jsobject(global, width, height)
            }
        }
    }

    #[allow(unsafe_code)]
    unsafe fn new_with_jsobject(
        global: &GlobalScope,
        width: u32,
        opt_height: Option<u32>,
        jsobject: *mut JSObject,
    ) -> Fallible<DomRoot<ImageData>> {
        // checking jsobject type
        let cx = global.get_cx();
        typedarray!(in(*cx) let array_res: Uint8ClampedArray = jsobject);
        let array = array_res.map_err(|_| {
            Error::Type("Argument to Image data is not an Uint8ClampedArray".to_owned())
        })?;

        let byte_len = array.as_slice().len() as u32;
        if byte_len == 0 || byte_len % 4 != 0 {
            return Err(Error::InvalidState);
        }

        let len = byte_len / 4;
        if width == 0 || len % width != 0 {
            return Err(Error::IndexSize);
        }

        let height = len / width;
        if opt_height.map_or(false, |x| height != x) {
            return Err(Error::IndexSize);
        }

        let imagedata = Box::new(ImageData {
            reflector_: Reflector::new(),
            width: width,
            height: height,
            data: Heap::default(),
        });

        (*imagedata).data.set(jsobject);

        Ok(reflect_dom_object(
            imagedata,
            global,
            ImageDataBinding::Wrap,
        ))
    }

    #[allow(unsafe_code)]
    unsafe fn new_without_jsobject(
        global: &GlobalScope,
        width: u32,
        height: u32,
    ) -> Fallible<DomRoot<ImageData>> {
        if width == 0 || height == 0 {
            return Err(Error::IndexSize);
        }

        let imagedata = Box::new(ImageData {
            reflector_: Reflector::new(),
            width: width,
            height: height,
            data: Heap::default(),
        });

        let len = width * height * 4;
        let cx = global.get_cx();
        rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
        Uint8ClampedArray::create(*cx, CreateWith::Length(len), array.handle_mut()).unwrap();
        (*imagedata).data.set(array.get());

        Ok(reflect_dom_object(
            imagedata,
            global,
            ImageDataBinding::Wrap,
        ))
    }
    // https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-3
    #[allow(unsafe_code)]
    pub fn Constructor(global: &GlobalScope, width: u32, height: u32) -> Fallible<DomRoot<Self>> {
        unsafe { Self::new_without_jsobject(global, width, height) }
    }

    // https://html.spec.whatwg.org/multipage/#pixel-manipulation:dom-imagedata-4
    #[allow(unsafe_code)]
    #[allow(unused_variables)]
    pub unsafe fn Constructor_(
        cx: JSContext,
        global: &GlobalScope,
        jsobject: *mut JSObject,
        width: u32,
        opt_height: Option<u32>,
    ) -> Fallible<DomRoot<Self>> {
        Self::new_with_jsobject(global, width, opt_height, jsobject)
    }

    /// Nothing must change the array on the JS side while the slice is live.
    #[allow(unsafe_code)]
    pub unsafe fn as_slice(&self) -> &[u8] {
        assert!(!self.data.get().is_null());
        let cx = Runtime::get();
        assert!(!cx.is_null());
        typedarray!(in(cx) let array: Uint8ClampedArray = self.data.get());
        let array = array.as_ref().unwrap();
        // NOTE(nox): This is just as unsafe as `as_slice` itself even though we
        // are extending the lifetime of the slice, because the data in
        // this ImageData instance will never change. The method is thus unsafe
        // because the array may be manipulated from JS while the reference
        // is live.
        let ptr = array.as_slice() as *const _;
        &*ptr
    }

    #[allow(unsafe_code)]
    pub fn to_shared_memory(&self) -> IpcSharedMemory {
        IpcSharedMemory::from_bytes(unsafe { self.as_slice() })
    }

    #[allow(unsafe_code)]
    pub unsafe fn get_rect(&self, rect: Rect<u32>) -> Cow<[u8]> {
        pixels::rgba8_get_rect(self.as_slice(), self.get_size(), rect)
    }

    pub fn get_size(&self) -> Size2D<u32> {
        Size2D::new(self.Width(), self.Height())
    }
}

impl ImageDataMethods for ImageData {
    // https://html.spec.whatwg.org/multipage/#dom-imagedata-width
    fn Width(&self) -> u32 {
        self.width
    }

    // https://html.spec.whatwg.org/multipage/#dom-imagedata-height
    fn Height(&self) -> u32 {
        self.height
    }

    // https://html.spec.whatwg.org/multipage/#dom-imagedata-data
    fn Data(&self, _: JSContext) -> NonNull<JSObject> {
        NonNull::new(self.data.get()).expect("got a null pointer")
    }
}
<|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::WebSocketBinding;
use dom::bindings::codegen::Bindings::WebSocketBinding::WebSocketMethods;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::InheritTypes::EventTargetCast;
use dom::bindings::codegen::InheritTypes::EventCast;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::error::Error::InvalidAccess;
use dom::bindings::error::Error::Syntax;
use dom::bindings::global::{GlobalField, GlobalRef};
use dom::bindings::js::{Temporary, JSRef, Rootable};
use dom::bindings::refcounted::Trusted;
use dom::bindings::trace::JSTraceable;
use dom::bindings::utils::reflect_dom_object;
use dom::closeevent::CloseEvent;
use dom::event::{Event, EventBubbles, EventCancelable, EventHelpers};
use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId};
use script_task::Runnable;
use script_task::ScriptMsg;
use std::cell::{Cell, RefCell};
use std::borrow::ToOwned;
use util::str::DOMString;

use websocket::Message;
use websocket::ws::sender::Sender as Sender_Object;
use websocket::client::sender::Sender;
use websocket::client::receiver::Receiver;
use websocket::stream::WebSocketStream;
use websocket::client::request::Url;
use websocket::Client;

#[derive(PartialEq, Copy)]
#[jstraceable]
enum WebSocketRequestState {
    Connecting = 0,
    Open = 1,
    Closing = 2,
    Closed = 3,
}

no_jsmanaged_fields!(Sender<WebSocketStream>);
no_jsmanaged_fields!(Receiver<WebSocketStream>);

#[dom_struct]
pub struct WebSocket {
    eventtarget: EventTarget,
    url: DOMString,
    global: GlobalField,
    ready_state: Cell<WebSocketRequestState>,
    sender: RefCell<Option<Sender<WebSocketStream>>>,
    receiver: RefCell<Option<Receiver<WebSocketStream>>>,
    failed: Cell<bool>, //Flag to tell if websocket was closed due to failure
    full: Cell<bool>, //Flag to tell if websocket queue is full
    clean_close: Cell<bool>, //Flag to tell if the websocket closed cleanly (not due to full or fail)
    code: Cell<u16>, //Closing code
    reason: DOMRefCell<DOMString>, //Closing reason
    data: DOMRefCell<DOMString>, //Data from send - TODO: Remove after buffer is added.
    sendCloseFrame: Cell<bool>
}

impl WebSocket {
    pub fn new_inherited(global: GlobalRef, url: DOMString) -> WebSocket {
        WebSocket {
            eventtarget: EventTarget::new_inherited(EventTargetTypeId::WebSocket),
            url: url,
            global: GlobalField::from_rooted(&global),
            ready_state: Cell::new(WebSocketRequestState::Connecting),
            failed: Cell::new(false),
            sender: RefCell::new(None),
            receiver: RefCell::new(None),
            full: Cell::new(false),
            clean_close: Cell::new(true),
            code: Cell::new(0),
            reason: DOMRefCell::new("".to_owned()),
            data: DOMRefCell::new("".to_owned()),
            sendCloseFrame: Cell::new(false)
        }

    }

    pub fn new(global: GlobalRef, url: DOMString) -> Temporary<WebSocket> {
        /*TODO: This constructor is only a prototype, it does not accomplish the specs
          defined here:
          http://html.spec.whatwg.org
          All 9 items must be satisfied.
          TODO: This constructor should be responsible for spawning a thread for the
          receive loop after ws_root.r().Open() - See comment
        */
        let ws_root = reflect_dom_object(box WebSocket::new_inherited(global, url),
                          global,
                          WebSocketBinding::Wrap).root();
        let ws_root = ws_root.r();
        let parsed_url = Url::parse(ws_root.url.as_slice()).unwrap(); //Get url
        let request = Client::connect(parsed_url).unwrap(); //Set up the request
        let response = request.send().unwrap(); //Send the request to the server
        response.validate().unwrap(); //Validate the server's response
        ws_root.ready_state.set(WebSocketRequestState::Open); //Set state to open
        //Check to see if ready_state is Closing or Closed and failed = true - means we failed the websocket
        //if so return without setting any states
        let ready_state = ws_root.ready_state.get();
        let failed = ws_root.failed.get();
        if failed && (ready_state == WebSocketRequestState::Closed || ready_state == WebSocketRequestState::Closing) {
            //Do nothing else. Let the close finish.
            return Temporary::from_rooted(ws_root);
        }
        let (temp_sender, temp_receiver) = response.begin().split(); //Set send and receiver in the attributes
        let mut other_sender = ws_root.sender.borrow_mut();
        let mut other_receiver = ws_root.receiver.borrow_mut();
        *other_sender = Some(temp_sender);
        *other_receiver = Some(temp_receiver);

        //Create everything necessary for starting the open asynchronous task, then begin the task.
        let global_root = ws_root.global.root();
        let addr: Trusted<WebSocket> = Trusted::new(global_root.r().get_cx(), ws_root, global_root.r().script_chan().clone());
        let open_task = box WebSocketTaskHandler::new(addr.clone(), WebSocketTask::Open);
        global_root.r().script_chan().send(ScriptMsg::RunnableMsg(open_task)).unwrap();
        //TODO: Spawn thread here for receive loop
        /*TODO: Add receive loop here and make new thread run this
          Receive is an infinite loop "similiar" the one shown here:
          https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64
          TODO: The receive loop however does need to follow the spec. These are outlined here
          under "WebSocket message has been received" items 1-5:
          https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64
          TODO: The receive loop also needs to dispatch an asynchronous event as stated here:
          https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64
          TODO: When the receive loop receives a close message from the server,
          it confirms the websocket is now closed. This requires the close event
          to be fired (dispatch_close fires the close event - see implementation below)
        */
        Temporary::from_rooted(ws_root)
    }

    pub fn Constructor(global: GlobalRef, url: DOMString) -> Fallible<Temporary<WebSocket>> {
        Ok(WebSocket::new(global, url))
    }
}

impl<'a> WebSocketMethods for JSRef<'a, WebSocket> {
    event_handler!(open, GetOnopen, SetOnopen);
    event_handler!(close, GetOnclose, SetOnclose);
    event_handler!(error, GetOnerror, SetOnerror);

    fn Url(self) -> DOMString {
        self.url.clone()
    }

    fn ReadyState(self) -> u16 {
        self.ready_state.get() as u16
    }

    fn Send(self, data: Option<DOMString>)-> Fallible<()>{
        /*TODO: This is not up to spec see http://html.spec.whatwg.org/multipage/comms.html search for "If argument is a string"
          TODO: Need to buffer data
          TODO: bufferedAmount attribute returns the size of the buffer in bytes - this is a required attribute defined in the websocket.webidle file
          TODO: The send function needs to flag when full by using the following
          self.full.set(true). This needs to be done when the buffer is full
        */
        let mut other_sender = self.sender.borrow_mut();
        let my_sender = other_sender.as_mut().unwrap();
        if self.sendCloseFrame.get() { //TODO: Also check if the buffer is full
            self.sendCloseFrame.set(false); //Deflag the close frame request
            let _ = my_sender.send_message(Message::Close(None)); //Send close frame
            return Ok(());
        }
        let _ = my_sender.send_message(Message::Text(data.unwrap()));
        return Ok(())
    }

    fn Close(self, code: Option<u16>, reason: Option<DOMString>) -> Fallible<()>{
        if code.is_some() { //Code defined
            //Check code is NOT 1000 NOR in the range of 3000-4999 (inclusive)
            if  code != Some(1000) && (code < Some(3000) || code > Some(4999))
            {
                return Err(Error::InvalidAccess); //Throw InvalidAccessError and abort
            }
        }
        if reason.is_some() { //reason defined
            let a_reason = reason.as_ref().unwrap();
            if a_reason.as_bytes().len() > 123 //reason cannot be larger than 123 bytes
            {
                return Err(Error::Syntax); //Throw SyntaxError and abort
            }
        }

        match self.ready_state.get() {
            WebSocketRequestState::Closing | WebSocketRequestState::Closed  => {} //Do nothing
            WebSocketRequestState::Connecting => { //Connection is not yet established
                /*By setting the state to closing, the open function
                  will abort connecting the websocket*/
                self.ready_state.set(WebSocketRequestState::Closing);
                self.failed.set(true);
                self.sendCloseFrame.set(true);
                //Dispatch send task to send close frame
                //TODO: Sending here is just empty string, though no string is really needed. Another send, empty send, could be used.
                let _ = self.Send(None);
                //Note: After sending the close message, the receive loop confirms a close message from the server and must fire a close event
            }
            WebSocketRequestState::Open => {
                //Closing handshake not started - still in open
                //Start the closing by setting the code and reason if they exist
                if code.is_some() {
                    self.code.set(code.unwrap());
                }
                if reason.is_some() {
                    *self.reason.borrow_mut() = reason.unwrap();
                }
                self.ready_state.set(WebSocketRequestState::Closing);
                self.sendCloseFrame.set(true);
                //Dispatch send task to send close frame
                let _ = self.Send(None);
                //Note: After sending the close message, the receive loop confirms a close message from the server and must fire a close event
            }
        }
        Ok(()) //Return Ok
    }
}

pub enum WebSocketTask {
    Open,
    Close,
}

pub struct WebSocketTaskHandler {
    addr: Trusted<WebSocket>,
    task: WebSocketTask,
}

impl WebSocketTaskHandler {
    pub fn new(addr: Trusted<WebSocket>, task: WebSocketTask) -> WebSocketTaskHandler {
        WebSocketTaskHandler {
            addr: addr,
            task: task,
        }
    }

    fn dispatch_open(&self) {
        /*TODO: Items 1, 3, 4, & 5 under "WebSocket connection is established" as specified here:
          https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol
        */
        let ws = self.addr.to_temporary().root(); //Get root
        let ws = ws.r(); //Get websocket reference
        let global = ws.global.root();
        let event = Event::new(global.r(),
            "open".to_owned(),
            EventBubbles::DoesNotBubble,
            EventCancelable::Cancelable).root();
        let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws);
        event.r().fire(target);
    }

    fn dispatch_close(&self) {
        let ws = self.addr.to_temporary().root();
        let ws = ws.r();
        let global = ws.global.root();
        ws.ready_state.set(WebSocketRequestState::Closed);
        //If failed or full, fire error event
        if ws.failed.get() || ws.full.get() {
            ws.failed.set(false);
            ws.full.set(false);
            //A Bad close
            ws.clean_close.set(false);
            let event = Event::new(global.r(),
                                   "error".to_owned(),
                                   EventBubbles::DoesNotBubble,
                                   EventCancelable::Cancelable).root();
            let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws);
            event.r().fire(target);
        }
        let rsn = ws.reason.borrow();
        let rsn_clone = rsn.clone();
        /*In addition, we also have to fire a close even if error event fired
         https://html.spec.whatwg.org/multipage/#closeWebSocket
        */
        let close_event = CloseEvent::new(global.r(),
                                          "close".to_owned(),
                                          EventBubbles::DoesNotBubble,
                                          EventCancelable::Cancelable,
                                          ws.clean_close.get(),
                                          ws.code.get(),
                                          rsn_clone).root();
        let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws);
        let event: JSRef<Event> = EventCast::from_ref(close_event.r());
        event.fire(target);
    }
}

impl Runnable for WebSocketTaskHandler {
    fn handler(self: Box<WebSocketTaskHandler>) {
        match self.task {
            WebSocketTask::Open => {
                self.dispatch_open();
            }
            WebSocketTask::Close => {
                self.dispatch_close();
            }
        }
    }
}

<commit_msg>All review comments resolved.<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::WebSocketBinding;
use dom::bindings::codegen::Bindings::WebSocketBinding::WebSocketMethods;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::InheritTypes::EventTargetCast;
use dom::bindings::codegen::InheritTypes::EventCast;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::error::Error::InvalidAccess;
use dom::bindings::error::Error::Syntax;
use dom::bindings::global::{GlobalField, GlobalRef};
use dom::bindings::js::{Temporary, JSRef, Rootable};
use dom::bindings::refcounted::Trusted;
use dom::bindings::trace::JSTraceable;
use dom::bindings::utils::reflect_dom_object;
use dom::closeevent::CloseEvent;
use dom::event::{Event, EventBubbles, EventCancelable, EventHelpers};
use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId};
use script_task::Runnable;
use script_task::ScriptMsg;
use std::cell::{Cell, RefCell};
use std::borrow::ToOwned;
use util::str::DOMString;

use websocket::Message;
use websocket::ws::sender::Sender as Sender_Object;
use websocket::client::sender::Sender;
use websocket::client::receiver::Receiver;
use websocket::stream::WebSocketStream;
use websocket::client::request::Url;
use websocket::Client;

#[derive(PartialEq, Copy)]
#[jstraceable]
enum WebSocketRequestState {
    Connecting = 0,
    Open = 1,
    Closing = 2,
    Closed = 3,
}

no_jsmanaged_fields!(Sender<WebSocketStream>);
no_jsmanaged_fields!(Receiver<WebSocketStream>);

#[dom_struct]
pub struct WebSocket {
    eventtarget: EventTarget,
    url: DOMString,
    global: GlobalField,
    ready_state: Cell<WebSocketRequestState>,
    sender: RefCell<Option<Sender<WebSocketStream>>>,
    receiver: RefCell<Option<Receiver<WebSocketStream>>>,
    failed: Cell<bool>, //Flag to tell if websocket was closed due to failure
    full: Cell<bool>, //Flag to tell if websocket queue is full
    clean_close: Cell<bool>, //Flag to tell if the websocket closed cleanly (not due to full or fail)
    code: Cell<u16>, //Closing code
    reason: DOMRefCell<DOMString>, //Closing reason
    data: DOMRefCell<DOMString>, //Data from send - TODO: Remove after buffer is added.
    sendCloseFrame: Cell<bool>
}

impl WebSocket {
    pub fn new_inherited(global: GlobalRef, url: DOMString) -> WebSocket {
        WebSocket {
            eventtarget: EventTarget::new_inherited(EventTargetTypeId::WebSocket),
            url: url,
            global: GlobalField::from_rooted(&global),
            ready_state: Cell::new(WebSocketRequestState::Connecting),
            failed: Cell::new(false),
            sender: RefCell::new(None),
            receiver: RefCell::new(None),
            full: Cell::new(false),
            clean_close: Cell::new(true),
            code: Cell::new(0),
            reason: DOMRefCell::new("".to_owned()),
            data: DOMRefCell::new("".to_owned()),
            sendCloseFrame: Cell::new(false)
        }

    }

    pub fn new(global: GlobalRef, url: DOMString) -> Temporary<WebSocket> {
        /*TODO: This constructor is only a prototype, it does not accomplish the specs
          defined here:
          http://html.spec.whatwg.org
          All 9 items must be satisfied.
          TODO: This constructor should be responsible for spawning a thread for the
          receive loop after ws_root.r().Open() - See comment
        */
        let ws_root = reflect_dom_object(box WebSocket::new_inherited(global, url),
                          global,
                          WebSocketBinding::Wrap).root();
        let ws_root = ws_root.r();
        let parsed_url = Url::parse(ws_root.url.as_slice()).unwrap(); //Get url
        let request = Client::connect(parsed_url).unwrap(); //Set up the request
        let response = request.send().unwrap(); //Send the request to the server
        response.validate().unwrap(); //Validate the server's response
        ws_root.ready_state.set(WebSocketRequestState::Open); //Set state to open
        //Check to see if ready_state is Closing or Closed and failed = true - means we failed the websocket
        //if so return without setting any states
        let ready_state = ws_root.ready_state.get();
        let failed = ws_root.failed.get();
        if failed && (ready_state == WebSocketRequestState::Closed || ready_state == WebSocketRequestState::Closing) {
            //Do nothing else. Let the close finish.
            return Temporary::from_rooted(ws_root);
        }
        let (temp_sender, temp_receiver) = response.begin().split(); //Set send and receiver in the attributes
        let mut other_sender = ws_root.sender.borrow_mut();
        let mut other_receiver = ws_root.receiver.borrow_mut();
        *other_sender = Some(temp_sender);
        *other_receiver = Some(temp_receiver);

        //Create everything necessary for starting the open asynchronous task, then begin the task.
        let global_root = ws_root.global.root();
        let addr: Trusted<WebSocket> = Trusted::new(global_root.r().get_cx(), ws_root, global_root.r().script_chan().clone());
        let open_task = box WebSocketTaskHandler::new(addr.clone(), WebSocketTask::Open);
        global_root.r().script_chan().send(ScriptMsg::RunnableMsg(open_task)).unwrap();
        //TODO: Spawn thread here for receive loop
        /*TODO: Add receive loop here and make new thread run this
          Receive is an infinite loop "similiar" the one shown here:
          https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64
          TODO: The receive loop however does need to follow the spec. These are outlined here
          under "WebSocket message has been received" items 1-5:
          https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64
          TODO: The receive loop also needs to dispatch an asynchronous event as stated here:
          https://github.com/cyderize/rust-websocket/blob/master/examples/client.rs#L64
          TODO: When the receive loop receives a close message from the server,
          it confirms the websocket is now closed. This requires the close event
          to be fired (dispatch_close fires the close event - see implementation below)
        */
        Temporary::from_rooted(ws_root)
    }

    pub fn Constructor(global: GlobalRef, url: DOMString) -> Fallible<Temporary<WebSocket>> {
        Ok(WebSocket::new(global, url))
    }
}

impl<'a> WebSocketMethods for JSRef<'a, WebSocket> {
    event_handler!(open, GetOnopen, SetOnopen);
    event_handler!(close, GetOnclose, SetOnclose);
    event_handler!(error, GetOnerror, SetOnerror);

    fn Url(self) -> DOMString {
        self.url.clone()
    }

    fn ReadyState(self) -> u16 {
        self.ready_state.get() as u16
    }

    fn Send(self, data: Option<DOMString>)-> Fallible<()>{
        /*TODO: This is not up to spec see http://html.spec.whatwg.org/multipage/comms.html search for "If argument is a string"
          TODO: Need to buffer data
          TODO: bufferedAmount attribute returns the size of the buffer in bytes - this is a required attribute defined in the websocket.webidle file
          TODO: The send function needs to flag when full by using the following
          self.full.set(true). This needs to be done when the buffer is full
        */
        let mut other_sender = self.sender.borrow_mut();
        let my_sender = other_sender.as_mut().unwrap();
        if self.sendCloseFrame.get() { //TODO: Also check if the buffer is full
            self.sendCloseFrame.set(false); //Deflag the close frame request
            let _ = my_sender.send_message(Message::Close(None)); //Send close frame
            return Ok(());
        }
        let _ = my_sender.send_message(Message::Text(data.unwrap()));
        return Ok(())
    }

    fn Close(self, code: Option<u16>, reason: Option<DOMString>) -> Fallible<()>{
        if let Some(code) = code{ //Code defined
            //Check code is NOT 1000 NOR in the range of 3000-4999 (inclusive)
            if  code != 1000 && (code < 3000 || code > 4999)
            {
                return Err(Error::InvalidAccess); //Throw InvalidAccessError and abort
            }
        }
        if let Some(ref reason) = reason { //reason defined
            if reason.as_bytes().len() > 123 //reason cannot be larger than 123 bytes
            {
                return Err(Error::Syntax); //Throw SyntaxError and abort
            }
        }

        match self.ready_state.get() {
            WebSocketRequestState::Closing | WebSocketRequestState::Closed  => {} //Do nothing
            WebSocketRequestState::Connecting => { //Connection is not yet established
                /*By setting the state to closing, the open function
                  will abort connecting the websocket*/
                self.ready_state.set(WebSocketRequestState::Closing);
                self.failed.set(true);
                self.sendCloseFrame.set(true);
                //Dispatch send task to send close frame
                //TODO: Sending here is just empty string, though no string is really needed. Another send, empty send, could be used.
                let _ = self.Send(None);
                //Note: After sending the close message, the receive loop confirms a close message from the server and must fire a close event
            }
            WebSocketRequestState::Open => {
                //Closing handshake not started - still in open
                //Start the closing by setting the code and reason if they exist
                if let Some(code)= code {
                    self.code.set(code);
                }
                if let Some(reason) = reason {
                    *self.reason.borrow_mut() = reason;
                }
                self.ready_state.set(WebSocketRequestState::Closing);
                self.sendCloseFrame.set(true);
                //Dispatch send task to send close frame
                let _ = self.Send(None);
                //Note: After sending the close message, the receive loop confirms a close message from the server and must fire a close event
            }
        }
        Ok(()) //Return Ok
    }
}

pub enum WebSocketTask {
    Open,
    Close,
}

pub struct WebSocketTaskHandler {
    addr: Trusted<WebSocket>,
    task: WebSocketTask,
}

impl WebSocketTaskHandler {
    pub fn new(addr: Trusted<WebSocket>, task: WebSocketTask) -> WebSocketTaskHandler {
        WebSocketTaskHandler {
            addr: addr,
            task: task,
        }
    }

    fn dispatch_open(&self) {
        /*TODO: Items 1, 3, 4, & 5 under "WebSocket connection is established" as specified here:
          https://html.spec.whatwg.org/multipage/#feedback-from-the-protocol
        */
        let ws = self.addr.to_temporary().root(); //Get root
        let ws = ws.r(); //Get websocket reference
        let global = ws.global.root();
        let event = Event::new(global.r(),
            "open".to_owned(),
            EventBubbles::DoesNotBubble,
            EventCancelable::Cancelable).root();
        let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws);
        event.r().fire(target);
    }

    fn dispatch_close(&self) {
        let ws = self.addr.to_temporary().root();
        let ws = ws.r();
        let global = ws.global.root();
        ws.ready_state.set(WebSocketRequestState::Closed);
        //If failed or full, fire error event
        if ws.failed.get() || ws.full.get() {
            ws.failed.set(false);
            ws.full.set(false);
            //A Bad close
            ws.clean_close.set(false);
            let event = Event::new(global.r(),
                                   "error".to_owned(),
                                   EventBubbles::DoesNotBubble,
                                   EventCancelable::Cancelable).root();
            let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws);
            event.r().fire(target);
        }
        let rsn = ws.reason.borrow();
        let rsn_clone = rsn.clone();
        /*In addition, we also have to fire a close even if error event fired
         https://html.spec.whatwg.org/multipage/#closeWebSocket
        */
        let close_event = CloseEvent::new(global.r(),
                                          "close".to_owned(),
                                          EventBubbles::DoesNotBubble,
                                          EventCancelable::Cancelable,
                                          ws.clean_close.get(),
                                          ws.code.get(),
                                          rsn_clone).root();
        let target: JSRef<EventTarget> = EventTargetCast::from_ref(ws);
        let event: JSRef<Event> = EventCast::from_ref(close_event.r());
        event.fire(target);
    }
}

impl Runnable for WebSocketTaskHandler {
    fn handler(self: Box<WebSocketTaskHandler>) {
        match self.task {
            WebSocketTask::Open => {
                self.dispatch_open();
            }
            WebSocketTask::Close => {
                self.dispatch_close();
            }
        }
    }
}

<|endoftext|>