#include "picker_state.h" #include "cdp_ws.h" #include "py_subprocess.h" // for py_resolve_registry_root // crude_json del vendor imgui-node-editor (ya linkado por CMakeLists.txt). #include "crude_json.h" #include #include #include #include #include #include namespace navegator { namespace { std::mutex g_mu; std::unique_ptr g_ws; std::atomic g_active{false}; std::atomic g_stop_pump{false}; std::thread g_pump_thread; PickedElement g_last; std::string slurp(const std::string& path) { std::ifstream f(path, std::ios::binary); if (!f) return ""; std::ostringstream ss; ss << f.rdbuf(); return ss.str(); } // JS-string-literal escape (NO JSON encoding — el JSON wrapper se hace al construir params). std::string js_str_escape(const std::string& s) { std::string out; out.reserve(s.size() + 8); for (char c : s) { switch (c) { case '\\': out += "\\\\"; break; case '"': out += "\\\""; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: if ((unsigned char)c < 0x20) { char buf[8]; std::snprintf(buf, sizeof(buf), "\\u%04x", (unsigned)c); out += buf; } else out += c; } } return out; } void pump_loop() { while (!g_stop_pump.load()) { std::vector msgs; { std::lock_guard lk(g_mu); if (!g_ws || !g_ws->is_connected()) break; msgs = g_ws->drain(64); } for (const auto& m : msgs) { crude_json::value v = crude_json::value::parse(m); if (!v.is_object()) continue; // Filtrar method=Runtime.consoleAPICalled, args[0].value=="__fn_picked__" if (!v.contains("method")) continue; const auto& method = v["method"]; if (!method.is_string()) continue; if (method.get() != "Runtime.consoleAPICalled") continue; if (!v.contains("params")) continue; const auto& params = v["params"]; if (!params.is_object() || !params.contains("args")) continue; const auto& args = params["args"]; if (!args.is_array()) continue; const auto& args_arr = args.get(); if (args_arr.size() < 2) continue; const auto& a0 = args_arr[0]; if (!a0.is_object() || !a0.contains("value")) continue; const auto& v0 = a0["value"]; if (!v0.is_string() || v0.get() != "__fn_picked__") continue; const auto& a1 = args_arr[1]; if (!a1.is_object() || !a1.contains("value")) continue; const auto& v1 = a1["value"]; // v1.value puede ser string (JSON serializado) o un objeto. El JS // hace console.log("__fn_picked__", JSON.stringify(payload)). std::string payload; if (v1.is_string()) payload = v1.get(); else payload = v1.dump(); crude_json::value p = crude_json::value::parse(payload); if (!p.is_object()) continue; PickedElement el; auto get = [&](const char* k) -> std::string { if (!p.contains(k)) return ""; const auto& x = p[k]; if (x.is_string()) return x.get(); if (x.is_null()) return ""; return x.dump(); }; el.selector = get("selector"); el.xpath = get("xpath"); el.tag = get("tag"); el.text = get("text"); if (p.contains("rect")) { el.rect_str = p["rect"].dump(); } el.valid = true; { std::lock_guard lk(g_mu); g_last = std::move(el); } } std::this_thread::sleep_for(std::chrono::milliseconds(80)); } g_active.store(false); } } // anon std::string picker_load_js() { std::string root = py_resolve_registry_root(); if (root.empty()) return ""; #ifdef _WIN32 std::string path = root + "\\functions\\browser\\cdp_pick_element_js.js"; #else std::string path = root + "/functions/browser/cdp_pick_element_js.js"; #endif return slurp(path); } std::string picker_start(int /*port*/, const std::string& /*tab_id*/, const std::string& ws_url) { if (ws_url.empty()) return "no ws_url"; picker_stop(); std::string js = picker_load_js(); if (js.empty()) return "could not load cdp_pick_element_js.js (set FN_REGISTRY_ROOT)"; std::string host, path; int p = 0; if (!CdpWs::parse_ws_url(ws_url, host, p, path)) return "invalid ws_url"; auto ws = std::make_unique(); CdpWsConfig cfg; cfg.host = host; cfg.port = p; cfg.path = path; cfg.timeout_ms = 5000; std::string err; if (!ws->connect(cfg, &err)) return "ws connect failed: " + err; // Enable Runtime (necesario para consoleAPICalled). ws->send_command("Runtime.enable", ""); // Inyectar el JS via Runtime.evaluate. expression es el codigo. // El payload del JS termina con un IIFE; envolvemos en wrapper sin returnByValue. std::ostringstream params; params << "{\"expression\":\"" << js_str_escape(js) << "\",\"includeCommandLineAPI\":true" << ",\"awaitPromise\":false" << ",\"returnByValue\":false}"; ws->send_command("Runtime.evaluate", params.str()); { std::lock_guard lk(g_mu); g_ws = std::move(ws); } g_stop_pump.store(false); g_active.store(true); g_pump_thread = std::thread(pump_loop); return ""; } void picker_stop() { g_stop_pump.store(true); { std::lock_guard lk(g_mu); if (g_ws) g_ws->close(); } if (g_pump_thread.joinable()) g_pump_thread.join(); { std::lock_guard lk(g_mu); g_ws.reset(); } g_active.store(false); } bool picker_is_active() { return g_active.load(); } PickedElement picker_last() { std::lock_guard lk(g_mu); return g_last; } void picker_clear_last() { std::lock_guard lk(g_mu); g_last = PickedElement{}; } } // namespace navegator