use serde_json::Value;
use super::a_class_handlers;
use super::b_class_handlers;
use super::debugger_handlers;
use super::e_class;
use super::error::BridgeError;
use super::servo_backend::ServoBackend;
pub fn dispatch_command(
backend: &dyn ServoBackend,
method: &str,
params: Value,
target_id: &str,
) -> Result<Value, BridgeError> {
let (domain, command) = method
.split_once('.')
.ok_or_else(|| BridgeError::InvalidMethod(method.to_string()))?;
if e_class::is_e_class(domain, command) {
return Err(e_class::not_supported(domain, command));
}
match (domain, command) {
("Page", "navigate") => a_class_handlers::page_navigate(backend, target_id, ¶ms),
("Page", "reload") => a_class_handlers::page_reload(backend, target_id, ¶ms),
("Page", "captureScreenshot") => {
a_class_handlers::page_capture_screenshot(backend, target_id, ¶ms)
}
("Page", "getFrameTree") => {
a_class_handlers::page_get_frame_tree(backend, target_id, ¶ms)
}
("Page", "getNavigationHistory") => {
a_class_handlers::page_get_navigation_history(backend, target_id, ¶ms)
}
("Page", "navigateToHistoryEntry") => {
a_class_handlers::page_navigate_to_history_entry(backend, target_id, ¶ms)
}
("Page", "setContent") => a_class_handlers::page_set_content(backend, target_id, ¶ms),
("Page", "close") => a_class_handlers::page_close(backend, target_id, ¶ms),
("Page", "bringToFront") => {
a_class_handlers::page_bring_to_front(backend, target_id, ¶ms)
}
("Page", "getLayoutMetrics") => {
a_class_handlers::page_get_layout_metrics(backend, target_id, ¶ms)
}
("Runtime", "evaluate") => a_class_handlers::runtime_evaluate(backend, target_id, ¶ms),
("Runtime", "callFunctionOn") => {
a_class_handlers::runtime_call_function_on(backend, target_id, ¶ms)
}
("Runtime", "getProperties") => {
a_class_handlers::runtime_get_properties(backend, target_id, ¶ms)
}
("Runtime", "releaseObject") => {
a_class_handlers::runtime_release_object(backend, target_id, ¶ms)
}
("Runtime", "enable") => a_class_handlers::runtime_enable(backend, target_id, ¶ms),
("Runtime", "disable") => a_class_handlers::runtime_disable(backend, target_id, ¶ms),
("DOM", "getDocument") => a_class_handlers::dom_get_document(backend, target_id, ¶ms),
("DOM", "querySelector") => {
a_class_handlers::dom_query_selector(backend, target_id, ¶ms)
}
("DOM", "querySelectorAll") => {
a_class_handlers::dom_query_selector_all(backend, target_id, ¶ms)
}
("DOM", "getBoxModel") => a_class_handlers::dom_get_box_model(backend, target_id, ¶ms),
("DOM", "resolveNode") => a_class_handlers::dom_resolve_node(backend, target_id, ¶ms),
("DOM", "describeNode") => a_class_handlers::dom_describe_node(backend, target_id, ¶ms),
("DOM", "setAttributeValue") => {
a_class_handlers::dom_set_attribute_value(backend, target_id, ¶ms)
}
("DOM", "removeAttribute") => {
a_class_handlers::dom_remove_attribute(backend, target_id, ¶ms)
}
("DOM", "getOuterHTML") => {
a_class_handlers::dom_get_outer_html(backend, target_id, ¶ms)
}
("DOM", "setOuterHTML") => {
a_class_handlers::dom_set_outer_html(backend, target_id, ¶ms)
}
("DOM", "requestNode") => a_class_handlers::dom_request_node(backend, target_id, ¶ms),
("Network", "enable") => a_class_handlers::network_enable(backend, target_id, ¶ms),
("Network", "disable") => a_class_handlers::network_disable(backend, target_id, ¶ms),
("Network", "getResponseBody") => {
a_class_handlers::network_get_response_body(backend, target_id, ¶ms)
}
("Network", "setCacheDisabled") => {
a_class_handlers::network_set_cache_disabled(backend, target_id, ¶ms)
}
("Input", "dispatchMouseEvent") => {
a_class_handlers::input_dispatch_mouse_event(backend, target_id, ¶ms)
}
("Input", "dispatchKeyEvent") => {
a_class_handlers::input_dispatch_key_event(backend, target_id, ¶ms)
}
("Input", "dispatchTouchEvent") => {
a_class_handlers::input_dispatch_touch_event(backend, target_id, ¶ms)
}
("Input", "setIgnoreInputEvents") => {
a_class_handlers::input_set_ignore_input_events(backend, target_id, ¶ms)
}
("Emulation", "setDeviceMetricsOverride") => {
a_class_handlers::emulation_set_device_metrics_override(backend, target_id, ¶ms)
}
("Emulation", "clearDeviceMetricsOverride") => {
a_class_handlers::emulation_clear_device_metrics_override(backend, target_id, ¶ms)
}
("Emulation", "setUserAgentOverride") => {
a_class_handlers::emulation_set_user_agent_override(backend, target_id, ¶ms)
}
("Emulation", "setGeolocationOverride") => {
a_class_handlers::emulation_set_geolocation_override(backend, target_id, ¶ms)
}
("Target", "getTargets") => {
a_class_handlers::target_get_targets(backend, target_id, ¶ms)
}
("Target", "createTarget") => {
a_class_handlers::target_create_target(backend, target_id, ¶ms)
}
("Target", "closeTarget") => {
a_class_handlers::target_close_target(backend, target_id, ¶ms)
}
("Target", "attachToTarget") => {
a_class_handlers::target_attach_to_target(backend, target_id, ¶ms)
}
("Target", "detachFromTarget") => {
a_class_handlers::target_detach_from_target(backend, target_id, ¶ms)
}
("Target", "setAutoAttach") => {
a_class_handlers::target_set_auto_attach(backend, target_id, ¶ms)
}
("CSS", "getComputedStyleForNode") => {
a_class_handlers::css_get_computed_style_for_node(backend, target_id, ¶ms)
}
("CSS", "getMatchedStylesForNode") => {
a_class_handlers::css_get_matched_styles_for_node(backend, target_id, ¶ms)
}
("Debugger", "enable") => debugger_handlers::debugger_enable(backend, target_id, ¶ms),
("Debugger", "disable") => debugger_handlers::debugger_disable(backend, target_id, ¶ms),
("Debugger", "setBreakpoint") => {
debugger_handlers::debugger_set_breakpoint(backend, target_id, ¶ms)
}
("Debugger", "setBreakpointByUrl") => {
debugger_handlers::debugger_set_breakpoint_by_url(backend, target_id, ¶ms)
}
("Debugger", "removeBreakpoint") => {
debugger_handlers::debugger_remove_breakpoint(backend, target_id, ¶ms)
}
("Debugger", "pause") => debugger_handlers::debugger_pause(backend, target_id, ¶ms),
("Debugger", "resume") => debugger_handlers::debugger_resume(backend, target_id, ¶ms),
("Debugger", "stepOver") => {
debugger_handlers::debugger_step_over(backend, target_id, ¶ms)
}
("Debugger", "stepInto") => {
debugger_handlers::debugger_step_into(backend, target_id, ¶ms)
}
("Debugger", "stepOut") => {
debugger_handlers::debugger_step_out(backend, target_id, ¶ms)
}
("Debugger", "evaluateOnCallFrame") => {
debugger_handlers::debugger_evaluate_on_call_frame(backend, target_id, ¶ms)
}
("Debugger", "getPossibleBreakpoints") => {
debugger_handlers::debugger_get_possible_breakpoints(backend, target_id, ¶ms)
}
("Debugger", "getScriptSource") => {
debugger_handlers::debugger_get_script_source(backend, target_id, ¶ms)
}
("Debugger", "setBreakpointsActive") => {
debugger_handlers::debugger_set_breakpoints_active(backend, target_id, ¶ms)
}
("Page", "title") => b_class_handlers::page_title(backend, target_id, ¶ms),
("Page", "url") => b_class_handlers::page_url(backend, target_id, ¶ms),
("Page", "content") => b_class_handlers::page_content(backend, target_id, ¶ms),
("Page", "viewport") => b_class_handlers::page_viewport(backend, target_id, ¶ms),
("Page", "setViewport") => b_class_handlers::page_set_viewport(backend, target_id, ¶ms),
("Page", "opener") => b_class_handlers::page_opener(backend, target_id, ¶ms),
("Page", "frames") => b_class_handlers::page_frames(backend, target_id, ¶ms),
("Page", "mainFrame") => b_class_handlers::page_main_frame(backend, target_id, ¶ms),
("Page", "setDefaultNavigationTimeout") => {
b_class_handlers::page_set_default_navigation_timeout(backend, target_id, ¶ms)
}
("Page", "setDefaultTimeout") => {
b_class_handlers::page_set_default_timeout(backend, target_id, ¶ms)
}
("Page", "waitForLoadState") => {
b_class_handlers::page_wait_for_load_state(backend, target_id, ¶ms)
}
("Page", "waitForURL") => b_class_handlers::page_wait_for_url(backend, target_id, ¶ms),
("Page", "waitForRequest") => {
b_class_handlers::page_wait_for_request(backend, target_id, ¶ms)
}
("Page", "waitForResponse") => {
b_class_handlers::page_wait_for_response(backend, target_id, ¶ms)
}
("Page", "waitForEvent") => {
b_class_handlers::page_wait_for_event(backend, target_id, ¶ms)
}
("Page", "goBack") => b_class_handlers::page_go_back(backend, target_id, ¶ms),
("Page", "goForward") => b_class_handlers::page_go_forward(backend, target_id, ¶ms),
("Page", "emulateMedia") => {
b_class_handlers::page_emulate_media(backend, target_id, ¶ms)
}
("Page", "addScriptTag") => {
b_class_handlers::page_add_script_tag(backend, target_id, ¶ms)
}
("Page", "addStyleTag") => {
b_class_handlers::page_add_style_tag(backend, target_id, ¶ms)
}
("Page", "exposeFunction") => {
b_class_handlers::page_expose_function(backend, target_id, ¶ms)
}
("Page", "pdf") => b_class_handlers::page_pdf(backend, target_id, ¶ms),
("Page", "screenshot") => b_class_handlers::page_screenshot(backend, target_id, ¶ms),
("Page", "tap") => b_class_handlers::page_tap(backend, target_id, ¶ms),
("Page", "hover") => b_class_handlers::page_hover(backend, target_id, ¶ms),
("Page", "focus") => b_class_handlers::page_focus(backend, target_id, ¶ms),
("Page", "type") => b_class_handlers::page_type(backend, target_id, ¶ms),
("Page", "fill") => b_class_handlers::page_fill(backend, target_id, ¶ms),
("Page", "press") => b_class_handlers::page_press(backend, target_id, ¶ms),
("Page", "check") => b_class_handlers::page_check(backend, target_id, ¶ms),
("Page", "uncheck") => b_class_handlers::page_uncheck(backend, target_id, ¶ms),
("Page", "selectOption") => {
b_class_handlers::page_select_option(backend, target_id, ¶ms)
}
("Page", "setInputFiles") => {
b_class_handlers::page_set_input_files(backend, target_id, ¶ms)
}
("Page", "requestGC") => b_class_handlers::page_request_gc(backend, target_id, ¶ms),
("ElementHandle", "click") => b_class_handlers::page_tap(backend, target_id, ¶ms),
("ElementHandle", "contentFrame") => {
b_class_handlers::element_content_frame(backend, target_id, ¶ms)
}
("ElementHandle", "ownerFrame") => {
b_class_handlers::element_owner_frame(backend, target_id, ¶ms)
}
("ElementHandle", "getAttribute") => {
b_class_handlers::element_get_attribute(backend, target_id, ¶ms)
}
("ElementHandle", "innerHTML") => {
b_class_handlers::element_inner_html(backend, target_id, ¶ms)
}
("ElementHandle", "innerText") => {
b_class_handlers::element_inner_text(backend, target_id, ¶ms)
}
("ElementHandle", "textContent") => {
b_class_handlers::element_text_content(backend, target_id, ¶ms)
}
("ElementHandle", "isChecked") => {
b_class_handlers::element_is_checked(backend, target_id, ¶ms)
}
("ElementHandle", "isDisabled") => {
b_class_handlers::element_is_disabled(backend, target_id, ¶ms)
}
("ElementHandle", "isEditable") => {
b_class_handlers::element_is_editable(backend, target_id, ¶ms)
}
("ElementHandle", "isEnabled") => {
b_class_handlers::element_is_enabled(backend, target_id, ¶ms)
}
("ElementHandle", "isHidden") => {
b_class_handlers::element_is_hidden(backend, target_id, ¶ms)
}
("ElementHandle", "isVisible") => {
b_class_handlers::element_is_visible(backend, target_id, ¶ms)
}
("ElementHandle", "scrollIntoViewIfNeeded") => {
b_class_handlers::element_scroll_into_view(backend, target_id, ¶ms)
}
("ElementHandle", "waitForElementState") => {
b_class_handlers::element_wait_for_element_state(backend, target_id, ¶ms)
}
("ElementHandle", "waitForSelector") => {
b_class_handlers::element_wait_for_selector(backend, target_id, ¶ms)
}
("JSHandle", "asElement") => {
b_class_handlers::js_handle_as_element(backend, target_id, ¶ms)
}
("JSHandle", "dispose") => b_class_handlers::js_handle_dispose(backend, target_id, ¶ms),
("JSHandle", "evaluate") => {
b_class_handlers::js_handle_evaluate(backend, target_id, ¶ms)
}
("JSHandle", "evaluateHandle") => {
b_class_handlers::js_handle_evaluate_handle(backend, target_id, ¶ms)
}
("JSHandle", "getProperties") => {
b_class_handlers::js_handle_get_properties(backend, target_id, ¶ms)
}
("JSHandle", "getProperty") => {
b_class_handlers::js_handle_get_property(backend, target_id, ¶ms)
}
("JSHandle", "jsonValue") => {
b_class_handlers::js_handle_json_value(backend, target_id, ¶ms)
}
_ => Err(BridgeError::MethodNotFound(method.to_string())),
}
}
#[cfg(test)]
mod tests {
use super::super::servo_backend::MockServoBackend;
use super::*;
use serde_json::json;
fn empty_params() -> Value {
Value::Object(Default::default())
}
#[test]
fn dispatch_page_navigate_a_class() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Page.navigate", json!({"url":"https://x"}), "1").unwrap();
assert_eq!(r["frameId"], "FRAME_0");
}
#[test]
fn dispatch_runtime_evaluate_a_class() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Runtime.evaluate", json!({"expression":"1"}), "1").unwrap();
assert_eq!(r["result"]["type"], "string");
}
#[test]
fn dispatch_dom_query_selector_a_class() {
let b = MockServoBackend::new();
let r = dispatch_command(
&b,
"DOM.querySelector",
json!({"nodeId":1,"selector":"div"}),
"1",
)
.unwrap();
assert_eq!(r["nodeId"], 2);
}
#[test]
fn dispatch_target_create_target_a_class() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Target.createTarget", json!({"url":"x"}), "1").unwrap();
assert!(r["targetId"].is_string());
}
#[test]
fn dispatch_heap_profiler_e_class() {
let b = MockServoBackend::new();
let err =
dispatch_command(&b, "HeapProfiler.takeHeapSnapshot", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::NotSupported(_)));
assert_eq!(err.cdp_error_code(), -32601);
}
#[test]
fn dispatch_profiler_enable_e_class() {
let b = MockServoBackend::new();
let err = dispatch_command(&b, "Profiler.enable", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::NotSupported(_)));
}
#[test]
fn dispatch_dom_storage_e_class() {
let b = MockServoBackend::new();
let err =
dispatch_command(&b, "DOMStorage.getDOMStorageItems", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::NotSupported(_)));
}
#[test]
fn dispatch_indexed_db_e_class() {
let b = MockServoBackend::new();
let err = dispatch_command(&b, "IndexedDB.requestDatabaseNames", empty_params(), "1")
.unwrap_err();
assert!(matches!(err, BridgeError::NotSupported(_)));
}
#[test]
fn dispatch_service_worker_e_class() {
let b = MockServoBackend::new();
let err = dispatch_command(&b, "ServiceWorker.enable", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::NotSupported(_)));
}
#[test]
fn dispatch_page_print_to_pdf_e_class() {
let b = MockServoBackend::new();
let err = dispatch_command(&b, "Page.printToPDF", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::NotSupported(_)));
}
#[test]
fn dispatch_debugger_pause_routes_to_backend() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Debugger.pause", empty_params(), "1").unwrap();
assert!(r.as_object().unwrap().is_empty());
let log = b.call_log.lock().unwrap();
assert!(log
.iter()
.any(|(_, m, p)| m == "debugger_pause" && p == "Interrupt"));
}
#[test]
fn dispatch_debugger_enable_returns_debugger_id() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Debugger.enable", empty_params(), "1").unwrap();
assert_eq!(r["debuggerId"], 1);
}
#[test]
fn dispatch_debugger_set_breakpoint_by_url_routes() {
let b = MockServoBackend::new();
let r = dispatch_command(
&b,
"Debugger.setBreakpointByUrl",
json!({"url":"x.js","lineNumber":5,"columnNumber":0}),
"1",
)
.unwrap();
assert!(r["breakpointId"].is_string());
}
#[test]
fn dispatch_debugger_step_over_routes_to_resume_next() {
let b = MockServoBackend::new();
dispatch_command(&b, "Debugger.stepOver", empty_params(), "1").unwrap();
let log = b.call_log.lock().unwrap();
assert!(log
.iter()
.any(|(_, m, p)| m == "debugger_resume" && p == "Resume(next)"));
}
#[test]
fn dispatch_performance_metrics_e_class() {
let b = MockServoBackend::new();
let err = dispatch_command(&b, "Performance.getMetrics", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::NotSupported(_)));
}
#[test]
fn dispatch_page_title_b_class_routes_to_handler() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Page.title", empty_params(), "1").unwrap();
let v = r["result"]["value"].as_str().unwrap();
assert!(v.contains("(function(){"));
assert!(v.contains("return document.title;"));
}
#[test]
fn dispatch_page_url_b_class_routes_to_handler() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Page.url", empty_params(), "1").unwrap();
let v = r["result"]["value"].as_str().unwrap();
assert!(v.contains("return location.href;"));
}
#[test]
fn dispatch_page_content_b_class_routes_to_handler() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Page.content", empty_params(), "1").unwrap();
let v = r["result"]["value"].as_str().unwrap();
assert!(v.contains("document.documentElement.outerHTML"));
}
#[test]
fn dispatch_page_viewport_b_class_routes_to_handler() {
let b = MockServoBackend::new();
let r = dispatch_command(&b, "Page.viewport", empty_params(), "1").unwrap();
assert!(r["width"].is_number());
}
#[test]
fn dispatch_element_handle_click_b_class_routes_to_tap() {
let b = MockServoBackend::new();
let r =
dispatch_command(&b, "ElementHandle.click", json!({"selector":"button"}), "1").unwrap();
let v = r["result"]["value"].as_str().unwrap();
assert!(v.contains("var s=__args[0]"));
}
#[test]
fn dispatch_invalid_method_no_dot_returns_invalid_method() {
let b = MockServoBackend::new();
let err = dispatch_command(&b, "noDotHere", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::InvalidMethod(_)));
}
#[test]
fn dispatch_unknown_method_returns_method_not_found() {
let b = MockServoBackend::new();
let err = dispatch_command(&b, "Unknown.foo", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::MethodNotFound(_)));
}
#[test]
fn dispatch_unknown_method_in_known_domain_returns_method_not_found() {
let b = MockServoBackend::new();
let err = dispatch_command(&b, "Page.totallyBogus", empty_params(), "1").unwrap_err();
assert!(matches!(err, BridgeError::MethodNotFound(_)));
}
}