aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-07-16 21:15:24 -0700
committerGravatar GitHub <noreply@github.com> 2023-07-16 21:15:24 -0700
commit209dc981c0ef52de5c80eab5d24ec8a8eba765e8 (patch)
treec3d203d03d109161bcaf25558c05fda49c5e864e
parent7fc392b182fa45d1fa33e654c2b4d1f1022a1ac3 (diff)
downloadbun-209dc981c0ef52de5c80eab5d24ec8a8eba765e8.tar.gz
bun-209dc981c0ef52de5c80eab5d24ec8a8eba765e8.tar.zst
bun-209dc981c0ef52de5c80eab5d24ec8a8eba765e8.zip
Implement Workers (#3645)
* copy files * format * options * Introduce `Worker`, `onmessage`, `onerror`, and `postMessage` globals * Stub `Worker.prototype.ref` & `Worker.prototype.unref` * Update web_worker.zig * Worker works * Add "mini" mode * add wakeup * Partially fix the keep-alive issue * clean up refer behavior * Implement `serialize` & `deserialize` in `bun:jsc` & add polyfill for `node:v8` * Types & docs * Update globals.d.ts * Add mutex * Fixes --------- Co-authored-by: Dylan Conway <dylan.conway567@gmail.com> Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--docs/api/worker.md162
-rw-r--r--docs/runtime/nodejs-apis.md2
-rw-r--r--docs/runtime/web-apis.md7
-rw-r--r--packages/bun-types/globals.d.ts74
-rw-r--r--packages/bun-types/jsc.d.ts36
-rw-r--r--src/bun.js/api/bun.zig7
-rw-r--r--src/bun.js/bindings/BunJSCModule.cpp74
-rw-r--r--src/bun.js/bindings/BunWorkerGlobalScope.cpp10
-rw-r--r--src/bun.js/bindings/BunWorkerGlobalScope.h38
-rw-r--r--src/bun.js/bindings/ScriptExecutionContext.cpp10
-rw-r--r--src/bun.js/bindings/ScriptExecutionContext.h15
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp231
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h17
-rw-r--r--src/bun.js/bindings/exports.zig58
-rw-r--r--src/bun.js/bindings/headers.h2
-rw-r--r--src/bun.js/bindings/headers.zig2
-rw-r--r--src/bun.js/bindings/webcore/ActiveDOMObject.h2
-rw-r--r--src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h2
-rw-r--r--src/bun.js/bindings/webcore/DOMIsoSubspaces.h2
-rw-r--r--src/bun.js/bindings/webcore/EventTargetFactory.cpp8
-rw-r--r--src/bun.js/bindings/webcore/EventTargetHeaders.h6
-rw-r--r--src/bun.js/bindings/webcore/JSEventTarget.cpp6
-rw-r--r--src/bun.js/bindings/webcore/JSEventTarget.h4
-rw-r--r--src/bun.js/bindings/webcore/JSEventTargetCustom.cpp16
-rw-r--r--src/bun.js/bindings/webcore/JSWorker.cpp524
-rw-r--r--src/bun.js/bindings/webcore/JSWorker.h99
-rw-r--r--src/bun.js/bindings/webcore/JSWorkerOptions.cpp83
-rw-r--r--src/bun.js/bindings/webcore/JSWorkerOptions.h30
-rw-r--r--src/bun.js/bindings/webcore/SerializedScriptValue.cpp79
-rw-r--r--src/bun.js/bindings/webcore/SerializedScriptValue.h3
-rw-r--r--src/bun.js/bindings/webcore/StructuredSerializeOptions.h44
-rw-r--r--src/bun.js/bindings/webcore/Worker.cpp390
-rw-r--r--src/bun.js/bindings/webcore/Worker.h139
-rw-r--r--src/bun.js/bindings/webcore/WorkerOptions.h20
-rw-r--r--src/bun.js/event_loop.zig10
-rw-r--r--src/bun.js/javascript.zig120
-rw-r--r--src/bun.js/node/types.zig8
-rw-r--r--src/bun.js/web_worker.zig406
-rw-r--r--src/js/bun/jsc.ts2
-rw-r--r--src/js/node/v8.ts9
-rw-r--r--src/js/out/modules/bun/jsc.js4
-rw-r--r--src/js/out/modules/node/v8.js9
-rw-r--r--test/js/bun/jsc/bun-jsc.test.ts26
-rw-r--r--test/js/web/web-globals.test.js51
-rw-r--r--test/js/web/worker-fixture.js6
-rw-r--r--test/js/web/worker.test.ts18
46 files changed, 2796 insertions, 75 deletions
diff --git a/docs/api/worker.md b/docs/api/worker.md
new file mode 100644
index 000000000..c5a3da1a9
--- /dev/null
+++ b/docs/api/worker.md
@@ -0,0 +1,162 @@
+[`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) lets you start and communicate with a new JavaScript instance running on a separate thread while sharing I/O resources with the main thread. You can use TypeScript, CommonJS, ESM, JSX, etc in your workers. `Worker` support was added in Bun v0.6.15.
+
+Bun implements a minimal version of the [Web Workers API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) with extensions that make it work better for server-side use cases.
+
+## Usage
+
+Like in browsers, [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) is a global. Use it to create a new worker thread.
+
+Main thread:
+
+```js
+const worker = new Worker(new URL("worker.ts", import.meta.url).href);
+worker.postMessage("hello");
+worker.onmessage = event => {
+ console.log(event.data);
+};
+```
+
+Worker thread:
+
+{% codetabs %}
+
+```ts#worker.ts
+self.onmessage = (event: MessageEvent) => {
+ console.log(event.data);
+ postMessage("world");
+};
+```
+
+{% /codetabs %}
+
+### Sending & receiving messages with `postMessage`
+
+To send messages, use [`worker.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage) and [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). This leverages the [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).
+
+```js
+// On the worker thread, `postMessage` is automatically "routed" to the parent thread.
+postMessage({ hello: "world" });
+
+// On the main thread
+worker.postMessage({ hello: "world" });
+```
+
+To receive messages, use the [`message` event handler](https://developer.mozilla.org/en-US/docs/Web/API/Worker/message_event) on the worker and main thread.
+
+```js
+// Worker thread:
+self.addEventListener("message", = event => {
+ console.log(event.data);
+});
+// or use the setter:
+// self.onmessage = fn
+
+// if on the main thread
+worker.addEventListener("message", = event => {
+ console.log(event.data);
+});
+// or use the setter:
+// worker.onmessage = fn
+```
+
+### Terminating a worker
+
+A `Worker` instance terminate automatically when Bun's process exits. To terminate a `Worker` sooner, call `worker.terminate()`.
+
+```ts
+const worker = new Worker(new URL("worker.ts", import.meta.url).href);
+
+// ...some time later
+worker.terminate();
+```
+
+### Managing lifetime with `worker.ref` and `worker.unref`
+
+By default, a `Worker` will **not** keep the process alive. To keep the process alive until the `Worker` terminates, call `worker.ref()`.
+
+```ts
+const worker = new Worker(new URL("worker.ts", import.meta.url).href);
+worker.ref();
+```
+
+You can also pass an `options` object to `Worker`:
+
+```ts
+const worker = new Worker(new URL("worker.ts", import.meta.url).href, {
+ bun: {
+ ref: true,
+ },
+});
+```
+
+To stop keeping the process alive, call `worker.unref()`.
+
+```ts
+const worker = new Worker(new URL("worker.ts", import.meta.url).href);
+worker.ref();
+// ...later on
+worker.unref();
+```
+
+Note: `worker.ref()` and `worker.unref()` do not exist in browsers.
+
+### Memory Usage
+
+JavaScript instances sometimes use a lot of memory.
+
+Bun's `Worker` supports a `smol` mode that reduces memory usage, at a cost of performance. To enable `smol` mode, pass `smol: true` to the `options` object in the `Worker` constructor.
+
+```js
+const worker = new Worker("./i-am-smol.ts", {
+ bun: {
+ smol: true,
+ },
+});
+```
+
+#### What does `smol` mode actually do?
+
+It sets ` JSC::HeapSize` to be `Small` instead of the default `Large`
+
+### Modules work
+
+Like the rest of Bun, `Worker` in Bun support CommonJS, ES Modules, TypeScript, JSX, TSX and more out of the box. No extra build steps are necessary. You can use `import` and `export` in your worker code. This is different than browsers, where `"type": "module"` is necessary to use ES Modules.
+
+To simplify error handling, the initial script to load is resolved at the time `new Worker(url)` is called.
+
+```js
+const worker = new Worker("/not-found.js");
+// throws an error immediately
+```
+
+The specifier passed to `Worker` is resolved relative to the project root (like typing `bun ./path/to/file.js`).
+
+### `"open"` event
+
+The `"open"` event is emitted when a worker is created and ready to receive messages.
+
+```ts
+const worker = new Worker(new URL("worker.ts", import.meta.url).href);
+worker.addEventListener("open", () => {
+ console.log("worker is ready");
+});
+```
+
+This event does not exist in browsers.
+
+### `"close"` event
+
+The `"close"` event is emitted when a worker has been terminated. It can take some time for the worker to actually terminate, so this event is emitted when the worker has been marked as terminated.
+
+```ts
+const worker = new Worker(new URL("worker.ts", import.meta.url).href);
+worker.addEventListener("close", () => {
+ console.log("worker is ready");
+});
+```
+
+This event does not exist in browsers.
+
+### `process.exit()` inside a worker
+
+Calling `process.exit()` in a Worker terminates the worker, but does not terminate the main process. Like in Node.js, `process.on('beforeExit', callback)` and `process.on('exit', callback)` are emitted on the worker thread (and not on the main thread).
diff --git a/docs/runtime/nodejs-apis.md b/docs/runtime/nodejs-apis.md
index 2e45fd945..db4844b7e 100644
--- a/docs/runtime/nodejs-apis.md
+++ b/docs/runtime/nodejs-apis.md
@@ -231,7 +231,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
- {% anchor id="node_v8" %} [`node:v8`](https://nodejs.org/api/v8.html) {% /anchor %}
- 🔴
-- Not implemented or planned. For profiling, use [`bun:jsc`](/docs/project/benchmarking#bunjsc) instead.
+- `serialize` and `deserialize` use JavaScriptCore's wire format instead of V8's. Otherwise, not implemented. For profiling, use [`bun:jsc`](/docs/project/benchmarking#bunjsc) instead.
---
diff --git a/docs/runtime/web-apis.md b/docs/runtime/web-apis.md
index e1d95fa9f..46728bb62 100644
--- a/docs/runtime/web-apis.md
+++ b/docs/runtime/web-apis.md
@@ -16,6 +16,13 @@ The following Web APIs are partially or completely supported.
---
+---
+
+- Web Workers
+- [`Worker`](https://developer.mozilla.org/en-US/docs/Web/API/Worker) [`self.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope/postMessage) [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone)
+
+---
+
- Streams
- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) and associated classes
diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts
index 62bc37a82..e4fefcc11 100644
--- a/packages/bun-types/globals.d.ts
+++ b/packages/bun-types/globals.d.ts
@@ -2283,6 +2283,8 @@ interface FetchEvent extends Event {
interface EventMap {
fetch: FetchEvent;
+ message: MessageEvent;
+ messageerror: MessageEvent;
// exit: Event;
}
@@ -3498,6 +3500,78 @@ interface EventSourceEventMap {
open: Event;
}
+interface Worker extends EventTarget {
+ onerror: ((this: Worker, ev: ErrorEvent) => any) | null;
+ onmessage: ((this: Worker, ev: MessageEvent) => any) | null;
+ onmessageerror: ((this: Worker, ev: MessageEvent) => any) | null;
+
+ addEventListener<K extends keyof WorkerEventMap>(
+ type: K,
+ listener: (this: Worker, ev: WorkerEventMap[K]) => any,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+
+ removeEventListener<K extends keyof WorkerEventMap>(
+ type: K,
+ listener: (this: Worker, ev: WorkerEventMap[K]) => any,
+ options?: boolean | EventListenerOptions,
+ ): void;
+
+ terminate(): void;
+
+ postMessage(message: any, transfer?: Transferable[]): void;
+
+ /**
+ * Keep the process alive until the worker is terminated or `unref`'d
+ */
+ ref(): void;
+ /**
+ * Undo a previous `ref()`
+ */
+ unref(): void;
+}
+
+/**
+ * Post a message to the parent thread.
+ *
+ * Only useful in a worker thread; calling this from the main thread does nothing.
+ */
+declare function postMessage(message: any, transfer?: Transferable[]): void;
+
+declare var Worker: {
+ prototype: Worker;
+ new (stringUrl: string | URL, options?: WorkerOptions): Worker;
+};
+
+interface WorkerOptions {
+ name?: string;
+ bun?: {
+ /**
+ * Use less memory, but make the worker slower.
+ *
+ * Internally, this sets the heap size configuration in JavaScriptCore to be
+ * the small heap instead of the large heap.
+ */
+ smol?: boolean;
+
+ /**
+ * When `true`, the worker will keep the parent thread alive until the worker is terminated or `unref`'d.
+ * When `false`, the worker will not keep the parent thread alive.
+ *
+ * By default, this is `false`.
+ */
+ ref?: boolean;
+ };
+}
+
+interface WorkerEventMap {
+ message: MessageEvent;
+ messageerror: MessageEvent;
+ error: ErrorEvent;
+ open: Event;
+ close: Event;
+}
+
interface EventSource extends EventTarget {
onerror: ((this: EventSource, ev: ErrorEvent) => any) | null;
onmessage: ((this: EventSource, ev: MessageEvent) => any) | null;
diff --git a/packages/bun-types/jsc.d.ts b/packages/bun-types/jsc.d.ts
index 08e8ba680..3e7e8947c 100644
--- a/packages/bun-types/jsc.d.ts
+++ b/packages/bun-types/jsc.d.ts
@@ -40,6 +40,42 @@ declare module "bun:jsc" {
export function drainMicrotasks(): void;
/**
+ * Convert a JavaScript value to a binary representation that can be sent to another Bun instance.
+ *
+ * Internally, this uses the serialization format from WebKit/Safari.
+ *
+ * @param value A JavaScript value, usually an object or array, to be converted.
+ * @returns A SharedArrayBuffer that can be sent to another Bun instance.
+ *
+ */
+ export function serialize(
+ value: any,
+ options?: { binaryMode?: "arraybuffer" },
+ ): SharedArrayBuffer;
+
+ /**
+ * Convert a JavaScript value to a binary representation that can be sent to another Bun instance.
+ *
+ * Internally, this uses the serialization format from WebKit/Safari.
+ *
+ * @param value A JavaScript value, usually an object or array, to be converted.
+ * @returns A Buffer that can be sent to another Bun instance.
+ */
+ export function serialize(
+ value: any,
+ options?: { binaryMode: "nodebuffer" },
+ ): Buffer;
+
+ /**
+ * Convert an ArrayBuffer or Buffer to a JavaScript value compatible with the HTML Structured Clone Algorithm.
+ *
+ * @param value A serialized value, usually an ArrayBuffer or Buffer, to be converted.
+ */
+ export function deserialize(
+ value: ArrayBufferLike | TypedArray | Buffer,
+ ): any;
+
+ /**
* Set the timezone used by Intl, Date, etc.
*
* @param timeZone A string representing the time zone to use, such as "America/Los_Angeles"
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index fbf567446..97ad056e8 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -4101,10 +4101,9 @@ pub const Timer = struct {
this.poll_ref.unref(vm);
this.timer.deinit();
- if (this.did_unref_timer) {
- // balance double-unrefing
- vm.uws_event_loop.?.num_polls += 1;
- }
+
+ // balance double unreffing in doUnref
+ vm.uws_event_loop.?.num_polls += @as(i32, @intFromBool(this.did_unref_timer));
this.callback.deinit();
this.arguments.deinit();
diff --git a/src/bun.js/bindings/BunJSCModule.cpp b/src/bun.js/bindings/BunJSCModule.cpp
index 5809a9813..a145f51c5 100644
--- a/src/bun.js/bindings/BunJSCModule.cpp
+++ b/src/bun.js/bindings/BunJSCModule.cpp
@@ -24,15 +24,19 @@
#include "JavaScriptCore/DeferTermination.h"
#include "JavaScriptCore/SamplingProfiler.h"
#include "JavaScriptCore/VMTrapsInlines.h"
+#include "SerializedScriptValue.h"
+#include "ExceptionOr.h"
#if ENABLE(REMOTE_INSPECTOR)
#include "JavaScriptCore/RemoteInspectorServer.h"
#endif
#include "mimalloc.h"
+#include "JSDOMConvertBase.h"
using namespace JSC;
using namespace WTF;
+using namespace WebCore;
JSC_DECLARE_HOST_FUNCTION(functionStartRemoteDebugger);
JSC_DEFINE_HOST_FUNCTION(functionStartRemoteDebugger, (JSGlobalObject * globalObject, CallFrame* callFrame))
@@ -525,6 +529,74 @@ JSC_DEFINE_HOST_FUNCTION(functionGenerateHeapSnapshotForDebugging, (JSGlobalObje
return JSValue::encode(JSONParse(globalObject, WTFMove(jsonString)));
}
+JSC_DEFINE_HOST_FUNCTION(functionSerialize, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto* globalObject = jsCast<JSDOMGlobalObject*>(lexicalGlobalObject);
+ JSC::VM& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ JSValue value = callFrame->argument(0);
+ JSValue optionsObject = callFrame->argument(1);
+ bool asNodeBuffer = false;
+ if (optionsObject.isObject()) {
+ JSC::JSObject* options = optionsObject.getObject();
+ if (JSC::JSValue binaryTypeValue = options->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "binaryType"_s))) {
+ if (!binaryTypeValue.isString()) {
+ throwTypeError(globalObject, throwScope, "binaryType must be a string"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ asNodeBuffer = binaryTypeValue.toWTFString(globalObject) == "nodebuffer"_s;
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ }
+ }
+
+ Vector<JSC::Strong<JSC::JSObject>> transferList;
+
+ ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList));
+
+ if (serialized.hasException()) {
+ WebCore::propagateException(*globalObject, throwScope, serialized.releaseException());
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto serializedValue = serialized.releaseReturnValue();
+ auto arrayBuffer = serializedValue->toArrayBuffer();
+
+ if (asNodeBuffer) {
+ size_t byteLength = arrayBuffer->byteLength();
+ JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, globalObject->JSBufferSubclassStructure(), WTFMove(arrayBuffer), 0, byteLength);
+ return JSValue::encode(uint8Array);
+ }
+
+ if (arrayBuffer->isShared()) {
+ return JSValue::encode(JSArrayBuffer::create(vm, globalObject->arrayBufferStructureWithSharingMode<ArrayBufferSharingMode::Shared>(), WTFMove(arrayBuffer)));
+ }
+
+ return JSValue::encode(JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(), WTFMove(arrayBuffer)));
+}
+JSC_DEFINE_HOST_FUNCTION(functionDeserialize, (JSGlobalObject * globalObject, CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSValue value = callFrame->argument(0);
+
+ JSValue result;
+
+ if (auto* jsArrayBuffer = jsDynamicCast<JSArrayBuffer*>(value)) {
+ result = SerializedScriptValue::fromArrayBuffer(*globalObject, globalObject, jsArrayBuffer->impl(), 0, jsArrayBuffer->impl()->byteLength());
+ } else if (auto* view = jsDynamicCast<JSArrayBufferView*>(value)) {
+ auto arrayBuffer = view->possiblySharedImpl()->possiblySharedBuffer();
+ result = SerializedScriptValue::fromArrayBuffer(*globalObject, globalObject, arrayBuffer.get(), view->byteOffset(), view->byteLength());
+ } else {
+ throwTypeError(globalObject, throwScope, "First argument must be an ArrayBuffer"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined()));
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+}
+
JSC::JSObject* createJSCModule(JSC::JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
@@ -561,6 +633,8 @@ JSC::JSObject* createJSCModule(JSC::JSGlobalObject* globalObject)
object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "generateHeapSnapshotForDebugging"_s), 0, functionGenerateHeapSnapshotForDebugging, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "profile"_s), 0, functionRunProfiler, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "setTimeZone"_s), 0, functionSetTimeZone, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
+ object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "serialize"_s), 0, functionSerialize, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
+ object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "deserialize"_s), 0, functionDeserialize, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
}
return object;
diff --git a/src/bun.js/bindings/BunWorkerGlobalScope.cpp b/src/bun.js/bindings/BunWorkerGlobalScope.cpp
new file mode 100644
index 000000000..60b615e18
--- /dev/null
+++ b/src/bun.js/bindings/BunWorkerGlobalScope.cpp
@@ -0,0 +1,10 @@
+#include "config.h"
+
+#include "BunWorkerGlobalScope.h"
+
+namespace Bun {
+using namespace WebCore;
+
+WTF_MAKE_ISO_ALLOCATED_IMPL(GlobalScope);
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/BunWorkerGlobalScope.h b/src/bun.js/bindings/BunWorkerGlobalScope.h
new file mode 100644
index 000000000..767289716
--- /dev/null
+++ b/src/bun.js/bindings/BunWorkerGlobalScope.h
@@ -0,0 +1,38 @@
+#include "root.h"
+
+#include "EventTarget.h"
+#include "ContextDestructionObserver.h"
+#include "ExceptionOr.h"
+#include <wtf/URL.h>
+#include <wtf/HashSet.h>
+#include <wtf/Lock.h>
+
+namespace Bun {
+class GlobalScope final : public RefCounted<GlobalScope>, public EventTargetWithInlineData {
+ WTF_MAKE_ISO_ALLOCATED(GlobalScope);
+
+public:
+ GlobalScope(ScriptExecutionContext* context)
+ : EventTargetWithInlineData()
+ , m_context(context)
+ {
+ }
+ using RefCounted::deref;
+ using RefCounted::ref;
+
+ static Ref<GlobalScope> create(ScriptExecutionContext* context)
+ {
+ return adoptRef(*new GlobalScope(context));
+ }
+
+ ~GlobalScope() = default;
+
+ EventTargetInterface eventTargetInterface() const final { return EventTargetInterface::DOMWindowEventTargetInterfaceType; }
+ ScriptExecutionContext* scriptExecutionContext() const final { return m_context; }
+ void refEventTarget() final { ref(); }
+ void derefEventTarget() final { deref(); }
+ void eventListenersDidChange() final {}
+
+ ScriptExecutionContext* m_context;
+};
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/ScriptExecutionContext.cpp b/src/bun.js/bindings/ScriptExecutionContext.cpp
index 0293ecc35..b7b7ef16d 100644
--- a/src/bun.js/bindings/ScriptExecutionContext.cpp
+++ b/src/bun.js/bindings/ScriptExecutionContext.cpp
@@ -110,6 +110,16 @@ us_socket_context_t* ScriptExecutionContext::connectedWebSocketKindClientSSL()
return registerWebSocketClientContext<true>(this, webSocketContextSSL());
}
+ScriptExecutionContextIdentifier ScriptExecutionContext::generateIdentifier()
+{
+ Locker locker { allScriptExecutionContextsMapLock };
+
+ // ASSERT(allScriptExecutionContextsMap().contains(m_identifier));
+ // allScriptExecutionContextsMap().remove(m_identifier);
+
+ return ++lastUniqueIdentifier;
+}
+
void ScriptExecutionContext::regenerateIdentifier()
{
Locker locker { allScriptExecutionContextsMapLock };
diff --git a/src/bun.js/bindings/ScriptExecutionContext.h b/src/bun.js/bindings/ScriptExecutionContext.h
index fcf65f477..c3fbd7c9b 100644
--- a/src/bun.js/bindings/ScriptExecutionContext.h
+++ b/src/bun.js/bindings/ScriptExecutionContext.h
@@ -81,6 +81,15 @@ public:
regenerateIdentifier();
}
+ ScriptExecutionContext(JSC::VM* vm, JSC::JSGlobalObject* globalObject, ScriptExecutionContextIdentifier identifier)
+ : m_vm(vm)
+ , m_globalObject(globalObject)
+ , m_identifier(identifier)
+ {
+ }
+
+ static ScriptExecutionContextIdentifier generateIdentifier();
+
JSC::JSGlobalObject* jsGlobalObject()
{
return m_globalObject;
@@ -170,6 +179,12 @@ public:
JSC::VM& vm() { return *m_vm; }
ScriptExecutionContextIdentifier identifier() const { return m_identifier; }
+ bool isWorker = false;
+ void setGlobalObject(JSC::JSGlobalObject* globalObject)
+ {
+ m_globalObject = globalObject;
+ }
+
private:
JSC::VM* m_vm = nullptr;
JSC::JSGlobalObject* m_globalObject = nullptr;
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index e6663bfed..7c0e1668b 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -115,6 +115,8 @@
#include "BunPlugin.h"
#include "JSEnvironmentVariableMap.h"
#include "DOMIsoSubspaces.h"
+#include "BunWorkerGlobalScope.h"
+#include "JSWorker.h"
#if ENABLE(REMOTE_INSPECTOR)
#include "JavaScriptCore/RemoteInspectorServer.h"
@@ -160,6 +162,7 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers;
#include "StructuredClone.h"
#include "JSWebSocket.h"
#include "JSMessageEvent.h"
+#include "JSEventListener.h"
#include "ReadableStream.h"
#include "JSSink.h"
@@ -475,9 +478,9 @@ static String computeErrorInfo(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsi
}
extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* globalObjectClass, int count,
- void* console_client)
+ void* console_client, int32_t executionContextId, bool miniMode)
{
- auto heapSize = JSC::HeapType::Large;
+ auto heapSize = miniMode ? JSC::HeapType::Small : JSC::HeapType::Large;
JSC::VM& vm = JSC::VM::create(heapSize).leakRef();
@@ -487,7 +490,21 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* globalObje
WebCore::JSVMClientData::create(&vm);
JSC::JSLockHolder locker(vm);
- Zig::GlobalObject* globalObject = Zig::GlobalObject::create(vm, Zig::GlobalObject::createStructure(vm, JSC::JSGlobalObject::create(vm, JSC::JSGlobalObject::createStructure(vm, JSC::jsNull())), JSC::jsNull()));
+ Zig::GlobalObject* globalObject;
+
+ if (UNLIKELY(executionContextId > -1)) {
+ ScriptExecutionContext* context = new WebCore::ScriptExecutionContext(&vm, nullptr, static_cast<ScriptExecutionContextIdentifier>(executionContextId));
+ globalObject = Zig::GlobalObject::create(
+ vm,
+ Zig::GlobalObject::createStructure(vm, JSC::JSGlobalObject::create(vm, JSC::JSGlobalObject::createStructure(vm, JSC::jsNull())), JSC::jsNull()),
+ context);
+ } else {
+ globalObject = Zig::GlobalObject::create(
+ vm,
+ Zig::GlobalObject::createStructure(vm, JSC::JSGlobalObject::create(vm, JSC::JSGlobalObject::createStructure(vm, JSC::jsNull())),
+ JSC::jsNull()));
+ }
+
globalObject->setConsole(globalObject);
globalObject->isThreadLocalDefaultGlobalObject = true;
globalObject->setStackTraceLimit(DEFAULT_ERROR_STACK_TRACE_LIMIT); // Node.js defaults to 10
@@ -752,10 +769,26 @@ GlobalObject::GlobalObject(JSC::VM& vm, JSC::Structure* structure)
, m_world(WebCore::DOMWrapperWorld::create(vm, WebCore::DOMWrapperWorld::Type::Normal))
, m_worldIsNormal(true)
, m_builtinInternalFunctions(vm)
+ , globalEventScope(Bun::GlobalScope::create(nullptr))
{
- mockModule = Bun::JSMockModule::create(this);
m_scriptExecutionContext = new WebCore::ScriptExecutionContext(&vm, this);
+ globalEventScope->m_context = m_scriptExecutionContext;
+ mockModule = Bun::JSMockModule::create(this);
+}
+
+GlobalObject::GlobalObject(JSC::VM& vm, JSC::Structure* structure, WebCore::ScriptExecutionContext* context)
+ : JSC::JSGlobalObject(vm, structure, &s_globalObjectMethodTable)
+ , m_bunVM(Bun__getVM())
+ , m_constructors(makeUnique<WebCore::DOMConstructors>())
+ , m_world(WebCore::DOMWrapperWorld::create(vm, WebCore::DOMWrapperWorld::Type::Normal))
+ , m_worldIsNormal(true)
+ , m_builtinInternalFunctions(vm)
+ , globalEventScope(Bun::GlobalScope::create(context))
+{
+ mockModule = Bun::JSMockModule::create(this);
+ m_scriptExecutionContext = context;
+ context->setGlobalObject(this);
}
GlobalObject::~GlobalObject()
@@ -820,6 +853,53 @@ void GlobalObject::setConsole(void* console)
#pragma mark - Globals
+JSC_DEFINE_CUSTOM_GETTER(globalGetterOnMessage,
+ (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue,
+ JSC::PropertyName))
+{
+ Zig::GlobalObject* thisObject = JSC::jsCast<Zig::GlobalObject*>(JSValue::decode(thisValue));
+ return JSValue::encode(eventHandlerAttribute(thisObject->eventTarget(), eventNames().messageEvent, thisObject->world()));
+}
+
+JSC_DEFINE_CUSTOM_GETTER(globalGetterOnError,
+ (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue,
+ JSC::PropertyName))
+{
+ Zig::GlobalObject* thisObject = JSC::jsCast<Zig::GlobalObject*>(JSValue::decode(thisValue));
+ return JSValue::encode(eventHandlerAttribute(thisObject->eventTarget(), eventNames().errorEvent, thisObject->world()));
+}
+
+JSC_DEFINE_CUSTOM_SETTER(globalSetterOnMessage,
+ (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue,
+ JSC::EncodedJSValue encodedValue, JSC::PropertyName property))
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ JSValue value = JSValue::decode(encodedValue);
+ auto* thisObject = jsCast<Zig::GlobalObject*>(JSValue::decode(thisValue));
+ setEventHandlerAttribute<JSEventListener>(thisObject->eventTarget(), eventNames().messageEvent, value, *thisObject);
+ vm.writeBarrier(thisObject, value);
+ ensureStillAliveHere(value);
+ return true;
+}
+
+JSC_DEFINE_CUSTOM_SETTER(globalSetterOnError,
+ (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue,
+ JSC::EncodedJSValue encodedValue, JSC::PropertyName property))
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ JSValue value = JSValue::decode(encodedValue);
+ auto* thisObject = jsCast<Zig::GlobalObject*>(JSValue::decode(thisValue));
+ setEventHandlerAttribute<JSEventListener>(thisObject->eventTarget(), eventNames().errorEvent, value, *thisObject);
+ vm.writeBarrier(thisObject, value);
+ ensureStillAliveHere(value);
+ return true;
+}
+
+Ref<WebCore::EventTarget> GlobalObject::eventTarget()
+{
+ return globalEventScope;
+}
+
JSC_DECLARE_CUSTOM_GETTER(functionLazyLoadStreamPrototypeMap_getter);
JSC_DEFINE_CUSTOM_GETTER(functionLazyLoadStreamPrototypeMap_getter,
@@ -906,6 +986,9 @@ WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSURLSearchParams);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(JSDOMFormData);
WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSDOMFormData);
+WEBCORE_GENERATED_CONSTRUCTOR_GETTER(JSWorker);
+WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSWorker);
+
JSC_DECLARE_CUSTOM_GETTER(JSEvent_getter);
JSC_DEFINE_CUSTOM_GETTER(JSEvent_getter,
@@ -3399,6 +3482,75 @@ void GlobalObject::finishCreation(VM& vm)
consoleObject->putDirectBuiltinFunction(vm, this, clientData->builtinNames().writePublicName(), consoleObjectWriteCodeGenerator(vm), PropertyAttribute::Builtin | PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete);
}
+extern "C" WebCore::Worker* WebWorker__getParentWorker(void*);
+JSC_DEFINE_HOST_FUNCTION(jsFunctionPostMessage,
+ (JSC::JSGlobalObject * leixcalGlobalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = leixcalGlobalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ Zig::GlobalObject* globalObject = jsDynamicCast<Zig::GlobalObject*>(leixcalGlobalObject);
+ if (UNLIKELY(!globalObject))
+ return JSValue::encode(jsUndefined());
+
+ Worker* worker = WebWorker__getParentWorker(globalObject->bunVM());
+ if (worker == nullptr)
+ return JSValue::encode(jsUndefined());
+
+ ScriptExecutionContext* context = worker->scriptExecutionContext();
+
+ if (!context)
+ return JSValue::encode(jsUndefined());
+
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ JSC::JSValue value = callFrame->argument(0);
+ JSC::JSValue options = callFrame->argument(1);
+
+ Vector<JSC::Strong<JSC::JSObject>> transferList;
+
+ if (options.isObject()) {
+ JSC::JSObject* optionsObject = options.getObject();
+ JSC::JSValue transferListValue = optionsObject->get(globalObject, vm.propertyNames->transfer);
+ if (transferListValue.isObject()) {
+ JSC::JSObject* transferListObject = transferListValue.getObject();
+ if (auto* transferListArray = jsDynamicCast<JSC::JSArray*>(transferListObject)) {
+ for (unsigned i = 0; i < transferListArray->length(); i++) {
+ JSC::JSValue transferListValue = transferListArray->get(globalObject, i);
+ if (transferListValue.isObject()) {
+ JSC::JSObject* transferListObject = transferListValue.getObject();
+ transferList.append(JSC::Strong<JSC::JSObject>(vm, transferListObject));
+ }
+ }
+ }
+ }
+ }
+
+ ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList));
+ if (serialized.hasException()) {
+ WebCore::propagateException(*globalObject, throwScope, serialized.releaseException());
+ return JSValue::encode(jsUndefined());
+ }
+
+ ScriptExecutionContext::postTaskTo(context->identifier(), [message = serialized.releaseReturnValue(), protectedThis = Ref { *worker }](ScriptExecutionContext& context) {
+ Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(context.jsGlobalObject());
+ bool didFail = false;
+ JSValue value = message->deserialize(*globalObject, globalObject, SerializationErrorMode::NonThrowing, &didFail);
+ message->deref();
+
+ if (didFail) {
+ protectedThis->dispatchEvent(MessageEvent::create(eventNames().messageerrorEvent, MessageEvent::Init {}, MessageEvent::IsTrusted::Yes));
+ return;
+ }
+
+ WebCore::MessageEvent::Init init;
+ init.data = value;
+ protectedThis->dispatchEvent(MessageEvent::create(eventNames().messageEvent, WTFMove(init), MessageEvent::IsTrusted::Yes));
+ });
+
+ return JSValue::encode(jsUndefined());
+}
+
JSC_DEFINE_HOST_FUNCTION(functionBunPeek,
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
@@ -3711,94 +3863,128 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
auto& builtinNames = WebCore::builtinNames(vm);
WTF::Vector<GlobalPropertyInfo> extraStaticGlobals;
- extraStaticGlobals.reserveCapacity(45);
+ extraStaticGlobals.reserveCapacity(49);
JSC::Identifier queueMicrotaskIdentifier = JSC::Identifier::fromString(vm, "queueMicrotask"_s);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { JSC::Identifier::fromString(vm, "fetch"_s),
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 2,
+ JSC::JSFunction::create(vm, this, 2,
"fetch"_s, Bun__fetch, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { queueMicrotaskIdentifier,
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 2,
+ JSC::JSFunction::create(vm, this, 2,
"queueMicrotask"_s, functionQueueMicrotask, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { JSC::Identifier::fromString(vm, "setImmediate"_s),
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"setImmediate"_s, functionSetImmediate, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { JSC::Identifier::fromString(vm, "clearImmediate"_s),
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"clearImmediate"_s, functionClearTimeout, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { JSC::Identifier::fromString(vm, "structuredClone"_s),
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 2,
+ JSC::JSFunction::create(vm, this, 2,
"structuredClone"_s, functionStructuredClone, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
JSC::Identifier setTimeoutIdentifier = JSC::Identifier::fromString(vm, "setTimeout"_s);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { setTimeoutIdentifier,
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"setTimeout"_s, functionSetTimeout, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
JSC::Identifier clearTimeoutIdentifier = JSC::Identifier::fromString(vm, "clearTimeout"_s);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { clearTimeoutIdentifier,
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"clearTimeout"_s, functionClearTimeout, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
JSC::Identifier setIntervalIdentifier = JSC::Identifier::fromString(vm, "setInterval"_s);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { setIntervalIdentifier,
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"setInterval"_s, functionSetInterval, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
JSC::Identifier clearIntervalIdentifier = JSC::Identifier::fromString(vm, "clearInterval"_s);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { clearIntervalIdentifier,
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"clearInterval"_s, functionClearInterval, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
JSC::Identifier atobIdentifier = JSC::Identifier::fromString(vm, "atob"_s);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { atobIdentifier,
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"atob"_s, functionATOB, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
JSC::Identifier btoaIdentifier = JSC::Identifier::fromString(vm, "btoa"_s);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { btoaIdentifier,
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"btoa"_s, functionBTOA, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
JSC::Identifier reportErrorIdentifier = JSC::Identifier::fromString(vm, "reportError"_s);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { reportErrorIdentifier,
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
"reportError"_s, functionReportError, ImplementationVisibility::Public),
- JSC::PropertyAttribute::DontDelete | 0 });
+ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
+
+ {
+ JSC::Identifier postMessageIdentifier = JSC::Identifier::fromString(vm, "postMessage"_s);
+ extraStaticGlobals.uncheckedAppend(
+ GlobalPropertyInfo { postMessageIdentifier,
+ JSC::JSFunction::create(vm, this, 1,
+ "postMessage"_s, jsFunctionPostMessage, ImplementationVisibility::Public),
+ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
+ }
+
+ {
+ JSC::Identifier addEventListener = JSC::Identifier::fromString(vm, "addEventListener"_s);
+ extraStaticGlobals.uncheckedAppend(
+ GlobalPropertyInfo { addEventListener,
+ JSC::JSFunction::create(vm, this, 2,
+ "addEventListener"_s, WebCore::jsEventTargetPrototypeFunction_addEventListener, ImplementationVisibility::Public),
+ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
+ }
+
+ {
+ extraStaticGlobals.uncheckedAppend(
+ GlobalPropertyInfo { JSC::Identifier::fromString(vm, "dispatchEvent"_s),
+ JSC::JSFunction::create(vm, this, 1,
+ "dispatchEvent"_s, WebCore::jsEventTargetPrototypeFunction_dispatchEvent, ImplementationVisibility::Public),
+ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
+ }
+
+ {
+ extraStaticGlobals.uncheckedAppend(
+ GlobalPropertyInfo { JSC::Identifier::fromString(vm, "removeEventListener"_s),
+ JSC::JSFunction::create(vm, this, 1,
+ "removeEventListener"_s, WebCore::jsEventTargetPrototypeFunction_removeEventListener, ImplementationVisibility::Public),
+ JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
+ }
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { builtinNames.startDirectStreamPrivateName(),
- JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 1,
+ JSC::JSFunction::create(vm, this, 1,
String(), functionStartDirectStream, ImplementationVisibility::Public),
JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0 });
static NeverDestroyed<const String> BunLazyString(MAKE_STATIC_STRING_IMPL("Bun.lazy"));
static NeverDestroyed<const String> CommonJSSymbolKey(MAKE_STATIC_STRING_IMPL("CommonJS"));
JSC::Identifier BunLazyIdentifier = JSC::Identifier::fromUid(vm.symbolRegistry().symbolForKey(BunLazyString));
- JSC::JSFunction* lazyLoadFunction = JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject()), 0,
+ JSC::JSFunction* lazyLoadFunction = JSC::JSFunction::create(vm, this, 0,
BunLazyString, functionLazyLoad, ImplementationVisibility::Public);
extraStaticGlobals.uncheckedAppend(
GlobalPropertyInfo { BunLazyIdentifier,
@@ -3922,6 +4108,9 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "EventSource"_s), JSC::CustomGetterSetter::create(vm, EventSource_getter, EventSource_setter), 0);
+ putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "onmessage"_s), JSC::CustomGetterSetter::create(vm, globalGetterOnMessage, globalSetterOnMessage), 0);
+ putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "onerror"_s), JSC::CustomGetterSetter::create(vm, globalGetterOnError, globalSetterOnError), 0);
+
auto bufferAccessor = JSC::CustomGetterSetter::create(vm, JSBuffer_getter, JSBuffer_setter);
auto realBufferAccessor = JSC::CustomGetterSetter::create(vm, JSBuffer_privateGetter, nullptr);
@@ -3937,6 +4126,7 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
PUT_WEBCORE_GENERATED_CONSTRUCTOR("WebSocket"_s, JSWebSocket);
PUT_WEBCORE_GENERATED_CONSTRUCTOR("Headers"_s, JSFetchHeaders);
PUT_WEBCORE_GENERATED_CONSTRUCTOR("URLSearchParams"_s, JSURLSearchParams);
+ PUT_WEBCORE_GENERATED_CONSTRUCTOR("Worker"_s, JSWorker);
putDirectCustomAccessor(vm, builtinNames.TransformStreamPublicName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_TransformStreamConstructor, nullptr), attributesForStructure(static_cast<unsigned>(JSC::PropertyAttribute::DontEnum)));
putDirectCustomAccessor(vm, builtinNames.TransformStreamPrivateName(), CustomGetterSetter::create(vm, jsServiceWorkerGlobalScope_TransformStreamConstructor, nullptr), attributesForStructure(static_cast<unsigned>(JSC::PropertyAttribute::DontEnum)));
@@ -4287,6 +4477,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
visitor.append(thisObject->m_JSTextEncoderSetterValue);
visitor.append(thisObject->m_JSURLSearchParamsSetterValue);
visitor.append(thisObject->m_JSDOMFormDataSetterValue);
+ visitor.append(thisObject->m_JSWorkerSetterValue);
thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor);
thisObject->m_JSBufferListClassStructure.visit(visitor);
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 081ca33b3..c98e499b0 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -43,8 +43,13 @@ class DOMWrapperWorld;
#include "BunPlugin.h"
#include "JSMockFunction.h"
+namespace Bun {
+class GlobalScope;
+}
+
namespace WebCore {
class SubtleCrypto;
+class EventTarget;
}
extern "C" void Bun__reportError(JSC__JSGlobalObject*, JSC__JSValue);
@@ -128,6 +133,13 @@ public:
return ptr;
}
+ static GlobalObject* create(JSC::VM& vm, JSC::Structure* structure, WebCore::ScriptExecutionContext* scriptExecutionContext)
+ {
+ GlobalObject* ptr = new (NotNull, JSC::allocateCell<GlobalObject>(vm)) GlobalObject(vm, structure, scriptExecutionContext);
+ ptr->finishCreation(vm);
+ return ptr;
+ }
+
const JSDOMStructureMap& structures() const WTF_IGNORES_THREAD_SAFETY_ANALYSIS
{
ASSERT(!Thread::mayBeGCThread());
@@ -298,6 +310,9 @@ public:
EncodedJSValue assignToStream(JSValue stream, JSValue controller);
+ Ref<WebCore::EventTarget> eventTarget();
+ Ref<Bun::GlobalScope> globalEventScope;
+
enum class PromiseFunctions : uint8_t {
Bun__HTTPRequestContext__onReject,
Bun__HTTPRequestContext__onRejectStream,
@@ -366,6 +381,7 @@ public:
mutable WriteBarrier<Unknown> m_JSURLSearchParamsSetterValue;
mutable WriteBarrier<Unknown> m_JSWebSocketSetterValue;
mutable WriteBarrier<Unknown> m_JSDOMFormDataSetterValue;
+ mutable WriteBarrier<Unknown> m_JSWorkerSetterValue;
mutable WriteBarrier<Unknown> m_BunCommonJSModuleValue;
mutable WriteBarrier<JSFunction> m_thenables[promiseFunctionsSize + 1];
@@ -430,6 +446,7 @@ private:
friend void WebCore::JSBuiltinInternalFunctions::initialize(Zig::GlobalObject&);
WebCore::JSBuiltinInternalFunctions m_builtinInternalFunctions;
GlobalObject(JSC::VM& vm, JSC::Structure* structure);
+ GlobalObject(JSC::VM& vm, JSC::Structure* structure, WebCore::ScriptExecutionContext*);
std::unique_ptr<WebCore::DOMConstructors> m_constructors;
uint8_t m_worldIsNormal;
JSDOMStructureMap m_structures WTF_GUARDED_BY_LOCK(m_gcLock);
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig
index e9e9d3a8d..958a7ff20 100644
--- a/src/bun.js/bindings/exports.zig
+++ b/src/bun.js/bindings/exports.zig
@@ -40,8 +40,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 fn create(
+ class_ref: [*]CAPI.JSClassRef,
+ count: i32,
+ console: *anyopaque,
+ context_id: i32,
+ mini_mode: bool,
+ ) *JSGlobalObject {
+ var global = shim.cppFn("create", .{ class_ref, count, console, context_id, mini_mode });
Backtrace.reloadHandlers() catch unreachable;
return global;
}
@@ -959,6 +965,9 @@ pub const ZigConsoleClient = struct {
_,
};
+ var stderr_mutex: bun.Lock = bun.Lock.init();
+ var stdout_mutex: bun.Lock = bun.Lock.init();
+
/// https://console.spec.whatwg.org/#formatter
pub fn messageWithTypeAndLevel(
//console_: ZigConsoleClient.Type,
@@ -976,6 +985,21 @@ pub const ZigConsoleClient = struct {
var console = global.bunVM().console;
+ // Lock/unlock a mutex incase two JS threads are console.log'ing at the same time
+ // We do this the slightly annoying way to avoid assigning a pointer
+ if (level == .Warning or level == .Error or message_type == .Assert) {
+ stderr_mutex.lock();
+ } else {
+ stdout_mutex.lock();
+ }
+ defer {
+ if (level == .Warning or level == .Error or message_type == .Assert) {
+ stderr_mutex.unlock();
+ } else {
+ stdout_mutex.unlock();
+ }
+ }
+
if (message_type == .Clear) {
Output.resetTerminal();
return;
@@ -2505,14 +2529,30 @@ pub const ZigConsoleClient = struct {
}
},
.ErrorEvent => {
- writer.print(
- comptime Output.prettyFmt("<r><blue>error<d>:<r>\n", enable_ansi_colors),
- .{},
- );
+ {
+ const error_value = value.get(this.globalThis, "error").?;
- const data = value.get(this.globalThis, "error").?;
- const tag = Tag.getAdvanced(data, this.globalThis, .{ .hide_global = true });
- this.format(tag, Writer, writer_, data, this.globalThis, enable_ansi_colors);
+ if (!error_value.isEmptyOrUndefinedOrNull()) {
+ writer.print(
+ comptime Output.prettyFmt("<r><blue>error<d>:<r> ", enable_ansi_colors),
+ .{},
+ );
+
+ const tag = Tag.getAdvanced(error_value, this.globalThis, .{ .hide_global = true });
+ this.format(tag, Writer, writer_, error_value, this.globalThis, enable_ansi_colors);
+ }
+ }
+
+ const message_value = value.get(this.globalThis, "message").?;
+ if (message_value.isString()) {
+ writer.print(
+ comptime Output.prettyFmt("<r><blue>message<d>:<r> ", enable_ansi_colors),
+ .{},
+ );
+
+ const tag = Tag.getAdvanced(message_value, this.globalThis, .{ .hide_global = true });
+ this.format(tag, Writer, writer_, message_value, this.globalThis, enable_ansi_colors);
+ }
},
else => unreachable,
}
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index 1a6a40c9f..8a03d5233 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -578,7 +578,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 arg3, bool arg4);
CPP_DECL void* Zig__GlobalObject__getModuleRegistryMap(JSC__JSGlobalObject* arg0);
CPP_DECL bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject* arg0, void* arg1);
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index be2e4a626..343939cc2 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -350,7 +350,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, arg3: i32, arg4: bool) *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;
diff --git a/src/bun.js/bindings/webcore/ActiveDOMObject.h b/src/bun.js/bindings/webcore/ActiveDOMObject.h
index e483a46b7..52daebc6f 100644
--- a/src/bun.js/bindings/webcore/ActiveDOMObject.h
+++ b/src/bun.js/bindings/webcore/ActiveDOMObject.h
@@ -100,7 +100,7 @@
// return adoptRef(*new PendingActivity<T>(thisObject));
// }
-// bool isContextStopped() const;
+// bool isContextStopped() const;
// bool isAllowedToRunScript() const;
// template<typename T>
diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
index 82a2c6a24..229897fad 100644
--- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
@@ -859,7 +859,7 @@ public:
// std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWebFakeXRInputController;
// std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWebXRTest;
// std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDedicatedWorkerGlobalScope;
- // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWorker;
+ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWorker;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWorkerGlobalScope;
// std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWorkerLocation;
// std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExtendableEvent;
diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
index f1b290d25..682f27980 100644
--- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
@@ -852,7 +852,7 @@ public:
// std::unique_ptr<IsoSubspace> m_subspaceForWebFakeXRInputController;
// std::unique_ptr<IsoSubspace> m_subspaceForWebXRTest;
// std::unique_ptr<IsoSubspace> m_subspaceForDedicatedWorkerGlobalScope;
- // std::unique_ptr<IsoSubspace> m_subspaceForWorker;
+ std::unique_ptr<IsoSubspace> m_subspaceForWorker;
std::unique_ptr<IsoSubspace> m_subspaceForWorkerGlobalScope;
// std::unique_ptr<IsoSubspace> m_subspaceForWorkerLocation;
// std::unique_ptr<IsoSubspace> m_subspaceForExtendableEvent;
diff --git a/src/bun.js/bindings/webcore/EventTargetFactory.cpp b/src/bun.js/bindings/webcore/EventTargetFactory.cpp
index b52214fe0..97e66977b 100644
--- a/src/bun.js/bindings/webcore/EventTargetFactory.cpp
+++ b/src/bun.js/bindings/webcore/EventTargetFactory.cpp
@@ -63,8 +63,8 @@ JSC::JSValue toJS(JSC::JSGlobalObject* state, JSDOMGlobalObject* globalObject, E
// return toJS(state, globalObject, static_cast<Clipboard&>(impl));
// case DOMApplicationCacheEventTargetInterfaceType:
// return toJS(state, globalObject, static_cast<DOMApplicationCache&>(impl));
- // case DOMWindowEventTargetInterfaceType:
- // return toJS(state, globalObject, static_cast<DOMWindow&>(impl));
+ case DOMWindowEventTargetInterfaceType:
+ return globalObject;
// case DedicatedWorkerGlobalScopeEventTargetInterfaceType:
// return toJS(state, globalObject, static_cast<DedicatedWorkerGlobalScope&>(impl));
// case EventSourceEventTargetInterfaceType:
@@ -265,8 +265,8 @@ JSC::JSValue toJS(JSC::JSGlobalObject* state, JSDOMGlobalObject* globalObject, E
// case WebXRSystemEventTargetInterfaceType:
// return toJS(state, globalObject, static_cast<WebXRSystem&>(impl));
// #endif
- // case WorkerEventTargetInterfaceType:
- // return toJS(state, globalObject, static_cast<Worker&>(impl));
+ case WorkerEventTargetInterfaceType:
+ return toJS(state, globalObject, static_cast<Worker&>(impl));
// case WorkletGlobalScopeEventTargetInterfaceType:
// return toJS(state, globalObject, static_cast<WorkletGlobalScope&>(impl));
// case XMLHttpRequestEventTargetInterfaceType:
diff --git a/src/bun.js/bindings/webcore/EventTargetHeaders.h b/src/bun.js/bindings/webcore/EventTargetHeaders.h
index ae6ee0d3f..b36e49a66 100644
--- a/src/bun.js/bindings/webcore/EventTargetHeaders.h
+++ b/src/bun.js/bindings/webcore/EventTargetHeaders.h
@@ -254,8 +254,8 @@
// #include "JSWebXRSystem.h"
// #include "WebXRSystem.h"
// #endif
-// #include "Worker.h"
-// #include "JSWorker.h"
+#include "Worker.h"
+#include "JSWorker.h"
// #include "WorkletGlobalScope.h"
// #include "JSWorkletGlobalScope.h"
// #include "XMLHttpRequest.h"
@@ -263,4 +263,6 @@
// #include "XMLHttpRequestUpload.h"
// #include "JSXMLHttpRequestUpload.h"
+#include "BunWorkerGlobalScope.h"
+
#endif // EventTargetHeaders_h
diff --git a/src/bun.js/bindings/webcore/JSEventTarget.cpp b/src/bun.js/bindings/webcore/JSEventTarget.cpp
index 4f40b5397..9f97ac807 100644
--- a/src/bun.js/bindings/webcore/JSEventTarget.cpp
+++ b/src/bun.js/bindings/webcore/JSEventTarget.cpp
@@ -59,12 +59,6 @@
namespace WebCore {
using namespace JSC;
-// Functions
-
-static JSC_DECLARE_HOST_FUNCTION(jsEventTargetPrototypeFunction_addEventListener);
-static JSC_DECLARE_HOST_FUNCTION(jsEventTargetPrototypeFunction_removeEventListener);
-static JSC_DECLARE_HOST_FUNCTION(jsEventTargetPrototypeFunction_dispatchEvent);
-
// Attributes
static JSC_DECLARE_CUSTOM_GETTER(jsEventTargetConstructor);
diff --git a/src/bun.js/bindings/webcore/JSEventTarget.h b/src/bun.js/bindings/webcore/JSEventTarget.h
index ac19a2d35..ee2e34611 100644
--- a/src/bun.js/bindings/webcore/JSEventTarget.h
+++ b/src/bun.js/bindings/webcore/JSEventTarget.h
@@ -27,6 +27,10 @@
namespace WebCore {
+JSC_DECLARE_HOST_FUNCTION(jsEventTargetPrototypeFunction_addEventListener);
+JSC_DECLARE_HOST_FUNCTION(jsEventTargetPrototypeFunction_removeEventListener);
+JSC_DECLARE_HOST_FUNCTION(jsEventTargetPrototypeFunction_dispatchEvent);
+
class JSEventTarget : public JSDOMWrapper<EventTarget> {
public:
using Base = JSDOMWrapper<EventTarget>;
diff --git a/src/bun.js/bindings/webcore/JSEventTargetCustom.cpp b/src/bun.js/bindings/webcore/JSEventTargetCustom.cpp
index 670cac93e..75ee90c9f 100644
--- a/src/bun.js/bindings/webcore/JSEventTargetCustom.cpp
+++ b/src/bun.js/bindings/webcore/JSEventTargetCustom.cpp
@@ -55,8 +55,8 @@ EventTarget* JSEventTarget::toWrapped(VM& vm, JSValue value)
// return &jsCast<JSWindowProxy*>(asObject(value))->wrapped();
// if (value.inherits<JSDOMWindow>())
// return &jsCast<JSDOMWindow*>(asObject(value))->wrapped();
- // if (value.inherits<JSDOMGlobalObject>())
- // return &jsCast<JSDOMGlobalObject*>(asObject(value))->wrapped();
+ if (value.inherits<JSDOMGlobalObject>())
+ return jsCast<JSDOMGlobalObject*>(asObject(value))->eventTarget().ptr();
if (value.inherits<JSEventTarget>())
return &jsCast<JSEventTarget*>(asObject(value))->wrapped();
return nullptr;
@@ -66,6 +66,18 @@ std::unique_ptr<JSEventTargetWrapper> jsEventTargetCast(VM& vm, JSValue thisValu
{
if (auto* target = jsDynamicCast<JSEventTarget*>(thisValue))
return makeUnique<JSEventTargetWrapper>(target->wrapped(), *target);
+ if (!thisValue.isObject())
+ return nullptr;
+
+ JSObject* object = thisValue.getObject();
+ if (object->type() == GlobalProxyType) {
+ object = jsCast<JSGlobalProxy*>(object)->target();
+ if (!object)
+ return nullptr;
+ }
+ if (auto* global = jsDynamicCast<Zig::GlobalObject*>(object))
+ return makeUnique<JSEventTargetWrapper>(global->eventTarget(), *global);
+
// if (auto* window = toJSDOMGlobalObject<JSDOMGlobalObject>(vm, thisValue))
// return makeUnique<JSEventTargetWrapper>(*window, *window);
// if (auto* scope = toJSDOMGlobalObject<JSWorkerGlobalScope>(vm, thisValue))
diff --git a/src/bun.js/bindings/webcore/JSWorker.cpp b/src/bun.js/bindings/webcore/JSWorker.cpp
new file mode 100644
index 000000000..68e06a2a0
--- /dev/null
+++ b/src/bun.js/bindings/webcore/JSWorker.cpp
@@ -0,0 +1,524 @@
+/*
+ This file is part of the WebKit open source project.
+ This file has been generated by generate-bindings.pl. DO NOT MODIFY!
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+#include "JSWorker.h"
+
+#include "ActiveDOMObject.h"
+#include "EventNames.h"
+#include "ExtendedDOMClientIsoSubspaces.h"
+#include "ExtendedDOMIsoSubspaces.h"
+#include "IDLTypes.h"
+#include "JSDOMAttribute.h"
+#include "JSDOMBinding.h"
+#include "JSDOMConstructor.h"
+#include "JSDOMConvertAny.h"
+#include "JSDOMConvertBase.h"
+#include "JSDOMConvertDictionary.h"
+#include "JSDOMConvertInterface.h"
+#include "JSDOMConvertObject.h"
+#include "JSDOMConvertSequences.h"
+#include "JSDOMConvertStrings.h"
+#include "JSDOMExceptionHandling.h"
+#include "JSDOMGlobalObjectInlines.h"
+#include "JSDOMOperation.h"
+#include "JSDOMWrapperCache.h"
+#include "JSEventListener.h"
+#include "StructuredSerializeOptions.h"
+#include "JSWorkerOptions.h"
+#include "ScriptExecutionContext.h"
+#include "WebCoreJSClientData.h"
+#include <JavaScriptCore/HeapAnalyzer.h>
+#include <JavaScriptCore/IteratorOperations.h>
+#include <JavaScriptCore/JSArray.h>
+#include <JavaScriptCore/JSCInlines.h>
+#include <JavaScriptCore/JSDestructibleObjectHeapCellType.h>
+#include <JavaScriptCore/SlotVisitorMacros.h>
+#include <JavaScriptCore/SubspaceInlines.h>
+#include <wtf/GetPtr.h>
+#include <wtf/PointerPreparations.h>
+#include <wtf/URL.h>
+
+namespace WebCore {
+using namespace JSC;
+
+// Functions
+
+static JSC_DECLARE_HOST_FUNCTION(jsWorkerPrototypeFunction_terminate);
+static JSC_DECLARE_HOST_FUNCTION(jsWorkerPrototypeFunction_postMessage);
+static JSC_DECLARE_HOST_FUNCTION(jsWorkerPrototypeFunction_unref);
+static JSC_DECLARE_HOST_FUNCTION(jsWorkerPrototypeFunction_ref);
+
+// Attributes
+
+static JSC_DECLARE_CUSTOM_GETTER(jsWorkerConstructor);
+static JSC_DECLARE_CUSTOM_GETTER(jsWorker_onmessage);
+static JSC_DECLARE_CUSTOM_SETTER(setJSWorker_onmessage);
+static JSC_DECLARE_CUSTOM_GETTER(jsWorker_onmessageerror);
+static JSC_DECLARE_CUSTOM_SETTER(setJSWorker_onmessageerror);
+static JSC_DECLARE_CUSTOM_GETTER(jsWorker_onerror);
+static JSC_DECLARE_CUSTOM_SETTER(setJSWorker_onerror);
+
+class JSWorkerPrototype final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+ static JSWorkerPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ JSWorkerPrototype* ptr = new (NotNull, JSC::allocateCell<JSWorkerPrototype>(vm)) JSWorkerPrototype(vm, globalObject, structure);
+ ptr->finishCreation(vm);
+ return ptr;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWorkerPrototype, Base);
+ return &vm.plainObjectSpace();
+ }
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+ }
+
+private:
+ JSWorkerPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
+ : JSC::JSNonFinalObject(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&);
+};
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWorkerPrototype, JSWorkerPrototype::Base);
+
+using JSWorkerDOMConstructor = JSDOMConstructor<JSWorker>;
+
+template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWorkerDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
+{
+ VM& vm = lexicalGlobalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto* castedThis = jsCast<JSWorkerDOMConstructor*>(callFrame->jsCallee());
+ ASSERT(castedThis);
+ if (UNLIKELY(callFrame->argumentCount() < 1))
+ return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
+ auto* context = castedThis->scriptExecutionContext();
+ if (UNLIKELY(!context))
+ return throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, "Worker");
+ EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
+ auto scriptUrl = convert<IDLUSVString>(*lexicalGlobalObject, argument0.value());
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ EnsureStillAliveScope argument1 = callFrame->argument(1);
+
+ auto options = WorkerOptions {};
+ options.bun.unref = true;
+
+ if (JSObject* optionsObject = JSC::jsDynamicCast<JSC::JSObject*>(argument1.value())) {
+ if (auto nameValue = optionsObject->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "name"_s))) {
+ if (nameValue.isString()) {
+ options.name = nameValue.toWTFString(lexicalGlobalObject);
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ }
+ }
+
+ if (auto bunValue = optionsObject->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "bun"_s))) {
+ if (bunValue.isObject()) {
+ if (auto* bunObject = bunValue.getObject()) {
+ if (auto miniModeValue = bunObject->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "smol"_s))) {
+ options.bun.mini = miniModeValue.toBoolean(lexicalGlobalObject);
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ }
+
+ if (auto ref = bunObject->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "ref"_s))) {
+ options.bun.unref = !ref.toBoolean(lexicalGlobalObject);
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ }
+ }
+ }
+ }
+ }
+
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ auto object = Worker::create(*context, WTFMove(scriptUrl), WTFMove(options));
+ if constexpr (IsExceptionOr<decltype(object)>)
+ RETURN_IF_EXCEPTION(throwScope, {});
+ static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef);
+ auto jsValue = toJSNewlyCreated<IDLInterface<Worker>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTFMove(object));
+ if constexpr (IsExceptionOr<decltype(object)>)
+ RETURN_IF_EXCEPTION(throwScope, {});
+ setSubclassStructureIfNeeded<Worker>(lexicalGlobalObject, callFrame, asObject(jsValue));
+ RETURN_IF_EXCEPTION(throwScope, {});
+ return JSValue::encode(jsValue);
+}
+JSC_ANNOTATE_HOST_FUNCTION(JSWorkerDOMConstructorConstruct, JSWorkerDOMConstructor::construct);
+
+template<> const ClassInfo JSWorkerDOMConstructor::s_info = { "Worker"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWorkerDOMConstructor) };
+
+template<> JSValue JSWorkerDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
+{
+ return JSEventTarget::getConstructor(vm, &globalObject);
+}
+
+template<> void JSWorkerDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
+{
+ putDirect(vm, vm.propertyNames->length, jsNumber(1), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+ JSString* nameString = jsNontrivialString(vm, "Worker"_s);
+ m_originalName.set(vm, this, nameString);
+ putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+ putDirect(vm, vm.propertyNames->prototype, JSWorker::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
+}
+
+/* Hash table for prototype */
+
+static const HashTableValue JSWorkerPrototypeTableValues[] = {
+ { "constructor"_s, static_cast<unsigned>(PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsWorkerConstructor, 0 } },
+ { "onmessage"_s, JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsWorker_onmessage, setJSWorker_onmessage } },
+ { "onmessageerror"_s, JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsWorker_onmessageerror, setJSWorker_onmessageerror } },
+ { "onerror"_s, JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute, NoIntrinsic, { HashTableValue::GetterSetterType, jsWorker_onerror, setJSWorker_onerror } },
+ { "terminate"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsWorkerPrototypeFunction_terminate, 0 } },
+ { "postMessage"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsWorkerPrototypeFunction_postMessage, 1 } },
+ { "ref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsWorkerPrototypeFunction_ref, 0 } },
+ { "unref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsWorkerPrototypeFunction_unref, 0 } },
+};
+
+const ClassInfo JSWorkerPrototype::s_info = { "Worker"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWorkerPrototype) };
+
+void JSWorkerPrototype::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ reifyStaticProperties(vm, JSWorker::info(), JSWorkerPrototypeTableValues, *this);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+const ClassInfo JSWorker::s_info = { "Worker"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWorker) };
+
+JSWorker::JSWorker(Structure* structure, JSDOMGlobalObject& globalObject, Ref<Worker>&& impl)
+ : JSEventTarget(structure, globalObject, WTFMove(impl))
+{
+}
+
+// static_assert(std::is_base_of<ActiveDOMObject, Worker>::value, "Interface is marked as [ActiveDOMObject] but implementation class does not subclass ActiveDOMObject.");
+
+JSObject* JSWorker::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
+{
+ auto* structure = JSWorkerPrototype::createStructure(vm, &globalObject, JSEventTarget::prototype(vm, globalObject));
+ structure->setMayBePrototype(true);
+ return JSWorkerPrototype::create(vm, &globalObject, structure);
+}
+
+JSObject* JSWorker::prototype(VM& vm, JSDOMGlobalObject& globalObject)
+{
+ return getDOMPrototype<JSWorker>(vm, globalObject);
+}
+
+JSValue JSWorker::getConstructor(VM& vm, const JSGlobalObject* globalObject)
+{
+ return getDOMConstructor<JSWorkerDOMConstructor, DOMConstructorID::Worker>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
+}
+
+JSC_DEFINE_CUSTOM_GETTER(jsWorkerConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
+{
+ VM& vm = JSC::getVM(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto* prototype = jsDynamicCast<JSWorkerPrototype*>(JSValue::decode(thisValue));
+ if (UNLIKELY(!prototype))
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ return JSValue::encode(JSWorker::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject()));
+}
+
+static inline JSValue jsWorker_onmessageGetter(JSGlobalObject& lexicalGlobalObject, JSWorker& thisObject)
+{
+ UNUSED_PARAM(lexicalGlobalObject);
+ return eventHandlerAttribute(thisObject.wrapped(), eventNames().messageEvent, worldForDOMObject(thisObject));
+}
+
+JSC_DEFINE_CUSTOM_GETTER(jsWorker_onmessage, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ return IDLAttribute<JSWorker>::get<jsWorker_onmessageGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
+}
+
+static inline bool setJSWorker_onmessageSetter(JSGlobalObject& lexicalGlobalObject, JSWorker& thisObject, JSValue value)
+{
+ auto& vm = JSC::getVM(&lexicalGlobalObject);
+ UNUSED_PARAM(vm);
+ setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().messageEvent, value, thisObject);
+ vm.writeBarrier(&thisObject, value);
+ ensureStillAliveHere(value);
+
+ return true;
+}
+
+JSC_DEFINE_CUSTOM_SETTER(setJSWorker_onmessage, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName))
+{
+ return IDLAttribute<JSWorker>::set<setJSWorker_onmessageSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName);
+}
+
+static inline JSValue jsWorker_onmessageerrorGetter(JSGlobalObject& lexicalGlobalObject, JSWorker& thisObject)
+{
+ UNUSED_PARAM(lexicalGlobalObject);
+ return eventHandlerAttribute(thisObject.wrapped(), eventNames().messageerrorEvent, worldForDOMObject(thisObject));
+}
+
+JSC_DEFINE_CUSTOM_GETTER(jsWorker_onmessageerror, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ return IDLAttribute<JSWorker>::get<jsWorker_onmessageerrorGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
+}
+
+static inline bool setJSWorker_onmessageerrorSetter(JSGlobalObject& lexicalGlobalObject, JSWorker& thisObject, JSValue value)
+{
+ auto& vm = JSC::getVM(&lexicalGlobalObject);
+ UNUSED_PARAM(vm);
+ setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().messageerrorEvent, value, thisObject);
+ vm.writeBarrier(&thisObject, value);
+ ensureStillAliveHere(value);
+
+ return true;
+}
+
+JSC_DEFINE_CUSTOM_SETTER(setJSWorker_onmessageerror, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName))
+{
+ return IDLAttribute<JSWorker>::set<setJSWorker_onmessageerrorSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName);
+}
+
+static inline JSValue jsWorker_onerrorGetter(JSGlobalObject& lexicalGlobalObject, JSWorker& thisObject)
+{
+ UNUSED_PARAM(lexicalGlobalObject);
+ return eventHandlerAttribute(thisObject.wrapped(), eventNames().errorEvent, worldForDOMObject(thisObject));
+}
+
+JSC_DEFINE_CUSTOM_GETTER(jsWorker_onerror, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ return IDLAttribute<JSWorker>::get<jsWorker_onerrorGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName);
+}
+
+static inline bool setJSWorker_onerrorSetter(JSGlobalObject& lexicalGlobalObject, JSWorker& thisObject, JSValue value)
+{
+ auto& vm = JSC::getVM(&lexicalGlobalObject);
+ UNUSED_PARAM(vm);
+ setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().errorEvent, value, thisObject);
+ vm.writeBarrier(&thisObject, value);
+ ensureStillAliveHere(value);
+
+ return true;
+}
+
+JSC_DEFINE_CUSTOM_SETTER(setJSWorker_onerror, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName))
+{
+ return IDLAttribute<JSWorker>::set<setJSWorker_onerrorSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName);
+}
+
+static inline JSC::EncodedJSValue jsWorkerPrototypeFunction_terminateBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWorker>::ClassParameter castedThis)
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ UNUSED_PARAM(throwScope);
+ UNUSED_PARAM(callFrame);
+ auto& impl = castedThis->wrapped();
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.terminate(); })));
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsWorkerPrototypeFunction_terminate, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ return IDLOperation<JSWorker>::call<jsWorkerPrototypeFunction_terminateBody>(*lexicalGlobalObject, *callFrame, "terminate");
+}
+
+static inline JSC::EncodedJSValue jsWorkerPrototypeFunction_postMessage1Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWorker>::ClassParameter castedThis)
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ UNUSED_PARAM(throwScope);
+ UNUSED_PARAM(callFrame);
+ auto& impl = castedThis->wrapped();
+ EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
+ auto message = convert<IDLAny>(*lexicalGlobalObject, argument0.value());
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1);
+ auto transfer = convert<IDLSequence<IDLObject>>(*lexicalGlobalObject, argument1.value());
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.postMessage(*jsCast<JSDOMGlobalObject*>(lexicalGlobalObject), WTFMove(message), WTFMove(transfer)); })));
+}
+
+static inline JSC::EncodedJSValue jsWorkerPrototypeFunction_postMessage2Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWorker>::ClassParameter castedThis)
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ UNUSED_PARAM(throwScope);
+ UNUSED_PARAM(callFrame);
+ auto& impl = castedThis->wrapped();
+ EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
+ auto message = convert<IDLAny>(*lexicalGlobalObject, argument0.value());
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ EnsureStillAliveScope argument1 = callFrame->argument(1);
+ JSValue optionsValue = argument1.value();
+ StructuredSerializeOptions options;
+ if (optionsValue.isObject()) {
+ JSObject* optionsObject = asObject(optionsValue);
+ if (auto transferListValue = optionsObject->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "transfer"_s))) {
+ auto transferList = convert<IDLSequence<IDLObject>>(*lexicalGlobalObject, transferListValue);
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ options.transfer = WTFMove(transferList);
+ }
+ }
+
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.postMessage(*jsCast<JSDOMGlobalObject*>(lexicalGlobalObject), WTFMove(message), WTFMove(options)); })));
+}
+
+static inline JSC::EncodedJSValue jsWorkerPrototypeFunction_postMessageOverloadDispatcher(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWorker>::ClassParameter castedThis)
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ UNUSED_PARAM(throwScope);
+ UNUSED_PARAM(callFrame);
+ size_t argsCount = std::min<size_t>(2, callFrame->argumentCount());
+ if (argsCount == 1) {
+ RELEASE_AND_RETURN(throwScope, (jsWorkerPrototypeFunction_postMessage2Body(lexicalGlobalObject, callFrame, castedThis)));
+ }
+ if (argsCount == 2) {
+ JSValue distinguishingArg = callFrame->uncheckedArgument(1);
+ if (distinguishingArg.isUndefined())
+ RELEASE_AND_RETURN(throwScope, (jsWorkerPrototypeFunction_postMessage2Body(lexicalGlobalObject, callFrame, castedThis)));
+ if (distinguishingArg.isUndefinedOrNull())
+ RELEASE_AND_RETURN(throwScope, (jsWorkerPrototypeFunction_postMessage2Body(lexicalGlobalObject, callFrame, castedThis)));
+ {
+ bool success = hasIteratorMethod(lexicalGlobalObject, distinguishingArg);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ if (success)
+ RELEASE_AND_RETURN(throwScope, (jsWorkerPrototypeFunction_postMessage1Body(lexicalGlobalObject, callFrame, castedThis)));
+ }
+ if (distinguishingArg.isObject())
+ RELEASE_AND_RETURN(throwScope, (jsWorkerPrototypeFunction_postMessage2Body(lexicalGlobalObject, callFrame, castedThis)));
+ }
+ return argsCount < 1 ? throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)) : throwVMTypeError(lexicalGlobalObject, throwScope);
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsWorkerPrototypeFunction_postMessage, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ return IDLOperation<JSWorker>::call<jsWorkerPrototypeFunction_postMessageOverloadDispatcher>(*lexicalGlobalObject, *callFrame, "postMessage");
+}
+
+static inline JSC::EncodedJSValue jsWorkerPrototypeFunction_refBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWorker>::ClassParameter castedThis)
+{
+ castedThis->wrapped().setKeepAlive(true);
+ return JSValue::encode(jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsWorkerPrototypeFunction_ref, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ return IDLOperation<JSWorker>::call<jsWorkerPrototypeFunction_refBody>(*lexicalGlobalObject, *callFrame, "ref");
+}
+
+static inline JSC::EncodedJSValue jsWorkerPrototypeFunction_unrefBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWorker>::ClassParameter castedThis)
+{
+ castedThis->wrapped().setKeepAlive(false);
+ return JSValue::encode(jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsWorkerPrototypeFunction_unref, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ return IDLOperation<JSWorker>::call<jsWorkerPrototypeFunction_unrefBody>(*lexicalGlobalObject, *callFrame, "unref");
+}
+
+JSC::GCClient::IsoSubspace* JSWorker::subspaceForImpl(JSC::VM& vm)
+{
+ return WebCore::subspaceForImpl<JSWorker, UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForWorker.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForWorker = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForWorker.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForWorker = std::forward<decltype(space)>(space); });
+}
+
+void JSWorker::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<JSWorker*>(cell);
+ analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped());
+ if (thisObject->scriptExecutionContext())
+ analyzer.setLabelForCell(cell, "url "_s + thisObject->scriptExecutionContext()->url().string());
+ Base::analyzeHeap(cell, analyzer);
+}
+
+bool JSWorkerOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, const char** reason)
+{
+ auto* jsWorker = jsCast<JSWorker*>(handle.slot()->asCell());
+ auto& wrapped = jsWorker->wrapped();
+ if (wrapped.hasPendingActivity()) {
+ if (UNLIKELY(reason))
+ *reason = "ActiveDOMObject with pending activity";
+ return true;
+ }
+ UNUSED_PARAM(visitor);
+ UNUSED_PARAM(reason);
+ return false;
+}
+
+void JSWorkerOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
+{
+ auto* jsWorker = static_cast<JSWorker*>(handle.slot()->asCell());
+ auto& world = *static_cast<DOMWrapperWorld*>(context);
+ uncacheWrapper(world, &jsWorker->wrapped(), jsWorker);
+}
+
+#if ENABLE(BINDING_INTEGRITY)
+#if PLATFORM(WIN)
+#pragma warning(disable : 4483)
+extern "C" {
+extern void (*const __identifier("??_7Worker@WebCore@@6B@")[])();
+}
+#else
+extern "C" {
+extern void* _ZTVN7WebCore6WorkerE[];
+}
+#endif
+#endif
+
+JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<Worker>&& impl)
+{
+
+ if constexpr (std::is_polymorphic_v<Worker>) {
+#if ENABLE(BINDING_INTEGRITY)
+ const void* actualVTablePointer = getVTablePointer(impl.ptr());
+#if PLATFORM(WIN)
+ void* expectedVTablePointer = __identifier("??_7Worker@WebCore@@6B@");
+#else
+ void* expectedVTablePointer = &_ZTVN7WebCore6WorkerE[2];
+#endif
+
+ // If you hit this assertion you either have a use after free bug, or
+ // Worker has subclasses. If Worker has subclasses that get passed
+ // to toJS() we currently require Worker you to opt out of binding hardening
+ // by adding the SkipVTableValidation attribute to the interface IDL definition
+ RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer);
+#endif
+ }
+ return createWrapper<Worker>(globalObject, WTFMove(impl));
+}
+
+JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, Worker& impl)
+{
+ return wrap(lexicalGlobalObject, globalObject, impl);
+}
+
+Worker* JSWorker::toWrapped(JSC::VM&, JSC::JSValue value)
+{
+ if (auto* wrapper = jsDynamicCast<JSWorker*>(value))
+ return &wrapper->wrapped();
+ return nullptr;
+}
+}
diff --git a/src/bun.js/bindings/webcore/JSWorker.h b/src/bun.js/bindings/webcore/JSWorker.h
new file mode 100644
index 000000000..b3b2dde76
--- /dev/null
+++ b/src/bun.js/bindings/webcore/JSWorker.h
@@ -0,0 +1,99 @@
+/*
+ This file is part of the WebKit open source project.
+ This file has been generated by generate-bindings.pl. DO NOT MODIFY!
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#pragma once
+
+#include "JSDOMWrapper.h"
+#include "JSEventTarget.h"
+#include "Worker.h"
+#include <wtf/NeverDestroyed.h>
+
+namespace WebCore {
+
+class JSWorker : public JSEventTarget {
+public:
+ using Base = JSEventTarget;
+ using DOMWrapped = Worker;
+ static JSWorker* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<Worker>&& impl)
+ {
+ JSWorker* ptr = new (NotNull, JSC::allocateCell<JSWorker>(globalObject->vm())) JSWorker(structure, *globalObject, WTFMove(impl));
+ ptr->finishCreation(globalObject->vm());
+ return ptr;
+ }
+
+ static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
+ static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
+ static Worker* toWrapped(JSC::VM&, JSC::JSValue);
+
+ DECLARE_INFO;
+
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), JSC::NonArray);
+ }
+
+ static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return subspaceForImpl(vm);
+ }
+ static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+ Worker& wrapped() const
+ {
+ return static_cast<Worker&>(Base::wrapped());
+ }
+
+protected:
+ JSWorker(JSC::Structure*, JSDOMGlobalObject&, Ref<Worker>&&);
+
+ DECLARE_DEFAULT_FINISH_CREATION;
+};
+
+class JSWorkerOwner final : public JSC::WeakHandleOwner {
+public:
+ bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, const char**) final;
+ void finalize(JSC::Handle<JSC::Unknown>, void* context) final;
+};
+
+inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, Worker*)
+{
+ static NeverDestroyed<JSWorkerOwner> owner;
+ return &owner.get();
+}
+
+inline void* wrapperKey(Worker* wrappableObject)
+{
+ return wrappableObject;
+}
+
+JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, Worker&);
+inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, Worker* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); }
+JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<Worker>&&);
+inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<Worker>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); }
+
+template<> struct JSDOMWrapperConverterTraits<Worker> {
+ using WrapperClass = JSWorker;
+ using ToWrappedReturnType = Worker*;
+};
+
+} // namespace WebCore
diff --git a/src/bun.js/bindings/webcore/JSWorkerOptions.cpp b/src/bun.js/bindings/webcore/JSWorkerOptions.cpp
new file mode 100644
index 000000000..6bee7e290
--- /dev/null
+++ b/src/bun.js/bindings/webcore/JSWorkerOptions.cpp
@@ -0,0 +1,83 @@
+/*
+ This file is part of the WebKit open source project.
+ This file has been generated by generate-bindings.pl. DO NOT MODIFY!
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+#include "JSWorkerOptions.h"
+
+#include "JSDOMConvertEnumeration.h"
+#include "JSDOMConvertStrings.h"
+// #include "JSFetchRequestCredentials.h"
+// #include "JSWorkerType.h"
+#include <JavaScriptCore/JSCInlines.h>
+
+namespace WebCore {
+using namespace JSC;
+
+template<> WorkerOptions convertDictionary<WorkerOptions>(JSGlobalObject& lexicalGlobalObject, JSValue value)
+{
+ VM& vm = JSC::getVM(&lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ bool isNullOrUndefined = value.isUndefinedOrNull();
+ auto* object = isNullOrUndefined ? nullptr : value.getObject();
+ if (UNLIKELY(!isNullOrUndefined && !object)) {
+ throwTypeError(&lexicalGlobalObject, throwScope);
+ return {};
+ }
+ WorkerOptions result;
+ // JSValue credentialsValue;
+ // if (isNullOrUndefined)
+ // credentialsValue = jsUndefined();
+ // else {
+ // credentialsValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "credentials"_s));
+ // RETURN_IF_EXCEPTION(throwScope, {});
+ // }
+ // if (!credentialsValue.isUndefined()) {
+ // result.credentials = convert<IDLEnumeration<FetchRequestCredentials>>(lexicalGlobalObject, credentialsValue);
+ // RETURN_IF_EXCEPTION(throwScope, {});
+ // } else
+ // result.credentials = FetchRequestCredentials::SameOrigin;
+ JSValue nameValue;
+ if (isNullOrUndefined)
+ nameValue = jsUndefined();
+ else {
+ nameValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "name"_s));
+ RETURN_IF_EXCEPTION(throwScope, {});
+ }
+ if (!nameValue.isUndefined()) {
+ result.name = convert<IDLDOMString>(lexicalGlobalObject, nameValue);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ } else
+ result.name = emptyString();
+ // JSValue typeValue;
+ // if (isNullOrUndefined)
+ // typeValue = jsUndefined();
+ // else {
+ // typeValue = object->get(&lexicalGlobalObject, Identifier::fromString(vm, "type"_s));
+ // RETURN_IF_EXCEPTION(throwScope, { });
+ // }
+ // if (!typeValue.isUndefined()) {
+ // result.type = convert<IDLEnumeration<WorkerType>>(lexicalGlobalObject, typeValue);
+ // RETURN_IF_EXCEPTION(throwScope, { });
+ // } else
+ // result.type = WorkerType::Classic;
+ return result;
+}
+
+} // namespace WebCore
diff --git a/src/bun.js/bindings/webcore/JSWorkerOptions.h b/src/bun.js/bindings/webcore/JSWorkerOptions.h
new file mode 100644
index 000000000..afb5f3656
--- /dev/null
+++ b/src/bun.js/bindings/webcore/JSWorkerOptions.h
@@ -0,0 +1,30 @@
+/*
+ This file is part of the WebKit open source project.
+ This file has been generated by generate-bindings.pl. DO NOT MODIFY!
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#pragma once
+
+#include "JSDOMConvertDictionary.h"
+#include "WorkerOptions.h"
+
+namespace WebCore {
+
+template<> WorkerOptions convertDictionary<WorkerOptions>(JSC::JSGlobalObject&, JSC::JSValue);
+
+} // namespace WebCore
diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp
index e3a741a02..60729ea65 100644
--- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp
+++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp
@@ -74,6 +74,7 @@
#include <JavaScriptCore/ExceptionHelpers.h>
#include <JavaScriptCore/IterationKind.h>
#include <JavaScriptCore/JSArrayBuffer.h>
+#include <JavaScriptCore/ArrayBuffer.h>
#include <JavaScriptCore/JSArrayBufferView.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/JSDataView.h>
@@ -2527,7 +2528,7 @@ public:
Vector<std::unique_ptr<DetachedRTCDataChannel>>&& detachedRTCDataChannels
#endif
,
- ArrayBufferContentsArray* arrayBufferContentsArray, const Vector<uint8_t>& buffer, const Vector<String>& blobURLs, const Vector<String> blobFilePaths, ArrayBufferContentsArray* sharedBuffers
+ ArrayBufferContentsArray* arrayBufferContentsArray, const Span<uint8_t>& buffer, const Vector<String>& blobURLs, const Vector<String> blobFilePaths, ArrayBufferContentsArray* sharedBuffers
#if ENABLE(WEBASSEMBLY)
,
WasmModuleArray* wasmModules, WasmMemoryHandleArray* wasmMemoryHandles
@@ -2540,7 +2541,7 @@ public:
{
if (!buffer.size())
return std::make_pair(jsNull(), SerializationReturnCode::UnspecifiedError);
- CloneDeserializer deserializer(lexicalGlobalObject, globalObject, arrayBufferContentsArray, buffer, blobURLs, blobFilePaths, sharedBuffers
+ CloneDeserializer deserializer(lexicalGlobalObject, globalObject, arrayBufferContentsArray, Span<uint8_t> { buffer.begin(), buffer.end() }, blobURLs, blobFilePaths, sharedBuffers
#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS)
,
WTFMove(detachedOffscreenCanvases)
@@ -2660,7 +2661,7 @@ private:
// m_version = 0xFFFFFFFF;
// }
- CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, ArrayBufferContentsArray* arrayBufferContents, const Vector<uint8_t>& buffer
+ CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, ArrayBufferContentsArray* arrayBufferContents, const Span<uint8_t>& buffer
#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS)
,
Vector<std::unique_ptr<DetachedOffscreenCanvas>>&& detachedOffscreenCanvases = {}
@@ -2766,7 +2767,7 @@ private:
// m_version = 0xFFFFFFFF;
// }
- CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, ArrayBufferContentsArray* arrayBufferContents, const Vector<uint8_t>& buffer, const Vector<String>& blobURLs, const Vector<String> blobFilePaths, ArrayBufferContentsArray* sharedBuffers
+ CloneDeserializer(JSGlobalObject* lexicalGlobalObject, JSGlobalObject* globalObject, ArrayBufferContentsArray* arrayBufferContents, const Span<uint8_t>& buffer, const Vector<String>& blobURLs, const Vector<String> blobFilePaths, ArrayBufferContentsArray* sharedBuffers
#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS)
,
Vector<std::unique_ptr<DetachedOffscreenCanvas>>&& detachedOffscreenCanvases
@@ -5282,6 +5283,73 @@ String SerializedScriptValue::toString() const
return CloneDeserializer::deserializeString(m_data);
}
+Ref<JSC::ArrayBuffer> SerializedScriptValue::toArrayBuffer()
+{
+ if (this->m_data.size() == 0) {
+ return ArrayBuffer::create(static_cast<size_t>(0), static_cast<unsigned>(1));
+ }
+
+ this->ref();
+ auto arrayBuffer = ArrayBuffer::createFromBytes(
+ this->m_data.data(), this->m_data.size(), createSharedTask<void(void*)>([protectedThis = Ref { *this }](void* p) {
+ protectedThis->deref();
+ }));
+
+ // Note: using the SharedArrayBufferContents::create function directly didn't work.
+ arrayBuffer->makeShared();
+
+ return arrayBuffer;
+}
+
+JSC::JSValue SerializedScriptValue::fromArrayBuffer(JSC::JSGlobalObject& domGlobal, JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer* arrayBuffer, size_t byteOffset, size_t maxByteLength, SerializationErrorMode throwExceptions, bool* didFail)
+{
+ auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm());
+
+ if (!arrayBuffer || arrayBuffer->isDetached()) {
+ if (didFail)
+ *didFail = true;
+
+ if (throwExceptions == SerializationErrorMode::Throwing)
+ throwTypeError(globalObject, throwScope, "Cannot deserialize a detached ArrayBuffer"_s);
+
+ return JSC::jsUndefined();
+ }
+ auto blobURLs = Vector<String> {};
+ auto blobFiles = Vector<String> {};
+
+ if (arrayBuffer->isShared()) {
+ // prevent detaching while in-use
+ arrayBuffer->pin();
+ }
+
+ auto* data = static_cast<uint8_t*>(arrayBuffer->data()) + byteOffset;
+ auto size = std::min(arrayBuffer->byteLength(), maxByteLength);
+ auto span = Span<uint8_t> { data, size };
+
+ auto result = CloneDeserializer::deserialize(&domGlobal, globalObject, nullptr, span, blobURLs, blobFiles, nullptr
+#if ENABLE(WEBASSEMBLY)
+ ,
+ nullptr, nullptr
+#endif
+#if ENABLE(WEB_CODECS)
+ ,
+ WTFMove(m_serializedVideoChunks), WTFMove(m_serializedVideoFrames)
+#endif
+ );
+
+ if (arrayBuffer->isShared()) {
+ arrayBuffer->unpin();
+ }
+
+ if (didFail) {
+ *didFail = result.second != SerializationReturnCode::SuccessfullyCompleted;
+ }
+ if (throwExceptions == SerializationErrorMode::Throwing)
+ maybeThrowExceptionIfSerializationFailed(*globalObject, result.second);
+
+ return result.first ? result.first : jsNull();
+}
+
// JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, SerializationErrorMode throwExceptions, bool* didFail)
// {
// return deserialize(lexicalGlobalObject, globalObject, {}, throwExceptions, didFail);
@@ -5396,7 +5464,8 @@ JSValueRef SerializedScriptValue::deserialize(JSContextRef destinationContext, J
return toRef(lexicalGlobalObject, value);
}
-Ref<SerializedScriptValue> SerializedScriptValue::nullValue()
+Ref<SerializedScriptValue>
+SerializedScriptValue::nullValue()
{
return adoptRef(*new SerializedScriptValue(Vector<uint8_t>()));
}
diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.h b/src/bun.js/bindings/webcore/SerializedScriptValue.h
index 6e2cbae86..189945d52 100644
--- a/src/bun.js/bindings/webcore/SerializedScriptValue.h
+++ b/src/bun.js/bindings/webcore/SerializedScriptValue.h
@@ -108,6 +108,9 @@ public:
WEBCORE_EXPORT String toString() const;
+ WEBCORE_EXPORT Ref<JSC::ArrayBuffer> toArrayBuffer();
+ static JSC::JSValue fromArrayBuffer(JSC::JSGlobalObject&, JSC::JSGlobalObject*, JSC::ArrayBuffer* arrayBuffer, size_t byteOffset = 0, size_t maxByteLength = 0, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr);
+
// API implementation helpers. These don't expose special behavior for ArrayBuffers or MessagePorts.
WEBCORE_EXPORT static RefPtr<SerializedScriptValue> create(JSContextRef, JSValueRef, JSValueRef* exception);
WEBCORE_EXPORT JSValueRef deserialize(JSContextRef, JSValueRef* exception);
diff --git a/src/bun.js/bindings/webcore/StructuredSerializeOptions.h b/src/bun.js/bindings/webcore/StructuredSerializeOptions.h
new file mode 100644
index 000000000..c1470f94a
--- /dev/null
+++ b/src/bun.js/bindings/webcore/StructuredSerializeOptions.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019-2021 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. AND ITS CONTRIBUTORS ``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 ITS 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/JSObject.h>
+#include <JavaScriptCore/Strong.h>
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+struct StructuredSerializeOptions {
+ StructuredSerializeOptions() = default;
+ StructuredSerializeOptions(Vector<JSC::Strong<JSC::JSObject>>&& transfer)
+ : transfer(WTFMove(transfer))
+ {
+ }
+
+ Vector<JSC::Strong<JSC::JSObject>> transfer;
+};
+
+} // namespace WebCore
diff --git a/src/bun.js/bindings/webcore/Worker.cpp b/src/bun.js/bindings/webcore/Worker.cpp
new file mode 100644
index 000000000..fe7dc2544
--- /dev/null
+++ b/src/bun.js/bindings/webcore/Worker.cpp
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2008-2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2009 Google 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.
+ */
+
+#include "config.h"
+#include "Worker.h"
+
+// #include "ContentSecurityPolicy.h"
+// #include "DedicatedWorkerGlobalScope.h"
+#include "ErrorEvent.h"
+#include "Event.h"
+#include "EventNames.h"
+// #include "InspectorInstrumentation.h"
+// #include "LoaderStrategy.h"
+// #include "PlatformStrategies.h"
+#if ENABLE(WEB_RTC)
+#include "RTCRtpScriptTransform.h"
+#include "RTCRtpScriptTransformer.h"
+#endif
+// #include "ResourceResponse.h"
+// #include "SecurityOrigin.h"
+#include "StructuredSerializeOptions.h"
+// #include "WorkerGlobalScopeProxy.h"
+// #include "WorkerInitializationData.h"
+// #include "WorkerScriptLoader.h"
+// #include "WorkerThread.h"
+#include <JavaScriptCore/IdentifiersFactory.h>
+#include <JavaScriptCore/ScriptCallStack.h>
+#include <wtf/HashSet.h>
+#include <wtf/IsoMallocInlines.h>
+#include <wtf/MainThread.h>
+#include <wtf/NeverDestroyed.h>
+#include <wtf/Scope.h>
+#include "SerializedScriptValue.h"
+#include "ScriptExecutionContext.h"
+#include "JavaScriptCore/JSMap.h"
+#include "JavaScriptCore/JSModuleLoader.h"
+#include "JavaScriptCore/DeferredWorkTimer.h"
+#include "MessageEvent.h"
+#include <JavaScriptCore/HashMapImplInlines.h>
+
+namespace WebCore {
+
+WTF_MAKE_ISO_ALLOCATED_IMPL(Worker);
+
+extern "C" void WebWorker__terminate(
+ void* worker);
+
+static Lock allWorkersLock;
+static HashMap<ScriptExecutionContextIdentifier, Worker*>& allWorkers() WTF_REQUIRES_LOCK(allWorkersLock)
+{
+ static NeverDestroyed<HashMap<ScriptExecutionContextIdentifier, Worker*>> map;
+ return map;
+}
+
+void Worker::networkStateChanged(bool isOnline)
+{
+ // Locker locker { allWorkersLock };
+ // for (auto& contextIdentifier : allWorkers().keys()) {
+ // ScriptExecutionContext::postTaskTo(contextIdentifier, [isOnline](auto& context) {
+ // auto& globalScope = downcast<WorkerGlobalScope>(context);
+ // globalScope.setIsOnline(isOnline);
+ // globalScope.dispatchEvent(Event::create(isOnline ? eventNames().onlineEvent : eventNames().offlineEvent, Event::CanBubble::No, Event::IsCancelable::No));
+ // });
+ // }
+}
+
+Worker::Worker(ScriptExecutionContext& context, WorkerOptions&& options)
+ : EventTargetWithInlineData()
+ , ContextDestructionObserver(&context)
+ , m_options(WTFMove(options))
+ , m_identifier("worker:" + Inspector::IdentifiersFactory::createIdentifier())
+ , m_clientIdentifier(ScriptExecutionContext::generateIdentifier())
+{
+ // static bool addedListener;
+ // if (!addedListener) {
+ // platformStrategies()->loaderStrategy()->addOnlineStateChangeListener(&networkStateChanged);
+ // addedListener = true;
+ // }
+
+ Locker locker { allWorkersLock };
+ auto addResult = allWorkers().add(m_clientIdentifier, this);
+ ASSERT_UNUSED(addResult, addResult.isNewEntry);
+}
+
+extern "C" void* WebWorker__create(
+ Worker* worker,
+ void* parent,
+ BunString name,
+ BunString url,
+ BunString* errorMessage,
+ uint32_t parentContextId,
+ uint32_t contextId,
+ bool miniMode,
+ bool unrefByDefault);
+extern "C" void WebWorker__setRef(
+ void* worker,
+ bool ref);
+
+void Worker::setKeepAlive(bool keepAlive)
+{
+ WebWorker__setRef(impl_, keepAlive);
+}
+
+ExceptionOr<Ref<Worker>> Worker::create(ScriptExecutionContext& context, const String& urlInit, WorkerOptions&& options)
+{
+ auto worker = adoptRef(*new Worker(context, WTFMove(options)));
+
+ WTF::String url = urlInit;
+ if (url.startsWith("file://"_s)) {
+ url = WTF::URL(url).fileSystemPath();
+ }
+ BunString urlStr = Bun::toString(url);
+ BunString errorMessage = BunStringEmpty;
+ BunString nameStr = Bun::toString(worker->m_options.name);
+
+ bool miniMode = worker->m_options.bun.mini;
+ bool unrefByDefault = worker->m_options.bun.unref;
+
+ void* impl = WebWorker__create(
+ worker.ptr(),
+ jsCast<Zig::GlobalObject*>(context.jsGlobalObject())->bunVM(),
+ nameStr,
+ urlStr,
+ &errorMessage,
+ static_cast<uint32_t>(context.identifier()),
+ static_cast<uint32_t>(worker->m_clientIdentifier), miniMode, unrefByDefault);
+
+ if (!impl) {
+ return Exception { TypeError, Bun::toWTFString(errorMessage) };
+ }
+
+ worker->impl_ = impl;
+ worker->m_workerCreationTime = MonotonicTime::now();
+
+ return worker;
+}
+
+Worker::~Worker()
+{
+ {
+ Locker locker { allWorkersLock };
+ allWorkers().remove(m_clientIdentifier);
+ }
+ // m_contextProxy.workerObjectDestroyed();
+}
+
+ExceptionOr<void> Worker::postMessage(JSC::JSGlobalObject& state, JSC::JSValue messageValue, StructuredSerializeOptions&& options)
+{
+ if (m_wasTerminated)
+ return Exception { InvalidStateError, "Worker has been terminated"_s };
+
+ auto message = SerializedScriptValue::create(state, messageValue, WTFMove(options.transfer), SerializationForStorage::No, SerializationContext::WorkerPostMessage);
+ if (message.hasException())
+ return message.releaseException();
+
+ this->postTaskToWorkerGlobalScope([message = message.releaseReturnValue()](auto& context) {
+ Zig::GlobalObject* globalObject = jsCast<Zig::GlobalObject*>(context.jsGlobalObject());
+ bool didFail = false;
+ JSValue value = message->deserialize(*globalObject, globalObject, SerializationErrorMode::NonThrowing, &didFail);
+ message->deref();
+
+ if (didFail) {
+ globalObject->eventTarget()->dispatchEvent(MessageEvent::create(eventNames().messageerrorEvent, MessageEvent::Init {}, MessageEvent::IsTrusted::Yes));
+ return;
+ }
+
+ WebCore::MessageEvent::Init init;
+ init.data = value;
+ globalObject->eventTarget()->dispatchEvent(MessageEvent::create(eventNames().messageEvent, WTFMove(init), MessageEvent::IsTrusted::Yes));
+ });
+ return {};
+}
+
+void Worker::terminate()
+{
+ // m_contextProxy.terminateWorkerGlobalScope();
+ m_wasTerminated = true;
+ WebWorker__terminate(impl_);
+}
+
+// const char* Worker::activeDOMObjectName() const
+// {
+// return "Worker";
+// }
+
+// void Worker::stop()
+// {
+// terminate();
+// }
+
+// void Worker::suspend(ReasonForSuspension reason)
+// {
+// if (reason == ReasonForSuspension::BackForwardCache) {
+// m_contextProxy.suspendForBackForwardCache();
+// m_isSuspendedForBackForwardCache = true;
+// }
+// }
+
+// void Worker::resume()
+// {
+// if (m_isSuspendedForBackForwardCache) {
+// m_contextProxy.resumeForBackForwardCache();
+// m_isSuspendedForBackForwardCache = false;
+// }
+// }
+
+bool Worker::hasPendingActivity() const
+{
+ if (this->m_isOnline) {
+ return !this->m_isClosing;
+ }
+
+ return !this->m_wasTerminated;
+}
+
+void Worker::dispatchEvent(Event& event)
+{
+ if (m_wasTerminated)
+ return;
+
+ EventTarget::dispatchEvent(event);
+}
+
+#if ENABLE(WEB_RTC)
+void Worker::createRTCRtpScriptTransformer(RTCRtpScriptTransform& transform, MessageWithMessagePorts&& options)
+{
+ if (!scriptExecutionContext())
+ return;
+
+ m_contextProxy.postTaskToWorkerGlobalScope([transform = Ref { transform }, options = WTFMove(options)](auto& context) mutable {
+ if (auto transformer = downcast<DedicatedWorkerGlobalScope>(context).createRTCRtpScriptTransformer(WTFMove(options)))
+ transform->setTransformer(*transformer);
+ });
+}
+#endif
+
+void Worker::drainEvents()
+{
+ for (auto& task : m_pendingTasks)
+ postTaskToWorkerGlobalScope(WTFMove(task));
+ m_pendingTasks.clear();
+}
+
+void Worker::dispatchOnline(Zig::GlobalObject* workerGlobalObject)
+{
+
+ auto* ctx = scriptExecutionContext();
+ if (ctx) {
+ ScriptExecutionContext::postTaskTo(ctx->identifier(), [protectedThis = Ref { *this }](ScriptExecutionContext& context) -> void {
+ if (protectedThis->hasEventListeners(eventNames().openEvent)) {
+ auto event = Event::create(eventNames().openEvent, Event::CanBubble::No, Event::IsCancelable::No);
+ protectedThis->dispatchEvent(event);
+ }
+ });
+ }
+
+ this->m_isOnline = true;
+ auto* thisContext = workerGlobalObject->scriptExecutionContext();
+ if (!thisContext) {
+ return;
+ }
+
+ if (workerGlobalObject->eventTarget()->hasActiveEventListeners(eventNames().messageEvent)) {
+ auto tasks = std::exchange(this->m_pendingTasks, {});
+ for (auto& task : tasks) {
+ task(*thisContext);
+ }
+ } else {
+ auto tasks = std::exchange(this->m_pendingTasks, {});
+ thisContext->postTask([tasks = WTFMove(tasks)](auto& ctx) mutable {
+ for (auto& task : tasks) {
+ task(ctx);
+ }
+ tasks.clear();
+ });
+ }
+}
+void Worker::dispatchError(WTF::String message)
+{
+
+ auto* ctx = scriptExecutionContext();
+ if (!ctx)
+ return;
+
+ ScriptExecutionContext::postTaskTo(ctx->identifier(), [protectedThis = Ref { *this }, message = message.isolatedCopy()](ScriptExecutionContext& context) -> void {
+ ErrorEvent::Init init;
+ init.message = message;
+
+ auto event = ErrorEvent::create(eventNames().errorEvent, init, EventIsTrusted::Yes);
+ protectedThis->dispatchEvent(event);
+ });
+}
+void Worker::dispatchExit()
+{
+ auto* ctx = scriptExecutionContext();
+ if (!ctx)
+ return;
+
+ ScriptExecutionContext::postTaskTo(ctx->identifier(), [protectedThis = Ref { *this }](ScriptExecutionContext& context) -> void {
+ protectedThis->m_isOnline = false;
+ protectedThis->m_isClosing = true;
+ protectedThis->setKeepAlive(false);
+
+ if (protectedThis->hasEventListeners(eventNames().closeEvent)) {
+ auto event = Event::create(eventNames().closeEvent, Event::CanBubble::No, Event::IsCancelable::No);
+ protectedThis->dispatchEvent(event);
+ }
+ });
+}
+
+void Worker::postTaskToWorkerGlobalScope(Function<void(ScriptExecutionContext&)>&& task)
+{
+ if (!this->m_isOnline) {
+ this->m_pendingTasks.append(WTFMove(task));
+ return;
+ }
+
+ ScriptExecutionContext::postTaskTo(m_clientIdentifier, WTFMove(task));
+}
+
+void Worker::forEachWorker(const Function<Function<void(ScriptExecutionContext&)>()>& callback)
+{
+ Locker locker { allWorkersLock };
+ for (auto& contextIdentifier : allWorkers().keys())
+ ScriptExecutionContext::postTaskTo(contextIdentifier, callback());
+}
+
+extern "C" void WebWorker__dispatchExit(Zig::GlobalObject* globalObject, Worker* worker, int32_t exitCode)
+{
+ if (globalObject) {
+ auto& vm = globalObject->vm();
+
+ if (JSC::JSObject* obj = JSC::jsDynamicCast<JSC::JSObject*>(globalObject->moduleLoader())) {
+ auto id = JSC::Identifier::fromString(globalObject->vm(), "registry"_s);
+ if (auto* registry = JSC::jsDynamicCast<JSC::JSMap*>(obj->getIfPropertyExists(globalObject, id))) {
+ registry->clear(vm);
+ }
+ }
+ gcUnprotect(globalObject);
+ vm.deleteAllCode(JSC::DeleteAllCodeEffort::PreventCollectionAndDeleteAllCode);
+ vm.heap.reportAbandonedObjectGraph();
+ WTF::releaseFastMallocFreeMemoryForThisThread();
+ vm.notifyNeedTermination();
+ vm.deferredWorkTimer->doWork(vm);
+ }
+
+ worker->dispatchExit();
+}
+extern "C" void WebWorker__dispatchOnline(Worker* worker, Zig::GlobalObject* globalObject)
+{
+ worker->dispatchOnline(globalObject);
+}
+
+extern "C" void WebWorker__dispatchError(Zig::GlobalObject* globalObject, Worker* worker, BunString message, EncodedJSValue errorValue)
+{
+ JSValue error = JSC::JSValue::decode(errorValue);
+ ErrorEvent::Init init;
+ init.message = Bun::toWTFString(message).isolatedCopy();
+ init.error = error;
+ init.cancelable = false;
+ init.bubbles = false;
+
+ globalObject->eventTarget()->dispatchEvent(ErrorEvent::create(eventNames().errorEvent, init, EventIsTrusted::Yes));
+ worker->dispatchError(Bun::toWTFString(message));
+}
+
+} // namespace WebCore
diff --git a/src/bun.js/bindings/webcore/Worker.h b/src/bun.js/bindings/webcore/Worker.h
new file mode 100644
index 000000000..edd9f082d
--- /dev/null
+++ b/src/bun.js/bindings/webcore/Worker.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2008, 2010, 2016 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 "ActiveDOMObject.h"
+#include "EventTarget.h"
+// #include "MessagePort.h"
+#include "WorkerOptions.h"
+// #include "WorkerScriptLoaderClient.h"
+// #include "WorkerType.h"
+#include <JavaScriptCore/RuntimeFlags.h>
+#include <wtf/Deque.h>
+#include <wtf/MonotonicTime.h>
+#include <wtf/text/AtomStringHash.h>
+#include "ContextDestructionObserver.h"
+#include "Event.h"
+
+namespace JSC {
+class CallFrame;
+class JSObject;
+class JSValue;
+}
+
+namespace WebCore {
+
+class RTCRtpScriptTransform;
+class RTCRtpScriptTransformer;
+class ScriptExecutionContext;
+class WorkerGlobalScopeProxy;
+// class WorkerScriptLoader;
+
+struct StructuredSerializeOptions;
+struct WorkerOptions;
+
+class Worker final : public RefCounted<Worker>, public EventTargetWithInlineData, private ContextDestructionObserver {
+ WTF_MAKE_ISO_ALLOCATED_EXPORT(Worker, WEBCORE_EXPORT);
+
+public:
+ static ExceptionOr<Ref<Worker>> create(ScriptExecutionContext&, const String& url, WorkerOptions&&);
+ ~Worker();
+
+ ExceptionOr<void> postMessage(JSC::JSGlobalObject&, JSC::JSValue message, StructuredSerializeOptions&&);
+
+ using RefCounted::deref;
+ using RefCounted::ref;
+
+ void terminate();
+ bool wasTerminated() const { return m_wasTerminated; }
+ bool hasPendingActivity() const;
+
+ String identifier() const { return m_identifier; }
+ const String& name() const { return m_options.name; }
+
+ void dispatchEvent(Event&);
+ void setKeepAlive(bool);
+
+#if ENABLE(WEB_RTC)
+ void createRTCRtpScriptTransformer(RTCRtpScriptTransform&, MessageWithMessagePorts&&);
+#endif
+
+ // WorkerType type() const
+ // {
+ // return m_options.type;
+ // }
+
+ void postTaskToWorkerGlobalScope(Function<void(ScriptExecutionContext&)>&&);
+
+ static void forEachWorker(const Function<Function<void(ScriptExecutionContext&)>()>&);
+
+ void drainEvents();
+ void dispatchOnline(Zig::GlobalObject* workerGlobalObject);
+ void dispatchError(WTF::String message);
+ void dispatchExit();
+ ScriptExecutionContext* scriptExecutionContext() const final { return ContextDestructionObserver::scriptExecutionContext(); }
+
+private:
+ Worker(ScriptExecutionContext&, WorkerOptions&&);
+
+ EventTargetInterface eventTargetInterface() const final { return WorkerEventTargetInterfaceType; }
+ void refEventTarget() final { ref(); }
+ void derefEventTarget() final { deref(); }
+ void eventListenersDidChange() final {};
+
+ // void didReceiveResponse(ResourceLoaderIdentifier, const ResourceResponse&) final;
+ // void notifyFinished() final;
+
+ // ActiveDOMObject.
+ // void stop() final;
+ // void suspend(ReasonForSuspension) final;
+ // void resume() final;
+ // const char* activeDOMObjectName() const final;
+ // bool virtualHasPendingActivity() const final;
+
+ static void networkStateChanged(bool isOnLine);
+
+ // RefPtr<WorkerScriptLoader> m_scriptLoader;
+ const WorkerOptions m_options;
+ String m_identifier;
+ // WorkerGlobalScopeProxy& m_contextProxy; // The proxy outlives the worker to perform thread shutdown.
+ // std::optional<ContentSecurityPolicyResponseHeaders> m_contentSecurityPolicyResponseHeaders;
+ MonotonicTime m_workerCreationTime;
+ // bool m_shouldBypassMainWorldContentSecurityPolicy { false };
+ // bool m_isSuspendedForBackForwardCache { false };
+ // JSC::RuntimeFlags m_runtimeFlags;
+ Deque<RefPtr<Event>> m_pendingEvents;
+ Deque<Function<void(ScriptExecutionContext&)>> m_pendingTasks;
+ bool m_wasTerminated { false };
+ bool m_didStartWorkerGlobalScope { false };
+ bool m_isOnline { false };
+ bool m_isClosing { false };
+ const ScriptExecutionContextIdentifier m_clientIdentifier;
+ void* impl_ { nullptr };
+ size_t m_pendingActivityCount { 0 };
+};
+
+} // namespace WebCore
diff --git a/src/bun.js/bindings/webcore/WorkerOptions.h b/src/bun.js/bindings/webcore/WorkerOptions.h
new file mode 100644
index 000000000..bc2ef6ff0
--- /dev/null
+++ b/src/bun.js/bindings/webcore/WorkerOptions.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "root.h"
+
+namespace WebCore {
+
+struct BunOptions {
+ bool mini { false };
+ bool unref { false };
+};
+
+struct WorkerOptions {
+ // WorkerType type { WorkerType::Classic };
+ // FetchRequestCredentials credentials { FetchRequestCredentials::SameOrigin };
+ String name;
+
+ BunOptions bun {};
+};
+
+} // namespace WebCore
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index a3ccd16ad..d9befef8e 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -746,14 +746,16 @@ pub const EventLoop = struct {
this.virtual_machine.gc_controller.performGC();
}
+ pub fn wakeup(this: *EventLoop) void {
+ if (this.virtual_machine.uws_event_loop) |loop| {
+ loop.wakeup();
+ }
+ }
pub fn enqueueTaskConcurrent(this: *EventLoop, task: *ConcurrentTask) void {
JSC.markBinding(@src());
this.concurrent_tasks.push(task);
-
- if (this.virtual_machine.uws_event_loop) |loop| {
- loop.wakeup();
- }
+ this.wakeup();
}
};
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index beb2d1856..3534a5316 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -96,7 +96,7 @@ pub const GlobalConstructors = [_]type{
pub const GlobalClasses = [_]type{
Bun.Class,
WebCore.Crypto.Class,
- EventListenerMixin.addEventListener(VirtualMachine),
+ // EventListenerMixin.addEventListener(VirtualMachine),
// Fetch.Class,
js_ast.Macro.JSNode.BunJSXCallbackFunction,
@@ -353,7 +353,8 @@ pub const ExitHandler = struct {
JSC.markBinding(@src());
var vm = @fieldParentPtr(VirtualMachine, "exit_handler", this);
Process__dispatchOnExit(vm.global, this.exit_code);
- Bun__closeAllSQLiteDatabasesForTermination();
+ if (vm.isMainThread())
+ Bun__closeAllSQLiteDatabasesForTermination();
}
pub fn dispatchOnBeforeExit(this: *ExitHandler) void {
@@ -363,6 +364,8 @@ pub const ExitHandler = struct {
}
};
+pub const WebWorker = @import("./web_worker.zig").WebWorker;
+
/// TODO: rename this to ScriptExecutionContext
/// This is the shared global state for a single JS instance execution
/// Today, Bun is one VM per thread, so the name "VirtualMachine" sort of makes sense
@@ -482,11 +485,16 @@ pub const VirtualMachine = struct {
parser_arena: ?@import("root").bun.ArenaAllocator = null,
gc_controller: JSC.GarbageCollectionController = .{},
+ worker: ?*JSC.WebWorker = null,
pub const OnUnhandledRejection = fn (*VirtualMachine, globalObject: *JSC.JSGlobalObject, JSC.JSValue) void;
pub const OnException = fn (*ZigException) void;
+ pub fn isMainThread(this: *const VirtualMachine) bool {
+ return this.worker == null;
+ }
+
pub fn setOnException(this: *VirtualMachine, callback: *const OnException) void {
this.on_exception = callback;
}
@@ -877,6 +885,8 @@ pub const VirtualMachine = struct {
&global_classes,
@intCast(i32, global_classes.len),
vm.console,
+ -1,
+ false,
);
vm.regular_event_loop.global = vm.global;
vm.regular_event_loop.virtual_machine = vm;
@@ -976,6 +986,110 @@ pub const VirtualMachine = struct {
&global_classes,
@intCast(i32, global_classes.len),
vm.console,
+ -1,
+ false,
+ );
+ vm.regular_event_loop.global = vm.global;
+ vm.regular_event_loop.virtual_machine = vm;
+
+ if (source_code_printer == null) {
+ var writer = try js_printer.BufferWriter.init(allocator);
+ source_code_printer = allocator.create(js_printer.BufferPrinter) catch unreachable;
+ source_code_printer.?.* = js_printer.BufferPrinter.init(writer);
+ source_code_printer.?.ctx.append_null_byte = false;
+ }
+
+ return vm;
+ }
+
+ pub fn initWorker(
+ allocator: std.mem.Allocator,
+ _args: Api.TransformOptions,
+ _log: ?*logger.Log,
+ env_loader: ?*DotEnv.Loader,
+ store_fd: bool,
+ worker: *WebWorker,
+ ) anyerror!*VirtualMachine {
+ var log: *logger.Log = undefined;
+ if (_log) |__log| {
+ log = __log;
+ } else {
+ log = try allocator.create(logger.Log);
+ log.* = logger.Log.init(allocator);
+ }
+
+ VMHolder.vm = try allocator.create(VirtualMachine);
+ var console = try allocator.create(ZigConsoleClient);
+ console.* = ZigConsoleClient.init(Output.errorWriter(), Output.writer());
+ const bundler = try Bundler.init(
+ allocator,
+ log,
+ try Config.configureTransformOptionsForBunVM(allocator, _args),
+ null,
+ env_loader,
+ );
+ var vm = VMHolder.vm.?;
+
+ vm.* = VirtualMachine{
+ .global = undefined,
+ .allocator = allocator,
+ .entry_point = ServerEntryPoint{},
+ .event_listeners = EventListenerMixin.Map.init(allocator),
+ .bundler = bundler,
+ .console = console,
+ .node_modules = bundler.options.node_modules_bundle,
+ .log = log,
+ .flush_list = std.ArrayList(string).init(allocator),
+ .blobs = if (_args.serve orelse false) try Blob.Group.init(allocator) else null,
+ .origin = bundler.options.origin,
+ .saved_source_map_table = SavedSourceMap.HashTable.init(allocator),
+ .source_mappings = undefined,
+ .macros = MacroMap.init(allocator),
+ .macro_entry_points = @TypeOf(vm.macro_entry_points).init(allocator),
+ .origin_timer = std.time.Timer.start() catch @panic("Please don't mess with timers."),
+ .origin_timestamp = getOriginTimestamp(),
+ .ref_strings = JSC.RefString.Map.init(allocator),
+ .file_blobs = JSC.WebCore.Blob.Store.Map.init(allocator),
+ .parser_arena = @import("root").bun.ArenaAllocator.init(allocator),
+ .standalone_module_graph = worker.parent.standalone_module_graph,
+ .worker = worker,
+ };
+ vm.source_mappings = .{ .map = &vm.saved_source_map_table };
+ vm.regular_event_loop.tasks = EventLoop.Queue.init(
+ default_allocator,
+ );
+ vm.regular_event_loop.tasks.ensureUnusedCapacity(64) catch unreachable;
+ vm.regular_event_loop.concurrent_tasks = .{};
+ vm.event_loop = &vm.regular_event_loop;
+ vm.hot_reload = worker.parent.hot_reload;
+ vm.bundler.macro_context = null;
+ vm.bundler.resolver.store_fd = store_fd;
+ vm.bundler.resolver.prefer_module_field = false;
+ vm.bundler.resolver.onWakePackageManager = .{
+ .context = &vm.modules,
+ .handler = ModuleLoader.AsyncModule.Queue.onWakeHandler,
+ .onDependencyError = JSC.ModuleLoader.AsyncModule.Queue.onDependencyError,
+ };
+
+ vm.bundler.configureLinker();
+ try vm.bundler.configureFramework(false);
+
+ vm.bundler.macro_context = js_ast.Macro.MacroContext.init(&vm.bundler);
+
+ if (_args.serve orelse false) {
+ vm.bundler.linker.onImportCSS = Bun.onImportCSS;
+ }
+
+ var global_classes: [GlobalClasses.len]js.JSClassRef = undefined;
+ inline for (GlobalClasses, 0..) |Class, i| {
+ global_classes[i] = Class.get().*;
+ }
+ vm.global = ZigGlobalObject.create(
+ &global_classes,
+ @intCast(i32, global_classes.len),
+ vm.console,
+ @intCast(i32, worker.execution_context_id),
+ worker.mini,
);
vm.regular_event_loop.global = vm.global;
vm.regular_event_loop.virtual_machine = vm;
@@ -1753,7 +1867,7 @@ pub const VirtualMachine = struct {
return promise;
}
- pub fn loadEntryPoint(this: *VirtualMachine, entry_path: string) !*JSInternalPromise {
+ pub fn loadEntryPoint(this: *VirtualMachine, entry_path: string) anyerror!*JSInternalPromise {
var promise = try this.reloadEntryPoint(entry_path);
// pending_internal_promise can change if hot module reloading is enabled
diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig
index 64bd04e94..e035c01a6 100644
--- a/src/bun.js/node/types.zig
+++ b/src/bun.js/node/types.zig
@@ -2223,8 +2223,14 @@ pub const Process = struct {
}
pub fn exit(globalObject: *JSC.JSGlobalObject, code: i32) callconv(.C) void {
- globalObject.bunVM().onExit();
+ var vm = globalObject.bunVM();
+ if (vm.worker) |worker| {
+ vm.exit_handler.exit_code = @truncate(u8, @max(code, 0));
+ worker.terminate();
+ return;
+ }
+ vm.onExit();
std.os.exit(@truncate(u8, @intCast(u32, @max(code, 0))));
}
diff --git a/src/bun.js/web_worker.zig b/src/bun.js/web_worker.zig
new file mode 100644
index 000000000..df2e63c3a
--- /dev/null
+++ b/src/bun.js/web_worker.zig
@@ -0,0 +1,406 @@
+const bun = @import("root").bun;
+const JSC = bun.JSC;
+const Output = bun.Output;
+const log = Output.scoped(.Worker, true);
+const std = @import("std");
+const JSValue = JSC.JSValue;
+
+pub const WebWorker = struct {
+ // null when haven't started yet
+ vm: ?*JSC.VirtualMachine = null,
+ status: Status = .start,
+ requested_terminate: bool = false,
+ execution_context_id: u32 = 0,
+ parent_context_id: u32 = 0,
+ parent: *JSC.VirtualMachine,
+
+ /// Already resolved.
+ specifier: []const u8 = "",
+ store_fd: bool = false,
+ arena: bun.MimallocArena = undefined,
+ name: [:0]const u8 = "Worker",
+ cpp_worker: *void,
+ allowed_to_exit: bool = false,
+ mini: bool = false,
+ parent_poll_ref: JSC.PollRef = .{},
+ initial_poll_ref: JSC.PollRef = .{},
+
+ extern fn WebWorker__dispatchExit(?*JSC.JSGlobalObject, *void, i32) void;
+ extern fn WebWorker__dispatchOnline(this: *void, *JSC.JSGlobalObject) void;
+ extern fn WebWorker__dispatchError(*JSC.JSGlobalObject, *void, bun.String, JSValue) void;
+ export fn WebWorker__getParentWorker(vm: *JSC.VirtualMachine) ?*anyopaque {
+ var worker = vm.worker orelse return null;
+ return worker.cpp_worker;
+ }
+
+ pub fn hasPendingActivity(this: *WebWorker) callconv(.C) bool {
+ JSC.markBinding(@src());
+
+ if (this.vm == null) {
+ return !this.requested_terminate;
+ }
+
+ if (!this.allowed_to_exit) {
+ return true;
+ }
+
+ var vm = this.vm.?;
+
+ return vm.eventLoop().tasks.count > 0 or vm.active_tasks > 0 or vm.uws_event_loop.?.active > 0;
+ }
+
+ pub fn create(
+ cpp_worker: *void,
+ parent: *JSC.VirtualMachine,
+ name_str: bun.String,
+ specifier_str: bun.String,
+ error_message: *bun.String,
+ parent_context_id: u32,
+ this_context_id: u32,
+ mini: bool,
+ default_unref: bool,
+ ) callconv(.C) ?*WebWorker {
+ JSC.markBinding(@src());
+ var spec_slice = specifier_str.toUTF8(bun.default_allocator);
+ defer spec_slice.deinit();
+ var prev_log = parent.bundler.log;
+ var temp_log = bun.logger.Log.init(bun.default_allocator);
+ parent.bundler.setLog(&temp_log);
+ defer parent.bundler.setLog(prev_log);
+ defer temp_log.deinit();
+
+ var resolved_entry_point = parent.bundler.resolveEntryPoint(spec_slice.slice()) catch {
+ var out = temp_log.toJS(parent.global, bun.default_allocator, "Error resolving Worker entry point").toBunString(parent.global);
+ out.ref();
+ error_message.* = out;
+ return null;
+ };
+
+ var path = resolved_entry_point.path() orelse {
+ error_message.* = bun.String.static("Worker entry point is missing");
+ return null;
+ };
+
+ var worker = bun.default_allocator.create(WebWorker) catch @panic("OOM");
+ worker.* = WebWorker{
+ .cpp_worker = cpp_worker,
+ .parent = parent,
+ .parent_context_id = parent_context_id,
+ .execution_context_id = this_context_id,
+ .mini = mini,
+ .specifier = bun.default_allocator.dupe(u8, path.text) catch @panic("OOM"),
+ .store_fd = parent.bundler.resolver.store_fd,
+ .name = brk: {
+ if (!name_str.isEmpty()) {
+ break :brk std.fmt.allocPrintZ(bun.default_allocator, "{}", .{name_str}) catch @panic("OOM");
+ }
+ break :brk "";
+ },
+ };
+
+ worker.initial_poll_ref.ref(parent);
+
+ if (!default_unref) {
+ worker.allowed_to_exit = false;
+ worker.parent_poll_ref.ref(parent);
+ }
+
+ var thread = std.Thread.spawn(
+ .{ .stack_size = 2 * 1024 * 1024 },
+ startWithErrorHandling,
+ .{worker},
+ ) catch {
+ worker.deinit();
+ worker.requested_terminate = true;
+ worker.parent_poll_ref.unref(worker.parent);
+ worker.initial_poll_ref.unref(worker.parent);
+ return null;
+ };
+ thread.detach();
+
+ return worker;
+ }
+
+ fn queueInitialTask(this: *WebWorker) void {
+ const Unref = struct {
+ pub fn unref(worker: *WebWorker) void {
+ worker.initial_poll_ref.unref(worker.parent);
+ }
+ };
+
+ const AnyTask = JSC.AnyTask.New(WebWorker, Unref.unref);
+ var any_task = bun.default_allocator.create(JSC.AnyTask) catch @panic("OOM");
+ any_task.* = AnyTask.init(this);
+ var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("OOM");
+ this.parent.eventLoop().enqueueTaskConcurrent(concurrent_task.from(any_task));
+ }
+
+ pub fn startWithErrorHandling(
+ this: *WebWorker,
+ ) void {
+ start(this) catch |err| {
+ Output.panic("An unhandled error occurred while starting a worker: {s}\n", .{@errorName(err)});
+ };
+ }
+
+ pub fn start(
+ this: *WebWorker,
+ ) anyerror!void {
+ if (this.name.len > 0) {
+ Output.Source.configureNamedThread(this.name);
+ } else {
+ Output.Source.configureNamedThread("Worker");
+ }
+
+ if (this.requested_terminate) {
+ this.queueInitialTask();
+ this.deinit();
+ return;
+ }
+
+ std.debug.assert(this.status == .start);
+ std.debug.assert(this.vm == null);
+ this.arena = try bun.MimallocArena.init();
+ var vm = try JSC.VirtualMachine.initWorker(
+ this.arena.allocator(),
+ this.parent.bundler.options.transform_options,
+ null,
+ null,
+ this.store_fd,
+ this,
+ );
+ vm.allocator = this.arena.allocator();
+ vm.arena = &this.arena;
+
+ var b = &vm.bundler;
+
+ b.configureRouter(false) catch {
+ this.flushLogs();
+ this.onTerminate();
+ return;
+ };
+ b.configureDefines() catch {
+ this.flushLogs();
+ this.onTerminate();
+ return;
+ };
+
+ vm.loadExtraEnv();
+ vm.is_main_thread = false;
+ JSC.VirtualMachine.is_main_thread_vm = false;
+ vm.onUnhandledRejection = onUnhandledRejection;
+ var callback = JSC.OpaqueWrap(WebWorker, WebWorker.spin);
+
+ this.vm = vm;
+
+ vm.global.vm().holdAPILock(this, callback);
+ }
+
+ fn deinit(this: *WebWorker) void {
+ bun.default_allocator.free(this.specifier);
+ }
+
+ fn flushLogs(this: *WebWorker) void {
+ var vm = this.vm orelse return;
+ if (vm.log.msgs.items.len == 0) return;
+ const err = vm.log.toJS(vm.global, bun.default_allocator, "Error in worker");
+ const str = err.toBunString(vm.global);
+ WebWorker__dispatchError(vm.global, this.cpp_worker, str, err);
+ }
+
+ fn onUnhandledRejection(vm: *JSC.VirtualMachine, globalObject: *JSC.JSGlobalObject, error_instance: JSC.JSValue) void {
+ var array = bun.MutableString.init(bun.default_allocator, 0) catch unreachable;
+ defer array.deinit();
+
+ var buffered_writer_ = bun.MutableString.BufferedWriter{ .context = &array };
+ var buffered_writer = &buffered_writer_;
+ var worker = vm.worker orelse @panic("Assertion failure: no worker");
+
+ var writer = buffered_writer.writer();
+ const Writer = @TypeOf(writer);
+ // we buffer this because it'll almost always be < 4096
+ // when it's under 4096, we want to avoid the dynamic allocation
+ bun.JSC.ZigConsoleClient.format(
+ .Debug,
+ globalObject,
+ &[_]JSC.JSValue{error_instance},
+ 1,
+ Writer,
+ Writer,
+ writer,
+ .{
+ .enable_colors = false,
+ .add_newline = false,
+ .flush = false,
+ .max_depth = 32,
+ },
+ );
+ buffered_writer.flush() catch {
+ @panic("OOM");
+ };
+
+ WebWorker__dispatchError(globalObject, worker.cpp_worker, bun.String.create(array.toOwnedSliceLeaky()), error_instance);
+ }
+
+ fn setStatus(this: *WebWorker, status: Status) void {
+ log("status: {s}", .{@tagName(status)});
+ this.status = status;
+ }
+
+ fn unhandledError(this: *WebWorker, _: anyerror) void {
+ this.flushLogs();
+ }
+
+ fn spin(this: *WebWorker) void {
+ var vm = this.vm.?;
+ std.debug.assert(this.status == .start);
+ this.setStatus(.starting);
+
+ var promise = vm.loadEntryPoint(this.specifier) catch {
+ this.flushLogs();
+ this.onTerminate();
+ return;
+ };
+
+ this.queueInitialTask();
+
+ if (promise.status(vm.global.vm()) == .Rejected) {
+ vm.onUnhandledError(vm.global, promise.result(vm.global.vm()));
+
+ vm.exit_handler.exit_code = 1;
+ this.onTerminate();
+
+ return;
+ }
+
+ _ = promise.result(vm.global.vm());
+
+ this.flushLogs();
+
+ WebWorker__dispatchOnline(this.cpp_worker, vm.global);
+ this.setStatus(.running);
+
+ // don't run the GC if we don't actually need to
+ if (vm.eventLoop().tasks.count > 0 or vm.active_tasks > 0 or
+ vm.uws_event_loop.?.active > 0 or
+ vm.eventLoop().tickConcurrentWithCount() > 0)
+ {
+ vm.global.vm().releaseWeakRefs();
+ _ = vm.arena.gc(false);
+ _ = vm.global.vm().runGC(false);
+ vm.tick();
+ }
+
+ {
+ while (true) {
+ while (vm.eventLoop().tasks.count > 0 or vm.active_tasks > 0 or vm.uws_event_loop.?.active > 0) {
+ vm.tick();
+
+ vm.eventLoop().autoTickActive();
+ }
+
+ if (!this.allowed_to_exit) {
+ this.flushLogs();
+ vm.eventLoop().tickPossiblyForever();
+ continue;
+ }
+
+ vm.onBeforeExit();
+
+ if (!this.allowed_to_exit)
+ continue;
+
+ break;
+ }
+
+ this.flushLogs();
+ this.onTerminate();
+ }
+ }
+
+ pub const Status = enum {
+ start,
+ starting,
+ running,
+ terminated,
+ };
+
+ pub fn terminate(this: *WebWorker) callconv(.C) void {
+ if (this.requested_terminate) {
+ return;
+ }
+
+ this.allowed_to_exit = false;
+ _ = this.requestTerminate();
+ }
+
+ pub fn setRef(this: *WebWorker, value: bool) callconv(.C) void {
+ if (this.requested_terminate and value) {
+ this.parent_poll_ref.unref(this.parent);
+ return;
+ }
+
+ this.allowed_to_exit = value;
+ if (value) {
+ this.parent_poll_ref.ref(this.parent);
+ } else {
+ this.parent_poll_ref.unref(this.parent);
+ }
+
+ if (this.vm) |vm| {
+ vm.eventLoop().wakeup();
+ }
+ }
+
+ fn onTerminate(this: *WebWorker) void {
+ log("onTerminate", .{});
+
+ this.reallyExit();
+ }
+
+ comptime {
+ if (!JSC.is_bindgen) {
+ @export(hasPendingActivity, .{ .name = "WebWorker__hasPendingActivity" });
+ @export(create, .{ .name = "WebWorker__create" });
+ @export(terminate, .{ .name = "WebWorker__terminate" });
+ @export(setRef, .{ .name = "WebWorker__setRef" });
+ }
+ }
+
+ fn reallyExit(this: *WebWorker) void {
+ JSC.markBinding(@src());
+
+ if (this.requested_terminate) {
+ return;
+ }
+ this.requested_terminate = true;
+
+ var cpp_worker = this.cpp_worker;
+ var exit_code: i32 = 0;
+ var globalObject: ?*JSC.JSGlobalObject = null;
+ if (this.vm) |vm| {
+ this.vm = null;
+ vm.onExit();
+ exit_code = vm.exit_handler.exit_code;
+ globalObject = vm.global;
+ this.arena.deinit();
+ }
+ WebWorker__dispatchExit(globalObject, cpp_worker, exit_code);
+
+ this.deinit();
+ }
+
+ fn requestTerminate(this: *WebWorker) bool {
+ var vm = this.vm orelse {
+ this.requested_terminate = true;
+ return false;
+ };
+ this.allowed_to_exit = true;
+ log("requesting terminate", .{});
+ var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("OOM");
+ var task = bun.default_allocator.create(JSC.AnyTask) catch @panic("OOM");
+ task.* = JSC.AnyTask.New(WebWorker, onTerminate).init(this);
+ vm.eventLoop().enqueueTaskConcurrent(concurrent_task.from(task));
+ return true;
+ }
+};
diff --git a/src/js/bun/jsc.ts b/src/js/bun/jsc.ts
index 154e7345c..8063da948 100644
--- a/src/js/bun/jsc.ts
+++ b/src/js/bun/jsc.ts
@@ -34,3 +34,5 @@ export const profile = jsc.profile;
export default jsc;
export const setTimeZone = jsc.setTimeZone;
export const setTimezone = setTimeZone;
+export const serialize = jsc.serialize;
+export const deserialize = jsc.deserialize;
diff --git a/src/js/node/v8.ts b/src/js/node/v8.ts
index 3018c34e6..b043e10fe 100644
--- a/src/js/node/v8.ts
+++ b/src/js/node/v8.ts
@@ -1,6 +1,7 @@
// Hardcoded module "node:v8"
// This is a stub! None of this is actually implemented yet.
import { hideFromStack, throwNotImplemented } from "../shared";
+import { serialize as jscSerialize, deserialize as jscDeserialize } from "bun:jsc";
function notimpl(message) {
throwNotImplemented("node:v8 " + message);
@@ -42,8 +43,8 @@ function getHeapCodeStatistics() {
function setFlagsFromString() {
notimpl("setFlagsFromString");
}
-function deserialize() {
- notimpl("deserialize");
+function deserialize(value) {
+ return jscDeserialize(value);
}
function takeCoverage() {
notimpl("takeCoverage");
@@ -51,8 +52,8 @@ function takeCoverage() {
function stopCoverage() {
notimpl("stopCoverage");
}
-function serialize() {
- notimpl("serialize");
+function serialize(arg1) {
+ return jscSerialize(arg1, { binaryType: "nodebuffer" });
}
function writeHeapSnapshot() {
notimpl("writeHeapSnapshot");
diff --git a/src/js/out/modules/bun/jsc.js b/src/js/out/modules/bun/jsc.js
index df89b2584..45fb51bdd 100644
--- a/src/js/out/modules/bun/jsc.js
+++ b/src/js/out/modules/bun/jsc.js
@@ -1,4 +1,4 @@
-var jsc = globalThis[Symbol.for("Bun.lazy")]("bun:jsc"), callerSourceOrigin = jsc.callerSourceOrigin, jscDescribe = jsc.describe, jscDescribeArray = jsc.describeArray, describe = jscDescribe, describeArray = jscDescribeArray, drainMicrotasks = jsc.drainMicrotasks, edenGC = jsc.edenGC, fullGC = jsc.fullGC, gcAndSweep = jsc.gcAndSweep, getRandomSeed = jsc.getRandomSeed, heapSize = jsc.heapSize, heapStats = jsc.heapStats, startSamplingProfiler = jsc.startSamplingProfiler, samplingProfilerStackTraces = jsc.samplingProfilerStackTraces, isRope = jsc.isRope, memoryUsage = jsc.memoryUsage, noInline = jsc.noInline, noFTL = jsc.noFTL, noOSRExitFuzzing = jsc.noOSRExitFuzzing, numberOfDFGCompiles = jsc.numberOfDFGCompiles, optimizeNextInvocation = jsc.optimizeNextInvocation, releaseWeakRefs = jsc.releaseWeakRefs, reoptimizationRetryCount = jsc.reoptimizationRetryCount, setRandomSeed = jsc.setRandomSeed, startRemoteDebugger = jsc.startRemoteDebugger, totalCompileTime = jsc.totalCompileTime, getProtectedObjects = jsc.getProtectedObjects, generateHeapSnapshotForDebugging = jsc.generateHeapSnapshotForDebugging, profile = jsc.profile, jsc_default = jsc, setTimeZone = jsc.setTimeZone, setTimezone = setTimeZone;
+var jsc = globalThis[Symbol.for("Bun.lazy")]("bun:jsc"), callerSourceOrigin = jsc.callerSourceOrigin, jscDescribe = jsc.describe, jscDescribeArray = jsc.describeArray, describe = jscDescribe, describeArray = jscDescribeArray, drainMicrotasks = jsc.drainMicrotasks, edenGC = jsc.edenGC, fullGC = jsc.fullGC, gcAndSweep = jsc.gcAndSweep, getRandomSeed = jsc.getRandomSeed, heapSize = jsc.heapSize, heapStats = jsc.heapStats, startSamplingProfiler = jsc.startSamplingProfiler, samplingProfilerStackTraces = jsc.samplingProfilerStackTraces, isRope = jsc.isRope, memoryUsage = jsc.memoryUsage, noInline = jsc.noInline, noFTL = jsc.noFTL, noOSRExitFuzzing = jsc.noOSRExitFuzzing, numberOfDFGCompiles = jsc.numberOfDFGCompiles, optimizeNextInvocation = jsc.optimizeNextInvocation, releaseWeakRefs = jsc.releaseWeakRefs, reoptimizationRetryCount = jsc.reoptimizationRetryCount, setRandomSeed = jsc.setRandomSeed, startRemoteDebugger = jsc.startRemoteDebugger, totalCompileTime = jsc.totalCompileTime, getProtectedObjects = jsc.getProtectedObjects, generateHeapSnapshotForDebugging = jsc.generateHeapSnapshotForDebugging, profile = jsc.profile, jsc_default = jsc, setTimeZone = jsc.setTimeZone, setTimezone = setTimeZone, serialize = jsc.serialize, deserialize = jsc.deserialize;
export {
totalCompileTime,
startSamplingProfiler,
@@ -6,6 +6,7 @@ export {
setTimezone,
setTimeZone,
setRandomSeed,
+ serialize,
samplingProfilerStackTraces,
reoptimizationRetryCount,
releaseWeakRefs,
@@ -28,6 +29,7 @@ export {
fullGC,
edenGC,
drainMicrotasks,
+ deserialize,
describeArray,
describe,
jsc_default as default,
diff --git a/src/js/out/modules/node/v8.js b/src/js/out/modules/node/v8.js
index eb7a6568b..b2a42e898 100644
--- a/src/js/out/modules/node/v8.js
+++ b/src/js/out/modules/node/v8.js
@@ -17,6 +17,7 @@ class NotImplementedError extends Error {
}
// src/js/node/v8.ts
+import {serialize as jscSerialize, deserialize as jscDeserialize} from "bun:jsc";
var notimpl = function(message) {
throwNotImplemented("node:v8 " + message);
}, cachedDataVersionTag = function() {
@@ -31,14 +32,14 @@ var notimpl = function(message) {
notimpl("getHeapCodeStatistics");
}, setFlagsFromString = function() {
notimpl("setFlagsFromString");
-}, deserialize = function() {
- notimpl("deserialize");
+}, deserialize = function(value) {
+ return jscDeserialize(value);
}, takeCoverage = function() {
notimpl("takeCoverage");
}, stopCoverage = function() {
notimpl("stopCoverage");
-}, serialize = function() {
- notimpl("serialize");
+}, serialize = function(arg1) {
+ return jscSerialize(arg1, { binaryType: "nodebuffer" });
}, writeHeapSnapshot = function() {
notimpl("writeHeapSnapshot");
}, setHeapSnapshotNearHeapLimit = function() {
diff --git a/test/js/bun/jsc/bun-jsc.test.ts b/test/js/bun/jsc/bun-jsc.test.ts
index e8aa4229f..e993dd723 100644
--- a/test/js/bun/jsc/bun-jsc.test.ts
+++ b/test/js/bun/jsc/bun-jsc.test.ts
@@ -2,6 +2,8 @@ import { describe, expect, it } from "bun:test";
import {
describe as jscDescribe,
describeArray,
+ serialize,
+ deserialize,
gcAndSweep,
fullGC,
edenGC,
@@ -140,4 +142,28 @@ describe("bun:jsc", () => {
expect(Intl.DateTimeFormat().resolvedOptions().timeZone).toBe(origTimezone);
});
+
+ it("serialize", () => {
+ const serialized = serialize({ a: 1 });
+ expect(serialized).toBeInstanceOf(SharedArrayBuffer);
+ expect(deserialize(serialized)).toStrictEqual({ a: 1 });
+ const nested = serialize(serialized);
+ expect(deserialize(deserialize(nested))).toStrictEqual({ a: 1 });
+ });
+
+ it("serialize (binaryType: 'nodebuffer')", () => {
+ const serialized = serialize({ a: 1 }, { binaryType: "nodebuffer" });
+ expect(serialized).toBeInstanceOf(Buffer);
+ expect(serialized.buffer).toBeInstanceOf(SharedArrayBuffer);
+ expect(deserialize(serialized)).toStrictEqual({ a: 1 });
+ const nested = serialize(serialized);
+ expect(deserialize(deserialize(nested))).toStrictEqual({ a: 1 });
+ });
+
+ it("serialize GC test", () => {
+ for (let i = 0; i < 1000; i++) {
+ serialize({ a: 1 });
+ }
+ Bun.gc(true);
+ });
});
diff --git a/test/js/web/web-globals.test.js b/test/js/web/web-globals.test.js
index d687a1290..46422c210 100644
--- a/test/js/web/web-globals.test.js
+++ b/test/js/web/web-globals.test.js
@@ -19,8 +19,59 @@ test("exists", () => {
expect(typeof WebSocket !== "undefined").toBe(true);
expect(typeof Blob !== "undefined").toBe(true);
expect(typeof FormData !== "undefined").toBe(true);
+ expect(typeof Worker !== "undefined").toBe(true);
});
+const globalSetters = [
+ [ErrorEvent, "onerror", "error", "error"],
+ [MessageEvent, "onmessage", "message", "data"],
+];
+
+for (const [Constructor, name, eventName, prop] of globalSetters) {
+ test(`self.${name}`, () => {
+ var called = false;
+
+ const callback = ({ [prop]: data }) => {
+ expect(data).toBe("hello");
+ called = true;
+ };
+
+ try {
+ globalThis[name] = callback;
+ expect(globalThis[name]).toBe(callback);
+ dispatchEvent(new Constructor(eventName, { data: "hello", error: "hello" }));
+ expect(called).toBe(true);
+ } finally {
+ globalThis[name] = null;
+
+ called = false;
+ dispatchEvent(new Constructor(eventName, { data: "hello", error: "hello" }));
+ expect(called).toBe(false);
+ }
+ });
+
+ test(`self.addEventListener(${name})`, () => {
+ var called = false;
+
+ const callback = ({ [prop]: data }) => {
+ expect(data).toBe("hello");
+ called = true;
+ };
+
+ try {
+ addEventListener(eventName, callback);
+ dispatchEvent(new Constructor(eventName, { data: "hello", error: "hello" }));
+ expect(called).toBe(true);
+ } finally {
+ globalThis[name] = null;
+ removeEventListener(eventName, callback);
+ called = false;
+ dispatchEvent(new Constructor(eventName, { data: "hello", error: "hello" }));
+ expect(called).toBe(false);
+ }
+ });
+}
+
test("CloseEvent", () => {
var event = new CloseEvent("close", { reason: "world" });
expect(event.type).toBe("close");
diff --git a/test/js/web/worker-fixture.js b/test/js/web/worker-fixture.js
new file mode 100644
index 000000000..9e9dcad0a
--- /dev/null
+++ b/test/js/web/worker-fixture.js
@@ -0,0 +1,6 @@
+postMessage("initial message");
+onmessage = ({ data }) => {
+ postMessage({
+ received: data,
+ });
+};
diff --git a/test/js/web/worker.test.ts b/test/js/web/worker.test.ts
new file mode 100644
index 000000000..2120bd11a
--- /dev/null
+++ b/test/js/web/worker.test.ts
@@ -0,0 +1,18 @@
+import { expect, test } from "bun:test";
+
+test("worker", done => {
+ const worker = new Worker(new URL("worker-fixture.js", import.meta.url).href, {
+ bun: {
+ smol: true,
+ },
+ });
+ worker.postMessage("hello");
+ worker.onerror = e => {
+ done(e.error);
+ };
+ worker.onmessage = e => {
+ expect(e.data).toEqual("initial message");
+ worker.terminate();
+ done();
+ };
+});