use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{define_class, msg_send, DefinedClass, MainThreadMarker, MainThreadOnly};
use objc2_foundation::{NSObject, NSObjectProtocol, NSString};
use objc2_web_kit::{
WKScriptMessage, WKScriptMessageHandler, WKUserContentController, WKUserScript,
WKUserScriptInjectionTime,
};
use crate::backend::inspector_bridge::{
self, InspectorSlots, NetworkIngestSlot, CONSOLE_HANDLER, NETWORK_HANDLER,
};
#[derive(Copy, Clone, Debug)]
enum Channel {
Console,
Network,
}
pub(super) struct HandlerIvars {
slots: InspectorSlots,
channel: Channel,
}
define_class!(
#[unsafe(super = NSObject)]
#[thread_kind = MainThreadOnly]
#[name = "VsInspectorMessageHandler"]
#[ivars = HandlerIvars]
pub(super) struct InspectorMsgHandler;
impl InspectorMsgHandler {
#[unsafe(method(userContentController:didReceiveScriptMessage:))]
fn did_receive(
&self,
_ucc: &WKUserContentController,
message: &WKScriptMessage,
) {
let body = unsafe { message.body() };
let Ok(ns_str) = body.downcast::<NSString>() else {
return;
};
let json = ns_str.to_string();
let ivars = self.ivars();
match ivars.channel {
Channel::Console => {
let mut buf = ivars.slots.console.borrow_mut();
inspector_bridge::ingest_console(&mut buf, &json);
}
Channel::Network => {
let mut entries = ivars.slots.network.borrow_mut();
let mut details = ivars.slots.details.borrow_mut();
let mut pending = ivars.slots.pending.borrow_mut();
inspector_bridge::ingest_network(
NetworkIngestSlot {
entries: &mut entries,
details: &mut details,
pending: &mut pending,
},
&json,
);
}
}
}
}
unsafe impl NSObjectProtocol for InspectorMsgHandler {}
unsafe impl WKScriptMessageHandler for InspectorMsgHandler {}
);
impl InspectorMsgHandler {
fn new(mtm: MainThreadMarker, slots: InspectorSlots, channel: Channel) -> Retained<Self> {
let this = Self::alloc(mtm).set_ivars(HandlerIvars { slots, channel });
unsafe { msg_send![super(this), init] }
}
}
pub(super) fn install(
mtm: MainThreadMarker,
ucc: &WKUserContentController,
slots: &InspectorSlots,
) -> bool {
if std::env::var_os("VS_DISABLE_INSPECTOR").is_some() {
return false;
}
let source = NSString::from_str(inspector_bridge::SCRIPT);
let user_script = unsafe {
WKUserScript::initWithSource_injectionTime_forMainFrameOnly(
WKUserScript::alloc(mtm),
&source,
WKUserScriptInjectionTime::AtDocumentStart,
false,
)
};
unsafe { ucc.addUserScript(&user_script) };
let console_h = InspectorMsgHandler::new(mtm, slots.clone(), Channel::Console);
let network_h = InspectorMsgHandler::new(mtm, slots.clone(), Channel::Network);
let console_proto: &ProtocolObject<dyn WKScriptMessageHandler> =
ProtocolObject::from_ref(&*console_h);
let network_proto: &ProtocolObject<dyn WKScriptMessageHandler> =
ProtocolObject::from_ref(&*network_h);
let console_name = NSString::from_str(CONSOLE_HANDLER);
let network_name = NSString::from_str(NETWORK_HANDLER);
unsafe { ucc.addScriptMessageHandler_name(console_proto, &console_name) };
unsafe { ucc.addScriptMessageHandler_name(network_proto, &network_name) };
true
}