summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/node/lib.rs26
-rw-r--r--ext/node/ops/inspector.rs161
-rw-r--r--ext/node/ops/mod.rs1
-rw-r--r--ext/node/polyfills/inspector.js210
-rw-r--r--ext/node/polyfills/inspector.ts82
-rw-r--r--ext/node/polyfills/inspector/promises.js20
6 files changed, 416 insertions, 84 deletions
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index b08b0493b..9ca21e994 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -47,6 +47,11 @@ pub trait NodePermissions {
url: &Url,
api_name: &str,
) -> Result<(), PermissionCheckError>;
+ fn check_net(
+ &mut self,
+ host: (&str, Option<u16>),
+ api_name: &str,
+ ) -> Result<(), PermissionCheckError>;
#[must_use = "the resolved return value to mitigate time-of-check to time-of-use issues"]
#[inline(always)]
fn check_read(
@@ -90,6 +95,14 @@ impl NodePermissions for deno_permissions::PermissionsContainer {
deno_permissions::PermissionsContainer::check_net_url(self, url, api_name)
}
+ fn check_net(
+ &mut self,
+ host: (&str, Option<u16>),
+ api_name: &str,
+ ) -> Result<(), PermissionCheckError> {
+ deno_permissions::PermissionsContainer::check_net(self, &host, api_name)
+ }
+
#[inline(always)]
fn check_read_with_api_name(
&mut self,
@@ -398,6 +411,15 @@ deno_core::extension!(deno_node,
ops::process::op_node_process_kill,
ops::process::op_process_abort,
ops::tls::op_get_root_certificates,
+ ops::inspector::op_inspector_open<P>,
+ ops::inspector::op_inspector_close,
+ ops::inspector::op_inspector_url,
+ ops::inspector::op_inspector_wait,
+ ops::inspector::op_inspector_connect<P>,
+ ops::inspector::op_inspector_dispatch,
+ ops::inspector::op_inspector_disconnect,
+ ops::inspector::op_inspector_emit_protocol_event,
+ ops::inspector::op_inspector_enabled,
],
esm_entry_point = "ext:deno_node/02_init.js",
esm = [
@@ -606,8 +628,8 @@ deno_core::extension!(deno_node,
"node:http" = "http.ts",
"node:http2" = "http2.ts",
"node:https" = "https.ts",
- "node:inspector" = "inspector.ts",
- "node:inspector/promises" = "inspector.ts",
+ "node:inspector" = "inspector.js",
+ "node:inspector/promises" = "inspector/promises.js",
"node:module" = "01_require.js",
"node:net" = "net.ts",
"node:os" = "os.ts",
diff --git a/ext/node/ops/inspector.rs b/ext/node/ops/inspector.rs
new file mode 100644
index 000000000..34a7e004c
--- /dev/null
+++ b/ext/node/ops/inspector.rs
@@ -0,0 +1,161 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+use crate::NodePermissions;
+use deno_core::anyhow::Error;
+use deno_core::error::generic_error;
+use deno_core::futures::channel::mpsc;
+use deno_core::op2;
+use deno_core::v8;
+use deno_core::GarbageCollected;
+use deno_core::InspectorSessionKind;
+use deno_core::InspectorSessionOptions;
+use deno_core::JsRuntimeInspector;
+use deno_core::OpState;
+use std::cell::RefCell;
+use std::rc::Rc;
+
+#[op2(fast)]
+pub fn op_inspector_enabled() -> bool {
+ // TODO: hook up to InspectorServer
+ false
+}
+
+#[op2]
+pub fn op_inspector_open<P>(
+ _state: &mut OpState,
+ _port: Option<u16>,
+ #[string] _host: Option<String>,
+) -> Result<(), Error>
+where
+ P: NodePermissions + 'static,
+{
+ // TODO: hook up to InspectorServer
+ /*
+ let server = state.borrow_mut::<InspectorServer>();
+ if let Some(host) = host {
+ server.set_host(host);
+ }
+ if let Some(port) = port {
+ server.set_port(port);
+ }
+ state
+ .borrow_mut::<P>()
+ .check_net((server.host(), Some(server.port())), "inspector.open")?;
+ */
+
+ Ok(())
+}
+
+#[op2(fast)]
+pub fn op_inspector_close() {
+ // TODO: hook up to InspectorServer
+}
+
+#[op2]
+#[string]
+pub fn op_inspector_url() -> Option<String> {
+ // TODO: hook up to InspectorServer
+ None
+}
+
+#[op2(fast)]
+pub fn op_inspector_wait(state: &OpState) -> bool {
+ match state.try_borrow::<Rc<RefCell<JsRuntimeInspector>>>() {
+ Some(inspector) => {
+ inspector
+ .borrow_mut()
+ .wait_for_session_and_break_on_next_statement();
+ true
+ }
+ None => false,
+ }
+}
+
+#[op2(fast)]
+pub fn op_inspector_emit_protocol_event(
+ #[string] _event_name: String,
+ #[string] _params: String,
+) {
+ // TODO: inspector channel & protocol notifications
+}
+
+struct JSInspectorSession {
+ tx: RefCell<Option<mpsc::UnboundedSender<String>>>,
+}
+
+impl GarbageCollected for JSInspectorSession {}
+
+#[op2]
+#[cppgc]
+pub fn op_inspector_connect<'s, P>(
+ isolate: *mut v8::Isolate,
+ scope: &mut v8::HandleScope<'s>,
+ state: &mut OpState,
+ connect_to_main_thread: bool,
+ callback: v8::Local<'s, v8::Function>,
+) -> Result<JSInspectorSession, Error>
+where
+ P: NodePermissions + 'static,
+{
+ state
+ .borrow_mut::<P>()
+ .check_sys("inspector", "inspector.Session.connect")?;
+
+ if connect_to_main_thread {
+ return Err(generic_error("connectToMainThread not supported"));
+ }
+
+ let context = scope.get_current_context();
+ let context = v8::Global::new(scope, context);
+ let callback = v8::Global::new(scope, callback);
+
+ let inspector = state
+ .borrow::<Rc<RefCell<JsRuntimeInspector>>>()
+ .borrow_mut();
+
+ let tx = inspector.create_raw_session(
+ InspectorSessionOptions {
+ kind: InspectorSessionKind::NonBlocking {
+ wait_for_disconnect: false,
+ },
+ },
+ // The inspector connection does not keep the event loop alive but
+ // when the inspector sends a message to the frontend, the JS that
+ // that runs may keep the event loop alive so we have to call back
+ // synchronously, instead of using the usual LocalInspectorSession
+ // UnboundedReceiver<InspectorMsg> API.
+ Box::new(move |message| {
+ // SAFETY: This function is called directly by the inspector, so
+ // 1) The isolate is still valid
+ // 2) We are on the same thread as the Isolate
+ let scope = unsafe { &mut v8::CallbackScope::new(&mut *isolate) };
+ let context = v8::Local::new(scope, context.clone());
+ let scope = &mut v8::ContextScope::new(scope, context);
+ let scope = &mut v8::TryCatch::new(scope);
+ let recv = v8::undefined(scope);
+ if let Some(message) = v8::String::new(scope, &message.content) {
+ let callback = v8::Local::new(scope, callback.clone());
+ callback.call(scope, recv.into(), &[message.into()]);
+ }
+ }),
+ );
+
+ Ok(JSInspectorSession {
+ tx: RefCell::new(Some(tx)),
+ })
+}
+
+#[op2(fast)]
+pub fn op_inspector_dispatch(
+ #[cppgc] session: &JSInspectorSession,
+ #[string] message: String,
+) {
+ if let Some(tx) = &*session.tx.borrow() {
+ let _ = tx.unbounded_send(message);
+ }
+}
+
+#[op2(fast)]
+pub fn op_inspector_disconnect(#[cppgc] session: &JSInspectorSession) {
+ drop(session.tx.borrow_mut().take());
+}
diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs
index b562261f3..b53f19dc2 100644
--- a/ext/node/ops/mod.rs
+++ b/ext/node/ops/mod.rs
@@ -7,6 +7,7 @@ pub mod fs;
pub mod http;
pub mod http2;
pub mod idna;
+pub mod inspector;
pub mod ipc;
pub mod os;
pub mod process;
diff --git a/ext/node/polyfills/inspector.js b/ext/node/polyfills/inspector.js
new file mode 100644
index 000000000..7eb15ce91
--- /dev/null
+++ b/ext/node/polyfills/inspector.js
@@ -0,0 +1,210 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+
+import process from "node:process";
+import { EventEmitter } from "node:events";
+import { primordials } from "ext:core/mod.js";
+import {
+ op_get_extras_binding_object,
+ op_inspector_close,
+ op_inspector_connect,
+ op_inspector_disconnect,
+ op_inspector_dispatch,
+ op_inspector_emit_protocol_event,
+ op_inspector_enabled,
+ op_inspector_open,
+ op_inspector_url,
+ op_inspector_wait,
+} from "ext:core/ops";
+import {
+ isUint32,
+ validateFunction,
+ validateInt32,
+ validateObject,
+ validateString,
+} from "ext:deno_node/internal/validators.mjs";
+import {
+ ERR_INSPECTOR_ALREADY_ACTIVATED,
+ ERR_INSPECTOR_ALREADY_CONNECTED,
+ ERR_INSPECTOR_CLOSED,
+ ERR_INSPECTOR_COMMAND,
+ ERR_INSPECTOR_NOT_ACTIVE,
+ ERR_INSPECTOR_NOT_CONNECTED,
+ ERR_INSPECTOR_NOT_WORKER,
+} from "ext:deno_node/internal/errors.ts";
+
+const {
+ SymbolDispose,
+ JSONParse,
+ JSONStringify,
+ SafeMap,
+} = primordials;
+
+class Session extends EventEmitter {
+ #connection = null;
+ #nextId = 1;
+ #messageCallbacks = new SafeMap();
+
+ connect() {
+ if (this.#connection) {
+ throw new ERR_INSPECTOR_ALREADY_CONNECTED("The inspector session");
+ }
+ this.#connection = op_inspector_connect(false, (m) => this.#onMessage(m));
+ }
+
+ connectToMainThread() {
+ if (isMainThread) {
+ throw new ERR_INSPECTOR_NOT_WORKER();
+ }
+ if (this.#connection) {
+ throw new ERR_INSPECTOR_ALREADY_CONNECTED("The inspector session");
+ }
+ this.#connection = op_inspector_connect(true, (m) => this.#onMessage(m));
+ }
+
+ #onMessage(message) {
+ const parsed = JSONParse(message);
+ try {
+ if (parsed.id) {
+ const callback = this.#messageCallbacks.get(parsed.id);
+ this.#messageCallbacks.delete(parsed.id);
+ if (callback) {
+ if (parsed.error) {
+ return callback(
+ new ERR_INSPECTOR_COMMAND(
+ parsed.error.code,
+ parsed.error.message,
+ ),
+ );
+ }
+
+ callback(null, parsed.result);
+ }
+ } else {
+ this.emit(parsed.method, parsed);
+ this.emit("inspectorNotification", parsed);
+ }
+ } catch (error) {
+ process.emitWarning(error);
+ }
+ }
+
+ post(method, params, callback) {
+ validateString(method, "method");
+ if (!callback && typeof params === "function") {
+ callback = params;
+ params = null;
+ }
+ if (params) {
+ validateObject(params, "params");
+ }
+ if (callback) {
+ validateFunction(callback, "callback");
+ }
+
+ if (!this.#connection) {
+ throw new ERR_INSPECTOR_NOT_CONNECTED();
+ }
+ const id = this.#nextId++;
+ const message = { id, method };
+ if (params) {
+ message.params = params;
+ }
+ if (callback) {
+ this.#messageCallbacks.set(id, callback);
+ }
+ op_inspector_dispatch(this.#connection, JSONStringify(message));
+ }
+
+ disconnect() {
+ if (!this.#connection) {
+ return;
+ }
+ op_inspector_disconnect(this.#connection);
+ this.#connection = null;
+ // deno-lint-ignore prefer-primordials
+ for (const callback of this.#messageCallbacks.values()) {
+ process.nextTick(callback, new ERR_INSPECTOR_CLOSED());
+ }
+ this.#messageCallbacks.clear();
+ this.#nextId = 1;
+ }
+}
+
+function open(port, host, wait) {
+ if (op_inspector_enabled()) {
+ throw new ERR_INSPECTOR_ALREADY_ACTIVATED();
+ }
+ // inspectorOpen() currently does not typecheck its arguments and adding
+ // such checks would be a potentially breaking change. However, the native
+ // open() function requires the port to fit into a 16-bit unsigned integer,
+ // causing an integer overflow otherwise, so we at least need to prevent that.
+ if (isUint32(port)) {
+ validateInt32(port, "port", 0, 65535);
+ } else {
+ // equiv of handling args[0]->IsUint32()
+ port = undefined;
+ }
+ if (typeof host !== "string") {
+ // equiv of handling args[1]->IsString()
+ host = undefined;
+ }
+ op_inspector_open(port, host);
+ if (wait) {
+ op_inspector_wait();
+ }
+
+ return {
+ __proto__: null,
+ [SymbolDispose]() {
+ _debugEnd();
+ },
+ };
+}
+
+function close() {
+ op_inspector_close();
+}
+
+function url() {
+ return op_inspector_url();
+}
+
+function waitForDebugger() {
+ if (!op_inspector_wait()) {
+ throw new ERR_INSPECTOR_NOT_ACTIVE();
+ }
+}
+
+function broadcastToFrontend(eventName, params) {
+ validateString(eventName, "eventName");
+ if (params) {
+ validateObject(params, "params");
+ }
+ op_inspector_emit_protocol_event(eventName, JSONStringify(params ?? {}));
+}
+
+const Network = {
+ requestWillBeSent: (params) =>
+ broadcastToFrontend("Network.requestWillBeSent", params),
+ responseReceived: (params) =>
+ broadcastToFrontend("Network.responseReceived", params),
+ loadingFinished: (params) =>
+ broadcastToFrontend("Network.loadingFinished", params),
+ loadingFailed: (params) =>
+ broadcastToFrontend("Network.loadingFailed", params),
+};
+
+const console = op_get_extras_binding_object().console;
+
+export { close, console, Network, open, Session, url, waitForDebugger };
+
+export default {
+ open,
+ close,
+ url,
+ waitForDebugger,
+ console,
+ Session,
+ Network,
+};
diff --git a/ext/node/polyfills/inspector.ts b/ext/node/polyfills/inspector.ts
deleted file mode 100644
index 9de86ab14..000000000
--- a/ext/node/polyfills/inspector.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-// Copyright Joyent and Node contributors. All rights reserved. MIT license.
-
-import { EventEmitter } from "node:events";
-import { notImplemented } from "ext:deno_node/_utils.ts";
-import { primordials } from "ext:core/mod.js";
-
-const {
- SafeMap,
-} = primordials;
-
-class Session extends EventEmitter {
- #connection = null;
- #nextId = 1;
- #messageCallbacks = new SafeMap();
-
- /** Connects the session to the inspector back-end. */
- connect() {
- notImplemented("inspector.Session.prototype.connect");
- }
-
- /** Connects the session to the main thread
- * inspector back-end. */
- connectToMainThread() {
- notImplemented("inspector.Session.prototype.connectToMainThread");
- }
-
- /** Posts a message to the inspector back-end. */
- post(
- _method: string,
- _params?: Record<string, unknown>,
- _callback?: (...args: unknown[]) => void,
- ) {
- notImplemented("inspector.Session.prototype.post");
- }
-
- /** Immediately closes the session, all pending
- * message callbacks will be called with an
- * error.
- */
- disconnect() {
- notImplemented("inspector.Session.prototype.disconnect");
- }
-}
-
-/** Activates inspector on host and port.
- * See https://nodejs.org/api/inspector.html#inspectoropenport-host-wait */
-function open(_port?: number, _host?: string, _wait?: boolean) {
- notImplemented("inspector.Session.prototype.open");
-}
-
-/** Deactivate the inspector. Blocks until there are no active connections.
- * See https://nodejs.org/api/inspector.html#inspectorclose */
-function close() {
- notImplemented("inspector.Session.prototype.close");
-}
-
-/** Return the URL of the active inspector, or undefined if there is none.
- * See https://nodejs.org/api/inspector.html#inspectorurl */
-function url() {
- // TODO(kt3k): returns undefined for now, which means the inspector is not activated.
- return undefined;
-}
-
-/** Blocks until a client (existing or connected later) has sent Runtime.runIfWaitingForDebugger command.
- * See https://nodejs.org/api/inspector.html#inspectorwaitfordebugger */
-function waitForDebugger() {
- notImplemented("inspector.wairForDebugger");
-}
-
-const console = globalThis.console;
-
-export { close, console, open, Session, url, waitForDebugger };
-
-export default {
- close,
- console,
- open,
- Session,
- url,
- waitForDebugger,
-};
diff --git a/ext/node/polyfills/inspector/promises.js b/ext/node/polyfills/inspector/promises.js
new file mode 100644
index 000000000..3483e53f5
--- /dev/null
+++ b/ext/node/polyfills/inspector/promises.js
@@ -0,0 +1,20 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+
+import inspector from "node:inspector";
+import { promisify } from "ext:deno_node/internal/util.mjs";
+
+class Session extends inspector.Session {
+ constructor() {
+ super();
+ }
+}
+Session.prototype.post = promisify(inspector.Session.prototype.post);
+
+export * from "node:inspector";
+export { Session };
+
+export default {
+ ...inspector,
+ Session,
+};