Skip to content
Merged
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ FetchContent_Declare(llhttp
URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz"
EXCLUDE_FROM_ALL)
FetchContent_Declare(UrlLib
GIT_REPOSITORY https://github.com/BabylonJS/UrlLib.git
GIT_TAG e86ffb34e77092266145497681efc74e0a920ffe
GIT_REPOSITORY https://github.com/bkaradzic-microsoft/UrlLib.git
GIT_TAG 6437417e7c2886588d5656d1c1d2db28358cdb2e
EXCLUDE_FROM_ALL)
FetchContent_Declare(quickjs-ng
GIT_REPOSITORY https://github.com/quickjs-ng/quickjs.git
Expand Down
7 changes: 6 additions & 1 deletion Core/Node-API/Source/js_native_api_javascriptcore.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1547,7 +1547,12 @@ napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result)
case kJSTypeSymbol: *result = napi_symbol; break;
default:
JSObjectRef object{ToJSObject(env, value)};
if (JSObjectIsFunction(env->context, object)) {
// Consult JSObjectIsConstructor in addition to JSObjectIsFunction: some JSC builds (e.g.
// libjavascriptcoregtk) report constructors created via JSObjectMakeConstructor -- such as
// node-addon-api DefineClass constructors -- as not-a-function, which would otherwise be
// classified as napi_object and make Napi IsFunction() reject otherwise-valid constructors
// (see issue #194).
if (JSObjectIsFunction(env->context, object) || JSObjectIsConstructor(env->context, object)) {
*result = napi_function;
} else {
NativeInfo* info = NativeInfo::Get<NativeInfo>(object);
Expand Down
8 changes: 8 additions & 0 deletions Polyfills/Blob/Include/Babylon/Polyfills/Blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
#include <napi/env.h>
#include <Babylon/Api.h>

#include <cstddef>
#include <string>

namespace Babylon::Polyfills::Blob
{
void BABYLON_API Initialize(Napi::Env env);

// Synchronously reads the bytes and MIME type of a Blob JS object created by this
// polyfill. Returns false if `object` is not a Blob. The returned pointer remains
// valid only while `object` is alive, so it must be consumed synchronously.
bool BABYLON_API TryGetData(const Napi::Object& object, const std::byte*& outData, size_t& outSize, std::string& outType);
}
65 changes: 65 additions & 0 deletions Polyfills/Blob/Source/Blob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,69 @@ namespace Babylon::Polyfills::Blob
{
Internal::Blob::Initialize(env);
}

bool BABYLON_API TryGetData(const Napi::Object& object, const std::byte*& outData, size_t& outSize, std::string& outType)
{
const auto env = object.Env();
const auto global = env.Global();

// Verify `object` is a Blob (or a subclass such as File) by walking its prototype chain and
// comparing each link against Blob.prototype, using the JS-level Object.getPrototypeOf.
//
// We use Object.getPrototypeOf rather than the raw napi_get_prototype C API because the
// latter is not exposed by every adapter (e.g. JSI) and, on JSC, returns the raw
// [[Prototype]] which differs from the JS-visible prototype of a DefineClass instance.
// This keeps the check portable across QuickJS, V8, JavaScriptCore, Chakra and JSI, and it
// also accepts Blob subclasses (e.g. File). We deliberately avoid napi_instanceof, whose
// node-addon-api wrapper requires a Napi::Function.
const auto blobConstructor = global.Get("Blob");
if (!blobConstructor.IsFunction())
{
return false;
}

const auto blobPrototype = blobConstructor.As<Napi::Object>().Get("prototype");
if (!blobPrototype.IsObject())
{
return false;
}

const auto objectConstructor = global.Get("Object");
if (!objectConstructor.IsObject())
{
return false;
}

const auto getPrototypeOf = objectConstructor.As<Napi::Object>().Get("getPrototypeOf");
if (!getPrototypeOf.IsFunction())
{
return false;
}

const auto getPrototypeOfFn = getPrototypeOf.As<Napi::Function>();

bool isBlob = false;
Napi::Value current = getPrototypeOfFn.Call({object});
while (current.IsObject())
{
if (current.StrictEquals(blobPrototype))
{
isBlob = true;
break;
}
current = getPrototypeOfFn.Call({current});
}

if (!isBlob)
{
return false;
}

const auto* blob = Internal::Blob::Unwrap(object);
const auto& data = blob->Data();
outData = data.data();
outSize = data.size();
outType = blob->Type();
return true;
}
}
4 changes: 4 additions & 0 deletions Polyfills/Blob/Source/Blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ namespace Babylon::Polyfills::Internal

explicit Blob(const Napi::CallbackInfo& info);

// Synchronous accessors for internal cross-polyfill use (e.g. URL.createObjectURL).
const std::vector<std::byte>& Data() const { return m_data; }
const std::string& Type() const { return m_type; }

private:
Napi::Value GetSize(const Napi::CallbackInfo& info);
Napi::Value GetType(const Napi::CallbackInfo& info);
Expand Down
15 changes: 4 additions & 11 deletions Polyfills/Fetch/Source/Fetch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,9 @@ namespace Babylon::Polyfills::Internal
// when an error is thrown) -- in that case the rejection simply carries no synthetic frames.
std::string CaptureCallSiteStack(Napi::Env env)
{
// Detect the global Error constructor with IsUndefined()/IsNull() rather than
// IsFunction(): some JavaScriptCore/JSI builds classify constructor functions as
// typeof 'object', so napi_typeof reports napi_object and IsFunction() would
// incorrectly skip stack capture even though Error is callable (see the Blob check
// below for the same rationale). Error is always present, so this guard is defensive.
// Error is always present and callable; this guard is defensive.
const Napi::Value errorCtor = env.Global().Get("Error");
if (errorCtor.IsUndefined() || errorCtor.IsNull())
if (!errorCtor.IsFunction())
{
return {};
}
Expand Down Expand Up @@ -305,12 +301,9 @@ namespace Babylon::Polyfills::Internal
Napi::Env env = info.Env();
const auto deferred = Napi::Promise::Deferred::New(env);

// Use IsUndefined()/IsNull() rather than IsFunction() to detect the Blob
// polyfill: some JavaScriptCore/JSI builds classify constructor functions as
// typeof 'object', so napi_typeof reports napi_object and IsFunction() would
// incorrectly reject even when the Blob polyfill is installed.
// Require the Blob polyfill to be installed.
const auto blobConstructor = env.Global().Get("Blob");
if (blobConstructor.IsUndefined() || blobConstructor.IsNull())
if (!blobConstructor.IsFunction())
{
deferred.Reject(Napi::Error::New(env, "fetch: Blob is not available in this environment").Value());
return deferred.Promise();
Expand Down
8 changes: 2 additions & 6 deletions Polyfills/File/Source/File.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,9 @@ namespace Babylon::Polyfills::Internal

// Require the native Blob polyfill: File delegates byte storage to
// a Blob, so without it the constructor cannot produce useful
// instances. Use IsUndefined() rather than IsFunction() because
// some JavaScriptCore builds (notably libjavascriptcoregtk on
// Linux) classify constructors created via JSObjectMakeConstructor
// as typeof 'object', not 'function', so napi_typeof returns
// napi_object for them.
// instances.
auto blob = global.Get(JS_BLOB_CONSTRUCTOR_NAME);
if (blob.IsUndefined() || blob.IsNull())
if (!blob.IsFunction())
{
throw Napi::Error::New(env,
"File polyfill requires the Blob polyfill to be installed first.");
Expand Down
2 changes: 2 additions & 0 deletions Polyfills/URL/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ add_library(URL ${SOURCES})
target_include_directories(URL PUBLIC "Include")

target_link_libraries(URL
PRIVATE Blob
PRIVATE UrlLib
PUBLIC JsRuntime)

set_property(TARGET URL PROPERTY FOLDER Polyfills)
Expand Down
25 changes: 25 additions & 0 deletions Polyfills/URL/Include/Babylon/Polyfills/URL.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,32 @@
#include <napi/env.h>
#include <Babylon/Api.h>

#include <cstddef>
#include <memory>
#include <string>
#include <vector>

namespace Babylon::Polyfills::URL
{
void BABYLON_API Initialize(Napi::Env env);

// Blob URL store backing URL.createObjectURL / URL.revokeObjectURL. Native has no browser
// blob: URL store, so the URL polyfill keeps an in-memory registry keyed by a minted
// blob: URL. The store is process-global (shared across all JS environments in the process);
// the minted URLs embed a UUID so entries never collide between environments. Consumers do not
// resolve these URLs directly: the URL polyfill registers a process-global blob: resolver with
// UrlLib, so fetch, XMLHttpRequest, and any other UrlLib consumer serve blob: URLs uniformly
// through the transport layer.
//
// The store holds the Blob's bytes in a shared_ptr, so resolving a blob: URL hands out a
// reference to the immutable buffer rather than copying it. Bytes are released once the entry
// is revoked and every outstanding resolver has dropped its shared_ptr, matching how a browser
// Blob's bytes stay valid for an in-flight read even if the URL is revoked mid-flight.

// Copies `size` bytes into the process-global store and returns a freshly minted blob: URL.
// The `env` parameter is currently unused but kept for API symmetry and future per-env scoping.
std::string BABYLON_API RegisterObjectURL(Napi::Env env, const std::byte* data, size_t size, std::string type);

// Releases the entry for `url`, if any. Unknown URLs are ignored (matching the web platform).
void BABYLON_API RevokeObjectURL(Napi::Env env, const std::string& url);
}
6 changes: 2 additions & 4 deletions Polyfills/URL/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ Partial implementation for [`URL`](https://developer.mozilla.org/en-US/docs/Web/
- [`toJSON`](https://developer.mozilla.org/en-US/docs/Web/API/URL/toJSON)
- [`parse`](https://developer.mozilla.org/en-US/docs/Web/API/URL/parse_static)
- [`canParse`](https://developer.mozilla.org/en-US/docs/Web/API/URL/canParse_static)

## Not implemented
- [`createObjectURL`](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static)
- [`revokeObjectURL`](https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL_static)
- [`createObjectURL`](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static) (Blob only; mints a `blob:` URL backed by an in-memory, process-global object-URL store)
- [`revokeObjectURL`](https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL_static) (releases the stored bytes for a `blob:` URL previously returned by `createObjectURL`; subsequent fetch/XMLHttpRequest against that URL fail as a network error)

# URLSearchParams
Partial implementatioin for [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
Expand Down
Loading
Loading