<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::callback::ExceptionHandling;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::MutationObserverBinding;
use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationCallback;
use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverBinding::MutationObserverMethods;
use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverInit;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::mutationrecord::MutationRecord;
use dom::node::Node;
use dom::window::Window;
use dom_struct::dom_struct;
use html5ever::{Namespace, LocalName};
use microtask::Microtask;
use script_thread::ScriptThread;
use std::rc::Rc;

#[dom_struct]
pub struct MutationObserver {
    reflector_: Reflector,
    #[ignore_heap_size_of = "can't measure Rc values"]
    callback: Rc<MutationCallback>,
    record_queue: DOMRefCell<Vec<Root<MutationRecord>>>,
}

#[derive(Clone)]
pub enum Mutation {
    Attribute { name: LocalName, namespace: Namespace, old_value: DOMString }
}

#[derive(HeapSizeOf, JSTraceable)]
pub struct RegisteredObserver {
    observer: Root<MutationObserver>,
    options: ObserverOptions,
}

#[derive(HeapSizeOf, JSTraceable)]
pub struct ObserverOptions {
    attribute_old_value: bool,
    attributes: bool,
    character_data: bool,
    character_data_old_value: bool,
    child_list: bool,
    subtree: bool,
    attribute_filter: Vec<DOMString>,
}

impl MutationObserver {
    fn new(global: &Window, callback: Rc<MutationCallback>) -> Root<MutationObserver> {
        let boxed_observer = box MutationObserver::new_inherited(callback);
        reflect_dom_object(boxed_observer, global, MutationObserverBinding::Wrap)
    }

    fn new_inherited(callback: Rc<MutationCallback>) -> MutationObserver {
        MutationObserver {
            reflector_: Reflector::new(),
            callback: callback,
            record_queue: DOMRefCell::new(vec![]),
        }
    }

    pub fn Constructor(global: &Window, callback: Rc<MutationCallback>) -> Fallible<Root<MutationObserver>> {
        let observer = MutationObserver::new(global, callback);
        ScriptThread::add_mutation_observer(&*observer);
        Ok(observer)
    }

    /// https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask
    pub fn queue_mutation_observer_compound_microtask() {
        // Step 1
        if ScriptThread::is_mutation_observer_compound_microtask_queued() {
            return;
        }
        // Step 2
        ScriptThread::set_mutation_observer_compound_microtask_queued(true);
        // Step 3
        ScriptThread::enqueue_microtask(Microtask::NotifyMutationObservers);
    }

    /// https://dom.spec.whatwg.org/#notify-mutation-observers
    pub fn notify_mutation_observers() {
        // Step 1
        ScriptThread::set_mutation_observer_compound_microtask_queued(false);
        // Step 2
        let notify_list = ScriptThread::get_mutation_observers();
        // TODO: steps 3-4 (slots)
        // Step 5
        for mo in &notify_list {
            let queue: Vec<Root<MutationRecord>> = mo.record_queue.borrow().clone();
            mo.record_queue.borrow_mut().clear();
            // TODO: Step 5.3 Remove all transient registered observers whose observer is mo.
            if !queue.is_empty() {
                let _ = mo.callback.Call_(&**mo, queue, &**mo, ExceptionHandling::Report);
            }
        }
        // TODO: Step 6 (slot signals)
    }

    /// https://dom.spec.whatwg.org/#queueing-a-mutation-record
    pub fn queue_a_mutation_record(target: &Node, attr_type: Mutation) {
        // Step 1
        let mut interestedObservers: Vec<(Root<MutationObserver>, Option<DOMString>)> = vec![];
        // Step 2 & 3
        for node in target.inclusive_ancestors() {
            for registered in &*node.registered_mutation_observers() {
                if &*node != target && !registered.options.subtree {
                    continue;
                }

                match attr_type {
                    Mutation::Attribute { ref name, ref namespace, ref old_value } => {
                        // Step 3.1
                        if !registered.options.attributes {
                            continue;
                        }
                        if !registered.options.attribute_filter.is_empty() {
                            if *namespace != ns!() {
                                continue;
                            }
                            if registered.options.attribute_filter.iter()
                                .find(|s| &**s == &**name).is_some() {
                                continue;
                            }
                        }
                        // Step 3.1.2
                        let paired_string = if registered.options.attribute_old_value {
                            Some(old_value.clone())
                        } else {
                            None
                        };
                        // Step 3.1.1
                        let idx = interestedObservers.iter().position(|&(ref o, _)|
                            &**o as *const _ == &*registered.observer as *const _);
                        if let Some(idx) = idx {
                            interestedObservers[idx].1 = paired_string;
                        } else {
                            interestedObservers.push((Root::from_ref(&*registered.observer),
                                                      paired_string));
                        }
                    }
                }
            }
        }

        // Step 4
        for &(ref observer, ref paired_string) in &interestedObservers {
            // Steps 4.1-4.7
            let record = match attr_type {
                Mutation::Attribute { ref name, ref namespace, .. } => {
                    let namespace = if *namespace != ns!() {
                        Some(namespace)
                    } else {
                        None
                    };
                    MutationRecord::attribute_mutated(target, name, namespace, paired_string.clone())
                }
            };
            // Step 4.8
            observer.record_queue.borrow_mut().push(record);
        }

        // Step 5
        MutationObserver::queue_mutation_observer_compound_microtask();
    }

}

impl MutationObserverMethods for MutationObserver {
    /// https://dom.spec.whatwg.org/#dom-mutationobserver-observe
    fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> {
        let attribute_filter = options.attributeFilter.clone().unwrap_or(vec![]);
        let attribute_old_value = options.attributeOldValue.unwrap_or(false);
        let mut attributes = options.attributes.unwrap_or(false);
        let mut character_data = options.characterData.unwrap_or(false);
        let character_data_old_value = options.characterDataOldValue.unwrap_or(false);
        let child_list = options.childList;
        let subtree = options.subtree;

        // Step 1
        if (options.attributeOldValue.is_some() || options.attributeFilter.is_some()) &&
            options.attributes.is_none() {
            attributes = true;
        }

        // Step 2
        if options.characterDataOldValue.is_some() && options.characterData.is_none() {
            character_data = true;
        }

        // Step 3
        if !child_list && !attributes && !character_data {
            return Err(Error::Type("One of childList, attributes, or characterData must be true".into()));
        }

        // Step 4
        if attribute_old_value && !attributes {
            return Err(Error::Type("attributeOldValue is true but attributes is false".into()));
        }

        // Step 5
        if options.attributeFilter.is_some() && !attributes {
            return Err(Error::Type("attributeFilter is present but attributes is false".into()));
        }

        // Step 6
        if character_data_old_value && !character_data {
            return Err(Error::Type("characterDataOldValue is true but characterData is false".into()));
        }

        // Step 7
        let add_new_observer = {
            let mut replaced = false;
            for registered in &mut *target.registered_mutation_observers() {
                if &*registered.observer as *const MutationObserver != self as *const MutationObserver {
                    continue;
                }
                // TODO: remove matching transient registered observers
                registered.options.attribute_old_value = attribute_old_value;
                registered.options.attributes = attributes;
                registered.options.character_data = character_data;
                registered.options.character_data_old_value = character_data_old_value;
                registered.options.child_list = child_list;
                registered.options.subtree = subtree;
                registered.options.attribute_filter = attribute_filter.clone();
                replaced = true;
            }
            !replaced
        };

        // Step 8
        if add_new_observer {
            target.registered_mutation_observers().push(RegisteredObserver {
                observer: Root::from_ref(self),
                options: ObserverOptions {
                    attributes,
                    attribute_old_value,
                    character_data,
                    character_data_old_value,
                    subtree,
                    attribute_filter,
                    child_list
                },
            });
        }

        Ok(())
    }
}
<commit_msg>Silence a warning in script::dom::mutationobserver<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::callback::ExceptionHandling;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::MutationObserverBinding;
use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationCallback;
use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverBinding::MutationObserverMethods;
use dom::bindings::codegen::Bindings::MutationObserverBinding::MutationObserverInit;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::js::Root;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::mutationrecord::MutationRecord;
use dom::node::Node;
use dom::window::Window;
use dom_struct::dom_struct;
use html5ever::{Namespace, LocalName};
use microtask::Microtask;
use script_thread::ScriptThread;
use std::rc::Rc;

#[dom_struct]
pub struct MutationObserver {
    reflector_: Reflector,
    #[ignore_heap_size_of = "can't measure Rc values"]
    callback: Rc<MutationCallback>,
    record_queue: DOMRefCell<Vec<Root<MutationRecord>>>,
}

#[derive(Clone)]
pub enum Mutation {
    Attribute { name: LocalName, namespace: Namespace, old_value: DOMString }
}

#[derive(HeapSizeOf, JSTraceable)]
pub struct RegisteredObserver {
    observer: Root<MutationObserver>,
    options: ObserverOptions,
}

#[derive(HeapSizeOf, JSTraceable)]
pub struct ObserverOptions {
    attribute_old_value: bool,
    attributes: bool,
    character_data: bool,
    character_data_old_value: bool,
    child_list: bool,
    subtree: bool,
    attribute_filter: Vec<DOMString>,
}

impl MutationObserver {
    fn new(global: &Window, callback: Rc<MutationCallback>) -> Root<MutationObserver> {
        let boxed_observer = box MutationObserver::new_inherited(callback);
        reflect_dom_object(boxed_observer, global, MutationObserverBinding::Wrap)
    }

    fn new_inherited(callback: Rc<MutationCallback>) -> MutationObserver {
        MutationObserver {
            reflector_: Reflector::new(),
            callback: callback,
            record_queue: DOMRefCell::new(vec![]),
        }
    }

    pub fn Constructor(global: &Window, callback: Rc<MutationCallback>) -> Fallible<Root<MutationObserver>> {
        let observer = MutationObserver::new(global, callback);
        ScriptThread::add_mutation_observer(&*observer);
        Ok(observer)
    }

    /// https://dom.spec.whatwg.org/#queue-a-mutation-observer-compound-microtask
    pub fn queue_mutation_observer_compound_microtask() {
        // Step 1
        if ScriptThread::is_mutation_observer_compound_microtask_queued() {
            return;
        }
        // Step 2
        ScriptThread::set_mutation_observer_compound_microtask_queued(true);
        // Step 3
        ScriptThread::enqueue_microtask(Microtask::NotifyMutationObservers);
    }

    /// https://dom.spec.whatwg.org/#notify-mutation-observers
    pub fn notify_mutation_observers() {
        // Step 1
        ScriptThread::set_mutation_observer_compound_microtask_queued(false);
        // Step 2
        let notify_list = ScriptThread::get_mutation_observers();
        // TODO: steps 3-4 (slots)
        // Step 5
        for mo in &notify_list {
            let queue: Vec<Root<MutationRecord>> = mo.record_queue.borrow().clone();
            mo.record_queue.borrow_mut().clear();
            // TODO: Step 5.3 Remove all transient registered observers whose observer is mo.
            if !queue.is_empty() {
                let _ = mo.callback.Call_(&**mo, queue, &**mo, ExceptionHandling::Report);
            }
        }
        // TODO: Step 6 (slot signals)
    }

    /// https://dom.spec.whatwg.org/#queueing-a-mutation-record
    pub fn queue_a_mutation_record(target: &Node, attr_type: Mutation) {
        // Step 1
        let mut interestedObservers: Vec<(Root<MutationObserver>, Option<DOMString>)> = vec![];
        // Step 2 & 3
        for node in target.inclusive_ancestors() {
            for registered in &*node.registered_mutation_observers() {
                if &*node != target && !registered.options.subtree {
                    continue;
                }

                match attr_type {
                    Mutation::Attribute { ref name, ref namespace, ref old_value } => {
                        // Step 3.1
                        if !registered.options.attributes {
                            continue;
                        }
                        if !registered.options.attribute_filter.is_empty() {
                            if *namespace != ns!() {
                                continue;
                            }
                            if registered.options.attribute_filter.iter()
                                .find(|s| &**s == &**name).is_some() {
                                continue;
                            }
                        }
                        // Step 3.1.2
                        let paired_string = if registered.options.attribute_old_value {
                            Some(old_value.clone())
                        } else {
                            None
                        };
                        // Step 3.1.1
                        let idx = interestedObservers.iter().position(|&(ref o, _)|
                            &**o as *const _ == &*registered.observer as *const _);
                        if let Some(idx) = idx {
                            interestedObservers[idx].1 = paired_string;
                        } else {
                            interestedObservers.push((Root::from_ref(&*registered.observer),
                                                      paired_string));
                        }
                    }
                }
            }
        }

        // Step 4
        for &(ref observer, ref paired_string) in &interestedObservers {
            // Steps 4.1-4.7
            let record = match attr_type {
                Mutation::Attribute { ref name, ref namespace, .. } => {
                    let namespace = if *namespace != ns!() {
                        Some(namespace)
                    } else {
                        None
                    };
                    MutationRecord::attribute_mutated(target, name, namespace, paired_string.clone())
                }
            };
            // Step 4.8
            observer.record_queue.borrow_mut().push(record);
        }

        // Step 5
        MutationObserver::queue_mutation_observer_compound_microtask();
    }

}

impl MutationObserverMethods for MutationObserver {
    /// https://dom.spec.whatwg.org/#dom-mutationobserver-observe
    fn Observe(&self, target: &Node, options: &MutationObserverInit) -> Fallible<()> {
        let attribute_filter = options.attributeFilter.clone().unwrap_or(vec![]);
        let attribute_old_value = options.attributeOldValue.unwrap_or(false);
        let mut attributes = options.attributes.unwrap_or(false);
        let mut character_data = options.characterData.unwrap_or(false);
        let character_data_old_value = options.characterDataOldValue.unwrap_or(false);
        let child_list = options.childList;
        let subtree = options.subtree;

        // Step 1
        if (options.attributeOldValue.is_some() || options.attributeFilter.is_some()) &&
            options.attributes.is_none() {
            attributes = true;
        }

        // Step 2
        if options.characterDataOldValue.is_some() && options.characterData.is_none() {
            character_data = true;
        }

        // Step 3
        if !child_list && !attributes && !character_data {
            return Err(Error::Type("One of childList, attributes, or characterData must be true".into()));
        }

        // Step 4
        if attribute_old_value && !attributes {
            return Err(Error::Type("attributeOldValue is true but attributes is false".into()));
        }

        // Step 5
        if options.attributeFilter.is_some() && !attributes {
            return Err(Error::Type("attributeFilter is present but attributes is false".into()));
        }

        // Step 6
        if character_data_old_value && !character_data {
            return Err(Error::Type("characterDataOldValue is true but characterData is false".into()));
        }

        // Step 7
        let add_new_observer = {
            let mut replaced = false;
            for registered in &mut *target.registered_mutation_observers() {
                if &*registered.observer as *const MutationObserver != self as *const MutationObserver {
                    continue;
                }
                // TODO: remove matching transient registered observers
                registered.options.attribute_old_value = attribute_old_value;
                registered.options.attributes = attributes;
                registered.options.character_data = character_data;
                registered.options.character_data_old_value = character_data_old_value;
                registered.options.child_list = child_list;
                registered.options.subtree = subtree;
                registered.options.attribute_filter = attribute_filter.clone();
                replaced = true;
            }
            !replaced
        };

        // Step 8
        if add_new_observer {
            target.registered_mutation_observers().push(RegisteredObserver {
                observer: Root::from_ref(self),
                options: ObserverOptions {
                    attributes,
                    attribute_old_value,
                    character_data,
                    character_data_old_value,
                    subtree,
                    attribute_filter,
                    child_list
                },
            });
        }

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


<commit_before>use pipeline_matcher::PathMatch;
use std::collections::HashMap;
use std::sync::mpsc::{Receiver, Sender};
use time;
use util::PipelineInfo;

// ---------------------------------------------------------------------------------------------------------------------
// PipelineQueue
// ---------------------------------------------------------------------------------------------------------------------

pub trait PipelineQueue {
    fn exec( &mut self, rx: Receiver<PipelineInfo<PathMatch>>, tx: Sender<PipelineInfo<PathMatch>> );
}

// ---------------------------------------------------------------------------------------------------------------------
// SimplePipelineQueue
// ---------------------------------------------------------------------------------------------------------------------

pub struct SimplePipelineQueue {
    pub infos : Vec<String>,
    pub errors: Vec<String>,
    map       : HashMap<usize, PathMatch>,
    current_id: usize,
    time_beg  : u64,
    time_end  : u64,
    time_bsy  : u64,
}

impl SimplePipelineQueue {
    pub fn new() -> Self {
        SimplePipelineQueue {
            infos     : Vec::new(),
            errors    : Vec::new(),
            map       : HashMap::new(),
            current_id: 0,
            time_beg  : 0,
            time_end  : 0,
            time_bsy  : 0,
        }
    }
}

impl PipelineQueue for SimplePipelineQueue {
    fn exec( &mut self, rx: Receiver<PipelineInfo<PathMatch>>, tx: Sender<PipelineInfo<PathMatch>> ) {
        loop {
            match rx.recv() {
                Ok( PipelineInfo::Ok( p ) ) => {
                    let beg = time::precise_time_ns();

                    self.map.insert( p.id, p );
                    loop {
                        if !self.map.contains_key( &self.current_id ) {
                            break;
                        }
                        {
                            let ret = self.map.get( &self.current_id ).unwrap();
                            let _ = tx.send( PipelineInfo::Ok( ret.clone() ) );
                        }
                        let _ = self.map.remove( &self.current_id );
                        self.current_id += 1;
                    }

                    let end = time::precise_time_ns();
                    self.time_bsy += end - beg;
                },

                Ok( PipelineInfo::Beg( x ) ) => {
                    self.current_id = x;
                    self.infos  = Vec::new();
                    self.errors = Vec::new();

                    self.time_beg = time::precise_time_ns();
                    let _ = tx.send( PipelineInfo::Beg( x ) );
                },

                Ok( PipelineInfo::End( x ) ) => {
                    if !self.map.is_empty() { continue; }

                    for i in &self.infos  { let _ = tx.send( PipelineInfo::Info( i.clone() ) ); }
                    for e in &self.errors { let _ = tx.send( PipelineInfo::Err ( e.clone() ) ); }

                    self.time_end = time::precise_time_ns();
                    let _ = tx.send( PipelineInfo::Time( self.time_bsy, self.time_end - self.time_beg ) );
                    let _ = tx.send( PipelineInfo::End( x ) );
                    break;
                },

                Ok ( PipelineInfo::Info( e      ) ) => { let _ = tx.send( PipelineInfo::Info( e      ) ); },
                Ok ( PipelineInfo::Err ( e      ) ) => { let _ = tx.send( PipelineInfo::Err ( e      ) ); },
                Ok ( PipelineInfo::Time( t0, t1 ) ) => { let _ = tx.send( PipelineInfo::Time( t0, t1 ) ); },
                Err( _                            ) => break,
            }
        }
    }
}

// ---------------------------------------------------------------------------------------------------------------------
// Test
// ---------------------------------------------------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use pipeline_matcher::PathMatch;
    use std::path::PathBuf;
    use std::thread;
    use std::sync::mpsc;
    use util::PipelineInfo;

    #[test]
    fn test_simple_pipeline_queue() {
        let mut queue = SimplePipelineQueue::new();

        let ( in_tx , in_rx  ) = mpsc::channel();
        let ( out_tx, out_rx ) = mpsc::channel();
        thread::spawn( move || {
            queue.exec( in_rx, out_tx );
        } );

        let _ = in_tx.send( PipelineInfo::Beg( 0                                                                 ) );
        let _ = in_tx.send( PipelineInfo::Ok ( PathMatch{ id: 2, path: PathBuf::from( "./" ), matches: Vec::new() } ) );
        let _ = in_tx.send( PipelineInfo::Ok ( PathMatch{ id: 1, path: PathBuf::from( "./" ), matches: Vec::new() } ) );
        let _ = in_tx.send( PipelineInfo::Ok ( PathMatch{ id: 0, path: PathBuf::from( "./" ), matches: Vec::new() } ) );
        let _ = in_tx.send( PipelineInfo::End( 3                                                                 ) );

        let mut ret = Vec::new();
        let mut time_bsy = 0;
        let mut time_all = 0;
        loop {
            match out_rx.recv().unwrap() {
                PipelineInfo::Ok  ( x      ) => ret.push( x ),
                PipelineInfo::Time( t0, t1 ) => { time_bsy = t0; time_all = t1; },
                PipelineInfo::End ( _      ) => break,
                _                            => (),
            }
        }

        assert_eq!( ret[0].id, 0 );
        assert_eq!( ret[1].id, 1 );
        assert_eq!( ret[2].id, 2 );

        assert!( time_bsy != 0 );
        assert!( time_all != 0 );
        assert!( time_bsy < time_all );
    }
}

<commit_msg>fix queue waiting bug<commit_after>use pipeline_matcher::PathMatch;
use std::collections::HashMap;
use std::sync::mpsc::{Receiver, Sender};
use time;
use util::PipelineInfo;

// ---------------------------------------------------------------------------------------------------------------------
// PipelineQueue
// ---------------------------------------------------------------------------------------------------------------------

pub trait PipelineQueue {
    fn exec( &mut self, rx: Receiver<PipelineInfo<PathMatch>>, tx: Sender<PipelineInfo<PathMatch>> );
}

// ---------------------------------------------------------------------------------------------------------------------
// SimplePipelineQueue
// ---------------------------------------------------------------------------------------------------------------------

pub struct SimplePipelineQueue {
    pub infos : Vec<String>,
    pub errors: Vec<String>,
    map       : HashMap<usize, PathMatch>,
    current_id: usize,
    time_beg  : u64,
    time_end  : u64,
    time_bsy  : u64,
}

impl SimplePipelineQueue {
    pub fn new() -> Self {
        SimplePipelineQueue {
            infos     : Vec::new(),
            errors    : Vec::new(),
            map       : HashMap::new(),
            current_id: 0,
            time_beg  : 0,
            time_end  : 0,
            time_bsy  : 0,
        }
    }
}

impl PipelineQueue for SimplePipelineQueue {
    fn exec( &mut self, rx: Receiver<PipelineInfo<PathMatch>>, tx: Sender<PipelineInfo<PathMatch>> ) {
        loop {
            match rx.recv() {
                Ok( PipelineInfo::Ok( p ) ) => {
                    let beg = time::precise_time_ns();

                    self.map.insert( p.id, p );
                    loop {
                        if !self.map.contains_key( &self.current_id ) {
                            break;
                        }
                        {
                            let ret = self.map.get( &self.current_id ).unwrap();
                            let _ = tx.send( PipelineInfo::Ok( ret.clone() ) );
                        }
                        let _ = self.map.remove( &self.current_id );
                        self.current_id += 1;
                    }

                    let end = time::precise_time_ns();
                    self.time_bsy += end - beg;
                },

                Ok( PipelineInfo::Beg( x ) ) => {
                    self.current_id = x;
                    self.infos  = Vec::new();
                    self.errors = Vec::new();

                    self.time_beg = time::precise_time_ns();
                    let _ = tx.send( PipelineInfo::Beg( x ) );
                },

                Ok( PipelineInfo::End( x ) ) => {
                    if x != self.current_id { continue; }

                    for i in &self.infos  { let _ = tx.send( PipelineInfo::Info( i.clone() ) ); }
                    for e in &self.errors { let _ = tx.send( PipelineInfo::Err ( e.clone() ) ); }

                    self.time_end = time::precise_time_ns();
                    let _ = tx.send( PipelineInfo::Time( self.time_bsy, self.time_end - self.time_beg ) );
                    let _ = tx.send( PipelineInfo::End( x ) );
                    break;
                },

                Ok ( PipelineInfo::Info( e      ) ) => { let _ = tx.send( PipelineInfo::Info( e      ) ); },
                Ok ( PipelineInfo::Err ( e      ) ) => { let _ = tx.send( PipelineInfo::Err ( e      ) ); },
                Ok ( PipelineInfo::Time( t0, t1 ) ) => { let _ = tx.send( PipelineInfo::Time( t0, t1 ) ); },
                Err( _                            ) => break,
            }
        }
    }
}

// ---------------------------------------------------------------------------------------------------------------------
// Test
// ---------------------------------------------------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use pipeline_matcher::PathMatch;
    use std::path::PathBuf;
    use std::thread;
    use std::sync::mpsc;
    use util::PipelineInfo;

    #[test]
    fn test_simple_pipeline_queue() {
        let mut queue = SimplePipelineQueue::new();

        let ( in_tx , in_rx  ) = mpsc::channel();
        let ( out_tx, out_rx ) = mpsc::channel();
        thread::spawn( move || {
            queue.exec( in_rx, out_tx );
        } );

        let _ = in_tx.send( PipelineInfo::Beg( 0                                                                 ) );
        let _ = in_tx.send( PipelineInfo::Ok ( PathMatch{ id: 2, path: PathBuf::from( "./" ), matches: Vec::new() } ) );
        let _ = in_tx.send( PipelineInfo::Ok ( PathMatch{ id: 1, path: PathBuf::from( "./" ), matches: Vec::new() } ) );
        let _ = in_tx.send( PipelineInfo::Ok ( PathMatch{ id: 0, path: PathBuf::from( "./" ), matches: Vec::new() } ) );
        let _ = in_tx.send( PipelineInfo::End( 3                                                                 ) );

        let mut ret = Vec::new();
        let mut time_bsy = 0;
        let mut time_all = 0;
        loop {
            match out_rx.recv().unwrap() {
                PipelineInfo::Ok  ( x      ) => ret.push( x ),
                PipelineInfo::Time( t0, t1 ) => { time_bsy = t0; time_all = t1; },
                PipelineInfo::End ( _      ) => break,
                _                            => (),
            }
        }

        assert_eq!( ret[0].id, 0 );
        assert_eq!( ret[1].id, 1 );
        assert_eq!( ret[2].id, 2 );

        assert!( time_bsy != 0 );
        assert!( time_all != 0 );
        assert!( time_bsy < time_all );
    }
}

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

//! Specified values for font properties

#[cfg(feature = "gecko")]
use Atom;
use app_units::Au;
use cssparser::Parser;
use properties::longhands::system_font::SystemFont;
use std::fmt;
use style_traits::{ToCss, ParseError};
use values::computed::{font as computed, Context, NonNegativeLength, ToComputedValue};
use values::specified::{LengthOrPercentage, NoCalcLength};
use values::specified::length::FontBaseSize;

#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
/// A specified font-size value
pub enum FontSize {
    /// A length; e.g. 10px.
    Length(LengthOrPercentage),
    /// A keyword value, along with a ratio and absolute offset.
    /// The ratio in any specified keyword value
    /// will be 1 (with offset 0), but we cascade keywordness even
    /// after font-relative (percent and em) values
    /// have been applied, which is where the ratio
    /// comes in. The offset comes in if we cascaded a calc value,
    /// where the font-relative portion (em and percentage) will
    /// go into the ratio, and the remaining units all computed together
    /// will go into the offset.
    /// See bug 1355707.
    Keyword(computed::KeywordInfo),
    /// font-size: smaller
    Smaller,
    /// font-size: larger
    Larger,
    /// Derived from a specified system font.
    System(SystemFont)
}

impl ToCss for FontSize {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        match *self {
            FontSize::Length(ref lop) => lop.to_css(dest),
            FontSize::Keyword(info) => info.kw.to_css(dest),
            FontSize::Smaller => dest.write_str("smaller"),
            FontSize::Larger => dest.write_str("larger"),
            FontSize::System(sys) => sys.to_css(dest),
        }
    }
}

impl From<LengthOrPercentage> for FontSize {
    fn from(other: LengthOrPercentage) -> Self {
        FontSize::Length(other)
    }
}

/// CSS font keywords
#[derive(Animate, ComputeSquaredDistance, ToAnimatedValue, ToAnimatedZero)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[allow(missing_docs)]
pub enum KeywordSize {
    XXSmall = 1, // This is to enable the NonZero optimization
                 // which simplifies the representation of Option<KeywordSize>
                 // in bindgen
    XSmall,
    Small,
    Medium,
    Large,
    XLarge,
    XXLarge,
    // This is not a real font keyword and will not parse
    // HTML font-size 7 corresponds to this value
    XXXLarge,
}

impl KeywordSize {
    /// Parse a keyword size
    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
        try_match_ident_ignore_ascii_case! { input.expect_ident()?,
            "xx-small" => Ok(KeywordSize::XXSmall),
            "x-small" => Ok(KeywordSize::XSmall),
            "small" => Ok(KeywordSize::Small),
            "medium" => Ok(KeywordSize::Medium),
            "large" => Ok(KeywordSize::Large),
            "x-large" => Ok(KeywordSize::XLarge),
            "xx-large" => Ok(KeywordSize::XXLarge),
        }
    }

    /// Convert to an HTML <font size> value
    pub fn html_size(&self) -> u8 {
        match *self {
            KeywordSize::XXSmall => 0,
            KeywordSize::XSmall => 1,
            KeywordSize::Small => 2,
            KeywordSize::Medium => 3,
            KeywordSize::Large => 4,
            KeywordSize::XLarge => 5,
            KeywordSize::XXLarge => 6,
            KeywordSize::XXXLarge => 7,
        }
    }
}

impl Default for KeywordSize {
    fn default() -> Self {
        KeywordSize::Medium
    }
}

impl ToCss for KeywordSize {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        dest.write_str(match *self {
            KeywordSize::XXSmall => "xx-small",
            KeywordSize::XSmall => "x-small",
            KeywordSize::Small => "small",
            KeywordSize::Medium => "medium",
            KeywordSize::Large => "large",
            KeywordSize::XLarge => "x-large",
            KeywordSize::XXLarge => "xx-large",
            KeywordSize::XXXLarge => unreachable!("We should never serialize \
                                      specified values set via
                                      HTML presentation attributes"),
        })
    }
}

/// This is the ratio applied for font-size: larger
/// and smaller by both Firefox and Chrome
const LARGER_FONT_SIZE_RATIO: f32 = 1.2;

/// The default font size.
pub const FONT_MEDIUM_PX: i32 = 16;

#[cfg(feature = "servo")]
impl ToComputedValue for KeywordSize {
    type ComputedValue = NonNegativeLength;
    #[inline]
    fn to_computed_value(&self, _: &Context) -> NonNegativeLength {
        // https://drafts.csswg.org/css-fonts-3/#font-size-prop
        match *self {
            KeywordSize::XXSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 5,
            KeywordSize::XSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 4,
            KeywordSize::Small => Au::from_px(FONT_MEDIUM_PX) * 8 / 9,
            KeywordSize::Medium => Au::from_px(FONT_MEDIUM_PX),
            KeywordSize::Large => Au::from_px(FONT_MEDIUM_PX) * 6 / 5,
            KeywordSize::XLarge => Au::from_px(FONT_MEDIUM_PX) * 3 / 2,
            KeywordSize::XXLarge => Au::from_px(FONT_MEDIUM_PX) * 2,
            KeywordSize::XXXLarge => Au::from_px(FONT_MEDIUM_PX) * 3,
        }.into()
    }

    #[inline]
    fn from_computed_value(_: &NonNegativeLength) -> Self {
        unreachable!()
    }
}

#[cfg(feature = "gecko")]
impl ToComputedValue for KeywordSize {
    type ComputedValue = NonNegativeLength;
    #[inline]
    fn to_computed_value(&self, cx: &Context) -> NonNegativeLength {
        use values::specified::length::au_to_int_px;
        // Data from nsRuleNode.cpp in Gecko
        // Mapping from base size and HTML size to pixels
        // The first index is (base_size - 9), the second is the
        // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
        // since HTML size 0 is the same as 1.
        //
        //  xxs   xs      s      m     l      xl     xxl   -
        //  -     0/1     2      3     4      5      6     7
        static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
            [9,    9,     9,     9,    11,    14,    18,    27],
            [9,    9,     9,    10,    12,    15,    20,    30],
            [9,    9,    10,    11,    13,    17,    22,    33],
            [9,    9,    10,    12,    14,    18,    24,    36],
            [9,   10,    12,    13,    16,    20,    26,    39],
            [9,   10,    12,    14,    17,    21,    28,    42],
            [9,   10,    13,    15,    18,    23,    30,    45],
            [9,   10,    13,    16,    18,    24,    32,    48]
        ];

        static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];

        // XXXManishearth handle quirks mode (bug 1401322)

        let ref gecko_font = cx.style().get_font().gecko();
        let base_size = unsafe { Atom::with(gecko_font.mLanguage.mRawPtr, |atom| {
            cx.font_metrics_provider.get_size(atom, gecko_font.mGenericID).0
        }) };

        let base_size_px = au_to_int_px(base_size as f32);
        let html_size = self.html_size() as usize;
        if base_size_px >= 9 && base_size_px <= 16 {
            Au::from_px(FONT_SIZE_MAPPING[(base_size_px - 9) as usize][html_size]).into()
        } else {
            Au(FONT_SIZE_FACTORS[html_size] * base_size / 100).into()
        }
    }

    #[inline]
    fn from_computed_value(_: &NonNegativeLength) -> Self {
        unreachable!()
    }
}

impl FontSize {
    /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size
    pub fn from_html_size(size: u8) -> Self {
        FontSize::Keyword(match size {
            // If value is less than 1, let it be 1.
            0 | 1 => KeywordSize::XSmall,
            2 => KeywordSize::Small,
            3 => KeywordSize::Medium,
            4 => KeywordSize::Large,
            5 => KeywordSize::XLarge,
            6 => KeywordSize::XXLarge,
            // If value is greater than 7, let it be 7.
            _ => KeywordSize::XXXLarge,
        }.into())
    }

    /// Compute it against a given base font size
    pub fn to_computed_value_against(
        &self,
        context: &Context,
        base_size: FontBaseSize,
    ) -> computed::FontSize {
        use values::specified::length::FontRelativeLength;

        let compose_keyword = |factor| {
            context.style().get_parent_font()
                   .clone_font_size().keyword_info
                   .map(|i| i.compose(factor, Au(0).into()))
        };
        let mut info = None;
        let size = match *self {
            FontSize::Length(LengthOrPercentage::Length(
                    NoCalcLength::FontRelative(value))) => {
                if let FontRelativeLength::Em(em) = value {
                    // If the parent font was keyword-derived, this is too.
                    // Tack the em unit onto the factor
                    info = compose_keyword(em);
                }
                value.to_computed_value(context, base_size).into()
            }
            FontSize::Length(LengthOrPercentage::Length(
                    NoCalcLength::ServoCharacterWidth(value))) => {
                value.to_computed_value(base_size.resolve(context)).into()
            }
            FontSize::Length(LengthOrPercentage::Length(
                    NoCalcLength::Absolute(ref l))) => {
                context.maybe_zoom_text(l.to_computed_value(context).into())
            }
            FontSize::Length(LengthOrPercentage::Length(ref l)) => {
                l.to_computed_value(context).into()
            }
            FontSize::Length(LengthOrPercentage::Percentage(pc)) => {
                // If the parent font was keyword-derived, this is too.
                // Tack the % onto the factor
                info = compose_keyword(pc.0);
                base_size.resolve(context).scale_by(pc.0).into()
            }
            FontSize::Length(LengthOrPercentage::Calc(ref calc)) => {
                let parent = context.style().get_parent_font().clone_font_size();
                // if we contain em/% units and the parent was keyword derived, this is too
                // Extract the ratio/offset and compose it
                if (calc.em.is_some() || calc.percentage.is_some()) && parent.keyword_info.is_some() {
                    let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0);
                    // Compute it, but shave off the font-relative part (em, %)
                    // This will mean that other font-relative units like ex and ch will be computed against
                    // the old font even when the font changes. There's no particular "right answer" for what
                    // to do here -- Gecko recascades as if the font had changed, we instead track the changes
                    // and reapply, which means that we carry over old computed ex/ch values whilst Gecko
                    // recomputes new ones. This is enough of an edge case to not really matter.
                    let abs = calc.to_computed_value_zoomed(context, FontBaseSize::Custom(Au(0).into()))
                                  .length_component().into();
                    info = parent.keyword_info.map(|i| i.compose(ratio, abs));
                }
                let calc = calc.to_computed_value_zoomed(context, base_size);
                calc.to_used_value(Some(base_size.resolve(context))).unwrap().into()
            }
            FontSize::Keyword(i) => {
                // As a specified keyword, this is keyword derived
                info = Some(i);
                context.maybe_zoom_text(i.kw.to_computed_value(context).scale_by(i.factor) + i.offset)
            }
            FontSize::Smaller => {
                info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
                FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO)
                    .to_computed_value(context, base_size).into()
            }
            FontSize::Larger => {
                info = compose_keyword(LARGER_FONT_SIZE_RATIO);
                FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO)
                    .to_computed_value(context, base_size).into()
            }

            FontSize::System(_) => {
                #[cfg(feature = "servo")] {
                    unreachable!()
                }
                #[cfg(feature = "gecko")] {
                    context.cached_system_font.as_ref().unwrap().font_size.size
                }
            }
        };
        computed::FontSize {
            size: size,
            keyword_info: info,
        }
    }
}

impl ToComputedValue for FontSize {
    type ComputedValue = computed::FontSize;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> computed::FontSize {
        self.to_computed_value_against(context, FontBaseSize::InheritedStyle)
    }

    #[inline]
    fn from_computed_value(computed: &computed::FontSize) -> Self {
        FontSize::Length(LengthOrPercentage::Length(
            ToComputedValue::from_computed_value(&computed.size.0)
        ))
    }
}

impl FontSize {
    /// Construct a system font value.
    pub fn system_font(f: SystemFont) -> Self {
        FontSize::System(f)
    }

    /// Obtain the system font, if any
    pub fn get_system(&self) -> Option<SystemFont> {
        if let FontSize::System(s) = *self {
            Some(s)
        } else {
            None
        }
    }
}
<commit_msg>Auto merge of #18658 - canaltinova:font-size-quirk, r=Manishearth<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/. */

//! Specified values for font properties

#[cfg(feature = "gecko")]
use Atom;
use app_units::Au;
use cssparser::Parser;
use properties::longhands::system_font::SystemFont;
use std::fmt;
use style_traits::{ToCss, ParseError};
use values::computed::{font as computed, Context, NonNegativeLength, ToComputedValue};
use values::specified::{LengthOrPercentage, NoCalcLength};
use values::specified::length::FontBaseSize;

#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
/// A specified font-size value
pub enum FontSize {
    /// A length; e.g. 10px.
    Length(LengthOrPercentage),
    /// A keyword value, along with a ratio and absolute offset.
    /// The ratio in any specified keyword value
    /// will be 1 (with offset 0), but we cascade keywordness even
    /// after font-relative (percent and em) values
    /// have been applied, which is where the ratio
    /// comes in. The offset comes in if we cascaded a calc value,
    /// where the font-relative portion (em and percentage) will
    /// go into the ratio, and the remaining units all computed together
    /// will go into the offset.
    /// See bug 1355707.
    Keyword(computed::KeywordInfo),
    /// font-size: smaller
    Smaller,
    /// font-size: larger
    Larger,
    /// Derived from a specified system font.
    System(SystemFont)
}

impl ToCss for FontSize {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        match *self {
            FontSize::Length(ref lop) => lop.to_css(dest),
            FontSize::Keyword(info) => info.kw.to_css(dest),
            FontSize::Smaller => dest.write_str("smaller"),
            FontSize::Larger => dest.write_str("larger"),
            FontSize::System(sys) => sys.to_css(dest),
        }
    }
}

impl From<LengthOrPercentage> for FontSize {
    fn from(other: LengthOrPercentage) -> Self {
        FontSize::Length(other)
    }
}

/// CSS font keywords
#[derive(Animate, ComputeSquaredDistance, ToAnimatedValue, ToAnimatedZero)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[allow(missing_docs)]
pub enum KeywordSize {
    XXSmall = 1, // This is to enable the NonZero optimization
                 // which simplifies the representation of Option<KeywordSize>
                 // in bindgen
    XSmall,
    Small,
    Medium,
    Large,
    XLarge,
    XXLarge,
    // This is not a real font keyword and will not parse
    // HTML font-size 7 corresponds to this value
    XXXLarge,
}

impl KeywordSize {
    /// Parse a keyword size
    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
        try_match_ident_ignore_ascii_case! { input.expect_ident()?,
            "xx-small" => Ok(KeywordSize::XXSmall),
            "x-small" => Ok(KeywordSize::XSmall),
            "small" => Ok(KeywordSize::Small),
            "medium" => Ok(KeywordSize::Medium),
            "large" => Ok(KeywordSize::Large),
            "x-large" => Ok(KeywordSize::XLarge),
            "xx-large" => Ok(KeywordSize::XXLarge),
        }
    }

    /// Convert to an HTML <font size> value
    pub fn html_size(&self) -> u8 {
        match *self {
            KeywordSize::XXSmall => 0,
            KeywordSize::XSmall => 1,
            KeywordSize::Small => 2,
            KeywordSize::Medium => 3,
            KeywordSize::Large => 4,
            KeywordSize::XLarge => 5,
            KeywordSize::XXLarge => 6,
            KeywordSize::XXXLarge => 7,
        }
    }
}

impl Default for KeywordSize {
    fn default() -> Self {
        KeywordSize::Medium
    }
}

impl ToCss for KeywordSize {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        dest.write_str(match *self {
            KeywordSize::XXSmall => "xx-small",
            KeywordSize::XSmall => "x-small",
            KeywordSize::Small => "small",
            KeywordSize::Medium => "medium",
            KeywordSize::Large => "large",
            KeywordSize::XLarge => "x-large",
            KeywordSize::XXLarge => "xx-large",
            KeywordSize::XXXLarge => unreachable!("We should never serialize \
                                      specified values set via
                                      HTML presentation attributes"),
        })
    }
}

/// This is the ratio applied for font-size: larger
/// and smaller by both Firefox and Chrome
const LARGER_FONT_SIZE_RATIO: f32 = 1.2;

/// The default font size.
pub const FONT_MEDIUM_PX: i32 = 16;

#[cfg(feature = "servo")]
impl ToComputedValue for KeywordSize {
    type ComputedValue = NonNegativeLength;
    #[inline]
    fn to_computed_value(&self, _: &Context) -> NonNegativeLength {
        // https://drafts.csswg.org/css-fonts-3/#font-size-prop
        match *self {
            KeywordSize::XXSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 5,
            KeywordSize::XSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 4,
            KeywordSize::Small => Au::from_px(FONT_MEDIUM_PX) * 8 / 9,
            KeywordSize::Medium => Au::from_px(FONT_MEDIUM_PX),
            KeywordSize::Large => Au::from_px(FONT_MEDIUM_PX) * 6 / 5,
            KeywordSize::XLarge => Au::from_px(FONT_MEDIUM_PX) * 3 / 2,
            KeywordSize::XXLarge => Au::from_px(FONT_MEDIUM_PX) * 2,
            KeywordSize::XXXLarge => Au::from_px(FONT_MEDIUM_PX) * 3,
        }.into()
    }

    #[inline]
    fn from_computed_value(_: &NonNegativeLength) -> Self {
        unreachable!()
    }
}

#[cfg(feature = "gecko")]
impl ToComputedValue for KeywordSize {
    type ComputedValue = NonNegativeLength;
    #[inline]
    fn to_computed_value(&self, cx: &Context) -> NonNegativeLength {
        use context::QuirksMode;
        use values::specified::length::au_to_int_px;
        // Data from nsRuleNode.cpp in Gecko
        // Mapping from base size and HTML size to pixels
        // The first index is (base_size - 9), the second is the
        // HTML size. "0" is CSS keyword xx-small, not HTML size 0,
        // since HTML size 0 is the same as 1.
        //
        //  xxs   xs      s      m     l      xl     xxl   -
        //  -     0/1     2      3     4      5      6     7
        static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
            [9,    9,     9,     9,    11,    14,    18,    27],
            [9,    9,     9,    10,    12,    15,    20,    30],
            [9,    9,    10,    11,    13,    17,    22,    33],
            [9,    9,    10,    12,    14,    18,    24,    36],
            [9,   10,    12,    13,    16,    20,    26,    39],
            [9,   10,    12,    14,    17,    21,    28,    42],
            [9,   10,    13,    15,    18,    23,    30,    45],
            [9,   10,    13,    16,    18,    24,    32,    48]
        ];

        // Data from nsRuleNode.cpp in Gecko
        // (https://dxr.mozilla.org/mozilla-central/rev/35fbf14b9/layout/style/nsRuleNode.cpp#3303)
        //
        // This table gives us compatibility with WinNav4 for the default fonts only.
        // In WinNav4, the default fonts were:
        //
        //     Times/12pt ==   Times/16px at 96ppi
        //   Courier/10pt == Courier/13px at 96ppi
        //
        // xxs   xs     s      m      l     xl     xxl    -
        // -     1      2      3      4     5      6      7
        static QUIRKS_FONT_SIZE_MAPPING: [[i32; 8]; 8] = [
            [9,    9,     9,     9,    11,    14,    18,    28],
            [9,    9,     9,    10,    12,    15,    20,    31],
            [9,    9,     9,    11,    13,    17,    22,    34],
            [9,    9,    10,    12,    14,    18,    24,    37],
            [9,    9,    10,    13,    16,    20,    26,    40],
            [9,    9,    11,    14,    17,    21,    28,    42],
            [9,   10,    12,    15,    17,    23,    30,    45],
            [9,   10,    13,    16,    18,    24,    32,    48]
        ];

        static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300];

        let ref gecko_font = cx.style().get_font().gecko();
        let base_size = unsafe { Atom::with(gecko_font.mLanguage.mRawPtr, |atom| {
            cx.font_metrics_provider.get_size(atom, gecko_font.mGenericID).0
        }) };

        let base_size_px = au_to_int_px(base_size as f32);
        let html_size = self.html_size() as usize;
        if base_size_px >= 9 && base_size_px <= 16 {
            let mapping = if cx.quirks_mode == QuirksMode::Quirks {
                QUIRKS_FONT_SIZE_MAPPING
            } else {
                FONT_SIZE_MAPPING
            };
            Au::from_px(mapping[(base_size_px - 9) as usize][html_size]).into()
        } else {
            Au(FONT_SIZE_FACTORS[html_size] * base_size / 100).into()
        }
    }

    #[inline]
    fn from_computed_value(_: &NonNegativeLength) -> Self {
        unreachable!()
    }
}

impl FontSize {
    /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size
    pub fn from_html_size(size: u8) -> Self {
        FontSize::Keyword(match size {
            // If value is less than 1, let it be 1.
            0 | 1 => KeywordSize::XSmall,
            2 => KeywordSize::Small,
            3 => KeywordSize::Medium,
            4 => KeywordSize::Large,
            5 => KeywordSize::XLarge,
            6 => KeywordSize::XXLarge,
            // If value is greater than 7, let it be 7.
            _ => KeywordSize::XXXLarge,
        }.into())
    }

    /// Compute it against a given base font size
    pub fn to_computed_value_against(
        &self,
        context: &Context,
        base_size: FontBaseSize,
    ) -> computed::FontSize {
        use values::specified::length::FontRelativeLength;

        let compose_keyword = |factor| {
            context.style().get_parent_font()
                   .clone_font_size().keyword_info
                   .map(|i| i.compose(factor, Au(0).into()))
        };
        let mut info = None;
        let size = match *self {
            FontSize::Length(LengthOrPercentage::Length(
                    NoCalcLength::FontRelative(value))) => {
                if let FontRelativeLength::Em(em) = value {
                    // If the parent font was keyword-derived, this is too.
                    // Tack the em unit onto the factor
                    info = compose_keyword(em);
                }
                value.to_computed_value(context, base_size).into()
            }
            FontSize::Length(LengthOrPercentage::Length(
                    NoCalcLength::ServoCharacterWidth(value))) => {
                value.to_computed_value(base_size.resolve(context)).into()
            }
            FontSize::Length(LengthOrPercentage::Length(
                    NoCalcLength::Absolute(ref l))) => {
                context.maybe_zoom_text(l.to_computed_value(context).into())
            }
            FontSize::Length(LengthOrPercentage::Length(ref l)) => {
                l.to_computed_value(context).into()
            }
            FontSize::Length(LengthOrPercentage::Percentage(pc)) => {
                // If the parent font was keyword-derived, this is too.
                // Tack the % onto the factor
                info = compose_keyword(pc.0);
                base_size.resolve(context).scale_by(pc.0).into()
            }
            FontSize::Length(LengthOrPercentage::Calc(ref calc)) => {
                let parent = context.style().get_parent_font().clone_font_size();
                // if we contain em/% units and the parent was keyword derived, this is too
                // Extract the ratio/offset and compose it
                if (calc.em.is_some() || calc.percentage.is_some()) && parent.keyword_info.is_some() {
                    let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0);
                    // Compute it, but shave off the font-relative part (em, %)
                    // This will mean that other font-relative units like ex and ch will be computed against
                    // the old font even when the font changes. There's no particular "right answer" for what
                    // to do here -- Gecko recascades as if the font had changed, we instead track the changes
                    // and reapply, which means that we carry over old computed ex/ch values whilst Gecko
                    // recomputes new ones. This is enough of an edge case to not really matter.
                    let abs = calc.to_computed_value_zoomed(context, FontBaseSize::Custom(Au(0).into()))
                                  .length_component().into();
                    info = parent.keyword_info.map(|i| i.compose(ratio, abs));
                }
                let calc = calc.to_computed_value_zoomed(context, base_size);
                calc.to_used_value(Some(base_size.resolve(context))).unwrap().into()
            }
            FontSize::Keyword(i) => {
                // As a specified keyword, this is keyword derived
                info = Some(i);
                context.maybe_zoom_text(i.kw.to_computed_value(context).scale_by(i.factor) + i.offset)
            }
            FontSize::Smaller => {
                info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
                FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO)
                    .to_computed_value(context, base_size).into()
            }
            FontSize::Larger => {
                info = compose_keyword(LARGER_FONT_SIZE_RATIO);
                FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO)
                    .to_computed_value(context, base_size).into()
            }

            FontSize::System(_) => {
                #[cfg(feature = "servo")] {
                    unreachable!()
                }
                #[cfg(feature = "gecko")] {
                    context.cached_system_font.as_ref().unwrap().font_size.size
                }
            }
        };
        computed::FontSize {
            size: size,
            keyword_info: info,
        }
    }
}

impl ToComputedValue for FontSize {
    type ComputedValue = computed::FontSize;

    #[inline]
    fn to_computed_value(&self, context: &Context) -> computed::FontSize {
        self.to_computed_value_against(context, FontBaseSize::InheritedStyle)
    }

    #[inline]
    fn from_computed_value(computed: &computed::FontSize) -> Self {
        FontSize::Length(LengthOrPercentage::Length(
            ToComputedValue::from_computed_value(&computed.size.0)
        ))
    }
}

impl FontSize {
    /// Construct a system font value.
    pub fn system_font(f: SystemFont) -> Self {
        FontSize::System(f)
    }

    /// Obtain the system font, if any
    pub fn get_system(&self) -> Option<SystemFont> {
        if let FontSize::System(s) = *self {
            Some(s)
        } else {
            None
        }
    }
}
<|endoftext|>


<commit_before>//! List owned projects
//!
//! https://docs.gitlab.com/ce/api/projects.html#list-owned-projects
//!
//! # List owned projects
//!
//! Get a list of projects which are owned by the authenticated user.
//!
//! ```text
//! GET /projects/owned
//! ```
//!
//! Parameters:
//!
//! | Attribute | Type | Required | Description |
//! | --------- | ---- | -------- | ----------- |
//! | `archived` | boolean | no | Limit by archived status |
//! | `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
//! | `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
//! | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
//! | `search` | string | no | Return list of authorized projects matching the search criteria |

use serde_json;
use serde_urlencoded;

use BuildQuery;
use gitlab::GitLab;
use Projects;
use projects::{OwnedProjectListerInternal, ListingOrderBy};

#[derive(Debug, Clone)]
pub struct ProjectsLister<'a> {
    gl: &'a ::GitLab,
    internal: OwnedProjectListerInternal,
}

impl<'a> ProjectsLister<'a> {
    pub fn new(gl: &'a ::GitLab) -> ProjectsLister {
        ProjectsLister {
            gl: gl,
            internal: OwnedProjectListerInternal {
                archived: None,
                visibility: None,
                order_by: None,
                sort: None,
                search: None,
            },
        }
    }

    pub fn archived(&'a mut self, archived: bool) -> &'a mut ProjectsLister {
        self.internal.archived = Some(archived);
        self
    }

    pub fn visibility(&'a mut self, visibility: ::ListingVisibility) -> &'a mut ProjectsLister {
        self.internal.visibility = Some(visibility);
        self
    }

    pub fn order_by(&'a mut self, order_by: ListingOrderBy) -> &'a mut ProjectsLister {
        self.internal.order_by = Some(order_by);
        self
    }

    pub fn sort(&'a mut self, sort: ::ListingSort) -> &'a mut ProjectsLister {
        self.internal.sort = Some(sort);
        self
    }

    pub fn search(&'a mut self, search: String) -> &'a mut ProjectsLister {
        self.internal.search = Some(search);
        self
    }

    /// Commit the lister: Query GitLab and return a list of projects.
    pub fn list(&self) -> Projects {
        // let query = serde_urlencoded::to_string(&self);
        let query = self.build_query();
        debug!("query: {:?}", query);

        let projects: Result<Projects, serde_json::Error> = self.gl.get(&query);

        projects.unwrap()
    }
}

impl<'a> BuildQuery for ProjectsLister<'a> {
    fn build_query(&self) -> String {

        let encoded = serde_urlencoded::to_string(&self.internal).unwrap();
        let mut query = String::from("projects/owned");
        if !encoded.is_empty() {
            query.push_str("?");
            query.push_str(&encoded);
        }
        debug!("query: {}", query);

        query
    }
}

// impl BuildQuery for Listing {
//     fn build_query(&self) -> String {
//
//         let mut query = String::from("projects/owned");
//
//         let amp_char = "&";
//         let none_char = "";
//         let mut split_char = &none_char;
//
//         // Append a "?" only if at least one of the `Option` is `Some(_)` or if
//         // strings contain something.
//         query.push_str(match (&self.archived,
//                               &self.visibility,
//                               &self.order_by,
//                               &self.sort,
//                               self.search.is_empty()) {
//             (&None, &None, &None, &None, true) => "",
//             _ => "?",
//         });
//
//         self.archived.map(|archived| {
//             query.push_str(split_char);
//             split_char = &amp_char;
//
//             if archived {
//                 query.push_str("archived=true")
//             } else {
//                 query.push_str("archived=false")
//             }
//         });
//
//         self.visibility.map(|visibility| {
//             query.push_str(split_char);
//             split_char = &amp_char;
//
//             query.push_str("visibility=");
//             query.push_str(match visibility {
//                 ::ListingVisibility::Public => "public",
//                 ::ListingVisibility::Internal => "internal",
//                 ::ListingVisibility::Private => "private",
//             });
//         });
//
//         self.order_by.map(|order_by| {
//             query.push_str(split_char);
//             split_char = &amp_char;
//
//             query.push_str("order_by=");
//             query.push_str(match order_by {
//                 ListingOrderBy::Id => "id",
//                 ListingOrderBy::Name => "name",
//                 ListingOrderBy::Path => "path",
//                 ListingOrderBy::CreatedAt => "created_at",
//                 ListingOrderBy::UpdatedAt => "updated_at",
//                 ListingOrderBy::LastActivityAt => "last_activity_at",
//             });
//         });
//
//         self.sort.map(|sort| {
//             query.push_str(split_char);
//             split_char = &amp_char;
//
//             query.push_str("sort=");
//             query.push_str(match sort {
//                 ::ListingSort::Asc => "asc",
//                 ::ListingSort::Desc => "desc",
//             });
//         });
//
//         if !self.search.is_empty() {
//             query.push_str(split_char);
//             // split_char = &amp_char;
//
//             query.push_str("search=");
//             query.push_str(&self.search);
//         }
//
//         query
//     }
// }

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

    // #[test]
    // fn build_query_default() {
    //     let expected_string = "projects/owned";
    //     let listing: Listing = Default::default();
    //     let query = listing.build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned";
    //     let listing = Listing::new();
    //     let query = listing.build_query();
    //     assert_eq!(query, expected_string);
    // }
    //
    //
    // #[test]
    // fn build_query_archived() {
    //     let expected_string = "projects/owned?archived=true";
    //     let query = Listing::new().archived(true).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?archived=false";
    //     let query = Listing::new().archived(false).build_query();
    //     assert_eq!(query, expected_string);
    // }
    //
    //
    // #[test]
    // fn build_query_visibility() {
    //     let expected_string = "projects/owned?visibility=public";
    //     let query = Listing::new().visibility(::ListingVisibility::Public).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?visibility=internal";
    //     let query = Listing::new().visibility(::ListingVisibility::Internal).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?visibility=private";
    //     let query = Listing::new().visibility(::ListingVisibility::Private).build_query();
    //     assert_eq!(query, expected_string);
    // }
    //
    //
    // #[test]
    // fn build_query_order_by() {
    //     let expected_string = "projects/owned?order_by=id";
    //     let query = Listing::new().order_by(ListingOrderBy::Id).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?order_by=name";
    //     let query = Listing::new().order_by(ListingOrderBy::Name).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?order_by=path";
    //     let query = Listing::new().order_by(ListingOrderBy::Path).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?order_by=created_at";
    //     let query = Listing::new().order_by(ListingOrderBy::CreatedAt).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?order_by=updated_at";
    //     let query = Listing::new().order_by(ListingOrderBy::UpdatedAt).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?order_by=last_activity_at";
    //     let query = Listing::new().order_by(ListingOrderBy::LastActivityAt).build_query();
    //     assert_eq!(query, expected_string);
    // }
    //
    //
    // #[test]
    // fn build_query_sort() {
    //     let expected_string = "projects/owned?sort=asc";
    //     let query = Listing::new().sort(::ListingSort::Asc).build_query();
    //     assert_eq!(query, expected_string);
    //
    //     let expected_string = "projects/owned?sort=desc";
    //     let query = Listing::new().sort(::ListingSort::Desc).build_query();
    //     assert_eq!(query, expected_string);
    // }
    //
    //
    // #[test]
    // fn build_query_search() {
    //     let expected_string = "projects/owned?search=SearchPattern";
    //     let query = Listing::new().search(String::from("SearchPattern")).build_query();
    //     assert_eq!(query, expected_string);
    // }
    //
    //
    // #[test]
    // fn groups_build_query_multiple() {
    //     let expected_string = "projects/owned?archived=false&sort=asc";
    //     let query = Listing::new().archived(false).sort(::ListingSort::Asc).build_query();
    //     assert_eq!(query, expected_string);
    // }
}
<commit_msg>Port `owned` module to new API<commit_after>//! List owned projects
//!
//! https://docs.gitlab.com/ce/api/projects.html#list-owned-projects
//!
//! # List owned projects
//!
//! Get a list of projects which are owned by the authenticated user.
//!
//! ```text
//! GET /projects/owned
//! ```
//!
//! Parameters:
//!
//! | Attribute | Type | Required | Description |
//! | --------- | ---- | -------- | ----------- |
//! | `archived` | boolean | no | Limit by archived status |
//! | `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
//! | `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
//! | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
//! | `search` | string | no | Return list of authorized projects matching the search criteria |

use serde_json;
use serde_urlencoded;

use BuildQuery;
use gitlab::GitLab;
use Projects;
use projects::{OwnedProjectListerInternal, ListingOrderBy};

#[derive(Debug, Clone)]
pub struct ProjectsLister<'a> {
    gl: &'a ::GitLab,
    internal: OwnedProjectListerInternal,
}

impl<'a> ProjectsLister<'a> {
    pub fn new(gl: &'a ::GitLab) -> ProjectsLister {
        ProjectsLister {
            gl: gl,
            internal: OwnedProjectListerInternal {
                archived: None,
                visibility: None,
                order_by: None,
                sort: None,
                search: None,
            },
        }
    }

    pub fn archived(&'a mut self, archived: bool) -> &'a mut ProjectsLister {
        self.internal.archived = Some(archived);
        self
    }

    pub fn visibility(&'a mut self, visibility: ::ListingVisibility) -> &'a mut ProjectsLister {
        self.internal.visibility = Some(visibility);
        self
    }

    pub fn order_by(&'a mut self, order_by: ListingOrderBy) -> &'a mut ProjectsLister {
        self.internal.order_by = Some(order_by);
        self
    }

    pub fn sort(&'a mut self, sort: ::ListingSort) -> &'a mut ProjectsLister {
        self.internal.sort = Some(sort);
        self
    }

    pub fn search(&'a mut self, search: String) -> &'a mut ProjectsLister {
        self.internal.search = Some(search);
        self
    }

    /// Commit the lister: Query GitLab and return a list of projects.
    pub fn list(&self) -> Projects {
        // let query = serde_urlencoded::to_string(&self);
        let query = self.build_query();
        debug!("query: {:?}", query);

        let projects: Result<Projects, serde_json::Error> = self.gl.get(&query);

        projects.unwrap()
    }
}

impl<'a> BuildQuery for ProjectsLister<'a> {
    fn build_query(&self) -> String {

        let encoded = serde_urlencoded::to_string(&self.internal).unwrap();
        let mut query = String::from("projects/owned");
        if !encoded.is_empty() {
            query.push_str("?");
            query.push_str(&encoded);
        }
        debug!("query: {}", query);

        query
    }
}

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

    #[test]
    fn build_query_default() {
        let expected_string = "projects/owned";

        let gl = ::GitLab::new(&"localhost", "XXXXXXXXXXXXXXXXXXXX");
        // let gl: ::GitLab = Default::default();

        let projects_lister = gl.projects().owned();
        let query = projects_lister.build_query();
        assert_eq!(query, expected_string);

        let query = gl.projects().owned().build_query();
        assert_eq!(query, expected_string);
    }

    #[test]
    fn build_query_archived() {
        let gl = ::GitLab::new(&"localhost", "XXXXXXXXXXXXXXXXXXXX");
        // let gl: ::GitLab = Default::default();

        let expected_string = "projects/owned?archived=true";

        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.archived(true).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().archived(true).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?archived=false";

        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.archived(false).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().archived(false).build_query();
        assert_eq!(query, expected_string);
    }

    #[test]
    fn build_query_visibility() {
        let gl = ::GitLab::new(&"localhost", "XXXXXXXXXXXXXXXXXXXX");
        // let gl: ::GitLab = Default::default();

        let expected_string = "projects/owned?visibility=public";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.visibility(::ListingVisibility::Public).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().visibility(::ListingVisibility::Public).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?visibility=internal";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.visibility(::ListingVisibility::Internal).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().visibility(::ListingVisibility::Internal).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?visibility=private";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.visibility(::ListingVisibility::Private).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().visibility(::ListingVisibility::Private).build_query();
        assert_eq!(query, expected_string);
    }

    #[test]
    fn build_query_order_by() {
        let gl = ::GitLab::new(&"localhost", "XXXXXXXXXXXXXXXXXXXX");
        // let gl: ::GitLab = Default::default();

        let expected_string = "projects/owned?order_by=id";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.order_by(ListingOrderBy::Id).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().order_by(ListingOrderBy::Id).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?order_by=name";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.order_by(ListingOrderBy::Name).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().order_by(ListingOrderBy::Name).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?order_by=path";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.order_by(ListingOrderBy::Path).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().order_by(ListingOrderBy::Path).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?order_by=created_at";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.order_by(ListingOrderBy::CreatedAt).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().order_by(ListingOrderBy::CreatedAt).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?order_by=updated_at";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.order_by(ListingOrderBy::UpdatedAt).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().order_by(ListingOrderBy::UpdatedAt).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?order_by=last_activity_at";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.order_by(ListingOrderBy::LastActivityAt).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().order_by(ListingOrderBy::LastActivityAt).build_query();
        assert_eq!(query, expected_string);
    }

    #[test]
    fn build_query_sort() {
        let gl = ::GitLab::new(&"localhost", "XXXXXXXXXXXXXXXXXXXX");
        // let gl: ::GitLab = Default::default();

        let expected_string = "projects/owned?sort=asc";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.sort(::ListingSort::Asc).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().sort(::ListingSort::Asc).build_query();
        assert_eq!(query, expected_string);

        let expected_string = "projects/owned?sort=desc";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.sort(::ListingSort::Desc).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().sort(::ListingSort::Desc).build_query();
        assert_eq!(query, expected_string);
    }

    #[test]
    fn build_query_search() {
        let gl = ::GitLab::new(&"localhost", "XXXXXXXXXXXXXXXXXXXX");
        // let gl: ::GitLab = Default::default();

        let expected_string = "projects/owned?search=SearchPattern";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.search(String::from("SearchPattern")).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().search(String::from("SearchPattern")).build_query();
        assert_eq!(query, expected_string);
    }

    #[test]
    fn groups_build_query_multiple() {
        let gl = ::GitLab::new(&"localhost", "XXXXXXXXXXXXXXXXXXXX");
        // let gl: ::GitLab = Default::default();

        let expected_string = "projects/owned?archived=true&sort=desc";
        let mut projects_lister = gl.projects().owned();
        let query = projects_lister.archived(true).sort(::ListingSort::Desc).build_query();
        assert_eq!(query, expected_string);
        let query = gl.projects().owned().archived(true).sort(::ListingSort::Desc).build_query();
        assert_eq!(query, expected_string);
    }
}
<|endoftext|>


<commit_before>#[test]
fn empty_test() {}
<commit_msg>Remove empty tests<commit_after><|endoftext|>