From 0016a7ccc4c194aeef8f806055996b4157e8bbc6 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Mon, 4 May 2026 14:20:01 +0200 Subject: [PATCH] jsuno: Implement a basic Rhino compatibility layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- jsuno/Library_jsuno.mk | 1 + jsuno/qa/extras/testrhinocompatibility.js | 33 +++++++ jsuno/qa/unit/testuno.cxx | 2 + jsuno/source/jsuno.cxx | 84 ++++++++++++++++ jsuno/source/rhinocompatibility.cxx | 112 ++++++++++++++++++++++ jsuno/source/rhinocompatibility.hxx | 21 ++++ 6 files changed, 253 insertions(+) create mode 100644 jsuno/qa/extras/testrhinocompatibility.js create mode 100644 jsuno/source/rhinocompatibility.cxx create mode 100644 jsuno/source/rhinocompatibility.hxx diff --git a/jsuno/Library_jsuno.mk b/jsuno/Library_jsuno.mk index 1e7f16a628ca..fdba53ac4f7f 100644 --- a/jsuno/Library_jsuno.mk +++ b/jsuno/Library_jsuno.mk @@ -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, \ diff --git a/jsuno/qa/extras/testrhinocompatibility.js b/jsuno/qa/extras/testrhinocompatibility.js new file mode 100644 index 000000000000..45eb717b3539 --- /dev/null +++ b/jsuno/qa/extras/testrhinocompatibility.js @@ -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); diff --git a/jsuno/qa/unit/testuno.cxx b/jsuno/qa/unit/testuno.cxx index ca5d781ec7f3..44b844a97f90 100644 --- a/jsuno/qa/unit/testuno.cxx +++ b/jsuno/qa/unit/testuno.cxx @@ -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(); }; diff --git a/jsuno/source/jsuno.cxx b/jsuno/source/jsuno.cxx index cb8051c35886..4b8c3a1f873d 100644 --- a/jsuno/source/jsuno.cxx +++ b/jsuno/source/jsuno.cxx @@ -62,6 +62,7 @@ #include #include "jsvalue.hxx" +#include "rhinocompatibility.hxx" namespace { @@ -590,6 +591,25 @@ JSValue wrapUnoObject(JSContext* ctx, css::uno::Reference 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(shortName.data()), + shortName.length()); +} + +JSValue interfaceGetImportClassName(JSContext* ctx, JSValueConst this_val, int, JSValueConst*) +{ + typelib_TypeDescriptionReference* typeRef = static_cast( + 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( + 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( + 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 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 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)); diff --git a/jsuno/source/rhinocompatibility.cxx b/jsuno/source/rhinocompatibility.cxx new file mode 100644 index 000000000000..ce497e7eb2fa --- /dev/null +++ b/jsuno/source/rhinocompatibility.cxx @@ -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 + +#include "rhinocompatibility.hxx" + +#include +#include + +#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{ "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: */ diff --git a/jsuno/source/rhinocompatibility.hxx b/jsuno/source/rhinocompatibility.hxx new file mode 100644 index 000000000000..4840b10c4999 --- /dev/null +++ b/jsuno/source/rhinocompatibility.hxx @@ -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 +#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: */