Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
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