mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 23:48:52 +00:00
Compare commits
14 Commits
nektro-pat
...
debugger-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c9352392d | ||
|
|
fef9853d5a | ||
|
|
7dae496847 | ||
|
|
92b060c6e2 | ||
|
|
2a64e8b3bb | ||
|
|
fde3b7fbb6 | ||
|
|
c16e769383 | ||
|
|
5badc728d0 | ||
|
|
c4b3b321c2 | ||
|
|
cf599e77d9 | ||
|
|
b727689a9b | ||
|
|
4a36470588 | ||
|
|
ca08cf6b0a | ||
|
|
10bd0fac3a |
7
.vscode/c_cpp_properties.json
vendored
7
.vscode/c_cpp_properties.json
vendored
@@ -19,11 +19,11 @@
|
||||
"${workspaceFolder}/src/js/out",
|
||||
"${workspaceFolder}/src/deps/boringssl/include/",
|
||||
"${workspaceFolder}/src/deps",
|
||||
"${workspaceFolder}/src/deps/uws/uSockets/src"
|
||||
"${workspaceFolder}/src/deps/uws/uSockets/src",
|
||||
"${workspaceFolder}/src/deps/uws/src"
|
||||
],
|
||||
"browse": {
|
||||
"path": [
|
||||
"${workspaceFolder}/../webkit-build/include/",
|
||||
"${workspaceFolder}/bun-webkit/include/",
|
||||
"${workspaceFolder}/src/bun.js/WebKit/WebKitBuild/Release/",
|
||||
"${workspaceFolder}/src/bun.js/WebKit/WebKitBuild/Release/ICU/Headers/",
|
||||
@@ -39,7 +39,8 @@
|
||||
"${workspaceFolder}/src/bun.js/modules/*",
|
||||
"${workspaceFolder}/src/deps",
|
||||
"${workspaceFolder}/src/deps/boringssl/include/",
|
||||
"${workspaceFolder}/src/deps/uws/uSockets/src"
|
||||
"${workspaceFolder}/src/deps/uws/uSockets/src",
|
||||
"${workspaceFolder}/src/deps/uws/src"
|
||||
],
|
||||
"limitSymbolsToIncludedHeaders": true,
|
||||
"databaseFilename": ".vscode/cppdb"
|
||||
|
||||
14
.vscode/launch.json
generated
vendored
14
.vscode/launch.json
generated
vendored
@@ -136,6 +136,20 @@
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "bun run [Inspect]",
|
||||
"program": "bun-debug",
|
||||
"args": ["--inspect-brk", "${file}"],
|
||||
"cwd": "${fileDirname}",
|
||||
"env": {
|
||||
"FORCE_COLOR": "1",
|
||||
"BUN_DEBUG_QUIET_LOGS": "1"
|
||||
},
|
||||
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
||||
169
packages/bun-devtools/.gitignore
vendored
Normal file
169
packages/bun-devtools/.gitignore
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
\*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
\*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
\*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
\*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
.cache/
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.\*
|
||||
3
packages/bun-devtools/README.md
Normal file
3
packages/bun-devtools/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# bun-devtools
|
||||
|
||||
A set of auto-generated TypeScript types for the WebKit debugger protocol.
|
||||
BIN
packages/bun-devtools/bun.lockb
Executable file
BIN
packages/bun-devtools/bun.lockb
Executable file
Binary file not shown.
1
packages/bun-devtools/heap/bun.heapsnapshot
Normal file
1
packages/bun-devtools/heap/bun.heapsnapshot
Normal file
File diff suppressed because one or more lines are too long
14
packages/bun-devtools/heap/jsc.d.ts
vendored
Normal file
14
packages/bun-devtools/heap/jsc.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export namespace JSC {
|
||||
/**
|
||||
* @link https://github.com/WebKit/webkit/blob/main/Source/JavaScriptCore/heap/HeapSnapshotBuilder.h
|
||||
*/
|
||||
export type HeapSnapshot = {
|
||||
version: 2;
|
||||
type: "Inspector";
|
||||
nodes: number[];
|
||||
nodeClassNames: string[];
|
||||
edges: number[];
|
||||
edgeTypes: string[];
|
||||
edgeNames: string[];
|
||||
};
|
||||
}
|
||||
30
packages/bun-devtools/heap/v8.d.ts
vendored
Normal file
30
packages/bun-devtools/heap/v8.d.ts
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
export namespace V8 {
|
||||
/**
|
||||
* @link https://github.com/julianburr/chrome-heap-snapshot-parser/blob/master/index.js#L72
|
||||
* @link https://stackoverflow.com/questions/69802133/chrome-heap-snapshot-structure-explanation
|
||||
*/
|
||||
export type HeapSnapshot = {
|
||||
snapshot: {
|
||||
meta: {
|
||||
node_fields: string[];
|
||||
node_types: [string[], ...string[]]; // ?
|
||||
edge_fields: string[];
|
||||
edge_types: [string[], ...string[]]; // ?
|
||||
trace_function_info_fields: string[];
|
||||
trace_node_fields: string[];
|
||||
sample_fields: string[];
|
||||
location_fields: string[];
|
||||
node_count: number;
|
||||
edge_count: number;
|
||||
trace_function_count: number;
|
||||
};
|
||||
};
|
||||
nodes: number[];
|
||||
edges: number[];
|
||||
trace_tree: unknown[];
|
||||
trace_function_infos: unknown[];
|
||||
samples: unknown[];
|
||||
locations: number[];
|
||||
strings: string[];
|
||||
};
|
||||
}
|
||||
48430
packages/bun-devtools/heap/workerd.heapsnapshot
Normal file
48430
packages/bun-devtools/heap/workerd.heapsnapshot
Normal file
File diff suppressed because it is too large
Load Diff
2
packages/bun-devtools/index.ts
Normal file
2
packages/bun-devtools/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./protocol/jsc";
|
||||
export * from "./protocol/v8";
|
||||
24
packages/bun-devtools/package.json
Normal file
24
packages/bun-devtools/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "bun-devtools",
|
||||
"module": "./index.ts",
|
||||
"version": "0.0.2",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.ts",
|
||||
"require": "./index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"generate-protocol": "bun run scripts/generate-protocol.ts"
|
||||
},
|
||||
"files": [
|
||||
"index.ts",
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
"protocol"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
1953
packages/bun-devtools/protocol/jsc.d.ts
vendored
Normal file
1953
packages/bun-devtools/protocol/jsc.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3422
packages/bun-devtools/protocol/v8.d.ts
vendored
Normal file
3422
packages/bun-devtools/protocol/v8.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
129
packages/bun-devtools/scripts/client.ts
Normal file
129
packages/bun-devtools/scripts/client.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
// A DevTools client for JavaScriptCore.
|
||||
|
||||
import type { JSC } from "..";
|
||||
|
||||
type ClientOptions = {
|
||||
url: string | URL;
|
||||
event?: (event: JSC.Event<keyof JSC.EventMap>) => void;
|
||||
request?: (request: JSC.Request<keyof JSC.RequestMap>) => void;
|
||||
response?: (response: JSC.Response<keyof JSC.ResponseMap>) => void;
|
||||
};
|
||||
|
||||
class Client {
|
||||
#webSocket: WebSocket;
|
||||
#requestId: number;
|
||||
#pendingMessages: string[];
|
||||
#pendingRequests: Map<number, AbortController>;
|
||||
#ready: Promise<void>;
|
||||
|
||||
constructor(options: ClientOptions) {
|
||||
this.#webSocket = new WebSocket(options.url);
|
||||
this.#requestId = 1;
|
||||
this.#pendingMessages = [];
|
||||
this.#pendingRequests = new Map();
|
||||
this.#ready = new Promise((resolve, reject) => {
|
||||
this.#webSocket.addEventListener("open", () => {
|
||||
for (const message of this.#pendingMessages) {
|
||||
this.#send(message);
|
||||
}
|
||||
this.#pendingMessages.length = 0;
|
||||
resolve();
|
||||
});
|
||||
this.#webSocket.addEventListener("message", ({ data }) => {
|
||||
let response;
|
||||
try {
|
||||
response = { ...JSON.parse(data) };
|
||||
} catch {
|
||||
console.error("Received an invalid message:", data);
|
||||
return;
|
||||
}
|
||||
const { id, error, result, method, params } = response;
|
||||
if (method && params) {
|
||||
options.event?.(response);
|
||||
} else if (id && (result || error)) {
|
||||
try {
|
||||
options.response?.(response);
|
||||
} finally {
|
||||
const abort = this.#pendingRequests.get(id ?? -1);
|
||||
if (!abort) {
|
||||
console.error("Received an unexpected message:", response);
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
abort.abort(new Error(JSON.stringify(error)));
|
||||
} else {
|
||||
abort.abort(result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("Received an unexpected message:", response);
|
||||
}
|
||||
});
|
||||
this.#webSocket.addEventListener("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
this.#webSocket.addEventListener("close", ({ code, reason = ""}) => {
|
||||
reject(new Error(`WebSocket closed: ${code} ${reason}`.trimEnd()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get ready(): Promise<void> {
|
||||
return this.#ready;
|
||||
}
|
||||
|
||||
#send(message: string): void {
|
||||
const { readyState } = this.#webSocket;
|
||||
if (readyState === WebSocket.OPEN) {
|
||||
this.#webSocket.send(message);
|
||||
} else if (readyState === WebSocket.CONNECTING) {
|
||||
this.#pendingMessages.push(message);
|
||||
} else {
|
||||
const closed = readyState === WebSocket.CLOSING ? "closing" : "closed";
|
||||
throw new Error(`WebSocket is ${closed}`);
|
||||
}
|
||||
}
|
||||
|
||||
async fetch<T extends keyof JSC.RequestMap>(method: T, params: JSC.Request<T>["params"]): Promise<JSC.Response<T>> {
|
||||
const request: JSC.Request<T> = {
|
||||
id: this.#requestId++,
|
||||
method,
|
||||
params,
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const abort = new AbortController();
|
||||
abort.signal.addEventListener("abort", () => {
|
||||
this.#pendingRequests.delete(request.id);
|
||||
const { reason } = abort.signal;
|
||||
if (reason instanceof Error) {
|
||||
reject(reason);
|
||||
} else {
|
||||
resolve(reason);
|
||||
}
|
||||
});
|
||||
this.#pendingRequests.set(request.id, abort);
|
||||
this.#send(JSON.stringify(request));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const client = new Client({
|
||||
url: "ws://localhost:9229",
|
||||
event: (event) => console.log("EVENT:", event),
|
||||
request: (request) => console.log("REQUEST:", request),
|
||||
response: (response) => console.log("RESPONSE:", response),
|
||||
});
|
||||
await client.ready;
|
||||
|
||||
while (true) {
|
||||
const [method, ...param] = prompt(">")?.split(" ") ?? [];
|
||||
if (!method.trim()) {
|
||||
continue;
|
||||
}
|
||||
const params = !param?.length ? {} : JSON.parse(eval(`JSON.stringify(${param.join(" ")})`));
|
||||
try {
|
||||
await client.fetch(method.trim() as any, params);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
255
packages/bun-devtools/scripts/generate-protocol.ts
Normal file
255
packages/bun-devtools/scripts/generate-protocol.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import { join } from "node:path";
|
||||
import { writeFileSync, mkdirSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
async function download<V>(url: string): Promise<V> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${response.status}: ${url}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
type Protocol = {
|
||||
name: string;
|
||||
version: {
|
||||
major: number;
|
||||
minor: number;
|
||||
};
|
||||
domains: Domain[];
|
||||
};
|
||||
|
||||
type Domain = {
|
||||
domain: string;
|
||||
types: Property[];
|
||||
commands?: {
|
||||
name: string;
|
||||
description?: string;
|
||||
parameters?: Property[];
|
||||
returns?: Property[];
|
||||
}[];
|
||||
events?: {
|
||||
name: string;
|
||||
description?: string;
|
||||
parameters: Property[];
|
||||
}[];
|
||||
};
|
||||
|
||||
type Property = {
|
||||
id?: string;
|
||||
type?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
optional?: boolean;
|
||||
} & (
|
||||
| {
|
||||
type: "array";
|
||||
items?: Property;
|
||||
}
|
||||
| {
|
||||
type: "object";
|
||||
properties?: Property[];
|
||||
}
|
||||
| {
|
||||
type: "string";
|
||||
enum?: string[];
|
||||
}
|
||||
| {
|
||||
$ref: string;
|
||||
}
|
||||
);
|
||||
|
||||
function format(property: Property): string {
|
||||
if (property.id) {
|
||||
const comment = property.description
|
||||
? `/** ${property.description} */\n`
|
||||
: "";
|
||||
const body = format({ ...property, id: undefined });
|
||||
return `${comment}export type ${property.id} = ${body};\n`;
|
||||
}
|
||||
if (property.type === "array") {
|
||||
const type = "items" in property ? format(property.items!) : "unknown";
|
||||
return `Array<${type}>`;
|
||||
}
|
||||
if (property.type === "object") {
|
||||
if (!("properties" in property)) {
|
||||
return "Record<string, unknown>";
|
||||
}
|
||||
if (property.properties!.length === 0) {
|
||||
return "{}";
|
||||
}
|
||||
const properties = property
|
||||
.properties!.map((property) => {
|
||||
const comment = property.description
|
||||
? `/** ${property.description} */\n`
|
||||
: "";
|
||||
const name = `${property.name}${property.optional ? "?" : ""}`;
|
||||
return `${comment} ${name}: ${format(property)};`;
|
||||
})
|
||||
.join("\n");
|
||||
return `{\n${properties}}`;
|
||||
}
|
||||
if (property.type === "string") {
|
||||
if (!("enum" in property)) {
|
||||
return "string";
|
||||
}
|
||||
return property.enum!.map((v) => `"${v}"`).join(" | ");
|
||||
}
|
||||
if ("$ref" in property) {
|
||||
if (/^Page|DOM|Security|CSS|IO|Emulation\./.test(property.$ref)) {
|
||||
return "unknown";
|
||||
}
|
||||
return property.$ref;
|
||||
}
|
||||
if (property.type === "integer") {
|
||||
return "number";
|
||||
}
|
||||
return property.type;
|
||||
}
|
||||
|
||||
function formatAll(protocol: Protocol): string {
|
||||
let body = "";
|
||||
const append = (property: Property) => {
|
||||
body += format(property);
|
||||
};
|
||||
const titlize = (name: string) =>
|
||||
name.charAt(0).toUpperCase() + name.slice(1);
|
||||
const events = new Map();
|
||||
const commands = new Map();
|
||||
for (const domain of protocol.domains) {
|
||||
body += `export namespace ${domain.domain} {`;
|
||||
for (const type of domain.types ?? []) {
|
||||
append(type);
|
||||
}
|
||||
for (const event of domain.events ?? []) {
|
||||
const symbol = `${domain.domain}.${event.name}`;
|
||||
const title = titlize(event.name);
|
||||
events.set(symbol, `${domain.domain}.${title}`);
|
||||
append({
|
||||
id: `${title}Event`,
|
||||
type: "object",
|
||||
description: `\`${symbol}\``,
|
||||
properties: event.parameters ?? [],
|
||||
});
|
||||
}
|
||||
for (const command of domain.commands ?? []) {
|
||||
const symbol = `${domain.domain}.${command.name}`;
|
||||
const title = titlize(command.name);
|
||||
commands.set(symbol, `${domain.domain}.${title}`);
|
||||
append({
|
||||
id: `${title}Request`,
|
||||
type: "object",
|
||||
description: `\`${symbol}\``,
|
||||
properties: command.parameters ?? [],
|
||||
});
|
||||
append({
|
||||
id: `${title}Response`,
|
||||
type: "object",
|
||||
description: `\`${symbol}\``,
|
||||
properties: command.returns ?? [],
|
||||
});
|
||||
}
|
||||
body += "};";
|
||||
}
|
||||
for (const type of ["Event", "Request", "Response"]) {
|
||||
const source = type === "Event" ? events : commands;
|
||||
append({
|
||||
id: `${type}Map`,
|
||||
type: "object",
|
||||
properties: [...source.entries()].map(([name, title]) => ({
|
||||
name: `"${name}"`,
|
||||
$ref: `${title}${type}`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
body += `export type Event<T extends keyof EventMap> = {
|
||||
method: T;
|
||||
params: EventMap[T];
|
||||
};
|
||||
export type Request<T extends keyof RequestMap> = {
|
||||
id: number;
|
||||
method: T;
|
||||
params: RequestMap[T];
|
||||
};
|
||||
export type Response<T extends keyof ResponseMap> = {
|
||||
id: number;
|
||||
} & ({
|
||||
method?: T;
|
||||
result: ResponseMap[T];
|
||||
} | {
|
||||
error: {
|
||||
code?: string;
|
||||
message: string;
|
||||
};
|
||||
});`;
|
||||
return `export namespace ${protocol.name.toUpperCase()} {${body}};`;
|
||||
}
|
||||
|
||||
async function downloadV8(): Promise<Protocol> {
|
||||
const baseUrl =
|
||||
"https://raw.githubusercontent.com/ChromeDevTools/devtools-protocol/master/json";
|
||||
const filter = [
|
||||
"Runtime",
|
||||
"Network",
|
||||
"Console",
|
||||
"Debugger",
|
||||
"Profiler",
|
||||
"HeapProfiler",
|
||||
];
|
||||
return Promise.all([
|
||||
download<Protocol>(`${baseUrl}/js_protocol.json`),
|
||||
download<Protocol>(`${baseUrl}/browser_protocol.json`),
|
||||
]).then(([js, browser]) => ({
|
||||
name: "v8",
|
||||
version: js.version,
|
||||
domains: [...js.domains, ...browser.domains]
|
||||
.filter((domain) => filter.includes(domain.domain))
|
||||
.sort((a, b) => a.domain.localeCompare(b.domain)),
|
||||
}));
|
||||
}
|
||||
|
||||
async function downloadJsc(): Promise<Protocol> {
|
||||
const baseUrl =
|
||||
"https://raw.githubusercontent.com/WebKit/WebKit/main/Source/JavaScriptCore/inspector/protocol";
|
||||
return {
|
||||
name: "jsc",
|
||||
version: {
|
||||
major: 1,
|
||||
minor: 3,
|
||||
},
|
||||
domains: await Promise.all([
|
||||
download<Domain>(`${baseUrl}/Debugger.json`),
|
||||
download<Domain>(`${baseUrl}/Heap.json`),
|
||||
download<Domain>(`${baseUrl}/ScriptProfiler.json`),
|
||||
download<Domain>(`${baseUrl}/Runtime.json`),
|
||||
download<Domain>(`${baseUrl}/Network.json`),
|
||||
download<Domain>(`${baseUrl}/Console.json`),
|
||||
download<Domain>(`${baseUrl}/GenericTypes.json`),
|
||||
]).then((domains) =>
|
||||
domains.sort((a, b) => a.domain.localeCompare(b.domain))
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
async function run(cwd: string) {
|
||||
const [jsc, v8] = await Promise.all([downloadJsc(), downloadV8()]);
|
||||
try {
|
||||
mkdirSync(cwd);
|
||||
} catch (error) {
|
||||
if (error.code !== "EEXIST") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const write = (name: string, data: string) => {
|
||||
writeFileSync(join(cwd, name), data);
|
||||
spawnSync("bunx", ["prettier", "--write", name], { cwd, stdio: "ignore" });
|
||||
};
|
||||
// Note: Can be uncommented to inspect the JSON protocol files.
|
||||
// write("devtools/jsc.json", JSON.stringify(jsc));
|
||||
// write("devtools/v8.json", JSON.stringify(v8));
|
||||
write("jsc.d.ts", "// GENERATED - DO NOT EDIT\n" + formatAll(jsc));
|
||||
write("v8.d.ts", "// GENERATED - DO NOT EDIT\n" + formatAll(v8));
|
||||
}
|
||||
|
||||
run(join(__dirname, "..", "protocol"))
|
||||
.catch(console.error);
|
||||
18
packages/bun-devtools/tsconfig.json
Normal file
18
packages/bun-devtools/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"],
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"moduleDetection": "force",
|
||||
"allowImportingTsExtensions": true,
|
||||
"strict": true,
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowJs": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
54
src/bun.js/bindings/AugmentableInspectorController.h
Normal file
54
src/bun.js/bindings/AugmentableInspectorController.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
|
||||
|
||||
#include <JavaScriptCore/AugmentableInspectorControllerClient.h>
|
||||
#include <JavaScriptCore/InspectorBackendDispatcher.h>
|
||||
#include <JavaScriptCore/InspectorFrontendRouter.h>
|
||||
|
||||
namespace Inspector {
|
||||
|
||||
class InspectorAgentBase;
|
||||
|
||||
class AugmentableInspectorController {
|
||||
public:
|
||||
virtual ~AugmentableInspectorController() {}
|
||||
|
||||
virtual AugmentableInspectorControllerClient* augmentableInspectorControllerClient() const = 0;
|
||||
virtual void setAugmentableInspectorControllerClient(AugmentableInspectorControllerClient*) = 0;
|
||||
|
||||
virtual const FrontendRouter& frontendRouter() const = 0;
|
||||
virtual BackendDispatcher& backendDispatcher() = 0;
|
||||
virtual void registerAlternateAgent(std::unique_ptr<InspectorAgentBase>) = 0;
|
||||
|
||||
bool connected() const { return frontendRouter().hasFrontends(); }
|
||||
};
|
||||
|
||||
} // namespace Inspector
|
||||
|
||||
#endif // ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
|
||||
391
src/bun.js/bindings/BunInspector.cpp
Normal file
391
src/bun.js/bindings/BunInspector.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
#include "BunInspector.h"
|
||||
#include <JavaScriptCore/Heap.h>
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
#include <JavaScriptCore/JSGlobalObjectDebugger.h>
|
||||
|
||||
namespace Zig {
|
||||
|
||||
WTF_MAKE_ISO_ALLOCATED_IMPL(BunInspector);
|
||||
|
||||
extern "C" void Bun__waitForDebuggerToStart();
|
||||
extern "C" void Bun__debuggerIsReady();
|
||||
|
||||
static BunInspector* inspectorFromGlobal(Zig::GlobalObject* globalObject)
|
||||
{
|
||||
RELEASE_ASSERT(globalObject->bunInspectorPtr);
|
||||
return reinterpret_cast<BunInspector*>(globalObject->bunInspectorPtr);
|
||||
}
|
||||
|
||||
class BunInspectorConnection {
|
||||
|
||||
public:
|
||||
WTF::Deque<WTF::CString> m_messages;
|
||||
RefPtr<BunInspector> inspector;
|
||||
bool hasSentWelcomeMessage = false;
|
||||
BunInspectorConnection(RefPtr<BunInspector> inspector)
|
||||
: inspector(inspector)
|
||||
, m_messages()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void BunInspector::sendMessageToFrontend(const String& message)
|
||||
{
|
||||
|
||||
String out = message;
|
||||
auto jsonObject = WTF::JSONImpl::Value::parseJSON(message);
|
||||
if (jsonObject) {
|
||||
if (auto object = jsonObject->asObject()) {
|
||||
auto method = object->getString("method"_s);
|
||||
|
||||
// {
|
||||
// "scriptId": "384",
|
||||
// "url": "file:///private/tmp/empty.js",
|
||||
// "startLine": 0,
|
||||
// "startColumn": 0,
|
||||
// "endLine": 1,
|
||||
// "endColumn": 0,
|
||||
// "executionContextId": 1,
|
||||
// "hash": "a3b314362f7e47deabee6100e0d8081619194faf1b5741e0fe2f88b150557ddd",
|
||||
// "executionContextAuxData": { "isDefault": true },
|
||||
// "isLiveEdit": false,
|
||||
// "sourceMapURL": "",
|
||||
// "hasSourceURL": false,
|
||||
// "isModule": false,
|
||||
// "length": 21,
|
||||
// "stackTrace": {
|
||||
// "callFrames": [
|
||||
// {
|
||||
// "functionName": "internalCompileFunction",
|
||||
// "scriptId": "62",
|
||||
// "url": "node:internal/vm",
|
||||
// "lineNumber": 72,
|
||||
// "columnNumber": 17
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// "scriptLanguage": "JavaScript",
|
||||
// "embedderName": "file:///private/tmp/empty.js"
|
||||
// }
|
||||
if (method == "Debugger.scriptParsed"_s) {
|
||||
if (auto params = object->getObject("params"_s)) {
|
||||
params->setInteger("executionContextId"_s, 1);
|
||||
auto url = makeString("file://"_s, params->getString("url"_s));
|
||||
params->setString("url"_s, url);
|
||||
// TODO: content hash
|
||||
params->setInteger("hash"_s, url.hash());
|
||||
params->setBoolean("isModule"_s, true);
|
||||
params->setString("scriptLanguage"_s, "JavaScript"_s);
|
||||
params->setString("embedderName"_s, "Bun!"_s);
|
||||
}
|
||||
|
||||
out = object->toJSONString();
|
||||
}
|
||||
|
||||
if (method == "Debugger.enable"_s) {
|
||||
// debuggerId is missing from the response
|
||||
auto params = WTF::JSONImpl::Object::create();
|
||||
params->setString("debuggerId"_s, "3701622443570787625.-8711178633418819848"_s);
|
||||
object->setObject("params"_s, WTFMove(params));
|
||||
}
|
||||
|
||||
out = object->toJSONString();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
LockHolder locker(this->m_pendingMessagesLock);
|
||||
if (!this->hasSentWelcomeMessage) {
|
||||
this->hasSentWelcomeMessage = true;
|
||||
auto welcomeMessage = makeString(
|
||||
"{ \"method\": \"Runtime.executionContextCreated\", \"params\":{\"context\":{\"id\":"_s,
|
||||
this->scriptExecutionContext()->identifier(),
|
||||
",\"origin\":\"\",\"name\":\""_s,
|
||||
this->identifier(),
|
||||
"\",\"uniqueId\":\"1234\",\"auxData\":{\"isDefault\":true}}}}"_s);
|
||||
this->m_pendingMessages.append(WTFMove(welcomeMessage.isolatedCopy()));
|
||||
}
|
||||
|
||||
this->m_pendingMessages.append(WTFMove(out.isolatedCopy()));
|
||||
}
|
||||
us_wakeup_loop((us_loop_t*)this->loop);
|
||||
}
|
||||
|
||||
void BunInspector::drainIncomingMessages()
|
||||
{
|
||||
LockHolder locker(this->m_incomingMessagesLock);
|
||||
size_t size = this->m_incomingMessages.size();
|
||||
while (size > 0) {
|
||||
auto& message = this->m_incomingMessages.first();
|
||||
this->sendMessageToTargetBackend(message);
|
||||
this->m_incomingMessages.removeFirst();
|
||||
size = this->m_incomingMessages.size();
|
||||
}
|
||||
}
|
||||
|
||||
void BunInspector::didParseSource(SourceID id, const Debugger::Script& script)
|
||||
{
|
||||
}
|
||||
|
||||
void BunInspector::drainOutgoingMessages()
|
||||
{
|
||||
|
||||
if (this->server->numSubscribers("BunInspectorConnection") == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
LockHolder locker(this->m_pendingMessagesLock);
|
||||
size_t size = this->m_pendingMessages.size();
|
||||
while (size > 0) {
|
||||
auto& message = this->m_pendingMessages.first();
|
||||
auto utf8 = message.utf8();
|
||||
std::string_view view { utf8.data(), utf8.length() };
|
||||
if (!this->server->publish("BunInspectorConnection", view, uWS::OpCode::TEXT, false)) {
|
||||
return;
|
||||
}
|
||||
this->m_pendingMessages.removeFirst();
|
||||
size = this->m_pendingMessages.size();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void Bun__tickWhileWaitingForDebugger(JSC::JSGlobalObject* globalObject);
|
||||
|
||||
void BunInspector::startServer(WTF::String hostname, uint16_t port, WTF::URL url, WTF::String title)
|
||||
{
|
||||
uWS::App* app = new uWS::App();
|
||||
this->server = app;
|
||||
this->loop = uWS::Loop::get();
|
||||
|
||||
auto host = hostname.utf8();
|
||||
|
||||
// https://chromedevtools.github.io/devtools-protocol/ GET /json or /json/list
|
||||
app->get("/json", [hostname, port, url, title = title, inspector = this](auto* res, auto* /*req*/) {
|
||||
auto identifier = inspector->identifier();
|
||||
auto jsonString = makeString(
|
||||
"[ {\"faviconUrl\": \"https://bun.sh/favicon.svg\", \"description\": \"\", \"devtoolsFrontendUrl\": \"devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws="_s,
|
||||
hostname,
|
||||
":"_s,
|
||||
port,
|
||||
"/devtools/page/"_s,
|
||||
identifier,
|
||||
"\","_s
|
||||
" \"id\": \"6e99c4f9-6bb6-4f45-9749-5772545b2371\","_s,
|
||||
" \"title\": \""_s,
|
||||
title,
|
||||
"\","
|
||||
" \"type\": \"node\","_s,
|
||||
" \"url\": \"file://"_s,
|
||||
identifier,
|
||||
"\","_s
|
||||
" \"webSocketDebuggerUrl\": \"ws://"_s,
|
||||
hostname,
|
||||
":"_s,
|
||||
port,
|
||||
"/devtools/page/"_s,
|
||||
identifier,
|
||||
"\"} ]"_s);
|
||||
auto utf8 = jsonString.utf8();
|
||||
res->writeStatus("200 OK");
|
||||
res->writeHeader("Content-Type", "application/json");
|
||||
res->end(utf8.data(), utf8.length());
|
||||
})
|
||||
.get("/json/version", [](auto* res, auto* req) {
|
||||
auto out = makeString("{\"Browser\": \"Bun/"_s, Bun__version, "\",\"Protocol-Version\": \"1.1\"}"_s);
|
||||
auto utf8 = out.utf8();
|
||||
res->writeStatus("200 OK");
|
||||
res->writeHeader("Content-Type", "application/json");
|
||||
res->end({ utf8.data(), utf8.length() });
|
||||
})
|
||||
.ws<BunInspectorConnection*>("/*", { /* Settings */
|
||||
.compression = uWS::DISABLED,
|
||||
.maxPayloadLength = 1024 * 1024 * 1024,
|
||||
.idleTimeout = 512,
|
||||
.maxBackpressure = 64 * 1024 * 1024,
|
||||
.closeOnBackpressureLimit = false,
|
||||
.resetIdleTimeoutOnSend = false,
|
||||
.sendPingsAutomatically = true,
|
||||
/* Handlers */
|
||||
.upgrade = nullptr,
|
||||
.open = [inspector = this](auto* ws) {
|
||||
BunInspectorConnection** connectionPtr = ws->getUserData();
|
||||
*connectionPtr = new BunInspectorConnection(inspector);
|
||||
|
||||
|
||||
ws->subscribe("BunInspectorConnection");
|
||||
BunInspectorConnection* connection = *connectionPtr;
|
||||
Bun__debuggerIsReady(); },
|
||||
|
||||
.message = [inspector = this](auto* ws, std::string_view message, uWS::OpCode opCode) {
|
||||
if (opCode == uWS::OpCode::TEXT) {
|
||||
if (!inspector) {
|
||||
ws->close();
|
||||
return;
|
||||
}
|
||||
|
||||
BunInspectorConnection** connectionPtr = ws->getUserData();
|
||||
BunInspectorConnection* connection = *connectionPtr;
|
||||
|
||||
|
||||
|
||||
|
||||
connection->inspector->dispatchToBackend(message);
|
||||
connection->inspector->drainOutgoingMessages();
|
||||
|
||||
|
||||
} },
|
||||
.drain = [](auto* ws) {
|
||||
|
||||
/* Check ws->getBufferedAmount() here */
|
||||
BunInspectorConnection** connectionPtr = ws->getUserData();
|
||||
BunInspectorConnection* connection = *connectionPtr;
|
||||
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
while (connection->m_messages.size() > 0) {
|
||||
auto& message = connection->m_messages.first();
|
||||
std::string_view view { message.data(), message.length() };
|
||||
if (!ws->send(view, uWS::OpCode::TEXT, false, false)) {
|
||||
return;
|
||||
}
|
||||
connection->m_messages.removeFirst();
|
||||
}
|
||||
connection->inspector->drainOutgoingMessages(); },
|
||||
.ping = [](auto* /*ws*/, std::string_view) {
|
||||
/* Not implemented yet */ },
|
||||
.pong = [](auto* /*ws*/, std::string_view) {
|
||||
/* Not implemented yet */ },
|
||||
.close = [](auto* ws, int /*code*/, std::string_view /*message*/) {
|
||||
BunInspectorConnection** connectionPtr = ws->getUserData();
|
||||
BunInspectorConnection* connection = *connectionPtr;
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
if (connection->inspector.get()) {
|
||||
connection->inspector->disconnect();
|
||||
connection->inspector = nullptr;
|
||||
}
|
||||
|
||||
connection->m_messages.clear();
|
||||
delete connection; } })
|
||||
.any("/*", [](auto* res, auto* req) {
|
||||
res->writeStatus("404 Not Found");
|
||||
res->writeHeader("Content-Type", "text/plain");
|
||||
res->write(req->getUrl());
|
||||
res->end(" was not found");
|
||||
})
|
||||
.listen(std::string(host.data(), host.length()), port, [inspector = this](auto* listen_socket) {
|
||||
inspector->loop->addPostHandler(inspector, [inspector = inspector](uWS::Loop* loop) {
|
||||
inspector->drainOutgoingMessages();
|
||||
});
|
||||
WebCore::ScriptExecutionContext::postTaskTo(
|
||||
inspector->scriptExecutionContext()->identifier(),
|
||||
[inspector = inspector](WebCore::ScriptExecutionContext& ctx) mutable {
|
||||
inspector->readyToStartDebugger();
|
||||
});
|
||||
|
||||
inspector->loop->run();
|
||||
});
|
||||
}
|
||||
|
||||
void BunInspector::readyToStartDebugger()
|
||||
{
|
||||
this->ensureDebugger();
|
||||
|
||||
auto& inspectorController = globalObject()->inspectorController();
|
||||
auto* debugger = inspectorController.debugger();
|
||||
debugger->addObserver(*this);
|
||||
debugger->schedulePauseAtNextOpportunity();
|
||||
}
|
||||
|
||||
BunInspector* BunInspector::startWebSocketServer(
|
||||
Zig::GlobalObject* globalObject,
|
||||
WebCore::ScriptExecutionContext& context,
|
||||
WTF::String hostname,
|
||||
uint16_t port,
|
||||
WTF::Function<void(BunInspector*, bool success)>&& callback)
|
||||
{
|
||||
context.ensureURL();
|
||||
auto url = context.url();
|
||||
auto identifier = url.fileSystemPath();
|
||||
|
||||
auto title = makeString(
|
||||
url.fileSystemPath(),
|
||||
" (Bun "_s, Bun__version, ")"_s);
|
||||
|
||||
auto* inspector = new BunInspector(&context, nullptr, WTFMove(identifier));
|
||||
reinterpret_cast<Zig::GlobalObject*>(globalObject)->bunInspectorPtr = inspector;
|
||||
auto backgroundThreadFunction = [inspector = inspector, hostname = hostname.isolatedCopy(), port = port, url = WTFMove(url), title = WTFMove(title)]() -> void {
|
||||
inspector->startServer(hostname, port, url, WTFMove(title));
|
||||
};
|
||||
WTF::Thread::create("BunInspector", WTFMove(backgroundThreadFunction))->detach();
|
||||
|
||||
callback(inspector, true);
|
||||
Bun__waitForDebuggerToStart();
|
||||
|
||||
return inspector;
|
||||
}
|
||||
|
||||
void BunInspector::dispatchToBackend(std::string_view message)
|
||||
{
|
||||
WTF::CString data { message.data(), message.length() };
|
||||
WTF::String msg = WTF::String::fromUTF8(data.data(), data.length());
|
||||
bool needsTask = true;
|
||||
{
|
||||
LockHolder incomingMessagesLock(this->m_incomingMessagesLock);
|
||||
needsTask = this->m_incomingMessages.isEmpty();
|
||||
this->m_incomingMessages.append(WTFMove(msg.isolatedCopy()));
|
||||
}
|
||||
WebCore::ScriptExecutionContext::postTaskTo(
|
||||
scriptExecutionContext()->identifier(),
|
||||
[inspector = this](WebCore::ScriptExecutionContext& ctx) mutable {
|
||||
inspector->drainIncomingMessages();
|
||||
});
|
||||
}
|
||||
|
||||
void BunInspector::sendMessageToTargetBackend(const WTF::String& message)
|
||||
{
|
||||
globalObject()->inspectorController().dispatchMessageFromFrontend(message);
|
||||
}
|
||||
|
||||
void BunInspector::connect(Inspector::FrontendChannel::ConnectionType connectionType)
|
||||
{
|
||||
globalObject()->inspectorController().connectFrontend(*this, false, false);
|
||||
}
|
||||
|
||||
void BunInspector::disconnect()
|
||||
{
|
||||
globalObject()->inspectorController().disconnectFrontend(*this);
|
||||
}
|
||||
|
||||
void BunInspector::didPause(JSGlobalObject* jsGlobalObject, DebuggerCallFrame& callframe, JSValue exceptionOrCaughtValue)
|
||||
{
|
||||
printf("didPause\n");
|
||||
}
|
||||
void BunInspector::didContinue()
|
||||
{
|
||||
printf("didContinue\n");
|
||||
}
|
||||
|
||||
void BunInspector::waitForMessages()
|
||||
{
|
||||
this->m_incomingMessagesLock.lock();
|
||||
}
|
||||
|
||||
void BunInspector::ensureDebugger()
|
||||
{
|
||||
this->connect(Inspector::FrontendChannel::ConnectionType::Local);
|
||||
|
||||
auto* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(this->globalObject()->inspectorController().debugger());
|
||||
debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isResumed) {
|
||||
auto* inspector = inspectorFromGlobal(reinterpret_cast<Zig::GlobalObject*>(&globalObject));
|
||||
while (!isResumed) {
|
||||
inspector->drainIncomingMessages();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Zig
|
||||
104
src/bun.js/bindings/BunInspector.h
Normal file
104
src/bun.js/bindings/BunInspector.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <uws/src/App.h>
|
||||
#include <JavaScriptCore/InspectorTarget.h>
|
||||
#include <JavaScriptCore/InspectorFrontendChannel.h>
|
||||
#include "ContextDestructionObserver.h"
|
||||
#include <wtf/RefPtr.h>
|
||||
#include <JavaScriptCore/Debugger.h>
|
||||
#include <wtf/Deque.h>
|
||||
#include "JSGlobalObjectInspectorController.h"
|
||||
|
||||
namespace Zig {
|
||||
|
||||
using namespace JSC;
|
||||
using namespace WebCore;
|
||||
|
||||
class BunInspector final : public RefCounted<BunInspector>, ::Inspector::InspectorTarget, ::Inspector::FrontendChannel, public WebCore::ContextDestructionObserver, JSC::Debugger::Observer {
|
||||
public:
|
||||
WTF_MAKE_ISO_ALLOCATED(BunInspector);
|
||||
BunInspector(ScriptExecutionContext* context, uWS::App* server, WTF::String&& identifier)
|
||||
: server(server)
|
||||
, WebCore::ContextDestructionObserver(context)
|
||||
, m_identifier(WTFMove(identifier))
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
~BunInspector()
|
||||
{
|
||||
server->close();
|
||||
}
|
||||
|
||||
bool isProvisional() const override { return false; }
|
||||
String identifier() const override { return m_identifier; }
|
||||
Inspector::InspectorTargetType type() const override { return Inspector::InspectorTargetType::DedicatedWorker; }
|
||||
GlobalObject* globalObject() { return static_cast<GlobalObject*>(scriptExecutionContext()->jsGlobalObject()); }
|
||||
|
||||
void startServer(WTF::String hostname, uint16_t port, WTF::URL url, WTF::String title);
|
||||
|
||||
Lock m_mutex;
|
||||
|
||||
void ensureDebugger();
|
||||
JSC::Debugger* debugger() { return globalObject()->inspectorController().debugger(); }
|
||||
|
||||
void didPause(JSGlobalObject*, DebuggerCallFrame&, JSValue /* exceptionOrCaughtValue */) override;
|
||||
void didContinue() override;
|
||||
void didParseSource(SourceID, const Debugger::Script&) override;
|
||||
void failedToParseSource(const String& /* url */, const String& /* data */, int /* firstLine */, int /* errorLine */, const String& /* errorMessage */) override {}
|
||||
|
||||
void didCreateNativeExecutable(NativeExecutable&) override {}
|
||||
void willCallNativeExecutable(CallFrame*) override {}
|
||||
|
||||
void willEnter(CallFrame*) override {}
|
||||
|
||||
void didQueueMicrotask(JSGlobalObject*, MicrotaskIdentifier) override {}
|
||||
void willRunMicrotask(JSGlobalObject*, MicrotaskIdentifier) override {}
|
||||
void didRunMicrotask(JSGlobalObject*, MicrotaskIdentifier) override {}
|
||||
|
||||
void applyBreakpoints(CodeBlock*) override {}
|
||||
void breakpointActionLog(JSGlobalObject*, const String& /* data */) override {}
|
||||
void breakpointActionSound(BreakpointActionID) override {}
|
||||
void breakpointActionProbe(JSGlobalObject*, BreakpointActionID, unsigned /* batchId */, unsigned /* sampleId */, JSValue /* result */) override {}
|
||||
void didDeferBreakpointPause(BreakpointID) override {}
|
||||
|
||||
static BunInspector* startWebSocketServer(
|
||||
Zig::GlobalObject* globalObject,
|
||||
WebCore::ScriptExecutionContext& ctx,
|
||||
WTF::String hostname,
|
||||
uint16_t port,
|
||||
WTF::Function<void(BunInspector*, bool success)>&& callback);
|
||||
|
||||
// Connection management.
|
||||
void connect(Inspector::FrontendChannel::ConnectionType) override;
|
||||
void disconnect() override;
|
||||
void sendMessageToTargetBackend(const String&) override;
|
||||
bool hasConnectedFrontends() { return connectionCounter > 0; }
|
||||
|
||||
void sendMessageToFrontend(const String& message) override;
|
||||
Inspector::FrontendChannel::ConnectionType connectionType() const override { return Inspector::FrontendChannel::ConnectionType::Remote; }
|
||||
|
||||
int connectionCounter = 0;
|
||||
bool hasSentWelcomeMessage = false;
|
||||
|
||||
void drainOutgoingMessages();
|
||||
void drainIncomingMessages();
|
||||
void waitForMessages();
|
||||
|
||||
void readyToStartDebugger();
|
||||
|
||||
private:
|
||||
void dispatchToBackend(std::string_view message);
|
||||
|
||||
WTF::String m_identifier;
|
||||
WTF::Lock m_pendingMessagesLock;
|
||||
uWS::App* server;
|
||||
uWS::Loop* loop;
|
||||
Deque<WTF::String> m_pendingMessages;
|
||||
|
||||
Deque<WTF::String> m_incomingMessages;
|
||||
WTF::Lock m_incomingMessagesLock;
|
||||
};
|
||||
}
|
||||
@@ -34,6 +34,8 @@
|
||||
using namespace JSC;
|
||||
using namespace WTF;
|
||||
|
||||
extern "C" bool JSGlobalObject__startRemoteInspector(JSC::JSGlobalObject* globalObject, const char* host, uint16_t port);
|
||||
|
||||
JSC_DECLARE_HOST_FUNCTION(functionStartRemoteDebugger);
|
||||
JSC_DEFINE_HOST_FUNCTION(functionStartRemoteDebugger, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
@@ -45,38 +47,13 @@ JSC_DEFINE_HOST_FUNCTION(functionStartRemoteDebugger, (JSGlobalObject * globalOb
|
||||
|
||||
JSC::JSValue hostValue = callFrame->argument(0);
|
||||
JSC::JSValue portValue = callFrame->argument(1);
|
||||
const char* host = defaultHost;
|
||||
if (hostValue.isString()) {
|
||||
|
||||
auto str = hostValue.toWTFString(globalObject);
|
||||
if (!str.isEmpty())
|
||||
host = toCString(str).data();
|
||||
} else if (!hostValue.isUndefined()) {
|
||||
throwVMError(globalObject, scope, createTypeError(globalObject, "host must be a string"_s));
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
bool res = JSGlobalObject__startRemoteInspector(
|
||||
globalObject,
|
||||
hostValue.isUndefinedOrNull() ? defaultHost : hostValue.toWTFString(globalObject).utf8().data(),
|
||||
portValue.isUndefinedOrNull() ? defaultPort : portValue.toUInt32(globalObject));
|
||||
|
||||
uint16_t port = defaultPort;
|
||||
if (portValue.isNumber()) {
|
||||
auto port_int = portValue.toUInt32(globalObject);
|
||||
if (!(port_int > 0 && port_int < 65536)) {
|
||||
throwVMError(globalObject, scope, createRangeError(globalObject, "port must be between 0 and 65535"_s));
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
port = port_int;
|
||||
} else if (!portValue.isUndefined()) {
|
||||
throwVMError(globalObject, scope, createTypeError(globalObject, "port must be a number between 0 and 65535"_s));
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
globalObject->setInspectable(true);
|
||||
auto& server = Inspector::RemoteInspectorServer::singleton();
|
||||
if (!server.start(reinterpret_cast<const char*>(host), port)) {
|
||||
throwVMError(globalObject, scope, createError(globalObject, "Failed to start server \""_s + host + ":"_s + port + "\". Is port already in use?"_s));
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsUndefined()));
|
||||
RELEASE_AND_RETURN(scope, JSC::JSValue::encode(jsBoolean(res)));
|
||||
#else
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
149
src/bun.js/bindings/JSGlobalObjectInspectorController.h
Normal file
149
src/bun.js/bindings/JSGlobalObjectInspectorController.h
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2014, 2015 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JavaScriptCore/InspectorAgentRegistry.h>
|
||||
#include <JavaScriptCore/InspectorEnvironment.h>
|
||||
#include <JavaScriptCore/InspectorFrontendRouter.h>
|
||||
#include <JavaScriptCore/Strong.h>
|
||||
#include <wtf/Forward.h>
|
||||
#include <wtf/Noncopyable.h>
|
||||
#include <wtf/text/WTFString.h>
|
||||
|
||||
#if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
|
||||
#include <AugmentableInspectorController.h>
|
||||
#endif
|
||||
|
||||
namespace JSC {
|
||||
class CallFrame;
|
||||
class ConsoleClient;
|
||||
class Exception;
|
||||
class JSGlobalObject;
|
||||
}
|
||||
|
||||
namespace Inspector {
|
||||
|
||||
class BackendDispatcher;
|
||||
class FrontendChannel;
|
||||
class InjectedScriptManager;
|
||||
class InspectorAgent;
|
||||
class InspectorConsoleAgent;
|
||||
class InspectorDebuggerAgent;
|
||||
class InspectorScriptProfilerAgent;
|
||||
class JSGlobalObjectConsoleClient;
|
||||
class JSGlobalObjectDebugger;
|
||||
class ScriptCallStack;
|
||||
struct JSAgentContext;
|
||||
|
||||
class JSGlobalObjectInspectorController final
|
||||
: public InspectorEnvironment
|
||||
#if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
|
||||
,
|
||||
public AugmentableInspectorController
|
||||
#endif
|
||||
{
|
||||
WTF_MAKE_NONCOPYABLE(JSGlobalObjectInspectorController);
|
||||
WTF_MAKE_FAST_ALLOCATED;
|
||||
|
||||
public:
|
||||
JSGlobalObjectInspectorController(JSC::JSGlobalObject&);
|
||||
~JSGlobalObjectInspectorController() final;
|
||||
|
||||
void connectFrontend(FrontendChannel&, bool isAutomaticInspection, bool immediatelyPause);
|
||||
void disconnectFrontend(FrontendChannel&);
|
||||
|
||||
void dispatchMessageFromFrontend(const String&);
|
||||
|
||||
void globalObjectDestroyed();
|
||||
|
||||
bool includesNativeCallStackWhenReportingExceptions() const { return m_includeNativeCallStackWithExceptions; }
|
||||
void setIncludesNativeCallStackWhenReportingExceptions(bool includesNativeCallStack) { m_includeNativeCallStackWithExceptions = includesNativeCallStack; }
|
||||
|
||||
void reportAPIException(JSC::JSGlobalObject*, JSC::Exception*);
|
||||
|
||||
WeakPtr<JSC::ConsoleClient> consoleClient() const;
|
||||
|
||||
bool developerExtrasEnabled() const final;
|
||||
bool canAccessInspectedScriptState(JSC::JSGlobalObject*) const final { return true; }
|
||||
InspectorFunctionCallHandler functionCallHandler() const final;
|
||||
InspectorEvaluateHandler evaluateHandler() const final;
|
||||
void frontendInitialized() final;
|
||||
WTF::Stopwatch& executionStopwatch() const final;
|
||||
JSC::Debugger* debugger() final;
|
||||
JSC::VM& vm() final;
|
||||
|
||||
#if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
|
||||
AugmentableInspectorControllerClient* augmentableInspectorControllerClient() const final
|
||||
{
|
||||
return m_augmentingClient;
|
||||
}
|
||||
void setAugmentableInspectorControllerClient(AugmentableInspectorControllerClient* client) final { m_augmentingClient = client; }
|
||||
|
||||
const FrontendRouter& frontendRouter() const final { return m_frontendRouter.get(); }
|
||||
BackendDispatcher& backendDispatcher() final { return m_backendDispatcher.get(); }
|
||||
void registerAlternateAgent(std::unique_ptr<InspectorAgentBase>) final;
|
||||
#endif
|
||||
|
||||
private:
|
||||
void appendAPIBacktrace(ScriptCallStack&);
|
||||
|
||||
InspectorAgent& ensureInspectorAgent();
|
||||
InspectorDebuggerAgent& ensureDebuggerAgent();
|
||||
|
||||
JSAgentContext jsAgentContext();
|
||||
void createLazyAgents();
|
||||
|
||||
JSC::JSGlobalObject& m_globalObject;
|
||||
std::unique_ptr<InjectedScriptManager> m_injectedScriptManager;
|
||||
std::unique_ptr<JSGlobalObjectConsoleClient> m_consoleClient;
|
||||
Ref<WTF::Stopwatch> m_executionStopwatch;
|
||||
std::unique_ptr<JSGlobalObjectDebugger> m_debugger;
|
||||
|
||||
AgentRegistry m_agents;
|
||||
InspectorConsoleAgent* m_consoleAgent { nullptr };
|
||||
|
||||
// Lazy, but also on-demand agents.
|
||||
InspectorAgent* m_inspectorAgent { nullptr };
|
||||
InspectorDebuggerAgent* m_debuggerAgent { nullptr };
|
||||
|
||||
Ref<FrontendRouter> m_frontendRouter;
|
||||
Ref<BackendDispatcher> m_backendDispatcher;
|
||||
|
||||
// Used to keep the JSGlobalObject and VM alive while we are debugging it.
|
||||
JSC::Strong<JSC::JSGlobalObject> m_strongGlobalObject;
|
||||
RefPtr<JSC::VM> m_strongVM;
|
||||
|
||||
bool m_includeNativeCallStackWithExceptions { true };
|
||||
bool m_isAutomaticInspection { false };
|
||||
bool m_pauseAfterInitialization { false };
|
||||
bool m_didCreateLazyAgents { false };
|
||||
|
||||
#if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS)
|
||||
AugmentableInspectorControllerClient* m_augmentingClient { nullptr };
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Inspector
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "_libusockets.h"
|
||||
|
||||
extern "C" void Bun__startLoop(us_loop_t* loop);
|
||||
extern "C" void Bun__getMainPath(JSC::JSGlobalObject* globalObject, ZigString* out);
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
@@ -34,6 +35,18 @@ static void registerHTTPContextForWebSocket(ScriptExecutionContext* script, us_s
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptExecutionContext::setURL(const ZigString* filePath)
|
||||
{
|
||||
setURL(WTF::URL::fileURLWithFileSystemPath(toStringCopy(*filePath)).isolatedCopy());
|
||||
}
|
||||
|
||||
void ScriptExecutionContext::ensureURL()
|
||||
{
|
||||
ZigString filePath;
|
||||
Bun__getMainPath(m_globalObject, &filePath);
|
||||
setURL(&filePath);
|
||||
}
|
||||
|
||||
us_socket_context_t* ScriptExecutionContext::webSocketContextSSL()
|
||||
{
|
||||
if (!m_ssl_client_websockets_ctx) {
|
||||
|
||||
@@ -106,6 +106,11 @@ public:
|
||||
void reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception* exception, RefPtr<void*>&&, CachedScript* = nullptr, bool = false)
|
||||
{
|
||||
}
|
||||
|
||||
void setURL(const WTF::URL& url) { m_url = url; }
|
||||
void setURL(const ZigString* sourceFilePath);
|
||||
void ensureURL();
|
||||
|
||||
// void reportUnhandledPromiseRejection(JSC::JSGlobalObject&, JSC::JSPromise&, RefPtr<Inspector::ScriptCallStack>&&)
|
||||
// {
|
||||
// }
|
||||
|
||||
@@ -114,6 +114,8 @@
|
||||
#include "JSEnvironmentVariableMap.h"
|
||||
#include "DOMIsoSubspaces.h"
|
||||
|
||||
#include "BunInspector.h"
|
||||
|
||||
#if ENABLE(REMOTE_INSPECTOR)
|
||||
#include "JavaScriptCore/RemoteInspectorServer.h"
|
||||
#endif
|
||||
@@ -238,10 +240,33 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" bool JSGlobalObject__startRemoteInspector(Zig::GlobalObject* globalObject, const char* host, uint16_t port)
|
||||
{
|
||||
#if !ENABLE(REMOTE_INSPECTOR)
|
||||
return false;
|
||||
#else
|
||||
bool didSucceed = false;
|
||||
|
||||
// This function calls immediately.
|
||||
auto inspector = BunInspector::startWebSocketServer(
|
||||
globalObject,
|
||||
*globalObject->scriptExecutionContext(),
|
||||
WTF::String::fromUTF8(host),
|
||||
port, [port, &didSucceed](BunInspector* inspector, bool success) {
|
||||
didSucceed = success;
|
||||
|
||||
if (success) {
|
||||
inspector->ref();
|
||||
}
|
||||
});
|
||||
return didSucceed;
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" void* Bun__getVM();
|
||||
|
||||
extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* globalObjectClass, int count,
|
||||
void* console_client)
|
||||
void* console_client, int inspector)
|
||||
{
|
||||
auto heapSize = JSC::HeapType::Large;
|
||||
|
||||
@@ -259,9 +284,11 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* globalObje
|
||||
if (count > 0) {
|
||||
globalObject->installAPIGlobals(globalObjectClass, count, vm);
|
||||
}
|
||||
|
||||
if (inspector != 0) {
|
||||
globalObject->setInspectable(true);
|
||||
globalObject->inspectorController().debugger();
|
||||
}
|
||||
JSC::gcProtect(globalObject);
|
||||
|
||||
vm.ref();
|
||||
return globalObject;
|
||||
}
|
||||
@@ -526,6 +553,9 @@ WebCore::ScriptExecutionContext* GlobalObject::scriptExecutionContext() const
|
||||
void GlobalObject::reportUncaughtExceptionAtEventLoop(JSGlobalObject* globalObject,
|
||||
JSC::Exception* exception)
|
||||
{
|
||||
if (globalObject->hasDebugger()) {
|
||||
globalObject->debugger()->exception(globalObject, nullptr, exception, false);
|
||||
}
|
||||
Bun__reportUnhandledError(globalObject, JSValue::encode(JSValue(exception)));
|
||||
}
|
||||
|
||||
@@ -537,10 +567,16 @@ void GlobalObject::promiseRejectionTracker(JSGlobalObject* obj, JSC::JSPromise*
|
||||
|
||||
// Do this in C++ for now
|
||||
auto* globalObj = reinterpret_cast<GlobalObject*>(obj);
|
||||
|
||||
switch (operation) {
|
||||
case JSPromiseRejectionOperation::Reject:
|
||||
case JSPromiseRejectionOperation::Reject: {
|
||||
if (obj->hasDebugger()) {
|
||||
obj->debugger()->exception(obj, nullptr, promise->result(), false);
|
||||
}
|
||||
|
||||
globalObj->m_aboutToBeNotifiedRejectedPromises.append(JSC::Strong<JSPromise>(obj->vm(), promise));
|
||||
break;
|
||||
}
|
||||
case JSPromiseRejectionOperation::Handle:
|
||||
globalObj->m_aboutToBeNotifiedRejectedPromises.removeFirstMatching([&](Strong<JSPromise>& unhandledPromise) {
|
||||
return unhandledPromise.get() == promise;
|
||||
@@ -3759,17 +3795,6 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm
|
||||
extraStaticGlobals.releaseBuffer();
|
||||
}
|
||||
|
||||
extern "C" bool JSC__JSGlobalObject__startRemoteInspector(JSC__JSGlobalObject* globalObject, unsigned char* host, uint16_t arg1)
|
||||
{
|
||||
#if !ENABLE(REMOTE_INSPECTOR)
|
||||
return false;
|
||||
#else
|
||||
globalObject->setInspectable(true);
|
||||
auto& server = Inspector::RemoteInspectorServer::singleton();
|
||||
return server.start(reinterpret_cast<const char*>(host), arg1);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
|
||||
@@ -403,6 +403,8 @@ public:
|
||||
void* napiInstanceDataFinalizer = nullptr;
|
||||
void* napiInstanceDataFinalizerHint = nullptr;
|
||||
|
||||
void* bunInspectorPtr = nullptr;
|
||||
|
||||
#include "ZigGeneratedClasses+lazyStructureHeader.h"
|
||||
|
||||
private:
|
||||
|
||||
@@ -2772,8 +2772,10 @@ pub const JSGlobalObject = extern struct {
|
||||
return cppFn("handleRejectedPromises", .{this});
|
||||
}
|
||||
|
||||
extern fn JSGlobalObject__startRemoteInspector(*JSGlobalObject, [*:0]const u8, u16) bool;
|
||||
pub fn startRemoteInspector(this: *JSGlobalObject, host: [:0]const u8, port: u16) bool {
|
||||
return cppFn("startRemoteInspector", .{ this, host, port });
|
||||
if (comptime is_bindgen) unreachable;
|
||||
return JSGlobalObject__startRemoteInspector(this, host, port);
|
||||
}
|
||||
|
||||
extern fn ZigGlobalObject__readableStreamToArrayBuffer(*JSGlobalObject, JSValue) JSValue;
|
||||
@@ -2812,7 +2814,6 @@ pub const JSGlobalObject = extern struct {
|
||||
|
||||
"vm",
|
||||
"generateHeapSnapshot",
|
||||
"startRemoteInspector",
|
||||
"handleRejectedPromises",
|
||||
"createSyntheticModule_",
|
||||
"queueMicrotaskJob",
|
||||
|
||||
@@ -39,8 +39,14 @@ pub const ZigGlobalObject = extern struct {
|
||||
pub const namespace = shim.namespace;
|
||||
pub const Interface: type = NewGlobalObject(JS.VirtualMachine);
|
||||
|
||||
pub fn create(class_ref: [*]CAPI.JSClassRef, count: i32, console: *anyopaque) *JSGlobalObject {
|
||||
var global = shim.cppFn("create", .{ class_ref, count, console });
|
||||
pub const Inspect = enum(i32) {
|
||||
none = 0,
|
||||
port = 1,
|
||||
breakpoint = 2,
|
||||
};
|
||||
|
||||
pub fn create(class_ref: [*]CAPI.JSClassRef, count: i32, console: *anyopaque, inspect: Inspect) *JSGlobalObject {
|
||||
var global = shim.cppFn("create", .{ class_ref, count, console, @enumToInt(inspect) });
|
||||
Backtrace.reloadHandlers() catch unreachable;
|
||||
return global;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//-- AUTOGENERATED FILE -- 1681281993
|
||||
// clang-format off
|
||||
//-- GENERATED FILE. Do not edit --
|
||||
//
|
||||
|
||||
3
src/bun.js/bindings/headers.h
generated
3
src/bun.js/bindings/headers.h
generated
@@ -282,7 +282,6 @@ CPP_DECL void JSC__JSGlobalObject__handleRejectedPromises(JSC__JSGlobalObject* a
|
||||
CPP_DECL JSC__JSValue JSC__JSGlobalObject__putCachedObject(JSC__JSGlobalObject* arg0, const ZigString* arg1, JSC__JSValue JSValue2);
|
||||
CPP_DECL void JSC__JSGlobalObject__queueMicrotaskJob(JSC__JSGlobalObject* arg0, JSC__JSValue JSValue1, JSC__JSValue JSValue2, JSC__JSValue JSValue3, JSC__JSValue JSValue4);
|
||||
CPP_DECL void JSC__JSGlobalObject__reload(JSC__JSGlobalObject* arg0);
|
||||
CPP_DECL bool JSC__JSGlobalObject__startRemoteInspector(JSC__JSGlobalObject* arg0, unsigned char* arg1, uint16_t arg2);
|
||||
CPP_DECL JSC__VM* JSC__JSGlobalObject__vm(JSC__JSGlobalObject* arg0);
|
||||
|
||||
#pragma mark - JSC::JSMap
|
||||
@@ -567,7 +566,7 @@ ZIG_DECL JSC__JSValue Crypto__timingSafeEqual__slowpath(JSC__JSGlobalObject* arg
|
||||
|
||||
#pragma mark - Zig::GlobalObject
|
||||
|
||||
CPP_DECL JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* arg0, int32_t arg1, void* arg2);
|
||||
CPP_DECL JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* arg0, int32_t arg1, void* arg2, int32_t Inspect3);
|
||||
CPP_DECL void* Zig__GlobalObject__getModuleRegistryMap(JSC__JSGlobalObject* arg0);
|
||||
CPP_DECL bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject* arg0, void* arg1);
|
||||
|
||||
|
||||
3
src/bun.js/bindings/headers.zig
generated
3
src/bun.js/bindings/headers.zig
generated
@@ -189,7 +189,6 @@ pub extern fn JSC__JSGlobalObject__handleRejectedPromises(arg0: *bindings.JSGlob
|
||||
pub extern fn JSC__JSGlobalObject__putCachedObject(arg0: *bindings.JSGlobalObject, arg1: [*c]const ZigString, JSValue2: JSC__JSValue) JSC__JSValue;
|
||||
pub extern fn JSC__JSGlobalObject__queueMicrotaskJob(arg0: *bindings.JSGlobalObject, JSValue1: JSC__JSValue, JSValue2: JSC__JSValue, JSValue3: JSC__JSValue, JSValue4: JSC__JSValue) void;
|
||||
pub extern fn JSC__JSGlobalObject__reload(arg0: *bindings.JSGlobalObject) void;
|
||||
pub extern fn JSC__JSGlobalObject__startRemoteInspector(arg0: *bindings.JSGlobalObject, arg1: [*c]u8, arg2: u16) bool;
|
||||
pub extern fn JSC__JSGlobalObject__vm(arg0: *bindings.JSGlobalObject) *bindings.VM;
|
||||
pub extern fn JSC__JSMap__create(arg0: *bindings.JSGlobalObject) JSC__JSValue;
|
||||
pub extern fn JSC__JSMap__get_(arg0: ?*bindings.JSMap, arg1: *bindings.JSGlobalObject, JSValue2: JSC__JSValue) JSC__JSValue;
|
||||
@@ -340,7 +339,7 @@ pub extern fn Reader__intptr__put(arg0: *bindings.JSGlobalObject, JSValue1: JSC_
|
||||
pub extern fn Crypto__getRandomValues__put(arg0: *bindings.JSGlobalObject, JSValue1: JSC__JSValue) void;
|
||||
pub extern fn Crypto__randomUUID__put(arg0: *bindings.JSGlobalObject, JSValue1: JSC__JSValue) void;
|
||||
pub extern fn Crypto__timingSafeEqual__put(arg0: *bindings.JSGlobalObject, JSValue1: JSC__JSValue) void;
|
||||
pub extern fn Zig__GlobalObject__create(arg0: [*c]JSClassRef, arg1: i32, arg2: ?*anyopaque) *bindings.JSGlobalObject;
|
||||
pub extern fn Zig__GlobalObject__create(arg0: [*c]JSClassRef, arg1: i32, arg2: ?*anyopaque, Inspect3: i32) *bindings.JSGlobalObject;
|
||||
pub extern fn Zig__GlobalObject__getModuleRegistryMap(arg0: *bindings.JSGlobalObject) ?*anyopaque;
|
||||
pub extern fn Zig__GlobalObject__resetModuleRegistryMap(arg0: *bindings.JSGlobalObject, arg1: ?*anyopaque) bool;
|
||||
pub extern fn Bun__Path__create(arg0: *bindings.JSGlobalObject, arg1: bool) JSC__JSValue;
|
||||
|
||||
BIN
src/bun.js/bun.lockb
Executable file
BIN
src/bun.js/bun.lockb
Executable file
Binary file not shown.
@@ -879,3 +879,12 @@ pub const AnyEventLoop = union(enum) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub export fn Bun__tickWhileWaitingForDebugger(globalObject: *JSC.JSGlobalObject) callconv(.C) void {
|
||||
globalObject.bunVM().eventLoop().tickPossiblyForever();
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (!JSC.is_bindgen)
|
||||
_ = Bun__tickWhileWaitingForDebugger;
|
||||
}
|
||||
|
||||
@@ -256,6 +256,28 @@ pub export fn Bun__drainMicrotasks() void {
|
||||
JSC.VirtualMachine.get().eventLoop().tick();
|
||||
}
|
||||
|
||||
const WaitForDebugger = struct {
|
||||
var is_debugger_ready = std.atomic.Atomic(u32).init(0);
|
||||
pub fn ready() void {
|
||||
is_debugger_ready.store(1, .Monotonic);
|
||||
std.Thread.Futex.wake(&is_debugger_ready, 1);
|
||||
}
|
||||
|
||||
pub fn wait() void {
|
||||
while (is_debugger_ready.load(.Monotonic) == 0) {
|
||||
std.Thread.Futex.wait(&is_debugger_ready, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub export fn Bun__waitForDebuggerToStart() void {
|
||||
WaitForDebugger.wait();
|
||||
}
|
||||
|
||||
pub export fn Bun__debuggerIsReady() void {
|
||||
WaitForDebugger.ready();
|
||||
}
|
||||
|
||||
export fn Bun__readOriginTimer(vm: *JSC.VirtualMachine) u64 {
|
||||
return vm.origin_timer.read();
|
||||
}
|
||||
@@ -320,6 +342,12 @@ pub export fn Bun__handleRejectedPromise(global: *JSGlobalObject, promise: *JSC.
|
||||
jsc_vm.autoGarbageCollect();
|
||||
}
|
||||
|
||||
pub export fn Bun__getMainPath(globalObject: *JSC.JSGlobalObject, out: *ZigString) void {
|
||||
var jsc_vm = globalObject.bunVM();
|
||||
|
||||
out.* = ZigString.fromUTF8(jsc_vm.main);
|
||||
}
|
||||
|
||||
pub export fn Bun__onDidAppendPlugin(jsc_vm: *VirtualMachine, globalObject: *JSGlobalObject) void {
|
||||
if (jsc_vm.plugin_runner != null) {
|
||||
return;
|
||||
@@ -449,6 +477,8 @@ pub const VirtualMachine = struct {
|
||||
|
||||
gc_controller: JSC.GarbageCollectionController = .{},
|
||||
|
||||
auto_inspect: bool = false,
|
||||
|
||||
pub const OnUnhandledRejection = fn (*VirtualMachine, globalObject: *JSC.JSGlobalObject, JSC.JSValue) void;
|
||||
|
||||
pub const OnException = fn (*ZigException) void;
|
||||
@@ -812,6 +842,7 @@ pub const VirtualMachine = struct {
|
||||
&global_classes,
|
||||
@intCast(i32, global_classes.len),
|
||||
vm.console,
|
||||
.none,
|
||||
);
|
||||
vm.regular_event_loop.global = vm.global;
|
||||
vm.regular_event_loop.virtual_machine = vm;
|
||||
@@ -826,16 +857,21 @@ pub const VirtualMachine = struct {
|
||||
return vm;
|
||||
}
|
||||
|
||||
pub const VMOptions = struct {
|
||||
env_loader: ?*DotEnv.Loader = null,
|
||||
log: ?*logger.Log = null,
|
||||
existing_bundle: ?*NodeModuleBundle = null,
|
||||
store_fd: bool = false,
|
||||
inspector: JSC.ZigGlobalObject.Inspect = .none,
|
||||
};
|
||||
|
||||
pub fn init(
|
||||
allocator: std.mem.Allocator,
|
||||
_args: Api.TransformOptions,
|
||||
existing_bundle: ?*NodeModuleBundle,
|
||||
_log: ?*logger.Log,
|
||||
env_loader: ?*DotEnv.Loader,
|
||||
store_fd: bool,
|
||||
vm_opts: VMOptions,
|
||||
) !*VirtualMachine {
|
||||
var log: *logger.Log = undefined;
|
||||
if (_log) |__log| {
|
||||
if (vm_opts.log) |__log| {
|
||||
log = __log;
|
||||
} else {
|
||||
log = try allocator.create(logger.Log);
|
||||
@@ -849,8 +885,8 @@ pub const VirtualMachine = struct {
|
||||
allocator,
|
||||
log,
|
||||
try Config.configureTransformOptionsForBunVM(allocator, _args),
|
||||
existing_bundle,
|
||||
env_loader,
|
||||
vm_opts.existing_bundle,
|
||||
vm_opts.env_loader,
|
||||
);
|
||||
var vm = VMHolder.vm.?;
|
||||
|
||||
@@ -884,7 +920,7 @@ pub const VirtualMachine = struct {
|
||||
vm.event_loop = &vm.regular_event_loop;
|
||||
|
||||
vm.bundler.macro_context = null;
|
||||
vm.bundler.resolver.store_fd = store_fd;
|
||||
vm.bundler.resolver.store_fd = vm_opts.store_fd;
|
||||
|
||||
vm.bundler.resolver.onWakePackageManager = .{
|
||||
.context = &vm.modules,
|
||||
@@ -896,7 +932,7 @@ pub const VirtualMachine = struct {
|
||||
try vm.bundler.configureFramework(false);
|
||||
|
||||
vm.bundler.macro_context = js_ast.Macro.MacroContext.init(&vm.bundler);
|
||||
|
||||
vm.auto_inspect = vm_opts.inspector != .none;
|
||||
if (_args.serve orelse false) {
|
||||
vm.bundler.linker.onImportCSS = Bun.onImportCSS;
|
||||
}
|
||||
@@ -909,6 +945,7 @@ pub const VirtualMachine = struct {
|
||||
&global_classes,
|
||||
@intCast(i32, global_classes.len),
|
||||
vm.console,
|
||||
vm_opts.inspector,
|
||||
);
|
||||
vm.regular_event_loop.global = vm.global;
|
||||
vm.regular_event_loop.virtual_machine = vm;
|
||||
@@ -1672,6 +1709,11 @@ pub const VirtualMachine = struct {
|
||||
JSC.JSValue.fromCell(promise).ensureStillAlive();
|
||||
}
|
||||
|
||||
if (this.auto_inspect) {
|
||||
this.auto_inspect = false;
|
||||
_ = this.global.startRemoteInspector("0.0.0.0", 9229);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
@@ -2771,3 +2813,11 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
comptime {
|
||||
if (!JSC.is_bindgen) {
|
||||
_ = Bun__getMainPath;
|
||||
_ = Bun__waitForDebuggerToStart;
|
||||
_ = Bun__debuggerIsReady;
|
||||
}
|
||||
}
|
||||
|
||||
6
src/bun.js/package.json
Normal file
6
src/bun.js/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"expect": "^29.5.0"
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ pub const Run = struct {
|
||||
try bun.CLI.Arguments.loadConfigPath(ctx.allocator, true, "bunfig.toml", &ctx, .RunCommand);
|
||||
}
|
||||
|
||||
run = .{
|
||||
run = Run{
|
||||
.vm = try VirtualMachine.initWithModuleGraph(arena.allocator(), ctx.log, graph_ptr),
|
||||
.arena = arena,
|
||||
.ctx = ctx,
|
||||
@@ -147,10 +147,12 @@ pub const Run = struct {
|
||||
.vm = try VirtualMachine.init(
|
||||
arena.allocator(),
|
||||
ctx.args,
|
||||
null,
|
||||
ctx.log,
|
||||
null,
|
||||
ctx.debug.hot_reload != .none,
|
||||
.{
|
||||
.log = ctx.log,
|
||||
.store_fd = ctx.debug.hot_reload != .none,
|
||||
.inspector = ctx.debug.inspector,
|
||||
.env_loader = null,
|
||||
},
|
||||
),
|
||||
.arena = arena,
|
||||
.ctx = ctx,
|
||||
|
||||
17
src/cli.zig
17
src/cli.zig
@@ -173,6 +173,8 @@ pub const Arguments = struct {
|
||||
clap.parseParam("--prefer-offline Skip staleness checks for packages in bun's JavaScript runtime and resolve from disk") catch unreachable,
|
||||
clap.parseParam("--prefer-latest Use the latest matching versions of packages in bun's JavaScript runtime, always checking npm") catch unreachable,
|
||||
clap.parseParam("--silent Don't repeat the command for bun run") catch unreachable,
|
||||
clap.parseParam("--inspect-brk Start a debugger with a breakpoint set") catch unreachable,
|
||||
clap.parseParam("--inspect Start a debugger") catch unreachable,
|
||||
};
|
||||
|
||||
const public_params = shared_public_params ++ not_bun_dev_flags;
|
||||
@@ -185,8 +187,8 @@ pub const Arguments = struct {
|
||||
pub const dev_params = [_]ParamType{
|
||||
clap.parseParam("--disable-bun.js Disable bun.js from loading in the dev server") catch unreachable,
|
||||
clap.parseParam("--disable-react-fast-refresh Disable React Fast Refresh") catch unreachable,
|
||||
clap.parseParam("--bunfile <STR> Use a .bun file (default: node_modules.bun)") catch unreachable,
|
||||
clap.parseParam("--server-bunfile <STR> Use a .server.bun file (default: node_modules.server.bun)") catch unreachable,
|
||||
clap.parseParam("--bunfile <STR> Use a .bun file (DEPRECATED)") catch unreachable,
|
||||
clap.parseParam("--server-bunfile <STR> Use a .server.bun file (DEPRECATED)") catch unreachable,
|
||||
clap.parseParam("--public-dir <STR> Top-level directory for .html files, fonts or anything external. Defaults to \"<cwd>/public\", to match create-react-app and Next.js") catch unreachable,
|
||||
clap.parseParam("--disable-hmr Disable Hot Module Reloading (disables fast refresh too) in bun dev") catch unreachable,
|
||||
clap.parseParam("--use <STR> Choose a framework, e.g. \"--use next\". It checks first for a package named \"bun-framework-packagename\" and then \"packagename\".") catch unreachable,
|
||||
@@ -471,8 +473,7 @@ pub const Arguments = struct {
|
||||
|
||||
const print_help = args.flag("--help");
|
||||
if (print_help) {
|
||||
const params_len = if (cmd == .BuildCommand) build_params_public.len else public_params.len;
|
||||
clap.help(Output.writer(), params_to_use[0..params_len]) catch {};
|
||||
clap.help(Output.writer(), params_to_use) catch {};
|
||||
Output.prettyln("\n-------\n\n", .{});
|
||||
Output.flush();
|
||||
HelpCommand.printWithReason(.explicit);
|
||||
@@ -686,6 +687,12 @@ pub const Arguments = struct {
|
||||
// const ResolveMatcher = strings.ExactSizeMatcher(8);
|
||||
|
||||
opts.resolve = Api.ResolveMode.lazy;
|
||||
if (comptime cmd == .RunCommand or cmd == .AutoCommand or cmd == .TestCommand) {
|
||||
if (args.flag("--inspect-brk"))
|
||||
ctx.debug.inspector = .breakpoint
|
||||
else if (args.flag("--inspect"))
|
||||
ctx.debug.inspector = .port;
|
||||
}
|
||||
|
||||
const TargetMatcher = strings.ExactSizeMatcher(8);
|
||||
|
||||
@@ -906,6 +913,8 @@ pub const Command = struct {
|
||||
run_in_bun: bool = false,
|
||||
loaded_bunfig: bool = false,
|
||||
|
||||
inspector: bun.JSC.ZigGlobalObject.Inspect = .none,
|
||||
|
||||
// technical debt
|
||||
macros: MacroOptions = MacroOptions.unspecified,
|
||||
editor: string = "",
|
||||
|
||||
@@ -465,15 +465,19 @@ pub const TestCommand = struct {
|
||||
var vm = try JSC.VirtualMachine.init(
|
||||
ctx.allocator,
|
||||
ctx.args,
|
||||
null,
|
||||
ctx.log,
|
||||
env_loader,
|
||||
// we must store file descriptors because we reuse them for
|
||||
// iterating through the directory tree recursively
|
||||
//
|
||||
// in the future we should investigate if refactoring this to not
|
||||
// rely on the dir fd yields a performance improvement
|
||||
true,
|
||||
.{
|
||||
.log = ctx.log,
|
||||
.env_loader = env_loader,
|
||||
.existing_bundle = null,
|
||||
.store_fd =
|
||||
// we must store file descriptors because we reuse them for
|
||||
// iterating through the directory tree recursively
|
||||
//
|
||||
// in the future we should investigate if refactoring this to not
|
||||
// rely on the dir fd yields a performance improvement
|
||||
true,
|
||||
.inspector = ctx.debug.inspector,
|
||||
},
|
||||
);
|
||||
vm.argv = ctx.passthrough;
|
||||
vm.preload = ctx.preloads;
|
||||
|
||||
10
src/http.zig
10
src/http.zig
@@ -1467,10 +1467,12 @@ pub const RequestContext = struct {
|
||||
var vm: *JavaScript.VirtualMachine = JavaScript.VirtualMachine.init(
|
||||
bun.default_allocator,
|
||||
handler.args,
|
||||
null,
|
||||
handler.log,
|
||||
handler.env_loader,
|
||||
true,
|
||||
.{
|
||||
.log = handler.log,
|
||||
.env_loader = handler.env_loader,
|
||||
.store_fd = true,
|
||||
.inspector = .none,
|
||||
},
|
||||
) catch |err| {
|
||||
handler.handleJSError(.create_vm, err) catch {};
|
||||
javascript_disabled = true;
|
||||
|
||||
@@ -9595,14 +9595,13 @@ pub const Macro = struct {
|
||||
resolver.opts.transform_options.node_modules_bundle_path = null;
|
||||
resolver.opts.transform_options.node_modules_bundle_path_server = null;
|
||||
defer resolver.opts.transform_options = old_transform_options;
|
||||
var _vm = try JavaScript.VirtualMachine.init(
|
||||
default_allocator,
|
||||
resolver.opts.transform_options,
|
||||
null,
|
||||
log,
|
||||
env,
|
||||
false,
|
||||
);
|
||||
var _vm = try JavaScript.VirtualMachine.init(default_allocator, resolver.opts.transform_options, .{
|
||||
.log = log,
|
||||
.existing_bundle = null,
|
||||
.store_fd = false,
|
||||
.inspector = .none,
|
||||
.env_loader = env,
|
||||
});
|
||||
|
||||
_vm.enableMacroMode();
|
||||
_vm.eventLoop().ensureWaker();
|
||||
|
||||
Reference in New Issue
Block a user