* 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_dynamic_frontend.h"
#include <ani.h>
#include "ets_ani_expo.h"
#include "extractor.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 {
const std::string DC_ETS = "/ets/";
const std::string ENTRY_PREFIX = "/src/main/ets/";
const std::string ENTRY_SUFFIX = "/__EntryWrapper";
const std::string USERVIEW_SUFFIX = "/ComExampleTrivialApplication";
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}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",
":",
};
std::string GetErrorProperty(ani_env* aniEnv, ani_error aniError, const char* property)
{
TAG_LOGD(AceLogTag::ACE_DYNAMIC_COMPONENT, "called");
std::string propertyValue;
ani_status status = ANI_ERROR;
ani_type errorType = nullptr;
if ((status = aniEnv->Object_GetType(aniError, &errorType)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT, "Object_GetType failed, status : %{public}d", status);
return propertyValue;
}
ani_method getterMethod = nullptr;
if ((status = aniEnv->Class_FindGetter(static_cast<ani_class>(errorType), property, &getterMethod)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT, "Class_FindGetter failed, status : %{public}d", status);
return propertyValue;
}
ani_ref aniRef = nullptr;
if ((status = aniEnv->Object_CallMethod_Ref(aniError, getterMethod, &aniRef)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT, "Object_CallMethod_Ref failed, status : %{public}d", status);
return propertyValue;
}
ani_string aniString = reinterpret_cast<ani_string>(aniRef);
ani_size sz {};
if ((status = aniEnv->String_GetUTF8Size(aniString, &sz)) != ANI_OK) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT, "String_GetUTF8Size failed, status : %{public}d", status);
return propertyValue;
}
propertyValue.resize(sz + 1);
if ((status = aniEnv->String_GetUTF8SubString(
aniString, 0, sz, propertyValue.data(), propertyValue.size(), &sz))!= ANI_OK) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT, "String_GetUTF8SubString failed, status : %{public}d", status);
return propertyValue;
}
propertyValue.resize(sz);
return propertyValue;
}
void RunArkoalaEventLoop(ani_env* env, ani_ref app)
{
ani_boolean errorExists;
env->ExistUnhandledError(&errorExists);
ani_status status;
ani_class appClass;
if ((status = env->FindClass(KOALA_APP_INFO.className, &appClass)) != ANI_OK) {
ani_error aniError;
env->GetUnhandledError(&aniError);
env->ResetError();
std::string errorMsg = GetErrorProperty(env, aniError, "message");
std::string errorName = GetErrorProperty(env, aniError, "name");
std::string errorStack = GetErrorProperty(env, aniError, "stack");
LOGE("[%{public}s] Cannot load main class %{public}s, status: %{public}d, \nerrorMsg: %{public}s, \nerrorName: "
"%{public}s, \nerrorStack: %{public}s",
__func__, KOALA_APP_INFO.className, status, errorMsg.c_str(), errorName.c_str(), errorStack.c_str());
ark::ets::ETSAni::HandleUncaughtException(aniError);
return;
}
ani_method enter = nullptr;
if (env->Class_FindMethod(appClass, KOALA_APP_INFO.enterMethodName, KOALA_APP_INFO.enterMethodSig, &enter) !=
ANI_OK) {
LOGE("[%{public}s] Cannot find enter method %{public}s", __func__, KOALA_APP_INFO.enterMethodName);
return;
}
ani_int arg0 = 0;
ani_int arg1 = 0;
ani_boolean result;
if (env->Object_CallMethod_Boolean(static_cast<ani_object>(app), enter, &result, arg0, arg1, nullptr) != ANI_OK) {
LOGE("[%{public}s] Call enter method failed", __func__);
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);
}
void GetUrlByDcEntryPoint(
const std::string& bundleName, const std::string& moduleName,
const std::string& entryPoint, std::string& url)
{
std::string prefix;
prefix.append(bundleName).append("/").append(moduleName).append(DC_ETS);
if (prefix.size() > entryPoint.size()) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT,
"prefix: %{public}s dayu entryPoint: %{public}s", prefix.c_str(), entryPoint.c_str());
return;
}
url = entryPoint.substr(prefix.size());
}
void GetModuleNameFromEntryPoint(const std::string& bundleName,
const std::string& entryPoint, std::string& url)
{
std::string prefix = bundleName + "/";
size_t slashIndex = entryPoint.find('/', prefix.size());
if (slashIndex == std::string::npos) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT,
"Cannot find module name from entryPoint: %{public}s with bundleName: %{public}s",
entryPoint.c_str(), bundleName.c_str());
url = "";
return;
}
url = entryPoint.substr(prefix.size(), slashIndex - prefix.size());
}
}
ArktsDynamicFrontend::ArktsDynamicFrontend(void* runtime): ArktsFrontend(runtime)
{}
void ArktsDynamicFrontend::Destroy()
{
if (globalLinkObj_) {
auto* env = Ani::AniUtils::GetAniEnv(vm_);
if (env) {
env->GlobalReference_Delete(reinterpret_cast<ani_object>(globalLinkObj_));
}
}
ArktsFrontend::Destroy();
}
UIContentErrorCode ArktsDynamicFrontend::InnerRunDynamicPage(const DynamicOptions& options)
{
auto* env = Ani::AniUtils::GetAniEnv(vm_);
CHECK_NULL_RETURN(env, 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);
std::string appUrlStr;
std::string moduleNameFromEntryPoint;
GetModuleNameFromEntryPoint(bundleName_, options.entryPoint, moduleNameFromEntryPoint);
GetUrlByDcEntryPoint(bundleName_, moduleNameFromEntryPoint, options.entryPoint, appUrlStr);
ani_string aniUrl{};
env->String_NewUTF8(appUrlStr.c_str(), appUrlStr.size(), &aniUrl);
TAG_LOGI(AceLogTag::ACE_DYNAMIC_COMPONENT, "RunDynamicPage bundleName: %{public}s, moduleName: %{public}s,"
" appUrl: %{public}s", bundleName_.c_str(), moduleName_.c_str(), appUrlStr.c_str());
ani_string aniParams{};
env->String_NewUTF8(options.params.c_str(), options.params.size(), &aniParams);
std::string moduleAbcPath = AbilityBase::ExtractorUtil::GetLoadFilePath(options.hapPath);
NG::EntryLoader entryLoader {env, moduleAbcPath};
std::string entryPath = moduleNameFromEntryPoint + ENTRY_PREFIX + appUrlStr + ENTRY_SUFFIX;
ani_object entryPointObj = entryLoader.GetPageEntryObj(entryPath);
std::string legacyEntryPath = moduleNameFromEntryPoint + ENTRY_PREFIX + appUrlStr + USERVIEW_SUFFIX;
ani_object legacyEntryPointObj = entryLoader.GetPageEntryObj(legacyEntryPath);
ani_string module{};
env->String_NewUTF8(moduleName_.c_str(), moduleName_.size(), &module);
ani_ref optionalEntry;
env->GetUndefined(&optionalEntry);
TAG_LOGI(AceLogTag::ACE_DYNAMIC_COMPONENT, "RunDynamicPage entryPath: %{public}s, legacyEntryPath: %{public}s,",
entryPath.c_str(), legacyEntryPath.c_str());
ani_ref appLocal{};
ANI_CALL(env, Class_CallStaticMethod_Ref(
appClass, create, &appLocal, aniUrl, aniParams, false, module,
legacyEntryPointObj ? legacyEntryPointObj : optionalEntry,
entryPointObj ? entryPointObj : optionalEntry), return UIContentErrorCode::INVALID_URL);
env->GlobalReference_Create(appLocal, &app_);
auto linkObj = entryLoader.GetLinkObj();
env->GlobalReference_Create(reinterpret_cast<ani_ref>(linkObj), &globalLinkObj_);
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_FALSE),
return UIContentErrorCode::INVALID_URL);
return UIContentErrorCode::NO_ERRORS;
}
UIContentErrorCode ArktsDynamicFrontend::RunDynamicPage(const DynamicOptions& options)
{
TAG_LOGI(AceLogTag::ACE_DYNAMIC_COMPONENT, "RunDynamicPage start content: %{public}s, params: %{public}s,"
" entryPoint: %{public}s, hapPath: %{public}s", options.content.c_str(), options.params.c_str(),
options.entryPoint.c_str(), options.hapPath.c_str());
if (pageRouterManager_ == nullptr) {
pageRouterManager_ = NG::PageRouterManagerFactory::CreateManager();
}
std::vector<uint8_t> abcContent;
if (!Framework::GetAssetContentImpl(assetManager_, "ets/modules_static.abc", abcContent)) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT, "GetAssetContent fail: ets/modules_static.abc");
return UIContentErrorCode::INVALID_URL;
}
auto errorCode = InnerRunDynamicPage(options);
if (errorCode != UIContentErrorCode::NO_ERRORS) {
return errorCode;
}
CHECK_NULL_RETURN(pipeline_, UIContentErrorCode::NULL_POINTER);
auto inId = pipeline_->GetInstanceId();
pipeline_->SetVsyncListener([vm = vm_, app = app_, inId]() {
auto* env = Ani::AniUtils::GetAniEnv(vm);
if (env == nullptr) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT, "RunArkoalaEventLoop GetAniEnv failed");
return;
}
RunArkoalaEventLoop(env, app);
});
pipeline_->SetAsyncEventsHookListener([vm = vm_, app = app_]() {
auto* env = Ani::AniUtils::GetAniEnv(vm);
if (env == nullptr) {
TAG_LOGE(AceLogTag::ACE_DYNAMIC_COMPONENT, "FireAllArkoalaAsyncEvents GetAniEnv failed");
return;
}
FireAllArkoalaAsyncEvents(env, app);
});
return UIContentErrorCode::NO_ERRORS;
}
extern "C" ACE_FORCE_EXPORT Frontend* OHOS_ACE_CreatArkTsDynamicFrontend(void* runtime) {
if (!runtime) {
LOGE("runtime is nullptr.");
return nullptr;
}
return static_cast<Frontend*>(new ArktsDynamicFrontend(runtime));
}
}