* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bridge/arkts_frontend/arkts_plugin_frontend.h"
#include <ani.h>
#include "interfaces/inner_api/ace/constants.h"
#include "bridge/arkts_frontend/entry/arkts_entry_loader.h"
#include "core/pipeline/pipeline_context.h"
#include "utils/ani_utils.h"
namespace OHOS::Ace {
namespace {
struct AppInfo {
const char* className;
const char* createMethodName;
const char* createMethodSig;
const char* startMethodName;
const char* startMethodSig;
const char* enterMethodName;
const char* enterMethodSig;
const char* emitEventMethodName;
const char* emitEventMethodSig;
const char* checkCallbackMethodName;
const char* checkCallbackEventMethodSig;
};
const AppInfo KOALA_APP_INFO = {
"arkui.ArkUIEntry.Application",
"createApplication",
"C{std.core.String}C{std.core.String}C{std.core.String}zC{std.core.String}C{arkui.UserView.UserView}"
"C{arkui.component.customComponent.EntryPoint}:C{arkui.ArkUIEntry.Application}",
"start",
"z:l",
"enter",
"iil:z",
"emitEvent",
"iiii:",
"checkCallbacks",
":",
};
void RunArkoalaEventLoop(ani_env* env, ani_ref app)
{
ani_class appClass;
ANI_CALL(env, FindClass(KOALA_APP_INFO.className, &appClass), return);
ani_method enter = nullptr;
ANI_CALL(env, Class_FindMethod(
appClass, KOALA_APP_INFO.enterMethodName, KOALA_APP_INFO.enterMethodSig, &enter), return);
ani_int arg0 = 0;
ani_int arg1 = 0;
ani_boolean result;
ANI_CALL(env, Object_CallMethod_Boolean(
static_cast<ani_object>(app), enter, &result, arg0, arg1, nullptr), return);
}
void FireAllArkoalaAsyncEvents(ani_env* env, ani_ref app)
{
ani_class appClass;
ANI_CALL(env, FindClass(KOALA_APP_INFO.className, &appClass), return);
ani_method checkCallbacks = nullptr;
ANI_CALL(env,
Class_FindMethod(appClass, KOALA_APP_INFO.checkCallbackMethodName, KOALA_APP_INFO.checkCallbackEventMethodSig,
&checkCallbacks),
return);
ANI_CALL(env, Object_CallMethod_Void(static_cast<ani_object>(app), checkCallbacks), return);
}
}
ArktsPluginFrontend::ArktsPluginFrontend(void* runtime)
{
auto* env = reinterpret_cast<ani_env*>(runtime);
if (!env) {
LOGW("ArktsPluginFrontend AniEnv is invalid!");
return;
}
type_ = FrontendType::ARK_TS;
env->GetVM(&vm_);
}
UIContentErrorCode ArktsPluginFrontend::RunPage(
const std::shared_ptr<std::vector<uint8_t>>& content, const std::string& params)
{
return UIContentErrorCode::NO_ERRORS;
}
UIContentErrorCode ArktsPluginFrontend::RunPage(const std::string& url, const std::string& params)
{
auto* env = Ani::AniUtils::GetAniEnv(vm_);
CHECK_NULL_RETURN(env, UIContentErrorCode::INVALID_URL);
std::vector<uint8_t> abcContent;
if (!Framework::GetAssetContentImpl(assetManager_, "ets/modules_static.abc", abcContent)) {
LOGE("GetAssetContent fail: ets/modules_static.abc");
return UIContentErrorCode::INVALID_URL;
}
ani_class appClass{};
ANI_CALL(env, FindClass(KOALA_APP_INFO.className, &appClass), return UIContentErrorCode::INVALID_URL);
ani_static_method create;
ANI_CALL(env, Class_FindStaticMethod(appClass, KOALA_APP_INFO.createMethodName, KOALA_APP_INFO.createMethodSig,
&create), return UIContentErrorCode::INVALID_URL);
auto endsWith = [](const std::string& str, const char* suffix) -> bool {
auto len = strlen(suffix);
return str.length() >= len && strcmp(&str[str.length() - len], suffix) == 0;
};
std::string appUrl;
if (endsWith(url, ".js") || endsWith(url, ".ts")) {
appUrl = url.substr(0, url.length() - strlen(".js"));
} else if (endsWith(url, ".ets")) {
appUrl = url.substr(0, url.length() - strlen(".ets"));
} else {
appUrl = url;
}
ani_string aniUrl{};
env->String_NewUTF8(appUrl.c_str(), appUrl.size(), &aniUrl);
ani_string aniParams{};
env->String_NewUTF8(params.c_str(), params.size(), &aniParams);
NG::EntryLoader entryLoader {env, abcContent};
if (!entryLoader) {
return UIContentErrorCode::INVALID_URL;
}
std::string entryPath = pluginModuleName_ + "/src/main/ets/" + appUrl + "/__EntryWrapper";
ani_object entryPointObj = entryLoader.GetPageEntryObj(entryPath);
std::string legacyEntryPath = pluginModuleName_ + "/src/main/ets/" + appUrl + "/ComExampleTrivialApplication";
ani_object legacyEntryPointObj = entryPointObj ? nullptr : entryLoader.GetPageEntryObj(legacyEntryPath);
ani_string module{};
env->String_NewUTF8(pluginModuleName_.c_str(), pluginModuleName_.size(), &module);
ani_ref appLocal{};
ani_string startName{};
ANI_CALL(env, Class_CallStaticMethod_Ref(appClass, create, &appLocal, aniUrl, aniParams, startName, false, module,
legacyEntryPointObj, entryPointObj), return UIContentErrorCode::INVALID_URL);
env->GlobalReference_Create(appLocal, &app_);
ani_method start;
ANI_CALL(env, Class_FindMethod(appClass, KOALA_APP_INFO.startMethodName, KOALA_APP_INFO.startMethodSig, &start),
return UIContentErrorCode::INVALID_URL);
ani_long result;
ANI_CALL(env, Object_CallMethod_Long(static_cast<ani_object>(app_), start, &result, ANI_TRUE),
return UIContentErrorCode::INVALID_URL);
CHECK_NULL_RETURN(pipeline_, UIContentErrorCode::NULL_POINTER);
pipeline_->SetVsyncListener([vm = vm_, app = app_]() {
auto* env = Ani::AniUtils::GetAniEnv(vm);
RunArkoalaEventLoop(env, app);
});
pipeline_->SetAsyncEventsHookListener([vm = vm_, app = app_]() {
auto* env = Ani::AniUtils::GetAniEnv(vm);
FireAllArkoalaAsyncEvents(env, app);
});
return UIContentErrorCode::NO_ERRORS;
}
void ArktsPluginFrontend::AttachPipelineContext(const RefPtr<PipelineBase>& context)
{
pipeline_ = DynamicCast<PipelineContext>(context);
if (accessibilityManager_) {
accessibilityManager_->SetPipelineContext(context);
accessibilityManager_->InitializeCallback();
}
}
void ArktsPluginFrontend::Destroy()
{
CHECK_NULL_VOID(vm_);
auto* env = Ani::AniUtils::GetAniEnv(vm_);
CHECK_NULL_VOID(env);
env->GlobalReference_Delete(app_);
app_ = nullptr;
}
extern "C" ACE_FORCE_EXPORT PluginFrontend* OHOS_ACE_CreateArktsPluginFrontend(void* runtime) {
if (!runtime) {
LOGE("runtime is nullptr.");
return nullptr;
}
return new ArktsPluginFrontend(runtime);
}
}