diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 01081421fede6f..8036312a6a695a 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -20,6 +20,7 @@ "${workspaceFolder}/src/js/out", "${workspaceFolder}/src/deps/boringssl/include/", "${workspaceFolder}/src/deps", + "${workspaceFolder}/src/napi/*", "${workspaceFolder}/src/deps/uws/uSockets/src" ], "browse": { @@ -32,6 +33,8 @@ "${workspaceFolder}/src/bun.js/WebKit/WebKitBuild/Release/WTF/Headers/**", "${workspaceFolder}/src/bun.js/WebKit/WebKitBuild/Release/bmalloc/Headers/**", "${workspaceFolder}/src/bun.js/bindings/*", + "${workspaceFolder}/src/bun.js/bindings/*", + "${workspaceFolder}/src/napi/*", "${workspaceFolder}/src/bun.js/bindings/sqlite/", "${workspaceFolder}/src/bun.js/bindings/webcrypto/", "${workspaceFolder}/src/bun.js/bindings/webcore/", diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index d532e5444b6206..237f3d554bfecb 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -1244,18 +1244,30 @@ JSC_DEFINE_HOST_FUNCTION(NapiClass_ConstructorFunction, { JSC::VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - + JSObject* constructorTarget = asObject(callFrame->jsCallee()); JSObject* newTarget = asObject(callFrame->newTarget()); + NapiClass* napi = jsDynamicCast(constructorTarget); + while (!napi && constructorTarget) { + constructorTarget = constructorTarget->getPrototypeDirect().getObject(); + napi = jsDynamicCast(constructorTarget); + } - NapiClass* napi = jsDynamicCast(newTarget); if (UNLIKELY(!napi)) { JSC::throwVMError(globalObject, scope, JSC::createTypeError(globalObject, "NapiClass constructor called on an object that is not a NapiClass"_s)); return JSC::JSValue::encode(JSC::jsUndefined()); } - NapiPrototype* prototype = JSC::jsDynamicCast(napi->getDirect(vm, vm.propertyNames->prototype)); + NapiPrototype* prototype = JSC::jsDynamicCast(napi->getIfPropertyExists(globalObject, vm.propertyNames->prototype)); + RETURN_IF_EXCEPTION(scope, {}); + + if (!prototype) { + JSC::throwVMError(globalObject, scope, JSC::createTypeError(globalObject, "NapiClass constructor is missing the prototype"_s)); + return JSC::JSValue::encode(JSC::jsUndefined()); + } + auto* subclass = prototype->subclass(globalObject, newTarget); RETURN_IF_EXCEPTION(scope, {}); + callFrame->setThisValue(subclass); size_t count = callFrame->argumentCount(); MarkedArgumentBuffer args; @@ -1266,7 +1278,6 @@ JSC_DEFINE_HOST_FUNCTION(NapiClass_ConstructorFunction, } } - callFrame->setThisValue(prototype->subclass(newTarget)); napi->constructor()(globalObject, callFrame); RETURN_IF_EXCEPTION(scope, {}); diff --git a/src/bun.js/bindings/napi.h b/src/bun.js/bindings/napi.h index 5508039631e28a..b157c43673276f 100644 --- a/src/bun.js/bindings/napi.h +++ b/src/bun.js/bindings/napi.h @@ -213,15 +213,19 @@ class NapiPrototype : public JSC::JSDestructibleObject { return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); } - NapiPrototype* subclass(JSC::JSObject* newTarget) + NapiPrototype* subclass(JSC::JSGlobalObject* globalObject, JSC::JSObject* newTarget) { auto& vm = this->vm(); - auto* structure = InternalFunction::createSubclassStructure(globalObject(), - newTarget, - this->structure()); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* targetFunction = jsCast(newTarget); + FunctionRareData* rareData = targetFunction->ensureRareData(vm); + auto* prototype = newTarget->get(globalObject, vm.propertyNames->prototype).getObject(); + RETURN_IF_EXCEPTION(scope, nullptr); + auto* structure = rareData->createInternalFunctionAllocationStructureFromBase(vm, globalObject, prototype, this->structure()); + RETURN_IF_EXCEPTION(scope, nullptr); NapiPrototype* footprint = new (NotNull, allocateCell(vm)) NapiPrototype(vm, structure); footprint->finishCreation(vm); - return footprint; + RELEASE_AND_RETURN(scope, footprint); } NapiRef* napiRef = nullptr; diff --git a/test/bun.lockb b/test/bun.lockb index 01383ce5d3ea98..98c35844db66e6 100755 Binary files a/test/bun.lockb and b/test/bun.lockb differ diff --git a/test/js/third_party/resvg/bbox.test.js b/test/js/third_party/resvg/bbox.test.js new file mode 100644 index 00000000000000..d80e99cdd25901 --- /dev/null +++ b/test/js/third_party/resvg/bbox.test.js @@ -0,0 +1,53 @@ +import { test, expect } from "bun:test"; +import { Resvg } from "@resvg/resvg-js"; + +const svg = ` + + + + +`; +for (let Class of [ + Resvg, + // Test that subclasses work as well. + class ResvgSubclass extends Resvg { + constructor(...args) { + super(...args); + } + iShouldExist() { + return true; + } + }, +]) { + test(`bbox ${Class.name}`, () => { + const opts = { + fitTo: { + mode: "width", + value: 500, + }, + font: { + loadSystemFonts: false, + }, + }; + + const resvg = new Class(svg, opts); + const bbox = resvg.getBBox(); + + expect(resvg.width).toBe(180); + expect(resvg.height).toBe(260); + + if (bbox) resvg.cropByBBox(bbox); + const pngData = resvg.render(); + + expect(bbox.width).toBe(112.20712208389321); + expect(bbox.height).toBe(81); + + expect(pngData.width).toBe(500); + expect(pngData.height).toBe(362); + + if (Class !== Resvg) { + expect(resvg).toHaveProperty("iShouldExist"); + expect(resvg.iShouldExist()).toBeTrue(); + } + }); +} diff --git a/test/js/third_party/resvg/package.json b/test/js/third_party/resvg/package.json new file mode 100644 index 00000000000000..932f362211cfce --- /dev/null +++ b/test/js/third_party/resvg/package.json @@ -0,0 +1,6 @@ +{ + "name": "bun-integration-test-resvg", + "dependencies": { + "@resvg/resvg-js": "2.4.1" + } +} diff --git a/test/package.json b/test/package.json index 7d8156aa077625..f841d200365a18 100644 --- a/test/package.json +++ b/test/package.json @@ -25,6 +25,7 @@ "pg-connection-string": "2.6.1", "postgres": "3.3.5", "prisma": "5.1.1", + "@resvg/resvg-js": "2.4.1", "socket.io": "4.7.1", "socket.io-client": "4.7.1", "supertest": "6.3.3",