Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
79e03c1
Update Android build to NDK 28 and system V8
matthargett Sep 28, 2025
43d677a
Improve Android V8 header discovery
matthargett Sep 28, 2025
22d1f96
Improve Android V8 header hint coverage
matthargett Sep 28, 2025
435c798
Improve Android V8 discovery for SDK 35
matthargett Sep 28, 2025
e1d0f2f
Log Android V8 search hints and broaden header discovery
matthargett Sep 28, 2025
dbef5ba
Force Android builds to use NDK r28c and API 35
matthargett Sep 30, 2025
1159300
Relax Android NDK version guard
matthargett Sep 30, 2025
39f1536
Pin Gradle NDK wiring to r28c
matthargett Oct 1, 2025
82b17e8
Improve Android NDK detection for Android tests
matthargett Oct 1, 2025
2dd6185
Simplify Android NDK environment configuration
matthargett Oct 1, 2025
98c40b5
Fix(CI): Update Android CI to use NDK 28 and API 35
google-labs-jules[bot] Oct 1, 2025
02a7dc5
Merge pull request #2 from rebeckerspecialties/feat/android-ci-update
matthargett Oct 1, 2025
99c49b1
Fix(CI): Update Android CI to use NDK 28 and API 35
google-labs-jules[bot] Oct 1, 2025
a40c852
Revise tool requirements and Android version support
matthargett Oct 2, 2025
f98247a
New Java doesn't implicitly include bind support, we have to specify …
matthargett Oct 2, 2025
7faff92
work around latent unsafe file descriptor issue that causes intermitt…
matthargett Oct 2, 2025
b3480ca
fix the C++20 libc++ deprecation issue more elegantly
matthargett Oct 2, 2025
118450b
pull in the already-existing upstream fix for compatiblity with newer…
matthargett Oct 2, 2025
4d9eb96
revert to using third aprty prebuilt v8 so we can test things more in…
matthargett Oct 2, 2025
e5e5648
fix another fdsan-found issue that causes intermittent crashes on exit
matthargett Oct 3, 2025
d205c4a
Add tests for updated JS capabilities from newer JSC. This has an apk…
matthargett Oct 3, 2025
4633fa3
JSC's results deviate from the standard. for now, let's capture both …
matthargett Oct 3, 2025
465685e
Use latest Java LTS so that modern commandline options are available,…
matthargett Oct 3, 2025
fcc3ce1
skip tests Chakra doesn't support only on win32. Chakra support is ho…
matthargett Oct 3, 2025
c1fdff4
See if reducing resource usage of the simulator helps solve the timin…
matthargett Oct 3, 2025
a54a121
Give more specific failure information when tests fail in the device …
matthargett Oct 3, 2025
be1baf6
some of these tests won't work on jsc-android
matthargett Oct 3, 2025
a6a258d
it was worth a try to test this functionality, so that Babylon Native…
matthargett Oct 8, 2025
b9aa23c
properly polyfill globalThis. add inline sourcemaps to tests so that …
matthargett Oct 9, 2025
27eed75
try a more resilient way of getting globalThis on v8 that doesn't cau…
matthargett Oct 10, 2025
9a268bb
retry websocket tests in case public echo server (or routes) are down…
matthargett Oct 10, 2025
56be206
The reason we called the log directly is because fdsan (file descript…
matthargett Oct 10, 2025
f378d3f
A race can occur in CI where the boot is completed, but package insta…
matthargett Oct 10, 2025
ed53fd9
I keep hitting totally unrelated CI flakiness. This time, it's that t…
matthargett Oct 10, 2025
863f962
Update README.md
matthargett Oct 16, 2025
2e467dd
Update README.md
matthargett Oct 16, 2025
84005c0
PR feedback
matthargett Oct 16, 2025
9e3cfc9
now that Azure pipeline uses JDK 18, we don't need this original work…
matthargett Oct 16, 2025
98e80ef
fix wrong abi being forced. pull in AndroidExtensiosn bug fix branch.
matthargett Oct 19, 2025
499ae36
I simply cannot get address sanitizer to run well under Android simul…
matthargett Oct 20, 2025
51f1f45
fix macOS build of unit tests, enable sanitizers. they are finding bu…
matthargett Oct 20, 2025
496c67f
don't put build/bundle artifacts in the source dir, put them in build…
matthargett Oct 20, 2025
403b452
make globalThis test using ifdefs in consistent way with other skippe…
matthargett Oct 20, 2025
5253492
the existing tests crash under address sanitizer when skipping a test…
matthargett Oct 20, 2025
ebf6950
fix test failure that was an issue in the test rather than the implem…
matthargett Oct 20, 2025
cfb1af6
this is a change that should be contributed to upstream node-api -- b…
matthargett Oct 20, 2025
d30f735
fix ubsan identified issue with invalid nan conversion. great bug to …
matthargett Oct 20, 2025
d23737f
Merge branch 'main' of github.com:BabylonJS/JsRuntimeHost into update…
matthargett Nov 2, 2025
ada4a17
merge
matthargett Nov 9, 2025
56c1f3e
minor cleanup
matthargett Nov 10, 2025
04bcc75
use latest upstream hash to fix compile error .don't put things in th…
matthargett Nov 11, 2025
78176d0
reconcile with merged extractions
matthargett Jan 22, 2026
99539a6
Merge branch 'main' into update-jsruntimehost-for-android-xr-compatib…
matthargett Jan 22, 2026
a6eb7f5
Revert redundant files to match origin/main
matthargett Jan 22, 2026
3c25b52
a default C++ VS code add-in is now creating huge LLVM index files in…
matthargett Jan 26, 2026
46b9210
use latest AndroidExtensions hash
matthargett Jan 26, 2026
a6a6ec0
avoid non-standrd template specialization of basic_string that is dis…
matthargett Jan 26, 2026
f704440
don't change CI task name. include a little more context around errors
matthargett Jan 26, 2026
8fa7d57
merge from main
matthargett Jan 27, 2026
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
69 changes: 57 additions & 12 deletions .github/jobs/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,58 @@ jobs:
vmImage: macos-14

steps:
- task: JavaToolInstaller@0
displayName: 'Set up JDK 17'
inputs:
versionSpec: '17'
jdkArchitectureOption: 'x64'
jdkSourceOption: 'PreInstalled'

- script: |
echo Install Android image
export JAVA_HOME=$JAVA_HOME_8_X64
echo 'y' | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;android-27;default;x86_64'
echo 'y' | $ANDROID_HOME/tools/bin/sdkmanager --licenses
echo Create AVD
$ANDROID_HOME/tools/bin/avdmanager create avd -n Pixel_API_27 -d pixel -k 'system-images;android-27;default;x86_64'
echo "Install NDK and Android SDK"
# Use Java 17 which is compatible with modern Android tooling
export JAVA_HOME=$JAVA_HOME_17_X64
export PATH=$JAVA_HOME/bin:$PATH

echo "Java version:"
java -version

# Use cmdline-tools instead of deprecated tools/bin path
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "ndk;$(ndkVersion)"
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-35"
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "build-tools;35.0.0"
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "system-images;android-35;google_apis;x86_64"
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses
echo "Create AVD with Pixel 2 profile and optimized memory"
echo "no" | $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n Pixel_2_API_35 -d pixel_2 -k "system-images;android-35;google_apis;x86_64"
# Optimize AVD for CI - reduce memory and disk size
echo "hw.ramSize=1024" >> ~/.android/avd/Pixel_2_API_35.avd/config.ini
echo "disk.dataPartition.size=2G" >> ~/.android/avd/Pixel_2_API_35.avd/config.ini
echo "hw.gpu.enabled=yes" >> ~/.android/avd/Pixel_2_API_35.avd/config.ini
echo "hw.gpu.mode=swiftshader_indirect" >> ~/.android/avd/Pixel_2_API_35.avd/config.ini
displayName: 'Install Android Emulator'

- script: |
echo Start emulator
nohup $ANDROID_HOME/emulator/emulator -avd Pixel_API_27 -gpu host -no-window -no-audio -no-boot-anim 2>&1 &
export JAVA_HOME=$JAVA_HOME_17_X64
export PATH=$JAVA_HOME/bin:$PATH

echo Start emulator with optimized settings
nohup $ANDROID_HOME/emulator/emulator -avd Pixel_2_API_35 -gpu swiftshader_indirect -no-window -no-audio -no-boot-anim -no-snapshot-save -memory 1024 -partition-size 2048 2>&1 &
echo Wait for emulator
$ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do echo '.'; sleep 1; done'
$ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do echo "Waiting for boot..."; sleep 2; done'

# Additional wait for package manager to be ready
echo "Waiting for package manager..."
$ANDROID_HOME/platform-tools/adb shell 'while [[ -z $(pm list packages 2>/dev/null) ]]; do sleep 2; done'

# Disable animations for test stability
$ANDROID_HOME/platform-tools/adb shell settings put global window_animation_scale 0
$ANDROID_HOME/platform-tools/adb shell settings put global transition_animation_scale 0
$ANDROID_HOME/platform-tools/adb shell settings put global animator_duration_scale 0

# Increase ADB timeout
export ADB_INSTALL_TIMEOUT=120

$ANDROID_HOME/platform-tools/adb devices
displayName: 'Start Android Emulator'

Expand All @@ -37,13 +75,20 @@ jobs:
workingDirectory: 'Tests/UnitTests/Android'
options: '-PabiFilters=x86_64 -PjsEngine=${{parameters.jsEngine}} -PndkVersion=$(ndkVersion)'
tasks: 'connectedAndroidTest'
jdkVersionOption: 1.17
jdkVersionOption: '1.17'
displayName: 'Run Connected Android Test'
retryCountOnTaskFailure: 2

- script: |
# Dump test failure details when tests fail
if [ -f ./app/build/outputs/androidTest-results/connected/TEST-*.xml ]; then
echo "=== Test Results Summary ==="
grep -h "testcase\|failure" ./app/build/outputs/androidTest-results/connected/TEST-*.xml || true
fi
# Dump logcat output which contains our detailed error messages
find ./app/build/outputs/androidTest-results -name "*.txt" -print0 | while IFS= read -r -d '' file; do
echo "cat \"$file\""
cat "$file"
echo "=== Logcat Output from: $file ==="
cat "$file" | grep -5 -E "(FAILED|TEST FAILURES|Error:|Stack:|JsRuntimeHost)" || cat "$file"
done
workingDirectory: 'Tests/UnitTests/Android'
condition: succeededOrFailed()
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/Build
.cache/
23 changes: 22 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ include(FetchContent)
# --------------------------------------------------
FetchContent_Declare(AndroidExtensions
GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git
GIT_TAG f7ed149b5360cc8a4908fece66607c5ce1e6095b
GIT_TAG 2d5af72259cc73e5f249d3c99bee2010be9cb042
EXCLUDE_FROM_ALL)
FetchContent_Declare(arcana.cpp
GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git
Expand Down Expand Up @@ -57,6 +57,12 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fobjc-arc")
if(NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "Debug")

endif()
endif()

# --------------------------------------------------
# Options
Expand Down Expand Up @@ -115,6 +121,21 @@ set_property(TARGET arcana PROPERTY FOLDER Dependencies)
if(JSRUNTIMEHOST_POLYFILL_XMLHTTPREQUEST)
FetchContent_MakeAvailable_With_Message(UrlLib)
set_property(TARGET UrlLib PROPERTY FOLDER Dependencies)
if(APPLE)
FetchContent_GetProperties(UrlLib)
if(UrlLib_POPULATED)
target_compile_options(UrlLib PRIVATE -fobjc-arc)
set(_urllib_objc_sources
"${UrlLib_SOURCE_DIR}/Source/UrlRequest_Apple.mm"
"${UrlLib_SOURCE_DIR}/Source/WebSocket_Apple.mm"
"${UrlLib_SOURCE_DIR}/Source/WebSocket_Apple_ObjC.m")
foreach(_file IN LISTS _urllib_objc_sources)
if(EXISTS "${_file}")
set_source_files_properties("${_file}" PROPERTIES COMPILE_FLAGS "-fobjc-arc")
endif()
endforeach()
endif()
endif()
Comment on lines +124 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in UrlLib itself?

endif()

if(BABYLON_DEBUG_TRACE)
Expand Down
41 changes: 26 additions & 15 deletions Core/AppRuntime/V8Inspector/Source/V8InspectorAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,15 @@ namespace Babylon

void ConnectFrontend()
{
session_ = inspector_->connect(1, new ChannelImpl(agent_), v8_inspector::StringView(), v8_inspector::V8Inspector::kFullyTrusted);
session_ = inspector_->connect(
1, new ChannelImpl(agent_), v8_inspector::StringView()
// v8-android package and v8 nuget do not share the same V8 version. A change in V8_inspector API forces us to add this
// ifndef check. This will be fixed in a future nuget package update.
#ifndef ANDROID
,
v8_inspector::V8Inspector::kFullyTrusted
#endif
);
}

void DisconnectFrontend()
Expand Down Expand Up @@ -253,7 +261,7 @@ namespace Babylon
{
auto duration = std::chrono::system_clock::now().time_since_epoch();
return static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(duration)
.count());
.count());
}

void quitMessageLoopOnPause() override
Expand Down Expand Up @@ -324,8 +332,8 @@ namespace Babylon
config_object->Set(context, in_call_key, v8::True(isolate)).FromJust());
CHECK(
!inspector_method.As<v8::Function>()
->Call(context, info.Holder(), static_cast<int>(call_args.size()), call_args.data())
.IsEmpty());
->Call(context, info.Holder(), static_cast<int>(call_args.size()), call_args.data())
.IsEmpty());
}

v8::TryCatch try_catch(info.GetIsolate());
Expand All @@ -338,19 +346,19 @@ namespace Babylon

void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo<v8::Value>& args)
{

v8::Local<v8::Array> array =
v8::Array::New(v8::Isolate::GetCurrent(), args.Length());
CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 0, args[0])
.FromJust());
.FromJust());
CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 1, args[1])
.FromJust());
.FromJust());
CHECK(array->Set(v8::Isolate::GetCurrent()->GetCurrentContext(), 2, args[2])
.FromJust());
.FromJust());
args.GetReturnValue().Set(v8::Function::New(
v8::Isolate::GetCurrent()->GetCurrentContext(),
InspectorConsoleCall,
array).ToLocalChecked());
array)
.ToLocalChecked());
}

void AgentImpl::Start(const unsigned short port, const std::string& appName)
Expand All @@ -359,8 +367,9 @@ namespace Babylon
script_name_ = appName;

// be sure server_ is not still in use here or its allocation will be replace in the thread func
// this can happen if reusing the same AgentImpl object, stopping and restarting before the InspectorSocketServer is properly pulled down
if (server_) {
// this can happen if reusing the same AgentImpl object, stopping and restarting before the InspectorSocketServer is properly pulled down
if (server_)
{
throw std::runtime_error("can't start again the server as previous InspectorSocketServer is still active.");
}

Expand Down Expand Up @@ -419,9 +428,10 @@ namespace Babylon
}
v8::Local<v8::String> string_value = v8::Local<v8::String>::Cast(value);
int len = string_value->Length();
std::basic_string<char16_t> buffer(len, '\0');
string_value->Write(v8::Isolate::GetCurrent(), reinterpret_cast<uint16_t*>(&buffer[0]), 0, len); // Write expects uint16_t* but the template parameter is char16_t
return v8_inspector::StringBuffer::create(v8_inspector::StringView(reinterpret_cast<uint16_t*>(buffer.data()), len));
std::vector<uint16_t> buffer(len);
string_value->Write(v8::Isolate::GetCurrent(), buffer.data(), 0, len);
return v8_inspector::StringBuffer::create(
v8_inspector::StringView(buffer.data(), len));
}

bool AgentImpl::AppendMessage(
Expand Down Expand Up @@ -528,7 +538,8 @@ namespace Babylon
#endif
}
}
} while (!tasks.empty());
}
while (!tasks.empty());
dispatching_messages_ = false;
}

Expand Down
6 changes: 5 additions & 1 deletion Core/Node-API/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ if(NAPI_BUILD_ABI)
npm(install --no-package-lock --silent WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

file(GLOB_RECURSE ANDROID_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/node_modules/${V8_PACKAGE_NAME}/${aar_path}/*.aar")
if(NOT ANDROID_ARCHIVE)
message(FATAL_ERROR "Could not find archive at ${CMAKE_CURRENT_BINARY_DIR}/node_modules/${V8_PACKAGE_NAME}/${aar_path}/*.aar")
endif()
file(ARCHIVE_EXTRACT INPUT ${ANDROID_ARCHIVE} DESTINATION ${output_directory} PATTERNS jni)
message(STATUS "Extracting ${V8_PACKAGE_NAME} archive - done")

Expand All @@ -56,7 +59,8 @@ if(NAPI_BUILD_ABI)
if(ANDROID)
set(V8_PACKAGE_NAME "jsc-android")
set(JSC_ANDROID_DIR "${CMAKE_CURRENT_BINARY_DIR}/${V8_PACKAGE_NAME}")
napi_install_android_package(jsc "dist/org/webkit/android-jsc" ${JSC_ANDROID_DIR})
# Use android-jsc-intl for full intl support
napi_install_android_package(jsc "dist/org/webkit/android-jsc-intl" ${JSC_ANDROID_DIR})

# Add `JavaScriptCore` prefix to the include path
file(RENAME "${JSC_ANDROID_DIR}/include" "${JSC_ANDROID_DIR}/JavaScriptCore")
Expand Down
5 changes: 1 addition & 4 deletions Core/Node-API/Include/Shared/napi/napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4494,11 +4494,8 @@ inline napi_value InstanceWrap<T>::WrappedMethod(
////////////////////////////////////////////////////////////////////////////////
// ObjectWrap<T> class
////////////////////////////////////////////////////////////////////////////////

template <typename T>
// [BABYLON-NATIVE-ADDITION]
#ifndef _MSC_VER
__attribute__((no_sanitize("vptr")))
#endif
inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) {
napi_env env = callbackInfo.Env();
napi_value wrapper = callbackInfo.This();
Expand Down
39 changes: 6 additions & 33 deletions Core/Node-API/Source/js_native_api_javascriptcore.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,6 @@ struct napi_callback_info__ {
};

namespace {
size_t jschar_length(const JSChar* str) {
size_t len = 0;
while (str[len] != 0) { ++len; }
return len;
}

class JSString {
public:
JSString(const JSString&) = delete;
Expand All @@ -39,7 +33,7 @@ namespace {
}

JSString(const JSChar* string, size_t length = NAPI_AUTO_LENGTH)
: _string{JSStringCreateWithCharacters(string, length == NAPI_AUTO_LENGTH ? jschar_length(string) : length)} {
: _string{JSStringCreateWithCharacters(string, length == NAPI_AUTO_LENGTH ? std::char_traits<JSChar>::length(string) : length)} {
}

~JSString() {
Expand Down Expand Up @@ -477,10 +471,6 @@ namespace {
_finalizers.push_back(finalizer);
}

void RemoveFinalizers() {
_finalizers.clear();
}

protected:
BaseInfoT(napi_env env, const char* className)
: NativeInfo{TType}
Expand Down Expand Up @@ -1668,15 +1658,9 @@ napi_status napi_get_value_int32(napi_env env, napi_value value, int32_t* result
CHECK_ARG(env, result);

JSValueRef exception{};

double num = JSValueToNumber(env->context, ToJSValue(value), &exception);
*result = static_cast<int32_t>(JSValueToNumber(env->context, ToJSValue(value), &exception));
CHECK_JSC(env, exception);

if (std::isfinite(num)) {
*result = static_cast<int32_t>(num);
} else {
*result = 0;
}
return napi_ok;
}

Expand All @@ -1686,15 +1670,9 @@ napi_status napi_get_value_uint32(napi_env env, napi_value value, uint32_t* resu
CHECK_ARG(env, result);

JSValueRef exception{};

double num = JSValueToNumber(env->context, ToJSValue(value), &exception);
*result = static_cast<uint32_t>(JSValueToNumber(env->context, ToJSValue(value), &exception));
CHECK_JSC(env, exception);

if (std::isfinite(num)) {
*result = static_cast<uint32_t>(num);
} else {
*result = 0;
}
return napi_ok;
}

Expand Down Expand Up @@ -1912,20 +1890,15 @@ napi_status napi_remove_wrap(napi_env env, napi_value js_object, void** result)
CHECK_ENV(env);
CHECK_ARG(env, js_object);

// REVIEW: Should we remove the wrapper if we are removing finalizers anyway?
// Once an object is wrapped, it stays wrapped in order to support finalizer callbacks.

WrapperInfo* info{};
CHECK_NAPI(WrapperInfo::Unwrap(env, js_object, &info));
RETURN_STATUS_IF_FALSE(env, info != nullptr && info->Data() != nullptr, napi_invalid_arg);

if (result)
{
*result = info->Data();
}

*result = info->Data();
info->Data(nullptr);
info->RemoveFinalizers();


return napi_ok;
}

Expand Down
2 changes: 1 addition & 1 deletion Core/Node-API/package-jsc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"dependencies": {
"jsc-android": "250231.0.0"
"jsc-android": "294992.0.0"
}
}
Loading
Loading