/* 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::enter_realm; use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods; use crate::dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::conversions::{jsstring_to_str, ConversionResult, FromJSValConvertible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::document::AnimationFrameCallback; use crate::dom::element::Element; use crate::dom::globalscope::GlobalScope; use crate::dom::node::{window_from_node, Node, ShadowIncluding}; use crate::dom::window::Window; use crate::script_thread::Documents; use devtools_traits::TimelineMarkerType; use devtools_traits::{AutoMargins, CachedConsoleMessage, CachedConsoleMessageTypes}; use devtools_traits::{ComputedNodeLayout, ConsoleAPI, PageError}; use devtools_traits::{EvaluateJSReply, Modification, NodeInfo, TimelineMarker}; use ipc_channel::ipc::IpcSender; use js::jsval::UndefinedValue; use js::rust::wrappers::ObjectClassName; use msg::constellation_msg::PipelineId; use std::ffi::CStr; use std::str; use uuid::Uuid; #[allow(unsafe_code)] pub fn handle_evaluate_js(global: &GlobalScope, eval: String, reply: IpcSender) { // global.get_cx() returns a valid `JSContext` pointer, so this is safe. let result = unsafe { let cx = global.get_cx(); let _ac = enter_realm(global); rooted!(in(*cx) let mut rval = UndefinedValue()); global.evaluate_js_on_global_with_result(&eval, rval.handle_mut()); if rval.is_undefined() { EvaluateJSReply::VoidValue } else if rval.is_boolean() { EvaluateJSReply::BooleanValue(rval.to_boolean()) } else if rval.is_double() || rval.is_int32() { EvaluateJSReply::NumberValue( match FromJSValConvertible::from_jsval(*cx, rval.handle(), ()) { Ok(ConversionResult::Success(v)) => v, _ => unreachable!(), }, ) } else if rval.is_string() { EvaluateJSReply::StringValue(String::from(jsstring_to_str(*cx, rval.to_string()))) } else if rval.is_null() { EvaluateJSReply::NullValue } else { assert!(rval.is_object()); rooted!(in(*cx) let obj = rval.to_object()); let class_name = CStr::from_ptr(ObjectClassName(*cx, obj.handle())); let class_name = str::from_utf8(class_name.to_bytes()).unwrap(); EvaluateJSReply::ActorValue { class: class_name.to_owned(), uuid: Uuid::new_v4().to_string(), } } }; reply.send(result).unwrap(); } pub fn handle_get_root_node( documents: &Documents, pipeline: PipelineId, reply: IpcSender>, ) { let info = documents .find_document(pipeline) .map(|document| document.upcast::().summarize()); reply.send(info).unwrap(); } pub fn handle_get_document_element( documents: &Documents, pipeline: PipelineId, reply: IpcSender>, ) { let info = documents .find_document(pipeline) .and_then(|document| document.GetDocumentElement()) .map(|element| element.upcast::().summarize()); reply.send(info).unwrap(); } fn find_node_by_unique_id( documents: &Documents, pipeline: PipelineId, node_id: &str, ) -> Option> { documents.find_document(pipeline).and_then(|document| { document .upcast::() .traverse_preorder(ShadowIncluding::Yes) .find(|candidate| candidate.unique_id() == node_id) }) } pub fn handle_get_children( documents: &Documents, pipeline: PipelineId, node_id: String, reply: IpcSender>>, ) { match find_node_by_unique_id(documents, pipeline, &*node_id) { None => return reply.send(None).unwrap(), Some(parent) => { let children = parent.children().map(|child| child.summarize()).collect(); reply.send(Some(children)).unwrap(); }, }; } pub fn handle_get_layout( documents: &Documents, pipeline: PipelineId, node_id: String, reply: IpcSender>, ) { let node = match find_node_by_unique_id(documents, pipeline, &*node_id) { None => return reply.send(None).unwrap(), Some(found_node) => found_node, }; let elem = node .downcast::() .expect("should be getting layout of element"); let rect = elem.GetBoundingClientRect(); let width = rect.Width() as f32; let height = rect.Height() as f32; let window = window_from_node(&*node); let elem = node .downcast::() .expect("should be getting layout of element"); let computed_style = window.GetComputedStyle(elem, None); reply .send(Some(ComputedNodeLayout { display: String::from(computed_style.Display()), position: String::from(computed_style.Position()), zIndex: String::from(computed_style.ZIndex()), boxSizing: String::from(computed_style.BoxSizing()), autoMargins: determine_auto_margins(&window, &*node), marginTop: String::from(computed_style.MarginTop()), marginRight: String::from(computed_style.MarginRight()), marginBottom: String::from(computed_style.MarginBottom()), marginLeft: String::from(computed_style.MarginLeft()), borderTopWidth: String::from(computed_style.BorderTopWidth()), borderRightWidth: String::from(computed_style.BorderRightWidth()), borderBottomWidth: String::from(computed_style.BorderBottomWidth()), borderLeftWidth: String::from(computed_style.BorderLeftWidth()), paddingTop: String::from(computed_style.PaddingTop()), paddingRight: String::from(computed_style.PaddingRight()), paddingBottom: String::from(computed_style.PaddingBottom()), paddingLeft: String::from(computed_style.PaddingLeft()), width: width, height: height, })) .unwrap(); } fn determine_auto_margins(window: &Window, node: &Node) -> AutoMargins { let style = window.style_query(node.to_trusted_node_address()).unwrap(); let margin = style.get_margin(); AutoMargins { top: margin.margin_top.is_auto(), right: margin.margin_right.is_auto(), bottom: margin.margin_bottom.is_auto(), left: margin.margin_left.is_auto(), } } pub fn handle_get_cached_messages( _pipeline_id: PipelineId, message_types: CachedConsoleMessageTypes, reply: IpcSender>, ) { // TODO: check the messageTypes against a global Cache for console messages and page exceptions let mut messages = Vec::new(); if message_types.contains(CachedConsoleMessageTypes::PAGE_ERROR) { // TODO: make script error reporter pass all reported errors // to devtools and cache them for returning here. let msg = PageError { type_: "PageError".to_owned(), errorMessage: "page error test".to_owned(), sourceName: String::new(), lineText: String::new(), lineNumber: 0, columnNumber: 0, category: String::new(), timeStamp: 0, error: false, warning: false, exception: false, strict: false, private: false, }; messages.push(CachedConsoleMessage::PageError(msg)); } if message_types.contains(CachedConsoleMessageTypes::CONSOLE_API) { // TODO: do for real let msg = ConsoleAPI { type_: "ConsoleAPI".to_owned(), level: "error".to_owned(), filename: "http://localhost/~mihai/mozilla/test.html".to_owned(), lineNumber: 0, functionName: String::new(), timeStamp: 0, private: false, arguments: vec!["console error test".to_owned()], }; messages.push(CachedConsoleMessage::ConsoleAPI(msg)); } reply.send(messages).unwrap(); } pub fn handle_modify_attribute( documents: &Documents, pipeline: PipelineId, node_id: String, modifications: Vec, ) { let node = match find_node_by_unique_id(documents, pipeline, &*node_id) { None => { return warn!( "node id {} for pipeline id {} is not found", &node_id, &pipeline ); }, Some(found_node) => found_node, }; let elem = node .downcast::() .expect("should be getting layout of element"); for modification in modifications { match modification.newValue { Some(string) => { let _ = elem.SetAttribute( DOMString::from(modification.attributeName), DOMString::from(string), ); }, None => elem.RemoveAttribute(DOMString::from(modification.attributeName)), } } } pub fn handle_wants_live_notifications(global: &GlobalScope, send_notifications: bool) { global.set_devtools_wants_updates(send_notifications); } pub fn handle_set_timeline_markers( documents: &Documents, pipeline: PipelineId, marker_types: Vec, reply: IpcSender>, ) { match documents.find_window(pipeline) { None => reply.send(None).unwrap(), Some(window) => window.set_devtools_timeline_markers(marker_types, reply), } } pub fn handle_drop_timeline_markers( documents: &Documents, pipeline: PipelineId, marker_types: Vec, ) { if let Some(window) = documents.find_window(pipeline) { window.drop_devtools_timeline_markers(marker_types); } } pub fn handle_request_animation_frame(documents: &Documents, id: PipelineId, actor_name: String) { if let Some(doc) = documents.find_document(id) { doc.request_animation_frame(AnimationFrameCallback::DevtoolsFramerateTick { actor_name }); } } pub fn handle_reload(documents: &Documents, id: PipelineId) { if let Some(win) = documents.find_window(id) { win.Location().reload_without_origin_check(); } }