/* 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::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior}; use dom::bindings::inheritance::Castable; use dom::bindings::js::Root; use dom::element::Element; use dom::htmlelement::HTMLElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::htmlinputelement::HTMLInputElement; use dom::htmloptionelement::HTMLOptionElement; use dom::node::Node; use dom::window::ScriptHelpers; use euclid::point::Point2D; use euclid::rect::Rect; use euclid::size::Size2D; use ipc_channel::ipc::IpcSender; use js::jsapi::JSContext; use js::jsapi::{HandleValue, RootedValue}; use js::jsval::UndefinedValue; use msg::constellation_msg::{PipelineId, WindowSizeData}; use msg::webdriver_msg::{WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue}; use page::Page; use script_thread::get_page; use std::rc::Rc; use url::Url; use util::str::DOMString; fn find_node_by_unique_id(page: &Rc, pipeline: PipelineId, node_id: String) -> Option> { let page = get_page(&*page, pipeline); let document = page.document(); document.upcast::().traverse_preorder().find(|candidate| candidate.get_unique_id() == node_id) } #[allow(unsafe_code)] pub unsafe fn jsval_to_webdriver(cx: *mut JSContext, val: HandleValue) -> WebDriverJSResult { if val.get().is_undefined() { Ok(WebDriverJSValue::Undefined) } else if val.get().is_boolean() { Ok(WebDriverJSValue::Boolean(val.get().to_boolean())) } else if val.get().is_double() || val.get().is_int32() { Ok(WebDriverJSValue::Number(FromJSValConvertible::from_jsval(cx, val, ()).unwrap())) } else if val.get().is_string() { //FIXME: use jsstring_to_str when jsval grows to_jsstring let string: DOMString = FromJSValConvertible::from_jsval(cx, val, StringificationBehavior::Default).unwrap(); Ok(WebDriverJSValue::String(String::from(string))) } else if val.get().is_null() { Ok(WebDriverJSValue::Null) } else { Err(WebDriverJSError::UnknownType) } } #[allow(unsafe_code)] pub fn handle_execute_script(page: &Rc, pipeline: PipelineId, eval: String, reply: IpcSender) { let page = get_page(&*page, pipeline); let window = page.window(); let result = unsafe { let cx = window.get_cx(); let mut rval = RootedValue::new(cx, UndefinedValue()); window.evaluate_js_on_global_with_result(&eval, rval.handle_mut()); jsval_to_webdriver(cx, rval.handle()) }; reply.send(result).unwrap(); } pub fn handle_execute_async_script(page: &Rc, pipeline: PipelineId, eval: String, reply: IpcSender) { let page = get_page(&*page, pipeline); let window = page.window(); let cx = window.get_cx(); window.set_webdriver_script_chan(Some(reply)); let mut rval = RootedValue::new(cx, UndefinedValue()); window.evaluate_js_on_global_with_result(&eval, rval.handle_mut()); } pub fn handle_get_frame_id(page: &Rc, pipeline: PipelineId, webdriver_frame_id: WebDriverFrameId, reply: IpcSender, ()>>) { let window = match webdriver_frame_id { WebDriverFrameId::Short(_) => { // This isn't supported yet Ok(None) }, WebDriverFrameId::Element(x) => { match find_node_by_unique_id(page, pipeline, x) { Some(ref node) => { match node.downcast::() { Some(ref elem) => Ok(elem.GetContentWindow()), None => Err(()) } }, None => Err(()) } }, WebDriverFrameId::Parent => { let window = page.window(); Ok(window.parent()) } }; let frame_id = window.map(|x| x.map(|x| x.pipeline())); reply.send(frame_id).unwrap() } pub fn handle_find_element_css(page: &Rc, _pipeline: PipelineId, selector: String, reply: IpcSender, ()>>) { reply.send(match page.document().QuerySelector(DOMString::from(selector)) { Ok(node) => { Ok(node.map(|x| x.upcast::().get_unique_id())) } Err(_) => Err(()) }).unwrap(); } pub fn handle_find_elements_css(page: &Rc, _pipeline: PipelineId, selector: String, reply: IpcSender, ()>>) { reply.send(match page.document().QuerySelectorAll(DOMString::from(selector)) { Ok(ref nodes) => { let mut result = Vec::with_capacity(nodes.Length() as usize); for i in 0..nodes.Length() { if let Some(ref node) = nodes.Item(i) { result.push(node.get_unique_id()); } } Ok(result) }, Err(_) => { Err(()) } }).unwrap(); } pub fn handle_focus_element(page: &Rc, pipeline: PipelineId, element_id: String, reply: IpcSender>) { reply.send(match find_node_by_unique_id(page, pipeline, element_id) { Some(ref node) => { match node.downcast::() { Some(ref elem) => { // Need a way to find if this actually succeeded elem.Focus(); Ok(()) } None => Err(()) } }, None => Err(()) }).unwrap(); } pub fn handle_get_active_element(page: &Rc, _pipeline: PipelineId, reply: IpcSender>) { reply.send(page.document().GetActiveElement().map( |elem| elem.upcast::().get_unique_id())).unwrap(); } pub fn handle_get_title(page: &Rc, _pipeline: PipelineId, reply: IpcSender) { reply.send(String::from(page.document().Title())).unwrap(); } pub fn handle_get_rect(page: &Rc, pipeline: PipelineId, element_id: String, reply: IpcSender, ()>>) { reply.send(match find_node_by_unique_id(&*page, pipeline, element_id) { Some(elem) => { // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-calculate-the-absolute-position match elem.downcast::() { Some(html_elem) => { // Step 1 let mut x = 0; let mut y = 0; let mut offset_parent = html_elem.GetOffsetParent(); // Step 2 while let Some(element) = offset_parent { offset_parent = match element.downcast::() { Some(elem) => { x += elem.OffsetLeft(); y += elem.OffsetTop(); elem.GetOffsetParent() }, None => None }; } // Step 3 Ok(Rect::new(Point2D::new(x as f64, y as f64), Size2D::new(html_elem.OffsetWidth() as f64, html_elem.OffsetHeight() as f64))) }, None => Err(()) } }, None => Err(()) }).unwrap(); } pub fn handle_get_text(page: &Rc, pipeline: PipelineId, node_id: String, reply: IpcSender>) { reply.send(match find_node_by_unique_id(&*page, pipeline, node_id) { Some(ref node) => { Ok(node.GetTextContent().map_or("".to_owned(), String::from)) }, None => Err(()) }).unwrap(); } pub fn handle_get_name(page: &Rc, pipeline: PipelineId, node_id: String, reply: IpcSender>) { reply.send(match find_node_by_unique_id(&*page, pipeline, node_id) { Some(node) => { Ok(String::from(node.downcast::().unwrap().TagName())) }, None => Err(()) }).unwrap(); } pub fn handle_get_attribute(page: &Rc, pipeline: PipelineId, node_id: String, name: String, reply: IpcSender, ()>>) { reply.send(match find_node_by_unique_id(&*page, pipeline, node_id) { Some(node) => { Ok(node.downcast::().unwrap().GetAttribute(DOMString::from(name)) .map(String::from)) }, None => Err(()) }).unwrap(); } pub fn handle_get_css(page: &Rc, pipeline: PipelineId, node_id: String, name: String, reply: IpcSender>) { reply.send(match find_node_by_unique_id(&*page, pipeline, node_id) { Some(node) => { let window = page.window(); let elem = node.downcast::().unwrap(); Ok(String::from( window.GetComputedStyle(&elem, None).GetPropertyValue(DOMString::from(name)))) }, None => Err(()) }).unwrap(); } pub fn handle_get_url(page: &Rc, _pipeline: PipelineId, reply: IpcSender) { let document = page.document(); let url = document.url(); reply.send((*url).clone()).unwrap(); } pub fn handle_get_window_size(page: &Rc, _pipeline: PipelineId, reply: IpcSender>) { let window = page.window(); let size = window.window_size(); reply.send(size).unwrap(); } pub fn handle_is_enabled(page: &Rc, pipeline: PipelineId, element_id: String, reply: IpcSender>) { reply.send(match find_node_by_unique_id(page, pipeline, element_id) { Some(ref node) => { match node.downcast::() { Some(elem) => Ok(elem.get_enabled_state()), None => Err(()) } }, None => Err(()) }).unwrap(); } pub fn handle_is_selected(page: &Rc, pipeline: PipelineId, element_id: String, reply: IpcSender>) { reply.send(match find_node_by_unique_id(page, pipeline, element_id) { Some(ref node) => { if let Some(input_element) = node.downcast::() { Ok(input_element.Checked()) } else if let Some(option_element) = node.downcast::() { Ok(option_element.Selected()) } else if let Some(_) = node.downcast::() { Ok(false) // regular elements are not selectable } else { Err(()) } }, None => Err(()) }).unwrap(); }