mirror of
https://github.com/openharmony/ability_ability_runtime.git
synced 2026-08-24 22:21:36 -04:00
form support import
Signed-off-by: raul <laoyitong@huawei.com> Change-Id: Ieb3d2fdbf6ce98407f4bb52be1f47914a6564492
This commit is contained in:
@@ -359,6 +359,7 @@ ohos_shared_library("extensionkit_native") {
|
||||
]
|
||||
|
||||
sources = [
|
||||
"${ability_runtime_native_path}/ability/native/app_module_checker.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/extension.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/extension_config_mgr.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/extension_impl.cpp",
|
||||
@@ -382,6 +383,7 @@ ohos_shared_library("extensionkit_native") {
|
||||
"common_event_service:cesfwk_innerkits",
|
||||
"hilog:libhilog",
|
||||
"hitrace:hitrace_meter",
|
||||
"napi:ace_napi",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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 "app_module_checker.h"
|
||||
|
||||
#include "module_checker_delegate.h"
|
||||
#include "utils/log.h"
|
||||
|
||||
bool AppModuleChecker::CheckModuleLoadable(const char* moduleName)
|
||||
{
|
||||
HILOG_INFO("check blocklist, moduleName = %{public}s, processExtensionType_ = %{public}d",
|
||||
moduleName, static_cast<int32_t>(processExtensionType_));
|
||||
const auto& blockListIter = moduleBlocklist_.find(processExtensionType_);
|
||||
if (blockListIter == moduleBlocklist_.end()) {
|
||||
return true;
|
||||
}
|
||||
auto blockList = blockListIter->second;
|
||||
if (blockList.find(moduleName) == blockList.end()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -18,12 +18,12 @@
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "app_module_checker.h"
|
||||
#include "hilog_wrapper.h"
|
||||
|
||||
namespace OHOS::AbilityRuntime {
|
||||
namespace {
|
||||
constexpr char EXTENSION_BLOCKLIST_FILE_PATH[] = "/system/etc/extension_blocklist_config.json";
|
||||
constexpr char BACK_SLASH[] = "/";
|
||||
}
|
||||
|
||||
void ExtensionConfigMgr::Init()
|
||||
@@ -66,16 +66,6 @@ void ExtensionConfigMgr::Init()
|
||||
inFile.close();
|
||||
}
|
||||
|
||||
void ExtensionConfigMgr::UpdateBundleExtensionInfo(NativeEngine& engine, AppExecFwk::BundleInfo& bundleInfo)
|
||||
{
|
||||
std::unordered_map<std::string, int32_t> extensionInfo;
|
||||
for (const auto &info : bundleInfo.extensionInfos) {
|
||||
std::string path = info.moduleName + BACK_SLASH + info.srcEntrance;
|
||||
extensionInfo.emplace(path, static_cast<int32_t>(info.type));
|
||||
}
|
||||
engine.SetExtensionInfos(std::move(extensionInfo));
|
||||
}
|
||||
|
||||
void ExtensionConfigMgr::AddBlockListItem(const std::string& name, int32_t type)
|
||||
{
|
||||
HILOG_DEBUG("AddBlockListItem name = %{public}s, type = %{public}d", name.c_str(), type);
|
||||
@@ -87,8 +77,14 @@ void ExtensionConfigMgr::AddBlockListItem(const std::string& name, int32_t type)
|
||||
extensionBlocklist_.emplace(type, iter->second);
|
||||
}
|
||||
|
||||
void ExtensionConfigMgr::UpdateBlockListToEngine(NativeEngine& engine)
|
||||
void ExtensionConfigMgr::UpdateRuntimeModuleChecker(const std::unique_ptr<AbilityRuntime::Runtime> &runtime)
|
||||
{
|
||||
engine.SetModuleBlocklist(std::forward<decltype(extensionBlocklist_)>(extensionBlocklist_));
|
||||
if (!runtime) {
|
||||
HILOG_ERROR("UpdateRuntimeModuleChecker faild, runtime is null");
|
||||
return;
|
||||
}
|
||||
HILOG_INFO("UpdateRuntimeModuleChecker extensionType_ = %{public}d", extensionType_);
|
||||
auto moduleChecker = std::make_shared<AppModuleChecker>(extensionType_, std::move(extensionBlocklist_));
|
||||
runtime->SetModuleLoadChecker(moduleChecker);
|
||||
}
|
||||
}
|
||||
@@ -1621,7 +1621,6 @@ void MainThread::LoadAllExtensions(NativeEngine &nativeEngine, const std::string
|
||||
});
|
||||
}
|
||||
application_->SetExtensionTypeMap(extensionTypeMap);
|
||||
UpdateEngineExtensionBlockList(nativeEngine);
|
||||
}
|
||||
|
||||
bool MainThread::PrepareAbilityDelegator(const std::shared_ptr<UserTestRecord> &record, bool isStageBased,
|
||||
@@ -1742,7 +1741,8 @@ void MainThread::HandleLaunchAbility(const std::shared_ptr<AbilityLocalRecord> &
|
||||
|
||||
mainThreadState_ = MainThreadState::RUNNING;
|
||||
std::shared_ptr<AbilityRuntime::Context> stageContext = application_->AddAbilityStage(abilityRecord);
|
||||
UpdateProcessExtensionType(abilityRecord);
|
||||
SetProcessExtensionType(abilityRecord);
|
||||
UpdateRuntimeModuleChecker(runtime);
|
||||
#ifdef APP_ABILITY_USE_TWO_RUNNER
|
||||
AbilityThread::AbilityThreadMain(application_, abilityRecord, stageContext);
|
||||
#else
|
||||
@@ -2552,40 +2552,42 @@ void MainThread::NotifyAppFault(const FaultData &faultData)
|
||||
ApplicationDataManager::GetInstance().NotifyExceptionObject(faultErrorObj);
|
||||
}
|
||||
|
||||
void MainThread::UpdateProcessExtensionType(const std::shared_ptr<AbilityLocalRecord> &abilityRecord)
|
||||
void MainThread::SetProcessExtensionType(const std::shared_ptr<AbilityLocalRecord> &abilityRecord)
|
||||
{
|
||||
auto &runtime = application_->GetRuntime();
|
||||
if (!runtime) {
|
||||
HILOG_ERROR("Get runtime failed");
|
||||
if (!extensionConfigMgr_) {
|
||||
HILOG_ERROR("AddExtensionBlockItem failed, extensionConfigMgr_ is null");
|
||||
return;
|
||||
}
|
||||
if (!abilityRecord) {
|
||||
HILOG_ERROR("abilityRecord is nullptr");
|
||||
HILOG_ERROR("AddExtensionBlockItem failed, abilityRecord is null");
|
||||
return;
|
||||
}
|
||||
auto &abilityInfo = abilityRecord->GetAbilityInfo();
|
||||
if (!abilityInfo) {
|
||||
HILOG_ERROR("Get abilityInfo failed");
|
||||
if (!abilityRecord->GetAbilityInfo()) {
|
||||
HILOG_ERROR("AddExtensionBlockItem failed, abilityInfo is null");
|
||||
return;
|
||||
}
|
||||
runtime->UpdateExtensionType(static_cast<int32_t>(abilityInfo->extensionAbilityType));
|
||||
HILOG_DEBUG("UpdateExtensionType, type = %{public}d", static_cast<int32_t>(abilityInfo->extensionAbilityType));
|
||||
HILOG_INFO("SetProcessExtensionType, type = %{public}d",
|
||||
static_cast<int32_t>(abilityRecord->GetAbilityInfo()->extensionAbilityType));
|
||||
extensionConfigMgr_->SetProcessExtensionType(
|
||||
static_cast<int32_t>(abilityRecord->GetAbilityInfo()->extensionAbilityType));
|
||||
}
|
||||
|
||||
void MainThread::AddExtensionBlockItem(const std::string &extensionName, int32_t type)
|
||||
{
|
||||
if (!extensionConfigMgr_) {
|
||||
HILOG_ERROR("AddExtensionBlockItem failed, extensionConfigMgr_ is null");
|
||||
return;
|
||||
}
|
||||
extensionConfigMgr_->AddBlockListItem(extensionName, type);
|
||||
}
|
||||
|
||||
void MainThread::UpdateEngineExtensionBlockList(NativeEngine &nativeEngine)
|
||||
void MainThread::UpdateRuntimeModuleChecker(const std::unique_ptr<AbilityRuntime::Runtime> &runtime)
|
||||
{
|
||||
if (!extensionConfigMgr_) {
|
||||
HILOG_ERROR("UpdateRuntimeModuleChecker failed, extensionConfigMgr_ is null");
|
||||
return;
|
||||
}
|
||||
extensionConfigMgr_->UpdateBlockListToEngine(nativeEngine);
|
||||
extensionConfigMgr_->UpdateRuntimeModuleChecker(runtime);
|
||||
}
|
||||
|
||||
int MainThread::GetOverlayModuleInfos(const std::string &bundleName, const std::string &moduleName,
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "js_runtime_utils.h"
|
||||
#include "js_utils.h"
|
||||
#include "js_worker.h"
|
||||
#include "module_checker_delegate.h"
|
||||
#include "native_engine/impl/ark/ark_native_engine.h"
|
||||
#include "ohos_js_env_logger.h"
|
||||
#include "ohos_js_environment_impl.h"
|
||||
@@ -550,6 +551,7 @@ bool JsRuntime::Initialize(const Options& options)
|
||||
}
|
||||
|
||||
InitWorkerModule(options);
|
||||
SetModuleLoadChecker(options.moduleCheckerDelegate);
|
||||
|
||||
if (!InitLoop()) {
|
||||
HILOG_ERROR("Initialize loop failed.");
|
||||
@@ -967,18 +969,6 @@ void JsRuntime::PreloadSystemModule(const std::string& moduleName)
|
||||
nativeEngine->CallFunction(nativeEngine->GetGlobal(), methodRequireNapiRef_->Get(), &className, 1);
|
||||
}
|
||||
|
||||
void JsRuntime::UpdateExtensionType(int32_t extensionType)
|
||||
{
|
||||
auto nativeEngine = GetNativeEnginePointer();
|
||||
CHECK_POINTER(nativeEngine);
|
||||
NativeModuleManager* moduleManager = nativeEngine->GetModuleManager();
|
||||
if (moduleManager == nullptr) {
|
||||
HILOG_ERROR("UpdateExtensionType error, moduleManager is nullptr");
|
||||
return;
|
||||
}
|
||||
moduleManager->SetProcessExtensionType(extensionType);
|
||||
}
|
||||
|
||||
NativeEngine& JsRuntime::GetNativeEngine() const
|
||||
{
|
||||
return *GetNativeEnginePointer();
|
||||
@@ -1157,5 +1147,11 @@ void JsRuntime::ReInitJsEnvImpl(const Options& options)
|
||||
CHECK_POINTER(jsEnv_);
|
||||
jsEnv_->ReInitJsEnvImpl(std::make_unique<OHOSJsEnvironmentImpl>(options.eventRunner));
|
||||
}
|
||||
|
||||
void JsRuntime::SetModuleLoadChecker(const std::shared_ptr<ModuleCheckerDelegate>& moduleCheckerDelegate) const
|
||||
{
|
||||
CHECK_POINTER(jsEnv_);
|
||||
jsEnv_->SetModuleLoadChecker(moduleCheckerDelegate);
|
||||
}
|
||||
} // namespace AbilityRuntime
|
||||
} // namespace OHOS
|
||||
|
||||
@@ -90,7 +90,6 @@ public:
|
||||
bool RunScript(const std::string& path, const std::string& hapPath, bool useCommonChunk = false);
|
||||
|
||||
void PreloadSystemModule(const std::string& moduleName) override;
|
||||
void UpdateExtensionType(int32_t extensionType) override;
|
||||
void StartDebugMode(bool needBreakPoint) override;
|
||||
void StopDebugMode();
|
||||
bool LoadRepairPatch(const std::string& hqfFile, const std::string& hapPath) override;
|
||||
@@ -116,6 +115,7 @@ public:
|
||||
|
||||
void ReloadFormComponent(); // Reload ArkTS-Card component
|
||||
void DoCleanWorkAfterStageCleaned() override;
|
||||
void SetModuleLoadChecker(const std::shared_ptr<ModuleCheckerDelegate>& moduleCheckerDelegate) const override;
|
||||
|
||||
private:
|
||||
void FinishPreload() override;
|
||||
|
||||
@@ -27,6 +27,8 @@ struct JsFrames {
|
||||
uintptr_t *nativePointer = nullptr;
|
||||
};
|
||||
|
||||
class ModuleCheckerDelegate;
|
||||
|
||||
namespace OHOS {
|
||||
namespace AppExecFwk {
|
||||
class EventRunner;
|
||||
@@ -60,6 +62,7 @@ public:
|
||||
// ArkTsCard start
|
||||
bool isUnique = false;
|
||||
// ArkTsCard end
|
||||
std::shared_ptr<ModuleCheckerDelegate> moduleCheckerDelegate;
|
||||
};
|
||||
|
||||
static std::unique_ptr<Runtime> Create(const Options& options);
|
||||
@@ -80,10 +83,10 @@ public:
|
||||
virtual bool LoadRepairPatch(const std::string& patchFile, const std::string& baseFile) = 0;
|
||||
virtual bool NotifyHotReloadPage() = 0;
|
||||
virtual bool UnLoadRepairPatch(const std::string& patchFile) = 0;
|
||||
virtual void UpdateExtensionType(int32_t extensionType) = 0;
|
||||
virtual void RegisterQuickFixQueryFunc(const std::map<std::string, std::string>& moduleAndPath) = 0;
|
||||
virtual void StartProfiler(const std::string &perfCmd) = 0;
|
||||
virtual void DoCleanWorkAfterStageCleaned() = 0;
|
||||
virtual void SetModuleLoadChecker(const std::shared_ptr<ModuleCheckerDelegate>& moduleCheckerDelegate) const {}
|
||||
|
||||
Runtime(const Runtime&) = delete;
|
||||
Runtime(Runtime&&) = delete;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef OHOS_FORM_FWK_APP_MODULE_CHECKER_H
|
||||
#define OHOS_FORM_FWK_APP_MODULE_CHECKER_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "module_checker_delegate.h"
|
||||
|
||||
namespace {
|
||||
constexpr int32_t EXTENSION_TYPE_UNKNOWN = 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Form module load checker. check whether module can be loaded in form
|
||||
*
|
||||
*/
|
||||
class AppModuleChecker : public ModuleCheckerDelegate {
|
||||
public:
|
||||
AppModuleChecker(int32_t extensionType, std::unordered_map<int32_t, std::unordered_set<std::string>> &&blocklist)
|
||||
: processExtensionType_(extensionType), moduleBlocklist_(std::move(blocklist)) {}
|
||||
~AppModuleChecker() override = default;
|
||||
|
||||
bool CheckModuleLoadable(const char* moduleName) override;
|
||||
protected:
|
||||
int32_t processExtensionType_{EXTENSION_TYPE_UNKNOWN};
|
||||
std::unordered_map<int32_t, std::unordered_set<std::string>> moduleBlocklist_;
|
||||
};
|
||||
|
||||
#endif /* OHOS_FORM_FWK_APP_MODULE_CHECKER_H */
|
||||
@@ -23,11 +23,15 @@
|
||||
#include "bundle_info.h"
|
||||
#include "extension_ability_info.h"
|
||||
#include "native_engine/native_engine.h"
|
||||
#include "runtime.h"
|
||||
|
||||
namespace OHOS::AbilityRuntime {
|
||||
namespace ExtensionConfigItem {
|
||||
constexpr char ITEM_NAME_BLOCKLIST[] = "blocklist";
|
||||
}
|
||||
namespace {
|
||||
constexpr int32_t EXTENSION_TYPE_UNKNOWN = 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Manage extension configuration.
|
||||
@@ -45,11 +49,14 @@ public:
|
||||
void Init();
|
||||
|
||||
/**
|
||||
* @brief Update bundle extension information
|
||||
* @brief Set the Process Extension Type object
|
||||
*
|
||||
* @param engine JS NativeEngine
|
||||
* @param extensionType
|
||||
*/
|
||||
void UpdateBundleExtensionInfo(NativeEngine &engine, AppExecFwk::BundleInfo &bundleInfo);
|
||||
void SetProcessExtensionType(int32_t extensionType)
|
||||
{
|
||||
extensionType_ = extensionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add extension blocklist item
|
||||
@@ -60,15 +67,16 @@ public:
|
||||
void AddBlockListItem(const std::string &name, int32_t type);
|
||||
|
||||
/**
|
||||
* @brief Update extension blocklist to native engine
|
||||
* @brief Update runtime module checker
|
||||
*
|
||||
* @param engine JS NativeEngine
|
||||
* @param runtime the runtime pointer
|
||||
*/
|
||||
void UpdateBlockListToEngine(NativeEngine &engine);
|
||||
void UpdateRuntimeModuleChecker(const std::unique_ptr<AbilityRuntime::Runtime> &runtime);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> blocklistConfig_;
|
||||
std::unordered_map<int32_t, std::unordered_set<std::string>> extensionBlocklist_;
|
||||
int32_t extensionType_ = EXTENSION_TYPE_UNKNOWN;
|
||||
};
|
||||
} // namespace OHOS::AbilityRuntime
|
||||
|
||||
|
||||
@@ -474,11 +474,11 @@ private:
|
||||
const AppExecFwk::HapModuleInfo &entryHapModuleInfo);
|
||||
|
||||
/**
|
||||
* @brief Update current process extension type
|
||||
* @brief Set current process extension type
|
||||
*
|
||||
* @param abilityRecord current running ability record
|
||||
*/
|
||||
void UpdateProcessExtensionType(const std::shared_ptr<AbilityLocalRecord> &abilityRecord);
|
||||
void SetProcessExtensionType(const std::shared_ptr<AbilityLocalRecord> &abilityRecord);
|
||||
|
||||
/**
|
||||
* @brief Add Extension block item
|
||||
@@ -489,11 +489,11 @@ private:
|
||||
void AddExtensionBlockItem(const std::string &extensionName, int32_t type);
|
||||
|
||||
/**
|
||||
* @brief Update extension block list to nativeEngine
|
||||
* @brief Update runtime module checker
|
||||
*
|
||||
* @param nativeEngine nativeEngine instance
|
||||
* @param runtime runtime the ability runtime
|
||||
*/
|
||||
void UpdateEngineExtensionBlockList(NativeEngine &nativeEngine);
|
||||
void UpdateRuntimeModuleChecker(const std::unique_ptr<AbilityRuntime::Runtime> &runtime);
|
||||
|
||||
static void HandleDumpHeap(bool isPrivate);
|
||||
static void HandleSignal(int signal);
|
||||
|
||||
@@ -239,5 +239,14 @@ void JsEnvironment::ReInitJsEnvImpl(std::unique_ptr<JsEnvironmentImpl> impl)
|
||||
JSENV_LOG_I("ReInit jsenv impl.");
|
||||
impl_ = std::move(impl);
|
||||
}
|
||||
|
||||
void JsEnvironment::SetModuleLoadChecker(const std::shared_ptr<ModuleCheckerDelegate>& moduleCheckerDelegate)
|
||||
{
|
||||
if (engine_ == nullptr) {
|
||||
JSENV_LOG_E("SetModuleLoadChecker failed, engine_ is null");
|
||||
return;
|
||||
}
|
||||
engine_->SetModuleLoadChecker(moduleCheckerDelegate);
|
||||
}
|
||||
} // namespace JsEnv
|
||||
} // namespace OHOS
|
||||
|
||||
@@ -84,6 +84,8 @@ public:
|
||||
|
||||
void ReInitJsEnvImpl(std::unique_ptr<JsEnvironmentImpl> impl);
|
||||
|
||||
void SetModuleLoadChecker(const std::shared_ptr<ModuleCheckerDelegate>& moduleCheckerDelegate);
|
||||
|
||||
private:
|
||||
std::unique_ptr<JsEnvironmentImpl> impl_ = nullptr;
|
||||
NativeEngine* engine_ = nullptr;
|
||||
|
||||
@@ -63,10 +63,6 @@ public:
|
||||
{
|
||||
return;
|
||||
}
|
||||
void UpdateExtensionType(int32_t extensionType) override
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool RunScript(const std::string& path, const std::string& hapPath, bool useCommonChunk = false)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -159,7 +159,7 @@ HWTEST_F(ExtensionConfigMgrTest, AddBlockListItem_0100, TestSize.Level0)
|
||||
EXPECT_TRUE(result);
|
||||
mgr.AddBlockListItem(BLOCK_LIST_ITEM_FILE_ACCESS_EXTENSION, EXTENSION_TYPE_FILE_ACCESS);
|
||||
result = (mgr.extensionBlocklist_.find(EXTENSION_TYPE_FILE_ACCESS) != mgr.extensionBlocklist_.end());
|
||||
EXPECT_TRUE(result);
|
||||
EXPECT_TRUE(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -398,24 +398,6 @@ HWTEST_F(JsRuntimeTest, JsRuntimeLoadSystemModulesTest_0100, TestSize.Level0)
|
||||
HILOG_INFO("LoadSystemModule end");
|
||||
}
|
||||
|
||||
/**
|
||||
* @tc.name: JsRuntimeUpdateExtensionTypeTest_0100
|
||||
* @tc.desc: JsRuntime test for UpdateExtensionType.
|
||||
* @tc.type: FUNC
|
||||
*/
|
||||
HWTEST_F(JsRuntimeTest, JsRuntimeUpdateExtensionTypeTest_0100, TestSize.Level0)
|
||||
{
|
||||
HILOG_INFO("UpdateExtensionType start");
|
||||
|
||||
auto jsRuntime = std::make_unique<JsRuntime>();
|
||||
EXPECT_TRUE(jsRuntime != nullptr);
|
||||
|
||||
int32_t extensionType = 1;
|
||||
jsRuntime->UpdateExtensionType(extensionType);
|
||||
|
||||
HILOG_INFO("UpdateExtensionType end");
|
||||
}
|
||||
|
||||
/**
|
||||
* @tc.name: JsRuntimeStartDebugModeTest_0100
|
||||
* @tc.desc: JsRuntime test for StartDebugMode.
|
||||
|
||||
Reference in New Issue
Block a user