Compare commits

...

6 Commits

Author SHA1 Message Date
Claude Bot
1493dc2aca fix: make worker self.name an enumerable/configurable accessor
Replace the non-writable, non-configurable data property with an accessor
that has getter/setter per the DedicatedWorkerGlobalScope spec. The property
is now enumerable and configurable with [Replaceable] semantics.

- Getter returns the worker's name (or empty string if not set)
- Setter implements [Replaceable] by replacing accessor with data property
- Property attributes: Accessor | 0 (enumerable and configurable)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 22:46:56 +00:00
Alistair Smith
d5e5736388 Merge branch 'main' into claude/worker-self-name 2025-09-30 15:22:08 -07:00
Claude Bot
0acc90a79b fix: always set self.name property, default to empty string per spec 2025-09-30 02:21:33 +00:00
autofix-ci[bot]
040349c008 [autofix.ci] apply automated fixes 2025-09-30 02:16:51 +00:00
Claude Bot
14b1e2a003 fix: set self.name property in worker global scope 2025-09-30 00:24:43 +00:00
Claude Bot
a8d9805641 test: add repro for missing self.name in workers 2025-09-30 00:17:22 +00:00
4 changed files with 87 additions and 0 deletions

View File

@@ -1059,6 +1059,38 @@ extern "C" JSC::JSGlobalObject* Zig__GlobalObject__create(void* console_client,
globalObject->m_processEnvObject.set(vm, globalObject, env);
}
if (!options.name.isEmpty()) {
auto nameString = jsString(vm, options.name);
globalObject->putDirect(vm, JSC::Identifier::fromString(vm, "__workerName"_s), nameString, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum));
}
auto nameGetter = JSNativeStdFunction::create(vm, globalObject, 0, "get name"_s,
[](JSGlobalObject* globalObject, CallFrame*) -> JSC::EncodedJSValue {
auto& vm = JSC::getVM(globalObject);
JSValue nameValue = globalObject->getDirect(vm, JSC::Identifier::fromString(vm, "__workerName"_s));
return JSValue::encode(nameValue ? nameValue : jsEmptyString(vm));
});
auto nameSetter = JSNativeStdFunction::create(vm, globalObject, 1, "set name"_s,
[](JSGlobalObject* globalObject, CallFrame* callFrame) -> JSC::EncodedJSValue {
auto& vm = JSC::getVM(globalObject);
JSValue thisValue = callFrame->thisValue();
JSValue newValue = callFrame->argument(0);
if (thisValue.isObject()) {
auto* thisObject = asObject(thisValue);
thisObject->putDirect(vm, JSC::Identifier::fromString(vm, "name"_s), newValue, 0);
}
return JSValue::encode(jsUndefined());
});
globalObject->putDirectAccessor(
globalObject,
JSC::Identifier::fromString(vm, "name"_s),
GetterSetter::create(vm, globalObject, nameGetter, nameSetter),
JSC::PropertyAttribute::Accessor | 0);
// Ensure that the TerminationException singleton is constructed. Workers need this so
// that we can request their termination from another thread. For the main thread, we
// can delay this until we are actually requesting termination (until and unless we ever

View File

@@ -0,0 +1,6 @@
self.postMessage({
name: self.name,
hasName: "name" in self,
preloadHasName: globalThis.preloadHasName,
preloadName: globalThis.preloadName,
});

View File

@@ -0,0 +1,2 @@
globalThis.preloadHasName = "name" in self;
globalThis.preloadName = self.name;

View File

@@ -296,6 +296,53 @@ describe("web worker", () => {
expect(err.error).toBe(null);
});
});
test("self.name is available in worker", async () => {
const worker = new Worker(new URL("worker-name-fixture.js", import.meta.url).href, {
name: "test-worker",
});
const result = await waitForWorkerResult(worker, null);
expect(result).toEqual({
name: "test-worker",
hasName: true,
});
});
test("self.name is available in worker with preload", async () => {
const worker = new Worker(new URL("worker-name-fixture.js", import.meta.url).href, {
name: "test-worker-with-preload",
preload: new URL("worker-name-preload-fixture.js", import.meta.url).href,
});
const result = await waitForWorkerResult(worker, null);
expect(result.name).toBe("test-worker-with-preload");
expect(result.hasName).toBe(true);
expect(result.preloadHasName).toBe(true);
expect(result.preloadName).toBe("test-worker-with-preload");
});
test("self.name with blob URL", async () => {
const workerSource = `
self.postMessage({
name: self.name,
hasName: "name" in self,
});
`;
const worker = new Worker(URL.createObjectURL(new Blob([workerSource], { type: "application/javascript" })), {
name: "blob-worker",
});
const result = await waitForWorkerResult(worker, null);
expect(result).toEqual({
name: "blob-worker",
hasName: true,
});
});
test("self.name defaults to empty string when not provided", async () => {
const worker = new Worker(new URL("worker-name-fixture.js", import.meta.url).href);
const result = await waitForWorkerResult(worker, null);
expect(result.name).toBe("");
expect(result.hasName).toBe(true);
});
});
// TODO: move to node:worker_threads tests directory