Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/js/node/perf_hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ var {
PerformanceEntry,
PerformanceMark,
PerformanceMeasure,
PerformanceObserver: NodePerformanceObserver,
PerformanceObserverEntryList,
} = globalThis;

// globalThis.PerformanceObserver now resolves to this module's subclass, so the
// native class is fetched directly to avoid re-entering this module on load.
var NodePerformanceObserver = $cpp("JSPerformanceObserver.cpp", "getPerformanceObserverConstructor");

Comment thread
coderabbitai[bot] marked this conversation as resolved.
var constants = {
NODE_PERFORMANCE_ENTRY_TYPE_DNS: 4,
NODE_PERFORMANCE_ENTRY_TYPE_GC: 0,
Expand Down
19 changes: 18 additions & 1 deletion src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1190,7 +1190,22 @@ WEBCORE_GENERATED_CONSTRUCTOR_GETTER(Performance);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(PerformanceEntry);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(PerformanceMark);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(PerformanceMeasure);
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(PerformanceObserver);
// globalThis.PerformanceObserver === require("node:perf_hooks").PerformanceObserver.
// Not a static-table PropertyCallback: abstractResolve() reifies those under a
// VMInquiry slot that forbids VM re-entry, and loading perf_hooks runs JS.
JSC_DEFINE_CUSTOM_GETTER(getPerformanceObserverGlobal, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName property))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
JSValue perfHooks = globalObject->internalModuleRegistry()->requireId(globalObject, vm, Bun::InternalModuleRegistry::Field::NodePerfHooks);
RETURN_IF_EXCEPTION(scope, {});
JSValue result = perfHooks.getObject()->get(globalObject, Identifier::fromString(vm, "PerformanceObserver"_s));
RETURN_IF_EXCEPTION(scope, {});
if (auto* thisObject = JSValue::decode(thisValue).getObject())
thisObject->putDirect(vm, property, result, 0);
return JSValue::encode(result);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(PerformanceObserverEntryList)
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(PerformanceResourceTiming)
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(PerformanceServerTiming)
Expand Down Expand Up @@ -2986,6 +3001,8 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
JSFunction::create(vm, this, 0, "set"_s, functionSetSelf, ImplementationVisibility::Public)),
PropertyAttribute::Accessor | 0);

putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "PerformanceObserver"_s), JSC::CustomGetterSetter::create(vm, getPerformanceObserverGlobal, nullptr), PropertyAttribute::CustomValue | 0);

Comment thread
robobun marked this conversation as resolved.
// TODO: this should be usable on the lookup table. it crashed las time i tried it
putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "onmessage"_s), JSC::CustomGetterSetter::create(vm, globalOnMessage, setGlobalOnMessage), 0);
putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "onerror"_s), JSC::CustomGetterSetter::create(vm, globalOnError, setGlobalOnError), 0);
Expand Down
1 change: 0 additions & 1 deletion src/jsc/bindings/ZigGlobalObject.lut.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
PerformanceEntry PerformanceEntryConstructorCallback PropertyCallback
PerformanceMark PerformanceMarkConstructorCallback PropertyCallback
PerformanceMeasure PerformanceMeasureConstructorCallback PropertyCallback
PerformanceObserver PerformanceObserverConstructorCallback PropertyCallback
PerformanceObserverEntryList PerformanceObserverEntryListConstructorCallback PropertyCallback
PerformanceResourceTiming PerformanceResourceTimingConstructorCallback PropertyCallback
PerformanceServerTiming PerformanceServerTimingConstructorCallback PropertyCallback
Expand Down
5 changes: 5 additions & 0 deletions src/jsc/bindings/webcore/JSPerformanceObserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -423,4 +423,9 @@ PerformanceObserver* JSPerformanceObserver::toWrapped(JSC::VM&, JSC::JSValue val
return nullptr;
}

JSC::JSValue getPerformanceObserverConstructor(Zig::GlobalObject* globalObject)
{
return WebCore::JSPerformanceObserver::getConstructor(globalObject->vm(), globalObject);
}

}
2 changes: 2 additions & 0 deletions src/jsc/bindings/webcore/JSPerformanceObserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,6 @@ template<> struct JSDOMWrapperConverterTraits<PerformanceObserver> {
};
template<> PerformanceObserver::Init convertDictionary<PerformanceObserver::Init>(JSC::JSGlobalObject&, JSC::JSValue);

JSC::JSValue getPerformanceObserverConstructor(Zig::GlobalObject* globalObject);

} // namespace WebCore
78 changes: 78 additions & 0 deletions test/js/node/perf_hooks/perf_hooks.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
import perf from "perf_hooks";

test("stubs", () => {
Expand All @@ -21,3 +22,80 @@ test("doesn't throw", () => {
expect(() => performance.timeOrigin).not.toThrow();
expect(() => performance.markResourceTiming()).not.toThrow();
});

test("globalThis.PerformanceObserver is the node:perf_hooks PerformanceObserver", () => {
expect(globalThis.PerformanceObserver).toBe(perf.PerformanceObserver);
const types = globalThis.PerformanceObserver.supportedEntryTypes;
for (const nodeType of ["http", "net", "dns"]) {
expect(types).toContain(nodeType);
}
});

// Instrumentation written against globalThis.PerformanceObserver must receive the
// same node-only entries (http/net/dns) that the node:perf_hooks observer gets.
test("globalThis.PerformanceObserver delivers node-only entry types", async () => {
// The global is resolved as a bare identifier (not via globalThis.) so that
// scope resolution exercises the lazy custom accessor. node:perf_hooks is not
// loaded until after the observer is constructed.
const src = `
const http = require("node:http");

const srv = http.createServer((q, r) => r.end("hello"));
srv.listen(0, "127.0.0.1", () => {
const viaGlobal = [];
const viaModule = [];
const og = new PerformanceObserver(list => {
for (const e of list.getEntries()) viaGlobal.push(e.entryType + ":" + e.name);
});
og.observe({ entryTypes: ["http", "net"] });
const ModulePO = require("node:perf_hooks").PerformanceObserver;
const om = new ModulePO(list => {
for (const e of list.getEntries()) viaModule.push(e.entryType + ":" + e.name);
});
om.observe({ entryTypes: ["http", "net"] });

const req = http.request(
{ host: "127.0.0.1", port: srv.address().port, path: "/x", agent: false },
res => {
res.resume();
res.on("end", () => {
// Observers dispatch on a fresh tick; let the buffered entries flush.
setImmediate(() => {
setImmediate(() => {
og.disconnect();
om.disconnect();
srv.close();
process.stdout.write(
JSON.stringify({
sameClass: PerformanceObserver === ModulePO,
viaGlobal: viaGlobal.sort(),
viaModule: viaModule.sort(),
}),
);
});
});
});
},
);
req.on("error", err => {
process.stderr.write(String(err));
process.exit(1);
});
req.end();
});
`;
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", src],
env: bunEnv,
stdio: ["ignore", "pipe", "pipe"],
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
const result = JSON.parse(stdout);
expect(result).toEqual({
sameClass: true,
viaGlobal: ["http:HttpClient", "http:HttpRequest", "net:connect"],
viaModule: ["http:HttpClient", "http:HttpRequest", "net:connect"],
});
expect(exitCode).toBe(0);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading