jsuno: Implement a basic Rhino compatibility layer

Adds the following features in order to make the QuickJS engine slightly
more compatible with the Rhino engine:

Makes “Packages” be an alias for “uno.idl”

Adds an implementation of importClass. This tries to get the name of the
thing that is being imported and then sets a global variable with that
name to point to that thing. The name is retrieved by adding a
getImportClassName method to most of the objects returned by
moduleGetOwnProperty.

Inserts a com.sun.star.uno.UnoRuntime object with a hack on
moduleGetOwnProperty. This object has a method called “queryInterface”
to allow doing UnoRuntime.queryInterface(interface,obj). The
implementation just calls
obj.queryInterface(uno.type.interface(interface))

This is enough to run the HelloWorld example from the Rhino JavaScript
provider.

Change-Id: I569fe4dad4cd8cdc6029a60f8e5421a5de1588c2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/204912
Tested-by: Jenkins
Reviewed-by: Neil Roberts <nroberts@libreoffice.org>
This commit is contained in:
Neil Roberts
2026-05-14 11:03:03 +02:00
parent 6953efb07e
commit 0016a7ccc4
6 changed files with 253 additions and 0 deletions
+1
View File
@@ -13,6 +13,7 @@ $(eval $(call gb_Library_add_defs,jsuno,-DLO_DLLIMPLEMENTATION_JSUNO))
$(eval $(call gb_Library_add_exception_objects,jsuno, \
jsuno/source/jsuno \
jsuno/source/rhinocompatibility \
))
$(eval $(call gb_Library_use_externals,jsuno, \
+33
View File
@@ -0,0 +1,33 @@
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Check that we can access UNO entities with the Packages alias
console.assert(Packages.org.libreoffice.embindtest.Constants.Short === -1234);
assertError(function() { importClass() },
"TypeError: cannot read property 'getImportClassName' of undefined");
console.assert(Packages.org.libreoffice.embindtest.XTest.getImportClassName() == "XTest");
console.assert(Packages.org.libreoffice.embindtest.Enum.getImportClassName() == "Enum");
console.assert(Packages.org.getImportClassName() == "org");
console.assert(Packages.org.libreoffice.embindtest.getImportClassName() == "embindtest");
importClass(Packages.org.libreoffice.embindtest.XTest);
console.assert(XTest == Packages.org.libreoffice.embindtest.XTest);
importClass(Packages.org.libreoffice.embindtest.Enum);
console.assert(Enum == Packages.org.libreoffice.embindtest.Enum);
importClass(Packages.org.libreoffice.embindtest);
console.assert(embindtest == Packages.org.libreoffice.embindtest);
importClass(Packages.com.sun.star.uno.UnoRuntime);
let test = uno.idl.org.libreoffice.embindtest.Test.create(uno.componentContext);
let xTest = UnoRuntime.queryInterface(XTest, test);
console.assert(uno.sameUnoObject(xTest, test));
console.assert(UnoRuntime.queryInterface(XTest, uno.componentContext) === undefined);
+2
View File
@@ -27,10 +27,12 @@ class TestUno : public CppUnit::TestFixture
public:
void test() { executeScript(u"test.js"); }
void testMissingParams() { executeScript(u"testmissingparams.js"); }
void testRhinoCompatibility() { executeScript(u"testrhinocompatibility.js"); }
CPPUNIT_TEST_SUITE(TestUno);
CPPUNIT_TEST(test);
CPPUNIT_TEST(testMissingParams);
CPPUNIT_TEST(testRhinoCompatibility);
CPPUNIT_TEST_SUITE_END();
};
+84
View File
@@ -62,6 +62,7 @@
#include <typelib/typedescription.hxx>
#include "jsvalue.hxx"
#include "rhinocompatibility.hxx"
namespace
{
@@ -590,6 +591,25 @@ JSValue wrapUnoObject(JSContext* ctx, css::uno::Reference<css::uno::XInterface>
return val;
}
JSValue importClassNameFromFullName(JSContext* ctx, std::u16string_view fullName)
{
std::size_t lastDot = fullName.find_last_of(u'.');
std::u16string_view shortName
= lastDot == std::u16string_view::npos ? fullName : fullName.substr(lastDot + 1);
return JS_NewStringUTF16(ctx, reinterpret_cast<std::uint16_t const*>(shortName.data()),
shortName.length());
}
JSValue interfaceGetImportClassName(JSContext* ctx, JSValueConst this_val, int, JSValueConst*)
{
typelib_TypeDescriptionReference* typeRef = static_cast<typelib_TypeDescriptionReference*>(
JS_GetOpaque2(ctx, this_val, getRuntimeData(ctx)->interfaceClassId));
if (typeRef == nullptr)
return JS_EXCEPTION;
return importClassNameFromFullName(ctx, OUString(typeRef->pTypeName));
}
struct EnumeratorData
{
css::uno::Type type;
@@ -855,6 +875,16 @@ JSValue unoTypeInterface(JSContext* ctx, JSValueConst, int argc, JSValueConst* a
});
}
JSValue enumGetImportClassName(JSContext* ctx, JSValueConst this_val, int, JSValueConst*)
{
typelib_TypeDescriptionReference* typeRef = static_cast<typelib_TypeDescriptionReference*>(
JS_GetOpaque2(ctx, this_val, getRuntimeData(ctx)->enumClassId));
if (typeRef == nullptr)
return JS_EXCEPTION;
return importClassNameFromFullName(ctx, OUString(typeRef->pTypeName));
}
void enumFinalizer(JSRuntime* rt, JSValueConst val)
{
#if defined DBG_UTIL
@@ -1213,6 +1243,16 @@ JSValue getSingleton(JSContext* ctx, JSValueConst, int argc, JSValueConst* argv,
});
}
JSValue moduleGetImportClassName(JSContext* ctx, JSValueConst this_val, int, JSValueConst*)
{
ModuleData* moduleData = static_cast<ModuleData*>(
JS_GetOpaque2(ctx, this_val, getRuntimeData(ctx)->moduleClassId));
if (moduleData == nullptr)
return JS_EXCEPTION;
return importClassNameFromFullName(ctx, moduleData->name);
}
void moduleGCMark(JSRuntime* rt, JSValueConst val, JS_MarkFunc* markFunc)
{
ModuleData* moduleData
@@ -1234,6 +1274,10 @@ void moduleFinalizer(JSRuntime* rt, JSValueConst val)
ValueRef moduleGetOwnPropertyUncached(JSContext* ctx, const OUString& id)
{
// Special hack for rhino compatibility
if (id == u"com.sun.star.uno.UnoRuntime")
return jsuno::rhino_compatibility::createUnoRuntime(ctx);
css::uno::Reference<css::container::XHierarchicalNameAccess> mgr(
comphelper::getProcessComponentContext()->getValueByName(
u"/singletons/com.sun.star.reflection.theTypeDescriptionManager"_ustr),
@@ -2584,6 +2628,18 @@ void initializeTypePrototype(JSContext* ctx)
JS_SetClassProto(ctx, getRuntimeData(ctx)->typeClassId, proto.release());
}
void initializeEnumPrototype(JSContext* ctx)
{
static const JSCFunctionListEntry functions[] = {
JS_CFUNC_DEF("getImportClassName", 0, enumGetImportClassName),
};
ValueRef proto(ctx, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, proto, functions, SAL_N_ELEMENTS(functions));
JS_SetClassProto(ctx, getRuntimeData(ctx)->enumClassId, proto.release());
}
void initializeEnumeratorPrototype(JSContext* ctx)
{
static const JSCFunctionListEntry functions[] = {
@@ -2607,6 +2663,30 @@ void initializeWrapperPrototype(JSContext* ctx)
JS_SetClassProto(ctx, getRuntimeData(ctx)->wrapperClassId, proto.release());
}
void initializeInterfacePrototype(JSContext* ctx)
{
static const JSCFunctionListEntry functions[] = {
JS_CFUNC_DEF("getImportClassName", 0, interfaceGetImportClassName),
};
ValueRef proto(ctx, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, proto, functions, SAL_N_ELEMENTS(functions));
JS_SetClassProto(ctx, getRuntimeData(ctx)->interfaceClassId, proto.release());
}
void initializeModulePrototype(JSContext* ctx)
{
static const JSCFunctionListEntry functions[] = {
JS_CFUNC_DEF("getImportClassName", 0, moduleGetImportClassName),
};
ValueRef proto(ctx, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, proto, functions, SAL_N_ELEMENTS(functions));
JS_SetClassProto(ctx, getRuntimeData(ctx)->moduleClassId, proto.release());
}
}
OUString jsuno::execute(OUString const& script, VariableList aGlobalVariables)
@@ -2676,7 +2756,10 @@ OUString jsuno::execute(OUString const& script, VariableList aGlobalVariables)
auto const ctx = JS_NewContext(rt);
initializeTypePrototype(ctx);
initializeEnumeratorPrototype(ctx);
initializeEnumPrototype(ctx);
initializeWrapperPrototype(ctx);
initializeInterfacePrototype(ctx);
initializeModulePrototype(ctx);
std::optional<ExceptionData> exc;
OUString result;
{
@@ -2733,6 +2816,7 @@ OUString jsuno::execute(OUString const& script, VariableList aGlobalVariables)
JS_SetPropertyStr(ctx, uno, "componentContext",
wrapUnoObject(ctx, comphelper::getProcessComponentContext()));
JS_SetPropertyStr(ctx, global, "uno", uno.release());
rhino_compatibility::setUp(ctx);
for (const auto& rVariable : aGlobalVariables)
JS_SetPropertyStr(ctx, global, rVariable.first, wrapUnoObject(ctx, rVariable.second));
+112
View File
@@ -0,0 +1,112 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <sal/config.h>
#include "rhinocompatibility.hxx"
#include <array>
#include <sal/macros.h>
#include "jsvalue.hxx"
namespace jsuno::rhino_compatibility
{
namespace
{
JSValue importClass(JSContext* pContext, JSValueConst, int, JSValueConst* argv)
{
JSAtom pGetImportClassName = JS_NewAtom(pContext, "getImportClassName");
jsuno::ValueRef sShortName(pContext,
JS_Invoke(pContext, argv[0], pGetImportClassName, 0, nullptr));
JS_FreeAtom(pContext, pGetImportClassName);
if (JS_IsException(sShortName))
return JS_EXCEPTION;
JSAtom pAtom = JS_ValueToAtom(pContext, sShortName);
if (pAtom == JS_ATOM_NULL)
return JS_EXCEPTION;
const jsuno::ValueRef xGlobal(pContext, JS_GetGlobalObject(pContext));
int nRet = JS_SetProperty(pContext, xGlobal, pAtom, JS_DupValue(pContext, argv[0]));
JS_FreeAtom(pContext, pAtom);
return nRet == -1 ? JS_EXCEPTION : JS_UNDEFINED;
}
JSValue unoRuntimeGetImportClassName(JSContext* pContext, JSValueConst, int, JSValueConst*)
{
return JS_NewString(pContext, "UnoRuntime");
}
JSValue queryInterface(JSContext* pContext, JSValueConst, int, JSValueConst* argv)
{
// Redirect UnoRuntime.queryInterface(a, b) to b.queryInterface(uno.type.interface(a))
jsuno::ValueRef xUnoTypeInterface(pContext, JS_GetGlobalObject(pContext));
for (const auto part : std::array<const char*, 3>{ "uno", "type", "interface" })
{
JSAtom pAtom = JS_NewAtom(pContext, part);
xUnoTypeInterface = JS_GetProperty(pContext, xUnoTypeInterface, pAtom);
JS_FreeAtom(pContext, pAtom);
if (JS_IsException(xUnoTypeInterface))
return JS_EXCEPTION;
}
jsuno::ValueRef xType(pContext, JS_Call(pContext, xUnoTypeInterface, JS_NULL, 1, argv));
if (JS_IsException(xType))
return JS_EXCEPTION;
JSAtom pAtom = JS_NewAtom(pContext, "queryInterface");
JSValueConst args[] = { xType };
jsuno::ValueRef xRet(pContext, JS_Invoke(pContext, argv[1], pAtom, SAL_N_ELEMENTS(args), args));
JS_FreeAtom(pContext, pAtom);
return xRet.release();
}
}
void setUp(JSContext* pContext)
{
// Make “Packages” an alias for “uno.idl”
const jsuno::ValueRef xGlobal(pContext, JS_GetGlobalObject(pContext));
const jsuno::ValueRef xUno(pContext, JS_GetPropertyStr(pContext, xGlobal, "uno"));
assert(JS_IsObject(xUno));
jsuno::ValueRef xIdl(pContext, JS_GetPropertyStr(pContext, xUno, "idl"));
assert(JS_IsObject(xIdl));
JS_SetPropertyStr(pContext, xGlobal, "Packages", xIdl.release());
jsuno::ValueRef xImportClass(pContext,
JS_NewCFunction(pContext, importClass, "importClass", 1));
JS_SetPropertyStr(pContext, xGlobal, "importClass", xImportClass.release());
}
jsuno::ValueRef createUnoRuntime(JSContext* pContext)
{
jsuno::ValueRef xObject(pContext, JS_NewObject(pContext));
if (JS_IsException(xObject))
return xObject;
static const JSCFunctionListEntry functions[] = {
JS_CFUNC_DEF("queryInterface", 2, queryInterface),
JS_CFUNC_DEF("getImportClassName", 0, unoRuntimeGetImportClassName),
};
ValueRef proto(pContext, JS_NewObject(pContext));
JS_SetPropertyFunctionList(pContext, xObject, functions, SAL_N_ELEMENTS(functions));
return xObject;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
+21
View File
@@ -0,0 +1,21 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <quickjs.h>
#include "jsvalue.hxx"
namespace jsuno::rhino_compatibility
{
void setUp(JSContext* pContext);
jsuno::ValueRef createUnoRuntime(JSContext* pContext);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */