<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::compartments::InCompartment;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods;
use crate::dom::bindings::codegen::Bindings::XRBinding::XRSessionMode;
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType;
use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateInit;
use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods;
use crate::dom::bindings::codegen::Bindings::XRSessionBinding;
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XREnvironmentBlendMode;
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRFrameRequestCallback;
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRSessionMethods;
use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::XRWebGLLayerMethods;
use crate::dom::bindings::error::Error;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, MutDom, MutNullableDom};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::node::Node;
use crate::dom::node::NodeDamage;
use crate::dom::promise::Promise;
use crate::dom::xrframe::XRFrame;
use crate::dom::xrinputsource::XRInputSource;
use crate::dom::xrlayer::XRLayer;
use crate::dom::xrreferencespace::XRReferenceSpace;
use crate::dom::xrrenderstate::XRRenderState;
use crate::dom::xrspace::XRSpace;
use crate::dom::xrwebgllayer::XRWebGLLayer;
use crate::task_source::TaskSource;
use dom_struct::dom_struct;
use euclid::TypedRigidTransform3D;
use ipc_channel::ipc::IpcSender;
use ipc_channel::router::ROUTER;
use profile_traits::ipc;
use std::cell::Cell;
use std::mem;
use std::rc::Rc;
use webxr_api::{self, Frame, Session};

#[dom_struct]
pub struct XRSession {
    eventtarget: EventTarget,
    base_layer: MutNullableDom<XRLayer>,
    blend_mode: XREnvironmentBlendMode,
    viewer_space: MutNullableDom<XRSpace>,
    #[ignore_malloc_size_of = "defined in webxr"]
    session: DomRefCell<Session>,
    frame_requested: Cell<bool>,
    pending_render_state: MutNullableDom<XRRenderState>,
    active_render_state: MutDom<XRRenderState>,

    next_raf_id: Cell<i32>,
    #[ignore_malloc_size_of = "closures are hard"]
    raf_callback_list: DomRefCell<Vec<(i32, Option<Rc<XRFrameRequestCallback>>)>>,
    #[ignore_malloc_size_of = "defined in ipc-channel"]
    raf_sender: DomRefCell<Option<IpcSender<(f64, Frame)>>>,
    input_sources: DomRefCell<Vec<Dom<XRInputSource>>>,
}

impl XRSession {
    fn new_inherited(session: Session, render_state: &XRRenderState) -> XRSession {
        XRSession {
            eventtarget: EventTarget::new_inherited(),
            base_layer: Default::default(),
            // we don't yet support any AR devices
            blend_mode: XREnvironmentBlendMode::Opaque,
            viewer_space: Default::default(),
            session: DomRefCell::new(session),
            frame_requested: Cell::new(false),
            pending_render_state: MutNullableDom::new(None),
            active_render_state: MutDom::new(render_state),

            next_raf_id: Cell::new(0),
            raf_callback_list: DomRefCell::new(vec![]),
            raf_sender: DomRefCell::new(None),
            input_sources: DomRefCell::new(vec![]),
        }
    }

    pub fn new(global: &GlobalScope, session: Session) -> DomRoot<XRSession> {
        let render_state = XRRenderState::new(global, 0.1, 1000.0, None);
        let ret = reflect_dom_object(
            Box::new(XRSession::new_inherited(session, &render_state)),
            global,
            XRSessionBinding::Wrap,
        );
        {
            let mut input_sources = ret.input_sources.borrow_mut();
            for info in ret.session.borrow().initial_inputs() {
                // XXXManishearth we should be able to listen for updates
                // to the input sources
                let input = XRInputSource::new(global, &ret, *info);
                input_sources.push(Dom::from_ref(&input));
            }
        }
        ret
    }

    pub fn with_session<R, F: FnOnce(&Session) -> R>(&self, with: F) -> R {
        let session = self.session.borrow();
        with(&session)
    }

    /// https://immersive-web.github.io/webxr/#xr-animation-frame
    fn raf_callback(&self, (time, frame): (f64, Frame)) {
        // Step 1
        if let Some(pending) = self.pending_render_state.take() {
            // https://immersive-web.github.io/webxr/#apply-the-pending-render-state
            // (Steps 1-4 are implicit)
            // Step 5
            self.active_render_state.set(&pending);
            // Step 6-7: XXXManishearth handle inlineVerticalFieldOfView

            // XXXManishearth handle inline sessions and composition disabled flag
            let layer = pending.GetBaseLayer();
            if let Some(layer) = layer {
                let mut session = self.session.borrow_mut();
                if let Some(layer) = layer.downcast::<XRWebGLLayer>() {
                    session.update_webgl_external_image_api(
                        layer.Context().webgl_sender().webxr_external_image_api(),
                    );
                } else {
                    error!("updateRenderState() called with unknown layer type")
                }
            }
        }

        // Step 2
        let base_layer = match self.active_render_state.get().GetBaseLayer() {
            Some(layer) => layer,
            None => return,
        };

        // Step 3: XXXManishearth handle inline session

        // Step 4-5
        let mut callbacks = mem::replace(&mut *self.raf_callback_list.borrow_mut(), vec![]);

        let frame = XRFrame::new(&self.global(), self, frame);
        // Step 6,7
        frame.set_active(true);
        frame.set_animation_frame(true);

        // Step 8
        for (_, callback) in callbacks.drain(..) {
            if let Some(callback) = callback {
                let _ = callback.Call__(Finite::wrap(time), &frame, ExceptionHandling::Report);
            }
        }

        frame.set_active(false);
        self.session.borrow_mut().render_animation_frame();

        // If the canvas element is attached to the DOM, it is now dirty,
        // and we need to trigger a reflow.
        if let Some(webgl_layer) = base_layer.downcast::<XRWebGLLayer>() {
            webgl_layer
                .Context()
                .Canvas()
                .upcast::<Node>()
                .dirty(NodeDamage::OtherNodeDamage);
        }
    }
}

impl XRSessionMethods for XRSession {
    /// https://immersive-web.github.io/webxr/#dom-xrsession-mode
    fn Mode(&self) -> XRSessionMode {
        XRSessionMode::Immersive_vr
    }

    // https://immersive-web.github.io/webxr/#dom-xrsession-renderstate
    fn RenderState(&self) -> DomRoot<XRRenderState> {
        self.active_render_state.get()
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-updaterenderstate
    fn UpdateRenderState(&self, init: &XRRenderStateInit, _: InCompartment) {
        // XXXManishearth various checks:
        // If session’s ended value is true, throw an InvalidStateError and abort these steps
        // If newState’s baseLayer's was created with an XRSession other than session,
        // throw an InvalidStateError and abort these steps
        // If newState’s inlineVerticalFieldOfView is set and session is an
        // immersive session, throw an InvalidStateError and abort these steps.

        let pending = self
            .pending_render_state
            .or_init(|| self.active_render_state.get().clone_object());
        if let Some(near) = init.depthNear {
            pending.set_depth_near(*near);
        }
        if let Some(far) = init.depthFar {
            pending.set_depth_far(*far);
        }
        if let Some(ref layer) = init.baseLayer {
            pending.set_layer(Some(&layer))
        }
        // XXXManishearth handle inlineVerticalFieldOfView
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-requestanimationframe
    fn RequestAnimationFrame(&self, callback: Rc<XRFrameRequestCallback>) -> i32 {
        #[derive(serde::Serialize, serde::Deserialize)]
        pub struct FrameCallback {
            sender: IpcSender<(f64, Frame)>,
        }

        #[typetag::serde]
        impl webxr_api::FrameRequestCallback for FrameCallback {
            fn callback(&mut self, time: f64, frame: Frame) {
                let _ = self.sender.send((time, frame));
            }
        }

        // queue up RAF callback, obtain ID
        let raf_id = self.next_raf_id.get();
        self.next_raf_id.set(raf_id + 1);
        self.raf_callback_list
            .borrow_mut()
            .push((raf_id, Some(callback)));

        // set up listener for response, if necessary
        if self.raf_sender.borrow().is_none() {
            let this = Trusted::new(self);
            let global = self.global();
            let window = global.as_window();
            let (task_source, canceller) = window
                .task_manager()
                .dom_manipulation_task_source_with_canceller();
            let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
            *self.raf_sender.borrow_mut() = Some(sender);
            ROUTER.add_route(
                receiver.to_opaque(),
                Box::new(move |message| {
                    let this = this.clone();
                    let _ = task_source.queue_with_canceller(
                        task!(xr_raf_callback: move || {
                            this.root().raf_callback(message.to().unwrap());
                        }),
                        &canceller,
                    );
                }),
            );
        }
        let sender = self.raf_sender.borrow().clone().unwrap();

        // request animation frame
        self.session
            .borrow_mut()
            .request_animation_frame(FrameCallback { sender });

        raf_id
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-cancelanimationframe
    fn CancelAnimationFrame(&self, frame: i32) {
        let mut list = self.raf_callback_list.borrow_mut();
        if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
            pair.1 = None;
        }
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-environmentblendmode
    fn EnvironmentBlendMode(&self) -> XREnvironmentBlendMode {
        self.blend_mode
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-requestreferencespace
    fn RequestReferenceSpace(&self, ty: XRReferenceSpaceType, comp: InCompartment) -> Rc<Promise> {
        let p = Promise::new_in_current_compartment(&self.global(), comp);

        // https://immersive-web.github.io/webxr/#create-a-reference-space

        // XXXManishearth reject based on session type
        // https://github.com/immersive-web/webxr/blob/master/spatial-tracking-explainer.md#practical-usage-guidelines

        match ty {
            XRReferenceSpaceType::Bounded_floor | XRReferenceSpaceType::Unbounded => {
                // XXXManishearth eventually support these
                p.reject_error(Error::NotSupported)
            },
            ty => {
                p.resolve_native(&XRReferenceSpace::new(&self.global(), self, ty));
            },
        }

        p
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-getinputsources
    fn GetInputSources(&self) -> Vec<DomRoot<XRInputSource>> {
        self.input_sources
            .borrow()
            .iter()
            .map(|x| DomRoot::from_ref(&**x))
            .collect()
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-end
    fn End(&self) -> Rc<Promise> {
        // XXXManishearth implement device disconnection and session ending
        let p = Promise::new(&self.global());
        p.resolve_native(&());
        p
    }
}

#[derive(Clone, Copy, Debug)]
pub struct ApiSpace;
// The pose of an object in native-space. Should never be exposed.
pub type ApiPose = TypedRigidTransform3D<f32, ApiSpace, webxr_api::Native>;
// The pose of the viewer in some api-space.
pub type ApiViewerPose = TypedRigidTransform3D<f32, webxr_api::Viewer, ApiSpace>;
// A transform between objects in some API-space
pub type ApiRigidTransform = TypedRigidTransform3D<f32, ApiSpace, ApiSpace>;

#[allow(unsafe_code)]
pub fn cast_transform<T, U, V, W>(
    transform: TypedRigidTransform3D<f32, T, U>,
) -> TypedRigidTransform3D<f32, V, W> {
    unsafe { mem::transmute(transform) }
}
<commit_msg>Attach XR event callbacks<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::compartments::InCompartment;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods;
use crate::dom::bindings::codegen::Bindings::XRBinding::XRSessionMode;
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType;
use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateInit;
use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods;
use crate::dom::bindings::codegen::Bindings::XRSessionBinding;
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XREnvironmentBlendMode;
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRFrameRequestCallback;
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRSessionMethods;
use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::XRWebGLLayerMethods;
use crate::dom::bindings::error::Error;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, MutDom, MutNullableDom};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::node::Node;
use crate::dom::node::NodeDamage;
use crate::dom::promise::Promise;
use crate::dom::xrframe::XRFrame;
use crate::dom::xrinputsource::XRInputSource;
use crate::dom::xrlayer::XRLayer;
use crate::dom::xrreferencespace::XRReferenceSpace;
use crate::dom::xrrenderstate::XRRenderState;
use crate::dom::xrspace::XRSpace;
use crate::dom::xrwebgllayer::XRWebGLLayer;
use crate::task_source::TaskSource;
use dom_struct::dom_struct;
use euclid::TypedRigidTransform3D;
use ipc_channel::ipc::IpcSender;
use ipc_channel::router::ROUTER;
use profile_traits::ipc;
use std::cell::Cell;
use std::mem;
use std::rc::Rc;
use webxr_api::{self, Event, Frame, Session};

#[dom_struct]
pub struct XRSession {
    eventtarget: EventTarget,
    base_layer: MutNullableDom<XRLayer>,
    blend_mode: XREnvironmentBlendMode,
    viewer_space: MutNullableDom<XRSpace>,
    #[ignore_malloc_size_of = "defined in webxr"]
    session: DomRefCell<Session>,
    frame_requested: Cell<bool>,
    pending_render_state: MutNullableDom<XRRenderState>,
    active_render_state: MutDom<XRRenderState>,

    next_raf_id: Cell<i32>,
    #[ignore_malloc_size_of = "closures are hard"]
    raf_callback_list: DomRefCell<Vec<(i32, Option<Rc<XRFrameRequestCallback>>)>>,
    #[ignore_malloc_size_of = "defined in ipc-channel"]
    raf_sender: DomRefCell<Option<IpcSender<(f64, Frame)>>>,
    input_sources: DomRefCell<Vec<Dom<XRInputSource>>>,
}

impl XRSession {
    fn new_inherited(session: Session, render_state: &XRRenderState) -> XRSession {
        XRSession {
            eventtarget: EventTarget::new_inherited(),
            base_layer: Default::default(),
            // we don't yet support any AR devices
            blend_mode: XREnvironmentBlendMode::Opaque,
            viewer_space: Default::default(),
            session: DomRefCell::new(session),
            frame_requested: Cell::new(false),
            pending_render_state: MutNullableDom::new(None),
            active_render_state: MutDom::new(render_state),

            next_raf_id: Cell::new(0),
            raf_callback_list: DomRefCell::new(vec![]),
            raf_sender: DomRefCell::new(None),
            input_sources: DomRefCell::new(vec![]),
        }
    }

    pub fn new(global: &GlobalScope, session: Session) -> DomRoot<XRSession> {
        let render_state = XRRenderState::new(global, 0.1, 1000.0, None);
        let ret = reflect_dom_object(
            Box::new(XRSession::new_inherited(session, &render_state)),
            global,
            XRSessionBinding::Wrap,
        );
        {
            let mut input_sources = ret.input_sources.borrow_mut();
            for info in ret.session.borrow().initial_inputs() {
                // XXXManishearth we should be able to listen for updates
                // to the input sources
                let input = XRInputSource::new(global, &ret, *info);
                input_sources.push(Dom::from_ref(&input));
            }
        }
        ret.attach_event_handler();
        ret
    }

    pub fn with_session<R, F: FnOnce(&Session) -> R>(&self, with: F) -> R {
        let session = self.session.borrow();
        with(&session)
    }

    fn attach_event_handler(&self) {
        #[derive(serde::Serialize, serde::Deserialize)]
        pub struct EventCallback {
            sender: IpcSender<Event>,
        }

        #[typetag::serde]
        impl webxr_api::EventCallback for EventCallback {
            fn callback(&mut self, event: Event) {
                let _ = self.sender.send(event);
            }
        }

        let this = Trusted::new(self);
        let global = self.global();
        let window = global.as_window();
        let (task_source, canceller) = window
            .task_manager()
            .dom_manipulation_task_source_with_canceller();
        let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
        ROUTER.add_route(
            receiver.to_opaque(),
            Box::new(move |message| {
                let this = this.clone();
                let _ = task_source.queue_with_canceller(
                    task!(xr_event_callback: move || {
                        this.root().event_callback(message.to().unwrap());
                    }),
                    &canceller,
                );
            }),
        );

        // request animation frame
        self.session
            .borrow_mut()
            .set_event_callback(EventCallback { sender });
    }

    fn event_callback(&self, _: Event) {
        // XXXManishearth tbd
    }

    /// https://immersive-web.github.io/webxr/#xr-animation-frame
    fn raf_callback(&self, (time, frame): (f64, Frame)) {
        // Step 1
        if let Some(pending) = self.pending_render_state.take() {
            // https://immersive-web.github.io/webxr/#apply-the-pending-render-state
            // (Steps 1-4 are implicit)
            // Step 5
            self.active_render_state.set(&pending);
            // Step 6-7: XXXManishearth handle inlineVerticalFieldOfView

            // XXXManishearth handle inline sessions and composition disabled flag
            let layer = pending.GetBaseLayer();
            if let Some(layer) = layer {
                let mut session = self.session.borrow_mut();
                if let Some(layer) = layer.downcast::<XRWebGLLayer>() {
                    session.update_webgl_external_image_api(
                        layer.Context().webgl_sender().webxr_external_image_api(),
                    );
                } else {
                    error!("updateRenderState() called with unknown layer type")
                }
            }
        }

        // Step 2
        let base_layer = match self.active_render_state.get().GetBaseLayer() {
            Some(layer) => layer,
            None => return,
        };

        // Step 3: XXXManishearth handle inline session

        // Step 4-5
        let mut callbacks = mem::replace(&mut *self.raf_callback_list.borrow_mut(), vec![]);

        let frame = XRFrame::new(&self.global(), self, frame);
        // Step 6,7
        frame.set_active(true);
        frame.set_animation_frame(true);

        // Step 8
        for (_, callback) in callbacks.drain(..) {
            if let Some(callback) = callback {
                let _ = callback.Call__(Finite::wrap(time), &frame, ExceptionHandling::Report);
            }
        }

        frame.set_active(false);
        self.session.borrow_mut().render_animation_frame();

        // If the canvas element is attached to the DOM, it is now dirty,
        // and we need to trigger a reflow.
        if let Some(webgl_layer) = base_layer.downcast::<XRWebGLLayer>() {
            webgl_layer
                .Context()
                .Canvas()
                .upcast::<Node>()
                .dirty(NodeDamage::OtherNodeDamage);
        }
    }
}

impl XRSessionMethods for XRSession {
    /// https://immersive-web.github.io/webxr/#dom-xrsession-mode
    fn Mode(&self) -> XRSessionMode {
        XRSessionMode::Immersive_vr
    }

    // https://immersive-web.github.io/webxr/#dom-xrsession-renderstate
    fn RenderState(&self) -> DomRoot<XRRenderState> {
        self.active_render_state.get()
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-updaterenderstate
    fn UpdateRenderState(&self, init: &XRRenderStateInit, _: InCompartment) {
        // XXXManishearth various checks:
        // If session’s ended value is true, throw an InvalidStateError and abort these steps
        // If newState’s baseLayer's was created with an XRSession other than session,
        // throw an InvalidStateError and abort these steps
        // If newState’s inlineVerticalFieldOfView is set and session is an
        // immersive session, throw an InvalidStateError and abort these steps.

        let pending = self
            .pending_render_state
            .or_init(|| self.active_render_state.get().clone_object());
        if let Some(near) = init.depthNear {
            pending.set_depth_near(*near);
        }
        if let Some(far) = init.depthFar {
            pending.set_depth_far(*far);
        }
        if let Some(ref layer) = init.baseLayer {
            pending.set_layer(Some(&layer))
        }
        // XXXManishearth handle inlineVerticalFieldOfView
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-requestanimationframe
    fn RequestAnimationFrame(&self, callback: Rc<XRFrameRequestCallback>) -> i32 {
        #[derive(serde::Serialize, serde::Deserialize)]
        pub struct FrameCallback {
            sender: IpcSender<(f64, Frame)>,
        }

        #[typetag::serde]
        impl webxr_api::FrameRequestCallback for FrameCallback {
            fn callback(&mut self, time: f64, frame: Frame) {
                let _ = self.sender.send((time, frame));
            }
        }

        // queue up RAF callback, obtain ID
        let raf_id = self.next_raf_id.get();
        self.next_raf_id.set(raf_id + 1);
        self.raf_callback_list
            .borrow_mut()
            .push((raf_id, Some(callback)));

        // set up listener for response, if necessary
        if self.raf_sender.borrow().is_none() {
            let this = Trusted::new(self);
            let global = self.global();
            let window = global.as_window();
            let (task_source, canceller) = window
                .task_manager()
                .dom_manipulation_task_source_with_canceller();
            let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
            *self.raf_sender.borrow_mut() = Some(sender);
            ROUTER.add_route(
                receiver.to_opaque(),
                Box::new(move |message| {
                    let this = this.clone();
                    let _ = task_source.queue_with_canceller(
                        task!(xr_raf_callback: move || {
                            this.root().raf_callback(message.to().unwrap());
                        }),
                        &canceller,
                    );
                }),
            );
        }
        let sender = self.raf_sender.borrow().clone().unwrap();

        // request animation frame
        self.session
            .borrow_mut()
            .request_animation_frame(FrameCallback { sender });

        raf_id
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-cancelanimationframe
    fn CancelAnimationFrame(&self, frame: i32) {
        let mut list = self.raf_callback_list.borrow_mut();
        if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
            pair.1 = None;
        }
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-environmentblendmode
    fn EnvironmentBlendMode(&self) -> XREnvironmentBlendMode {
        self.blend_mode
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-requestreferencespace
    fn RequestReferenceSpace(&self, ty: XRReferenceSpaceType, comp: InCompartment) -> Rc<Promise> {
        let p = Promise::new_in_current_compartment(&self.global(), comp);

        // https://immersive-web.github.io/webxr/#create-a-reference-space

        // XXXManishearth reject based on session type
        // https://github.com/immersive-web/webxr/blob/master/spatial-tracking-explainer.md#practical-usage-guidelines

        match ty {
            XRReferenceSpaceType::Bounded_floor | XRReferenceSpaceType::Unbounded => {
                // XXXManishearth eventually support these
                p.reject_error(Error::NotSupported)
            },
            ty => {
                p.resolve_native(&XRReferenceSpace::new(&self.global(), self, ty));
            },
        }

        p
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-getinputsources
    fn GetInputSources(&self) -> Vec<DomRoot<XRInputSource>> {
        self.input_sources
            .borrow()
            .iter()
            .map(|x| DomRoot::from_ref(&**x))
            .collect()
    }

    /// https://immersive-web.github.io/webxr/#dom-xrsession-end
    fn End(&self) -> Rc<Promise> {
        // XXXManishearth implement device disconnection and session ending
        let p = Promise::new(&self.global());
        p.resolve_native(&());
        p
    }
}

#[derive(Clone, Copy, Debug)]
pub struct ApiSpace;
// The pose of an object in native-space. Should never be exposed.
pub type ApiPose = TypedRigidTransform3D<f32, ApiSpace, webxr_api::Native>;
// The pose of the viewer in some api-space.
pub type ApiViewerPose = TypedRigidTransform3D<f32, webxr_api::Viewer, ApiSpace>;
// A transform between objects in some API-space
pub type ApiRigidTransform = TypedRigidTransform3D<f32, ApiSpace, ApiSpace>;

#[allow(unsafe_code)]
pub fn cast_transform<T, U, V, W>(
    transform: TypedRigidTransform3D<f32, T, U>,
) -> TypedRigidTransform3D<f32, V, W> {
    unsafe { mem::transmute(transform) }
}
<|endoftext|>


<commit_before>// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

use super::cluster::{Cluster, Simulator};
use super::node::new_node_cluster;
use super::server::new_server_cluster;
use super::util::{must_get_equal, new_peer};

fn test_partition_write<T: Simulator>(cluster: &mut Cluster<T>) {
    cluster.bootstrap_region().expect("");
    cluster.start();

    let (key, value) = (b"k1", b"v1");
    let region_id = cluster.get_region_id(key);

    // transfer leader to (1, 1)
    cluster.transfer_leader(1, new_peer(1, 1));
    cluster.must_put(key, value);
    must_get_equal(&cluster.engines[&1], key, value);
    assert_eq!(cluster.leader_of_region(region_id), Some(new_peer(1, 1)));

    // leader in majority, partition doesn't affect write/read
    cluster.partition(vec![1, 2, 3], vec![4, 5]);
    cluster.must_put(key, value);
    assert_eq!(cluster.get(key), Some(value.to_vec()));
    assert_eq!(cluster.leader_of_region(region_id), Some(new_peer(1, 1)));
    cluster.reset_transport_hooks();

    // leader in minority, new leader should be elected
    cluster.partition(vec![1, 2], vec![3, 4, 5]);
    assert_eq!(cluster.get(key), Some(value.to_vec()));
    assert!(cluster.leader_of_region(region_id).unwrap().get_id() != 1);
    cluster.must_put(key, b"changed");
    cluster.reset_transport_hooks();

    // when network recover, old leader should sync data
    cluster.must_put(b"k2", b"v2");
    must_get_equal(&cluster.get_engine(1), b"k2", b"v2");
    must_get_equal(&cluster.get_engine(1), key, b"changed");
}

#[test]
fn test_node_partition_write() {
    let mut cluster = new_node_cluster(0, 5);
    test_partition_write(&mut cluster);
}

#[test]
fn test_server_partition_write() {
    let mut cluster = new_server_cluster(0, 5);
    test_partition_write(&mut cluster);
}
<commit_msg>tests/raftstore: partition write testcase fix (#616)<commit_after>// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

use super::cluster::{Cluster, Simulator};
use super::node::new_node_cluster;
use super::server::new_server_cluster;
use super::util::{must_get_equal, new_peer};

fn test_partition_write<T: Simulator>(cluster: &mut Cluster<T>) {
    cluster.bootstrap_region().expect("");
    cluster.start();

    let (key, value) = (b"k1", b"v1");
    let region_id = cluster.get_region_id(key);

    // transfer leader to (1, 1)
    cluster.transfer_leader(1, new_peer(1, 1));
    cluster.must_put(key, value);
    must_get_equal(&cluster.engines[&1], key, value);
    assert_eq!(cluster.leader_of_region(region_id), Some(new_peer(1, 1)));

    // leader in majority, partition doesn't affect write/read
    cluster.partition(vec![1, 2, 3], vec![4, 5]);
    cluster.must_put(key, value);
    assert_eq!(cluster.get(key), Some(value.to_vec()));
    assert_eq!(cluster.leader_of_region(region_id), Some(new_peer(1, 1)));
    cluster.reset_transport_hooks();

    // leader in minority, new leader should be elected
    cluster.partition(vec![1, 2], vec![3, 4, 5]);
    assert_eq!(cluster.get(key), Some(value.to_vec()));
    assert!(cluster.leader_of_region(region_id).unwrap().get_id() != 1);
    cluster.must_put(key, b"changed");
    cluster.reset_transport_hooks();

    // when network recover, old leader should sync data
    cluster.reset_leader_of_region(region_id);
    cluster.must_put(b"k2", b"v2");
    must_get_equal(&cluster.get_engine(1), b"k2", b"v2");
    must_get_equal(&cluster.get_engine(1), key, b"changed");
}

#[test]
fn test_node_partition_write() {
    let mut cluster = new_node_cluster(0, 5);
    test_partition_write(&mut cluster);
}

#[test]
fn test_server_partition_write() {
    let mut cluster = new_server_cluster(0, 5);
    test_partition_write(&mut cluster);
}
<|endoftext|>


<commit_before>//! Tests for `cargo-features` definitions.

use cargo_test_support::{project, registry};

#[cargo_test]
fn feature_required() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the `im-a-teapot` manifest key is unstable and may not work properly in England

Caused by:
  feature `test-dummy-unstable` is required

  consider adding `cargo-features = [\"test-dummy-unstable\"]` to the manifest
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the `im-a-teapot` manifest key is unstable and may not work properly in England

Caused by:
  feature `test-dummy-unstable` is required

  this Cargo does not support nightly features, but if you
  switch to nightly channel you can add
  `cargo-features = [\"test-dummy-unstable\"]` to enable this feature
",
        )
        .run();
}

#[cargo_test]
fn unknown_feature() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["foo"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  unknown cargo feature `foo`
",
        )
        .run();
}

#[cargo_test]
fn stable_feature_warns() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-stable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .with_stderr(
            "\
warning: the cargo feature `test-dummy-stable` has been stabilized in the 1.0 \
release and is no longer necessary to be listed in the manifest
  See https://doc.rust-lang.org/[..]cargo/ for more information about using this feature.
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn allow_features() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("-Zallow-features=test-dummy-unstable build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("-Zallow-features=test-dummy-unstable,print-im-a-teapot -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .with_stdout("im-a-teapot = true")
        .with_stderr("[FINISHED] [..]")
        .run();

    p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: the feature `print-im-a-teapot` is not in the list of allowed features: {\"test-dummy-unstable\"}
",
        )
        .run();

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the feature `test-dummy-unstable` is not in the list of allowed features: {}
",
        )
        .run();
}

#[cargo_test]
fn nightly_feature_requires_nightly() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn nightly_feature_requires_nightly_in_dep() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "b"
                version = "0.0.1"
                authors = []

                [dependencies]
                a = { path = "a" }
            "#,
        )
        .file("src/lib.rs", "")
        .file(
            "a/Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("a/src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[COMPILING] b [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
[ERROR] failed to get `a` as a dependency of package `b v0.0.1 ([..])`

Caused by:
  failed to load source for dependency `a`

Caused by:
  Unable to update [..]

Caused by:
  failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn cant_publish() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn z_flags_rejected() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build -Zprint-im-a-teapot")
        .with_status(101)
        .with_stderr(
            "error: the `-Z` flag is only accepted on the nightly \
             channel of Cargo, but this is the `stable` channel\n\
             See [..]",
        )
        .run();

    p.cargo("build -Zarg")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr("error: unknown `-Z` flag specified: arg")
        .run();

    p.cargo("build -Zprint-im-a-teapot")
        .masquerade_as_nightly_cargo()
        .with_stdout("im-a-teapot = true\n")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn publish_allowed() {
    registry::init();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("publish --token sekrit")
        .masquerade_as_nightly_cargo()
        .run();
}

#[cargo_test]
fn wrong_position() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                cargo-features = ["test-dummy-unstable"]
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("check")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at [..]

Caused by:
  cargo-features = [\"test-dummy-unstable\"] was found in the wrong location, it \
  should be set at the top of Cargo.toml before any tables
",
        )
        .run();
}

#[cargo_test]
fn z_stabilized() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("check -Z cache-messages")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
warning: flag `-Z cache-messages` has been stabilized in the 1.40 release, \
  and is no longer necessary
  Message caching is now always enabled.

[CHECKING] foo [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("check -Z offline")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: flag `-Z offline` has been stabilized in the 1.36 release
  Offline mode is now available via the --offline CLI option

",
        )
        .run();
}
<commit_msg>Test that allowed-features in config works<commit_after>//! Tests for `cargo-features` definitions.

use cargo_test_support::{project, registry};

#[cargo_test]
fn feature_required() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the `im-a-teapot` manifest key is unstable and may not work properly in England

Caused by:
  feature `test-dummy-unstable` is required

  consider adding `cargo-features = [\"test-dummy-unstable\"]` to the manifest
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the `im-a-teapot` manifest key is unstable and may not work properly in England

Caused by:
  feature `test-dummy-unstable` is required

  this Cargo does not support nightly features, but if you
  switch to nightly channel you can add
  `cargo-features = [\"test-dummy-unstable\"]` to enable this feature
",
        )
        .run();
}

#[cargo_test]
fn unknown_feature() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["foo"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  unknown cargo feature `foo`
",
        )
        .run();
}

#[cargo_test]
fn stable_feature_warns() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-stable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .with_stderr(
            "\
warning: the cargo feature `test-dummy-stable` has been stabilized in the 1.0 \
release and is no longer necessary to be listed in the manifest
  See https://doc.rust-lang.org/[..]cargo/ for more information about using this feature.
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn allow_features() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("-Zallow-features=test-dummy-unstable build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("-Zallow-features=test-dummy-unstable,print-im-a-teapot -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .with_stdout("im-a-teapot = true")
        .with_stderr("[FINISHED] [..]")
        .run();

    p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: the feature `print-im-a-teapot` is not in the list of allowed features: {\"test-dummy-unstable\"}
",
        )
        .run();

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the feature `test-dummy-unstable` is not in the list of allowed features: {}
",
        )
        .run();
}

#[cargo_test]
fn allow_features_in_cfg() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file(
            ".cargo/config.toml",
            r#"
                [unstable]
                allow-features = ["test-dummy-unstable", "print-im-a-teapot"]
            "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("-Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .with_stdout("im-a-teapot = true")
        .with_stderr("[FINISHED] [..]")
        .run();

    p.cargo("-Zunstable-options build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: the feature `unstable-options` is not in the list of allowed features: {[..]}
",
        )
        .run();

    // -Zallow-features overrides .cargo/config
    p.cargo("-Zallow-features=test-dummy-unstable -Zprint-im-a-teapot build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: the feature `print-im-a-teapot` is not in the list of allowed features: {\"test-dummy-unstable\"}
",
        )
        .run();

    p.cargo("-Zallow-features= build")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the feature `test-dummy-unstable` is not in the list of allowed features: {}
",
        )
        .run();
}

#[cargo_test]
fn nightly_feature_requires_nightly() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn nightly_feature_requires_nightly_in_dep() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "b"
                version = "0.0.1"
                authors = []

                [dependencies]
                a = { path = "a" }
            "#,
        )
        .file("src/lib.rs", "")
        .file(
            "a/Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("a/src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[COMPILING] b [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
[ERROR] failed to get `a` as a dependency of package `b v0.0.1 ([..])`

Caused by:
  failed to load source for dependency `a`

Caused by:
  Unable to update [..]

Caused by:
  failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn cant_publish() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("build")
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at `[..]`

Caused by:
  the cargo feature `test-dummy-unstable` requires a nightly version of Cargo, \
  but this is the `stable` channel
  See [..]
  See https://doc.rust-lang.org/[..]cargo/reference/unstable.html for more \
  information about using this feature.
",
        )
        .run();
}

#[cargo_test]
fn z_flags_rejected() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
                im-a-teapot = true
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("build -Zprint-im-a-teapot")
        .with_status(101)
        .with_stderr(
            "error: the `-Z` flag is only accepted on the nightly \
             channel of Cargo, but this is the `stable` channel\n\
             See [..]",
        )
        .run();

    p.cargo("build -Zarg")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr("error: unknown `-Z` flag specified: arg")
        .run();

    p.cargo("build -Zprint-im-a-teapot")
        .masquerade_as_nightly_cargo()
        .with_stdout("im-a-teapot = true\n")
        .with_stderr(
            "\
[COMPILING] a [..]
[FINISHED] [..]
",
        )
        .run();
}

#[cargo_test]
fn publish_allowed() {
    registry::init();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                cargo-features = ["test-dummy-unstable"]

                [package]
                name = "a"
                version = "0.0.1"
                authors = []
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("publish --token sekrit")
        .masquerade_as_nightly_cargo()
        .run();
}

#[cargo_test]
fn wrong_position() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.1.0"
                cargo-features = ["test-dummy-unstable"]
            "#,
        )
        .file("src/lib.rs", "")
        .build();
    p.cargo("check")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: failed to parse manifest at [..]

Caused by:
  cargo-features = [\"test-dummy-unstable\"] was found in the wrong location, it \
  should be set at the top of Cargo.toml before any tables
",
        )
        .run();
}

#[cargo_test]
fn z_stabilized() {
    let p = project().file("src/lib.rs", "").build();

    p.cargo("check -Z cache-messages")
        .masquerade_as_nightly_cargo()
        .with_stderr(
            "\
warning: flag `-Z cache-messages` has been stabilized in the 1.40 release, \
  and is no longer necessary
  Message caching is now always enabled.

[CHECKING] foo [..]
[FINISHED] [..]
",
        )
        .run();

    p.cargo("check -Z offline")
        .masquerade_as_nightly_cargo()
        .with_status(101)
        .with_stderr(
            "\
error: flag `-Z offline` has been stabilized in the 1.36 release
  Offline mode is now available via the --offline CLI option

",
        )
        .run();
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2016-2017 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

use glib::Cast;
use regex::Regex;

use webkit2gtk_webextension::{
    DOMCSSStyleDeclarationExt,
    DOMDocument,
    DOMDocumentExt,
    DOMDOMWindowExt,
    DOMElement,
    DOMElementExt,
    DOMEventTarget,
    DOMEventTargetExt,
    DOMHTMLButtonElement,
    DOMHTMLButtonElementExt,
    DOMHTMLCollection,
    DOMHTMLCollectionExt,
    DOMHTMLElement,
    DOMHTMLFieldSetElement,
    DOMHTMLFieldSetElementExtManual,
    DOMHTMLInputElement,
    DOMHTMLInputElementExt,
    DOMHTMLSelectElement,
    DOMHTMLSelectElementExt,
    DOMHTMLTextAreaElement,
    DOMHTMLTextAreaElementExt,
    DOMMouseEvent,
    DOMMouseEventExt,
    DOMNodeExt,
    DOMNodeList,
    DOMNodeListExt,
    WebPage,
    WebPageExt,
};

macro_rules! return_if_disabled {
    ($ty:ty, $element:expr) => {
        if $element.is::<$ty>() {
            if let Ok(element) = $element.clone().downcast::<$ty>() {
                if element.get_disabled() {
                    return false;
                }
            }
        }
    };
}

macro_rules! iter {
    ($name:ident, $list:ident) => {
        /// A `DOMElement` iterator for a node list.
        pub struct $name {
            index: u64,
            node_list: Option<$list>,
        }

        impl $name {
            /// Create a new dom element iterator.
            pub fn new(node_list: Option<$list>) -> Self {
                $name {
                    index: 0,
                    node_list,
                }
            }
        }

        impl Iterator for $name {
            type Item = DOMElement;

            fn next(&mut self) -> Option<Self::Item> {
                match self.node_list {
                    Some(ref list) => {
                        if self.index < list.get_length() {
                            let element = list.item(self.index);
                            self.index += 1;
                            element.and_then(|element| element.downcast::<DOMElement>().ok())
                        }
                        else {
                            None
                        }
                    },
                    None => None,
                }
            }
        }
    };
}

iter!(NodeIter, DOMNodeList);
iter!(ElementIter, DOMHTMLCollection);

#[derive(Debug)]
pub struct Pos {
    pub x: i64,
    pub y: i64,
}

/// Get the body element of the web page.
pub fn get_body(page: &WebPage) -> Option<DOMHTMLElement> {
    page.get_dom_document().and_then(|document|
        document.get_body()
    )
}

/// Get the document element of the web page.
pub fn get_document(page: &WebPage) -> Option<DOMElement> {
    page.get_dom_document().and_then(|document|
        document.get_document_element()
    )
}

/// Get the position of an element relative to the screen.
fn get_offset(element: &DOMElement) -> Pos {
    let document = element.get_owner_document();
    let mut top = 0;
    let mut left = 0;
    let mut element = Some(element.clone());
    let mut in_body = false;
    while let Some(el) = element {
        left += el.get_offset_left() as i64 - el.get_scroll_left();
        top += el.get_offset_top() as i64 - el.get_scroll_top();
        element = el.get_offset_parent();
        if el.get_tag_name() == Some("BODY".to_string()) {
            in_body = true;
        }
    }
    let mut margin_top = 0;
    if in_body {
        // The margin top property only takes effect if the element is relative to the body.
        // This means that static/fixed elements won't be affected by this margin.
        if let Some(document) = document {
            let property = document.get_elements_by_tag_name("html")
                .and_then(|htmls| htmls.item(0))
                .and_then(|html| html.downcast::<DOMElement>().ok())
                .and_then(|html| document.get_default_view().map(|window| (window, html)))
                .and_then(|(window, html)| window.get_computed_style(&html, None))
                .and_then(|style| style.get_property_value("margin-top"));
            if let Some(property) = property {
                if let Ok(value) = property[..property.len() - 2].parse() {
                    margin_top = value;
                }
            }
        }
    }
    Pos {
        x: left,
        y: top + margin_top,
    }
}

/// Get the position of an element relative to the iframe.
fn get_position_from_iframe(document: &DOMDocument, element: &DOMElement) -> Pos {
    let (left, top) =
        if let Some(body) = document.get_body() {
            let left = body.get_scroll_left();
            let top = body.get_scroll_top();
            (left, top)
        }
        else {
            (0, 0)
        };
    let mut pos = get_offset(element);
    pos.x += left;
    pos.y += top;
    pos
}

/// Get the position of an element relative to the page root.
pub fn get_position(element: &DOMElement) -> Option<Pos> {
    let document = wtry_opt!(element.get_owner_document());
    let mut pos = get_position_from_iframe(&document, element);
    let window = wtry_opt!(document.get_default_view());
    let mut frame = window.get_frame_element();
    while let Some(parent_frame) = frame {
        let parent_document = wtry_opt!(parent_frame.get_owner_document());
        let iframe_pos = get_position_from_iframe(&document, &parent_frame);
        pos.x += iframe_pos.x;
        pos.y += iframe_pos.y;
        let window = wtry_opt!(parent_document.get_default_view());
        frame = window.get_frame_element();
    }
    Some(pos)
}

/// Hide an element.
pub fn hide(element: &DOMElement) {
    let style = wtry_opt_no_ret!(element.get_style());
    wtry!(style.set_property("display", "none", ""));
}

/// Check if an input element is enabled.
/// Other element types return true.
pub fn is_enabled(element: &DOMElement) -> bool {
    let is_form_element =
        element.is::<DOMHTMLButtonElement>() ||
        element.is::<DOMHTMLInputElement>() ||
        element.is::<DOMHTMLSelectElement>() ||
        element.is::<DOMHTMLTextAreaElement>();
    if is_form_element {
        let mut element = Some(element.clone());
        while let Some(el) = element {
            if el.get_tag_name() == Some("BODY".to_string()) {
                break;
            }
            return_if_disabled!(DOMHTMLButtonElement, el);
            return_if_disabled!(DOMHTMLInputElement, el);
            return_if_disabled!(DOMHTMLSelectElement, el);
            return_if_disabled!(DOMHTMLTextAreaElement, el);
            return_if_disabled!(DOMHTMLFieldSetElement, el);
            element = el.get_parent_element();
        }
    }
    true
}

/// Check if an element is hidden.
/// This is not exactly the opposite as `is_visible` since `is_hidden` returns false for elements that
/// are visible, but outside the viewport.
pub fn is_hidden(document: &DOMDocument, element: &DOMElement) -> bool {
    let window = unwrap_opt_or_ret!(document.get_default_view(), true);
    let mut element = Some(element.clone());
    while let Some(el) = element {
        if el.get_tag_name() == Some("BODY".to_string()) {
            return false;
        }
        let style = unwrap_opt_or_ret!(window.get_computed_style(&el, None), true);
        if style.get_property_value("display") == Some("none".to_string()) ||
            style.get_property_value("visibility") == Some("hidden".to_string()) ||
            style.get_property_value("opacity") == Some("0".to_string())
        {
            return true;
        }
        element = el.get_offset_parent();
    }
    true
}

/// Check if an element is a text input element (including all its variant like number, tel,
/// search, …).
pub fn is_text_input(element: &DOMElement) -> bool {
    let input_type = element.clone().downcast::<DOMHTMLInputElement>().ok()
        .and_then(|input_element| input_element.get_input_type())
        .unwrap_or_else(|| "text".to_string());
    match input_type.as_ref() {
        "button" | "checkbox" | "color" | "file" | "hidden" | "image" | "radio" | "reset" | "submit" => false,
        _ => true,
    }
}

/// Check if an element is visible and in the viewport.
pub fn is_visible(document: &DOMDocument, element: &DOMElement) -> bool {
    let window = unwrap_opt_or_ret!(document.get_default_view(), false);
    let height = window.get_inner_height();
    let width = window.get_inner_width();
    let pos = get_offset(element);
    // FIXME: use the absolute position (including between frames).
    if pos.x < 0 || pos.x > width || pos.y < 0 || pos.y > height {
        return false;
    }
    let mut element = Some(element.clone());
    while let Some(el) = element {
        let style = unwrap_opt_or_ret!(window.get_computed_style(&el, None), false);
        if style.get_property_value("display") == Some("none".to_string()) ||
            style.get_property_value("visibility") == Some("hidden".to_string()) ||
            style.get_property_value("opacity") == Some("0".to_string())
        {
            return false;
        }
        element = el.get_parent_element();
    }
    true
}

/// Trigger a mouse down event on the element.
pub fn mouse_down(element: &DOMElement) {
    mouse_event("mousedown", element);
}

/* TODO: delete.
/// Trigger a mouse enter event on the element.
pub fn mouse_enter(element: &DOMElement) {
    mouse_event("mouseenter", element);
}*/

/// Trigger a mouse event on the element.
pub fn mouse_event(event_name: &str, element: &DOMElement) {
    let event = wtry_opt_no_ret!(element.get_owner_document()
        .and_then(|document| document.create_event("MouseEvents").ok()));
    let window = wtry_opt_no_ret!(element.get_owner_document()
        .and_then(|document| document.get_default_view()));
    let event = wtry_no_show!(event.downcast::<DOMMouseEvent>());
    // TODO: use the previously hovered element for the last parameter.
    event.init_mouse_event(event_name, true, true, &window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
    let element: DOMEventTarget = element.clone().upcast();
    wtry!(element.dispatch_event(&event));
}

/// Trigger a mouse out event on the element.
pub fn mouse_out(element: &DOMElement) {
    mouse_event("mouseout", element);
}

/// Trigger a mouse over event on the element.
pub fn mouse_over(element: &DOMElement) {
    mouse_event("mouseover", element);
}

/// Show an element.
pub fn show(element: &DOMElement) {
    let style = wtry_opt_no_ret!(element.get_style());
    let _ = wtry!(style.remove_property("display"));
}

/// Lookup dom elements by tag and regex
pub fn match_pattern(document: &DOMDocument, selector: &str, regex: Regex) -> Option<DOMElement> {
    let iter = NodeIter::new(document.get_elements_by_tag_name(selector));

    for element in iter {
        if let Some(text) = element.get_inner_html() {
            if regex.is_match(&text) {
                return Some(element);
            }
        }
    }

    None
}

/// Get all dom elements that match regex
pub fn match_pattern_all(document: &DOMDocument, selector: &str, regex: Regex) -> Vec<DOMElement> {
    let iter = NodeIter::new(document.get_elements_by_tag_name(selector));
    let mut results = Vec::new();

    for element in iter {
        if let Some(text) = element.get_inner_html() {
            if regex.is_match(&text) {
                results.push(element);
            }
        }
    }

    results
}<commit_msg>removed deadcode<commit_after>/*
 * Copyright (c) 2016-2017 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

use glib::Cast;
use regex::Regex;

use webkit2gtk_webextension::{
    DOMCSSStyleDeclarationExt,
    DOMDocument,
    DOMDocumentExt,
    DOMDOMWindowExt,
    DOMElement,
    DOMElementExt,
    DOMEventTarget,
    DOMEventTargetExt,
    DOMHTMLButtonElement,
    DOMHTMLButtonElementExt,
    DOMHTMLCollection,
    DOMHTMLCollectionExt,
    DOMHTMLElement,
    DOMHTMLFieldSetElement,
    DOMHTMLFieldSetElementExtManual,
    DOMHTMLInputElement,
    DOMHTMLInputElementExt,
    DOMHTMLSelectElement,
    DOMHTMLSelectElementExt,
    DOMHTMLTextAreaElement,
    DOMHTMLTextAreaElementExt,
    DOMMouseEvent,
    DOMMouseEventExt,
    DOMNodeExt,
    DOMNodeList,
    DOMNodeListExt,
    WebPage,
    WebPageExt,
};

macro_rules! return_if_disabled {
    ($ty:ty, $element:expr) => {
        if $element.is::<$ty>() {
            if let Ok(element) = $element.clone().downcast::<$ty>() {
                if element.get_disabled() {
                    return false;
                }
            }
        }
    };
}

macro_rules! iter {
    ($name:ident, $list:ident) => {
        /// A `DOMElement` iterator for a node list.
        pub struct $name {
            index: u64,
            node_list: Option<$list>,
        }

        impl $name {
            /// Create a new dom element iterator.
            pub fn new(node_list: Option<$list>) -> Self {
                $name {
                    index: 0,
                    node_list,
                }
            }
        }

        impl Iterator for $name {
            type Item = DOMElement;

            fn next(&mut self) -> Option<Self::Item> {
                match self.node_list {
                    Some(ref list) => {
                        if self.index < list.get_length() {
                            let element = list.item(self.index);
                            self.index += 1;
                            element.and_then(|element| element.downcast::<DOMElement>().ok())
                        }
                        else {
                            None
                        }
                    },
                    None => None,
                }
            }
        }
    };
}

iter!(NodeIter, DOMNodeList);
iter!(ElementIter, DOMHTMLCollection);

#[derive(Debug)]
pub struct Pos {
    pub x: i64,
    pub y: i64,
}

/// Get the body element of the web page.
pub fn get_body(page: &WebPage) -> Option<DOMHTMLElement> {
    page.get_dom_document().and_then(|document|
        document.get_body()
    )
}

/// Get the document element of the web page.
pub fn get_document(page: &WebPage) -> Option<DOMElement> {
    page.get_dom_document().and_then(|document|
        document.get_document_element()
    )
}

/// Get the position of an element relative to the screen.
fn get_offset(element: &DOMElement) -> Pos {
    let document = element.get_owner_document();
    let mut top = 0;
    let mut left = 0;
    let mut element = Some(element.clone());
    let mut in_body = false;
    while let Some(el) = element {
        left += el.get_offset_left() as i64 - el.get_scroll_left();
        top += el.get_offset_top() as i64 - el.get_scroll_top();
        element = el.get_offset_parent();
        if el.get_tag_name() == Some("BODY".to_string()) {
            in_body = true;
        }
    }
    let mut margin_top = 0;
    if in_body {
        // The margin top property only takes effect if the element is relative to the body.
        // This means that static/fixed elements won't be affected by this margin.
        if let Some(document) = document {
            let property = document.get_elements_by_tag_name("html")
                .and_then(|htmls| htmls.item(0))
                .and_then(|html| html.downcast::<DOMElement>().ok())
                .and_then(|html| document.get_default_view().map(|window| (window, html)))
                .and_then(|(window, html)| window.get_computed_style(&html, None))
                .and_then(|style| style.get_property_value("margin-top"));
            if let Some(property) = property {
                if let Ok(value) = property[..property.len() - 2].parse() {
                    margin_top = value;
                }
            }
        }
    }
    Pos {
        x: left,
        y: top + margin_top,
    }
}

/// Get the position of an element relative to the iframe.
fn get_position_from_iframe(document: &DOMDocument, element: &DOMElement) -> Pos {
    let (left, top) =
        if let Some(body) = document.get_body() {
            let left = body.get_scroll_left();
            let top = body.get_scroll_top();
            (left, top)
        }
        else {
            (0, 0)
        };
    let mut pos = get_offset(element);
    pos.x += left;
    pos.y += top;
    pos
}

/// Get the position of an element relative to the page root.
pub fn get_position(element: &DOMElement) -> Option<Pos> {
    let document = wtry_opt!(element.get_owner_document());
    let mut pos = get_position_from_iframe(&document, element);
    let window = wtry_opt!(document.get_default_view());
    let mut frame = window.get_frame_element();
    while let Some(parent_frame) = frame {
        let parent_document = wtry_opt!(parent_frame.get_owner_document());
        let iframe_pos = get_position_from_iframe(&document, &parent_frame);
        pos.x += iframe_pos.x;
        pos.y += iframe_pos.y;
        let window = wtry_opt!(parent_document.get_default_view());
        frame = window.get_frame_element();
    }
    Some(pos)
}

/// Hide an element.
pub fn hide(element: &DOMElement) {
    let style = wtry_opt_no_ret!(element.get_style());
    wtry!(style.set_property("display", "none", ""));
}

/// Check if an input element is enabled.
/// Other element types return true.
pub fn is_enabled(element: &DOMElement) -> bool {
    let is_form_element =
        element.is::<DOMHTMLButtonElement>() ||
        element.is::<DOMHTMLInputElement>() ||
        element.is::<DOMHTMLSelectElement>() ||
        element.is::<DOMHTMLTextAreaElement>();
    if is_form_element {
        let mut element = Some(element.clone());
        while let Some(el) = element {
            if el.get_tag_name() == Some("BODY".to_string()) {
                break;
            }
            return_if_disabled!(DOMHTMLButtonElement, el);
            return_if_disabled!(DOMHTMLInputElement, el);
            return_if_disabled!(DOMHTMLSelectElement, el);
            return_if_disabled!(DOMHTMLTextAreaElement, el);
            return_if_disabled!(DOMHTMLFieldSetElement, el);
            element = el.get_parent_element();
        }
    }
    true
}

/// Check if an element is hidden.
/// This is not exactly the opposite as `is_visible` since `is_hidden` returns false for elements that
/// are visible, but outside the viewport.
pub fn is_hidden(document: &DOMDocument, element: &DOMElement) -> bool {
    let window = unwrap_opt_or_ret!(document.get_default_view(), true);
    let mut element = Some(element.clone());
    while let Some(el) = element {
        if el.get_tag_name() == Some("BODY".to_string()) {
            return false;
        }
        let style = unwrap_opt_or_ret!(window.get_computed_style(&el, None), true);
        if style.get_property_value("display") == Some("none".to_string()) ||
            style.get_property_value("visibility") == Some("hidden".to_string()) ||
            style.get_property_value("opacity") == Some("0".to_string())
        {
            return true;
        }
        element = el.get_offset_parent();
    }
    true
}

/// Check if an element is a text input element (including all its variant like number, tel,
/// search, …).
pub fn is_text_input(element: &DOMElement) -> bool {
    let input_type = element.clone().downcast::<DOMHTMLInputElement>().ok()
        .and_then(|input_element| input_element.get_input_type())
        .unwrap_or_else(|| "text".to_string());
    match input_type.as_ref() {
        "button" | "checkbox" | "color" | "file" | "hidden" | "image" | "radio" | "reset" | "submit" => false,
        _ => true,
    }
}

/// Check if an element is visible and in the viewport.
pub fn is_visible(document: &DOMDocument, element: &DOMElement) -> bool {
    let window = unwrap_opt_or_ret!(document.get_default_view(), false);
    let height = window.get_inner_height();
    let width = window.get_inner_width();
    let pos = get_offset(element);
    // FIXME: use the absolute position (including between frames).
    if pos.x < 0 || pos.x > width || pos.y < 0 || pos.y > height {
        return false;
    }
    let mut element = Some(element.clone());
    while let Some(el) = element {
        let style = unwrap_opt_or_ret!(window.get_computed_style(&el, None), false);
        if style.get_property_value("display") == Some("none".to_string()) ||
            style.get_property_value("visibility") == Some("hidden".to_string()) ||
            style.get_property_value("opacity") == Some("0".to_string())
        {
            return false;
        }
        element = el.get_parent_element();
    }
    true
}

/// Trigger a mouse down event on the element.
pub fn mouse_down(element: &DOMElement) {
    mouse_event("mousedown", element);
}

/* TODO: delete.
/// Trigger a mouse enter event on the element.
pub fn mouse_enter(element: &DOMElement) {
    mouse_event("mouseenter", element);
}*/

/// Trigger a mouse event on the element.
pub fn mouse_event(event_name: &str, element: &DOMElement) {
    let event = wtry_opt_no_ret!(element.get_owner_document()
        .and_then(|document| document.create_event("MouseEvents").ok()));
    let window = wtry_opt_no_ret!(element.get_owner_document()
        .and_then(|document| document.get_default_view()));
    let event = wtry_no_show!(event.downcast::<DOMMouseEvent>());
    // TODO: use the previously hovered element for the last parameter.
    event.init_mouse_event(event_name, true, true, &window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
    let element: DOMEventTarget = element.clone().upcast();
    wtry!(element.dispatch_event(&event));
}

/// Trigger a mouse out event on the element.
pub fn mouse_out(element: &DOMElement) {
    mouse_event("mouseout", element);
}

/// Trigger a mouse over event on the element.
pub fn mouse_over(element: &DOMElement) {
    mouse_event("mouseover", element);
}

/// Show an element.
pub fn show(element: &DOMElement) {
    let style = wtry_opt_no_ret!(element.get_style());
    let _ = wtry!(style.remove_property("display"));
}

/// Lookup dom elements by tag and regex
pub fn match_pattern(document: &DOMDocument, selector: &str, regex: Regex) -> Option<DOMElement> {
    let iter = NodeIter::new(document.get_elements_by_tag_name(selector));

    for element in iter {
        if let Some(text) = element.get_inner_html() {
            if regex.is_match(&text) {
                return Some(element);
            }
        }
    }

    None
}<|endoftext|>


<commit_before>use clap::{Arg, App, ArgGroup, SubCommand};

pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
    app
        .arg(Arg::with_name("id")
            .long("id")
            .short("i")
            .takes_value(true)
            .required(true)
            .help("View this entry at this store path")
            .value_name("ID"))

        .arg(Arg::with_name("view-header")
            .long("header")
            .short("h")
            .takes_value(false)
            .required(false)
            .help("View header"))
        .arg(Arg::with_name("view-content")
            .long("content")
            .short("C")
            .takes_value(false)
            .required(false)
            .help("View content"))

        .subcommand(SubCommand::with_name("view-in")
                   .about("View the entry in ...")
                   .version("0.1")

                   .arg(Arg::with_name("view-in-stdout")
                        .long("stdout")
                        .short("s")
                        .takes_value(false)
                        .required(false)
                        .help("View by printing to stdout"))

                   .arg(Arg::with_name("view-in-ui")
                        .long("ui")
                        .short("u")
                        .takes_value(false)
                        .required(false)
                        .help("View by opening own curses-like UI (default)"))

                   .arg(Arg::with_name("view-in-browser")
                        .long("browser")
                        .short("b")
                        .takes_value(true) // optional, which browser
                        .required(false)
                        .help("View content in $BROWSER (fails if no env variable $BROWSER)")
                        .value_name("BROWSER"))

                   .arg(Arg::with_name("view-in-texteditor")
                        .long("editor")
                        .short("e")
                        .takes_value(true) // optional, which editor
                        .required(false)
                        .help("View content in $EDITOR")
                        .value_name("EDITOR"))

                   .arg(Arg::with_name("view-in-custom")
                        .long("custom")
                        .short("c")
                        .takes_value(true) // non-optional, call-string
                        .required(false)
                        .help("View content in custom program, for example 'libreoffice %e', replace '%e' with entry path")
                        .value_name("PROGRAM"))

                   .group(ArgGroup::with_name("viewer")
                          .args(&["view-in-stdout",
                                  "view-in-ui",
                                  "view-in-browser",
                                  "view-in-texteditor",
                                  "view-in-custom",
                          ])
                          .required(false))
                   )

        .subcommand(SubCommand::with_name("compile")
                   .about("Compile content to other format for viewing, implies that the entry gets copied to /tmp")
                   .version("0.1")
                   .arg(Arg::with_name("from")
                        .long("from")
                        .short("f")
                        .takes_value(true) // "markdown" or "textile" or "restructuredtex"
                        .required(true)
                        .help("Compile from")
                        .value_name("FORMAT"))

                   .arg(Arg::with_name("to")
                        .long("to")
                        .short("t")
                        .takes_value(true) // "html" or "HTML" or ... json maybe?
                        .required(true)
                        .help("Compile to")
                        .value_name("FORMAT"))
                   )
}
<commit_msg>Respecify view-in-editor argument<commit_after>use clap::{Arg, App, ArgGroup, SubCommand};

pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
    app
        .arg(Arg::with_name("id")
            .long("id")
            .short("i")
            .takes_value(true)
            .required(true)
            .help("View this entry at this store path")
            .value_name("ID"))

        .arg(Arg::with_name("view-header")
            .long("header")
            .short("h")
            .takes_value(false)
            .required(false)
            .help("View header"))
        .arg(Arg::with_name("view-content")
            .long("content")
            .short("C")
            .takes_value(false)
            .required(false)
            .help("View content"))

        .subcommand(SubCommand::with_name("view-in")
                   .about("View the entry in ...")
                   .version("0.1")

                   .arg(Arg::with_name("view-in-stdout")
                        .long("stdout")
                        .short("s")
                        .takes_value(false)
                        .required(false)
                        .help("View by printing to stdout"))

                   .arg(Arg::with_name("view-in-ui")
                        .long("ui")
                        .short("u")
                        .takes_value(false)
                        .required(false)
                        .help("View by opening own curses-like UI (default)"))

                   .arg(Arg::with_name("view-in-browser")
                        .long("browser")
                        .short("b")
                        .takes_value(true) // optional, which browser
                        .required(false)
                        .help("View content in $BROWSER (fails if no env variable $BROWSER)")
                        .value_name("BROWSER"))

                   .arg(Arg::with_name("view-in-texteditor")
                        .long("in-editor")
                        .short("e")
                        .takes_value(false)
                        .required(false)
                        .help("View content in $EDITOR (can be passed via --editor)"))

                   .arg(Arg::with_name("view-in-custom")
                        .long("custom")
                        .short("c")
                        .takes_value(true) // non-optional, call-string
                        .required(false)
                        .help("View content in custom program, for example 'libreoffice %e', replace '%e' with entry path")
                        .value_name("PROGRAM"))

                   .group(ArgGroup::with_name("viewer")
                          .args(&["view-in-stdout",
                                  "view-in-ui",
                                  "view-in-browser",
                                  "view-in-texteditor",
                                  "view-in-custom",
                          ])
                          .required(false))
                   )

        .subcommand(SubCommand::with_name("compile")
                   .about("Compile content to other format for viewing, implies that the entry gets copied to /tmp")
                   .version("0.1")
                   .arg(Arg::with_name("from")
                        .long("from")
                        .short("f")
                        .takes_value(true) // "markdown" or "textile" or "restructuredtex"
                        .required(true)
                        .help("Compile from")
                        .value_name("FORMAT"))

                   .arg(Arg::with_name("to")
                        .long("to")
                        .short("t")
                        .takes_value(true) // "html" or "HTML" or ... json maybe?
                        .required(true)
                        .help("Compile to")
                        .value_name("FORMAT"))
                   )
}
<|endoftext|>