4#include <qohosplugincore.h>
7#include <QtCore/private/qohoslogger_p.h>
8#include <QtCore/private/qohosmemoizingjsthreadfetcher_p.h>
17#include <napi/native_api.h>
19#include <qohosutils.h>
20#include <render/qohosjswindowregistry.h>
31constexpr const char *forceLoadJsModulesEnvVariableName =
"IO__QT__OHOS__FORCE_LOAD_JS_MODULES";
33constexpr const char *napiLoadableModules[] = {
34 "@ohos.abilityAccessCtrl",
35 "@ohos.accessibility",
36 "@ohos.app.ability.AbilityConstant",
37 "@ohos.app.ability.ConfigurationConstant",
38 "@ohos.app.ability.childProcessManager",
39 "@ohos.app.ability.contextConstant",
40 "@ohos.app.ability.wantConstant",
41 "@ohos.arkui.uiExtension",
42 "@ohos.bluetooth.access",
43 "@ohos.bluetooth.connection",
44 "@ohos.bluetooth.socket",
45 "@ohos.bundle.bundleManager",
46 "@ohos.data.uniformTypeDescriptor",
52 "@ohos.geoLocationManager",
53 "@ohos.graphics.text",
57 "@ohos.multimedia.image",
58 "@ohos.multimedia.media",
59 "@ohos.multimodalInput.inputDevice",
60 "@ohos.multimodalInput.pointer",
61 "@ohos.net.connection",
62 "@ohos.notificationManager",
64 "@ohos.resourceManager",
74 DummyQAbilityPeer() =
default;
76 std::string instanceId()
override;
77 QNapi::Object uiContext()
override;
78 QNapi::Object qAbility()
override;
79 QNapi::Object launchWant()
override;
80 QObjectThreadSafeRef qWindowRef()
override;
81 std::optional<QNapi::Promise> qWindowDestroyPromise()
override;
82 void forceResolveQWindowDestroyPromiseIfPresent(Napi::Env env)
override;
83 std::shared_ptr<
std::atomic_bool> destroyAllowedFlag()
override;
85 void setQWindow(Napi::Env env, QObjectThreadSafeRef)
override;
87 void *tryCastWithTypeIdObject(
const void *matchTypeIdObject)
override;
90std::string DummyQAbilityPeer::instanceId()
95QNapi::Object DummyQAbilityPeer::uiContext()
97 return QNapi::Object();
100QNapi::Object DummyQAbilityPeer::qAbility()
102 return QNapi::Object();
105QNapi::Object DummyQAbilityPeer::launchWant()
107 return QNapi::Object();
110QObjectThreadSafeRef DummyQAbilityPeer::qWindowRef()
115std::optional<QNapi::Promise> DummyQAbilityPeer::qWindowDestroyPromise()
120void DummyQAbilityPeer::forceResolveQWindowDestroyPromiseIfPresent(Napi::Env)
124std::shared_ptr<
std::atomic_bool> DummyQAbilityPeer::destroyAllowedFlag()
126 return std::make_shared<
std::atomic_bool>(
false);
129void DummyQAbilityPeer::setQWindow(Napi::Env, QObjectThreadSafeRef)
133void *DummyQAbilityPeer::tryCastWithTypeIdObject(
const void *)
138template<
typename Task>
139class PreQueuingTasksExecutor
142 void setUnderlyingExecutor(
143 std::function<
void(Task)> executor,
144 std::function<
void(Task)> optSyncFlushExecutor =
nullptr);
145 void invokeTask(Task task);
148 std::recursive_mutex m_executorMutex;
149 std::function<
void(Task)> m_optUnderlyingExecutor;
150 std::deque<Task> m_pendingTasks;
153template<
typename Task>
154void PreQueuingTasksExecutor<Task>::setUnderlyingExecutor(
155 std::function<
void(Task)> executor,
std::function<
void(Task)> optSyncFlushExecutor)
157 std::lock_guard<
std::recursive_mutex> executorLock(m_executorMutex);
158 auto &flushExecutor = optSyncFlushExecutor ? optSyncFlushExecutor : executor;
159 while (!m_pendingTasks.empty()) {
160 auto task =
std::move(m_pendingTasks.front());
161 m_pendingTasks.pop_front();
162 flushExecutor(
std::move(task));
164 m_optUnderlyingExecutor =
std::move(executor);
167template<
typename Task>
168void PreQueuingTasksExecutor<Task>::invokeTask(Task task)
170 std::lock_guard<
std::recursive_mutex> executorLock(m_executorMutex);
171 if (m_optUnderlyingExecutor)
172 m_optUnderlyingExecutor(
std::move(task));
174 m_pendingTasks.push_back(std::move(task));
177using PreQueuingJsTasksExecutor = PreQueuingTasksExecutor<
std::function<
void(
JsState &)>>;
182 std::mutex pendingTasksMutex;
183 bool pendingTasksRunnerActive =
false;
184 std::vector<std::function<
void(JsState &)>> pendingTasks;
185 Napi::ThreadSafeFunction executorThreadSafeFunc;
187 auto ctx =
std::make_shared<Context>();
189 ctx->executorThreadSafeFunc = Napi::ThreadSafeFunction::New(
191 QNapi::Function::New(
193 [ctx = ctx.get()](
const CallbackInfo &cbInfo) {
195 std::vector<std::function<
void(JsState &)>> pendingTasks;
197 std::lock_guard<std::mutex> pendingTasksLock(ctx->pendingTasksMutex);
198 pendingTasks = std::exchange(ctx->pendingTasks, {});
199 if (pendingTasks.empty()) {
200 ctx->pendingTasksRunnerActive =
false;
204 for (
auto &task : pendingTasks)
205 task(cbInfo.jsState());
208 "QCoreOhosTasksExecutorFunc"),
209 "QCoreOhosTasksExecutor", 0, 1);
211 return [ctx](
std::function<
void(
JsState &)> task) {
212 std::lock_guard<
std::mutex> pendingTasksLock(ctx->pendingTasksMutex);
213 ctx->pendingTasks.push_back(
std::move(task));
214 if (!ctx->pendingTasksRunnerActive) {
215 ctx->executorThreadSafeFunc.NonBlockingCall();
216 ctx->pendingTasksRunnerActive =
true;
221QNapi::Symbol getJsWindowsTrackerIsClosingPropSymbol(
JsState &jsState)
227 return jsState.getJsSymbolForType<SymbolTag>();
230std::optional<QNapi::Object> tryLoadJsModuleViaNapi(napi_env env,
const std::string &moduleName)
232 qOhosPrintfDebug(
"%s: loading napi module '%s' via napi_load_module()", Q_FUNC_INFO, moduleName.c_str());
234 napi_value result =
nullptr;
235 auto status = napi_load_module(env, moduleName.c_str(), &result);
236 if (status != napi_ok) {
237 Napi::Env napiEnv(env);
238 if (napiEnv.IsExceptionPending())
239 napiEnv.GetAndClearPendingException();
241 "%s: napi_load_module failed for module '%s' (status=%d)",
242 Q_FUNC_INFO, moduleName.c_str(),
static_cast<
int>(status));
246 Napi::Value resultValue(env, result);
247 if (!resultValue.IsObject()) {
249 "%s: napi_load_module returned %s instead of an object for module '%s'",
250 Q_FUNC_INFO, QNapi::getValueTypeString(resultValue).c_str(), moduleName.c_str());
254 return QNapi::Object(env, result);
257QNapi::Object loadJsModuleViaNapiOrFail(napi_env env,
const std::string &moduleName)
259 auto optModule = tryLoadJsModuleViaNapi(env, moduleName);
261 qOhosReportFatalErrorAndAbort(
"%s: required module '%s' not loaded", Q_FUNC_INFO, moduleName.c_str());
263 return optModule.value();
266QNapi::Object loadJsModuleViaEtsFactoryOrFail(
267 const QNapi::Function &etsModuleFactory,
const std::string &moduleName)
269 qOhosPrintfDebug(
"%s: loading napi module '%s' via ets factory", Q_FUNC_INFO, moduleName.c_str());
271 auto result = etsModuleFactory.Call({});
272 if (!result.IsObject()) {
273 qOhosReportFatalErrorAndAbort(
274 "%s: ets factory for module '%s' did not return an object",
275 Q_FUNC_INFO, moduleName.c_str());
278 return QNapi::checkedCast<QNapi::Object>(result);
282std::function<T(
JsState &)> makeMemoizingJsValueSupplier(
std::function<T(
JsState &)> baseJsSupplier)
284 auto memoizedValue = moveToSharedPtr(QNapi::Reference<T>());
285 return [baseJsSupplier =
std::move(baseJsSupplier), memoizedValue](
JsState &jsState)
mutable {
286 if (memoizedValue->IsEmpty()) {
287 *memoizedValue = QNapi::Reference<T>::makePersistentFrom(baseJsSupplier(jsState));
288 baseJsSupplier =
nullptr;
290 return memoizedValue->Value();
294class JsStateImpl :
public JsState
297 JsStateImpl() =
default;
300 napi_env env, std::map<std::string, QNapi::Reference<QNapi::Function>> &&etsModulesFactories,
301 std::shared_ptr<AppFunctions> appFunctions, QtRunMode qtRunMode);
305 void addQAbilityPeerInJsThread(
std::shared_ptr<
QAbilityPeer> qAbilityPeer);
306 void removeMatchingQAbilityPeerInJsThread(QNapi::Object qAbility);
308 void dispatchNewWantInJsThread(QNapi::Object qAbility, QNapi::Object want, QNapi::Object launchParam);
310 void invokeTask(
std::function<
void(
JsState &)> &&task);
312 napi_env env()
override;
313 QNapi::Object getModule(
const std::string &moduleName);
315 QNapi::Object appLaunchWant()
override;
316 std::optional<QNapi::Object> optAppLaunchParam()
override;
317 std::optional<QNapi::Object> tryGetModule(
const std::string &moduleName)
override;
319 QNapi::Object defaultWindowStageOrEmpty()
override;
320 QNapi::Object defaultUiContextOrEmpty()
override;
323 std::shared_ptr<
QAbilityPeer> tryGetQAbilityPeerByInstanceId(
const std::string &instanceId)
override;
324 std::shared_ptr<
QAbilityPeer> tryGetQAbilityPeerByInstance(QNapi::Object qAbility)
override;
325 std::optional<QNapi::Object> tryGetJsWindowByQWindow(QObjectThreadSafeRef qwindow)
override;
326 std::optional<QNapi::Object> tryGetQAbilityByQWindow(QObjectThreadSafeRef qwindow)
override;
327 std::optional<QNapi::Object> defaultQAbility()
override;
329 void visitEachQAbilityPeer(
const std::function<
void(
std::shared_ptr<
QAbilityPeer>)> &visitor)
override;
331 void startNewQAbilityInstance(
332 QNapi::Object baseQAbility, QObjectThreadSafeRef qwindow,
333 QNapi::Object optStartOptions,
336 void startAppProcess(
337 const std::string &processId, QNapi::Object requestWant,
338 QNapi::Object optStartOptions, std::function<
void(QOhosJsState &)> continueFunc)
override;
340 std::shared_ptr<
void> registerNewWantConsumer(
341 QOhosConsumer<QOhosJsState &, QNapi::Object, QNapi::Object, QNapi::Object> wantConsumer)
override;
343 void setOnContinueRequestsHandler(
344 QNapi::Object qAbility,
345 std::function<
void(QOhosJsState &, QNapi::Object, QOhosConsumer<QOhosJsState &, QNapi::Number>)> requestsHandler)
override;
347 void setDestroyFromSystemAllowed(QNapi::Object qAbility,
bool destroyAllowed)
override;
349 void startNoUiChildProcess(
const std::string &libraryName,
const std::vector<std::string> &args)
override;
354 template<
typename PeerMatchFunc>
355 std::shared_ptr<
QAbilityPeer> tryFindMatchingQAbilityPeer(PeerMatchFunc &&matchFunc);
357 void *getAttachedObjectWithLazyCreate(
358 const std::type_info &objectTypeInfo, QOhosSupplier<std::shared_ptr<
void>> objectFactory)
override;
360 std::tuple<QNapi::Object, std::string> extractModuleFromEvalExpr(
const std::string &expr)
override;
361 QNapi::Number mapOhosEnumToJs(
int enumValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
override;
362 std::optional<
int> tryMapOhosEnumFromJs(QNapi::Number enumJsValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
override;
363 int mapOhosEnumFromJs(QNapi::Number enumJsValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
override;
364 QNapi::Symbol getJsSymbolForType(
const std::type_info &typeInfo)
override;
366 const std::vector<std::pair<
int,
double>> &getOhosEnumEnumerators(
367 const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)());
368 std::vector<std::pair<
int,
double>> resolveOhosEnumEnumerators(
const OhosEnumInfo &enumInfo);
370 void forceLoadAllJsModulesIfRequested();
372 pthread_t m_jsThread = {};
373 napi_env m_env =
nullptr;
374 QNapi::Reference<QNapi::Object> m_appLaunchWant;
375 QNapi::Reference<QNapi::Object> m_optAppLaunchParam;
377 std::map<std::string, std::shared_ptr<QAbilityPeer>> m_qAbilityPeers;
378 std::map<std::string, std::function<QNapi::Object(JsState &)>> m_jsModulesFactories;
379 std::map<std::string, QNapi::Reference<QNapi::Object>> m_onDemandJsModules;
381 PreQueuingJsTasksExecutor m_tasksExecutor;
382 std::map<std::uint64_t, std::shared_ptr<QOhosConsumer<QOhosJsState &, QNapi::Object, QNapi::Object, QNapi::Object>>> m_newWantConsumers;
383 std::uint64_t m_nextNewWantConsumerId = 1;
384 std::map<std::type_index, std::vector<std::pair<
int,
double>>> m_ohosEnumsEnumerators;
385 std::map<std::type_index, QNapi::Reference<QNapi::Symbol>> m_jsSymbolsRefs;
387 std::map<std::type_index, std::shared_ptr<
void>> m_attachedObjects;
390bool JsStateImpl::isJsThread()
392 return pthread_self() == m_jsThread;
395void JsStateImpl::initInJsThread(
396 napi_env env, std::map<std::string, QNapi::Reference<QNapi::Function>> &&etsModulesFactories,
397 std::shared_ptr<AppFunctions> appFunctions, QtRunMode qtRunMode)
399 m_jsThread = pthread_self();
401 m_defaultQAbilityPeer = std::make_shared<DummyQAbilityPeer>();
402 m_appFunctions = appFunctions;
403 m_qtRunMode = qtRunMode;
405 for (
const char *moduleName : napiLoadableModules) {
406 m_jsModulesFactories.emplace(
408 makeMemoizingJsValueSupplier<QNapi::Object>(
409 [moduleName](JsState &jsState) {
410 return loadJsModuleViaNapiOrFail(jsState.env(), moduleName);
413 for (
auto &etsModulesFactoryEntry : etsModulesFactories) {
414 const std::string &moduleName = etsModulesFactoryEntry.first;
415 auto etsModuleFactory = moveToSharedPtr(std::move(etsModulesFactoryEntry.second));
416 m_jsModulesFactories.emplace(
418 makeMemoizingJsValueSupplier<QNapi::Object>(
419 [moduleName, etsModuleFactory](JsState &) {
420 return loadJsModuleViaEtsFactoryOrFail(etsModuleFactory->Value(), moduleName);
423 m_jsModulesFactories.emplace(
425 [](JsState &jsState) -> QNapi::Object {
426 return Napi::Env(jsState.env()).Global();
429 m_tasksExecutor.setUnderlyingExecutor(makeJsThreadTasksExecutor(
this));
432void JsStateImpl::addQAbilityPeerInJsThread(
std::shared_ptr<
QAbilityPeer> qAbilityPeer)
434 forceLoadAllJsModulesIfRequested();
436 if (m_appLaunchWant.IsEmpty())
437 m_appLaunchWant = Napi::Persistent(qAbilityPeer->launchWant());
440 if (m_optAppLaunchParam.IsEmpty() && optQUiAbilityPeer) {
441 m_optAppLaunchParam = QNapi::Reference<>::makePersistentFrom(optQUiAbilityPeer->launchParam());
443 "JsStateImpl: appLaunchParam set from Ability with instanceId='%s'",
444 qAbilityPeer->instanceId().c_str());
447 if (m_qAbilityPeers.empty())
448 m_defaultQAbilityPeer = qAbilityPeer;
450 m_qAbilityPeers.emplace(qAbilityPeer->instanceId(), qAbilityPeer);
453 "JsStateImpl: added QAbilityPeer, instanceId: %s, qwindow: %s",
454 qAbilityPeer->instanceId().c_str(), qAbilityPeer->qWindowRef().refName().c_str());
457void JsStateImpl::removeMatchingQAbilityPeerInJsThread(QNapi::Object qAbility)
459 Napi::HandleScope funcScope{qAbility.Env()};
461 auto foundPeerIter =
std::find_if(
462 m_qAbilityPeers.begin(), m_qAbilityPeers.end(),
463 [&](
const auto &entry) {
464 return entry.second->qAbility() == qAbility;
467 if (foundPeerIter != m_qAbilityPeers.end()) {
468 auto removedPeer = foundPeerIter->second;
469 m_qAbilityPeers.erase(foundPeerIter);
470 if (m_defaultQAbilityPeer == removedPeer) {
471 m_defaultQAbilityPeer =
472 !m_qAbilityPeers.empty()
473 ? m_qAbilityPeers.begin()->second
474 : std::make_shared<DummyQAbilityPeer>();
477 "JsStateImpl: removed QAbilityPeer, instanceId: %s, qwindow: %s",
478 removedPeer->instanceId().c_str(), removedPeer->qWindowRef().refName().c_str());
482void JsStateImpl::dispatchNewWantInJsThread(
483 QNapi::Object qAbility, QNapi::Object want, QNapi::Object launchParam)
485 std::vector<std::uint64_t> consumersIds;
486 for (
const auto &consumerEntry : m_newWantConsumers)
487 consumersIds.push_back(consumerEntry.first);
489 for (
auto consumerId : consumersIds) {
490 auto consumerIter = m_newWantConsumers.find(consumerId);
491 if (consumerIter != m_newWantConsumers.end()) {
492 auto consumer = consumerIter->second;
493 (*consumer)(*
this, qAbility, want, launchParam);
498void JsStateImpl::invokeTask(
std::function<
void(
JsState &)> &&task)
500 m_tasksExecutor.invokeTask(std::move(task));
503napi_env JsStateImpl::env()
508void JsStateImpl::forceLoadAllJsModulesIfRequested()
510 if (qEnvironmentVariableIntValue(forceLoadJsModulesEnvVariableName) == 0)
513 qOhosPrintfDebug(
"%s: forcibly loading all JS modules", Q_FUNC_INFO);
515 for (
const auto &moduleFactoryEntry : m_jsModulesFactories)
516 getModule(moduleFactoryEntry.first);
519QNapi::Object JsStateImpl::getModule(
const std::string &moduleName)
521 using namespace std::string_literals;
523 auto moduleFactoryIter = m_jsModulesFactories.find(moduleName);
524 if (moduleFactoryIter == m_jsModulesFactories.end())
525 throw QNapi::makeLoggedException(m_env,
"JS module not found: "s + moduleName);
527 return moduleFactoryIter->second(*
this);
530std::optional<QNapi::Object> JsStateImpl::tryGetModule(
const std::string &moduleName)
532 auto moduleFactoryIter = m_jsModulesFactories.find(moduleName);
533 if (moduleFactoryIter != m_jsModulesFactories.end())
534 return moduleFactoryIter->second(*
this);
536 auto onDemandModuleIter = m_onDemandJsModules.find(moduleName);
537 if (onDemandModuleIter != m_onDemandJsModules.end())
538 return onDemandModuleIter->second.Value();
540 auto optModule = tryLoadJsModuleViaNapi(m_env, moduleName);
544 m_onDemandJsModules.emplace(moduleName, QNapi::Reference<>::makePersistentFrom(optModule.value()));
548QNapi::Object JsStateImpl::appLaunchWant()
550 return m_appLaunchWant.Value();
553std::optional<QNapi::Object> JsStateImpl::optAppLaunchParam()
555 return !m_optAppLaunchParam.IsEmpty()
556 ? std::optional(m_optAppLaunchParam.Value())
560QNapi::Object JsStateImpl::defaultWindowStageOrEmpty()
564 return QNapi::Object();
566 return qUiAbilityPeer->windowStage();
569QNapi::Object JsStateImpl::defaultUiContextOrEmpty()
571 return m_defaultQAbilityPeer->uiContext();
576 return m_defaultQAbilityPeer;
579std::shared_ptr<
QAbilityPeer> JsStateImpl::tryGetQAbilityPeerByInstanceId(
const std::string &instanceId)
581 auto foundIter = m_qAbilityPeers.find(instanceId);
582 return foundIter != m_qAbilityPeers.end() ? foundIter->second :
nullptr;
585std::shared_ptr<
QAbilityPeer> JsStateImpl::tryGetQAbilityPeerByInstance(QNapi::Object qAbility)
587 Napi::HandleScope findPeerScope(qAbility.Env());
588 return tryFindMatchingQAbilityPeer(
589 [&](
const auto &peer) {
590 return peer->qAbility() == qAbility;
594std::optional<QNapi::Object> JsStateImpl::tryGetJsWindowByQWindow(QObjectThreadSafeRef qwindow)
596 auto optJsWindowRef = QOhosJsWindowRegistry::instance(*
this).tryFindJsWindowByQWindowRef(qwindow);
597 return optJsWindowRef ?
std::optional(optJsWindowRef->jsObject()) :
std::nullopt;
600std::optional<QNapi::Object> JsStateImpl::tryGetQAbilityByQWindow(QObjectThreadSafeRef qwindow)
602 auto qAbilityPeer = tryFindMatchingQAbilityPeer(
603 [&](
const auto &peer) {
604 return peer->qWindowRef() == qwindow;
607 auto optJsWindowRef = QOhosJsWindowRegistry::instance(*
this).tryFindJsWindowByQWindowRef(qwindow);
609 qAbilityPeer = tryGetQAbilityPeerByInstanceId(
610 optJsWindowRef->owningQAbilityInstanceId());
612 return qAbilityPeer ?
std::optional(qAbilityPeer->qAbility()) :
std::nullopt;
615std::optional<QNapi::Object> JsStateImpl::defaultQAbility()
617 auto qAbility = defaultQAbilityPeer()->qAbility();
618 return !qAbility.IsEmpty() ?
std::optional(qAbility) :
std::nullopt;
621void JsStateImpl::setOnContinueRequestsHandler(
622 QNapi::Object qAbility,
623 std::function<
void(QOhosJsState &, QNapi::Object, QOhosConsumer<QOhosJsState &, QNapi::Number>)> requestsHandler)
626 tryGetQAbilityPeerByInstance(qAbility));
627 if (!uiAbilityPeer) {
629 "%s: no QUiAbilityPeer for the given qAbility, handler not set", Q_FUNC_INFO);
633 uiAbilityPeer->setOnContinueRequestsHandler(
634 [requestsHandler = std::move(requestsHandler)](
635 JsState &jsState, QNapi::Object wantParams,
636 QOhosConsumer<JsState &, QOhosAbilityOnContinueResult> resultConsumer) {
639 [resultConsumer = std::move(resultConsumer)](QOhosJsState &resultJsState, QNapi::Number result) {
641 if (!optResultEnum) {
643 "%s: got invalid OnContinueResult value (%f) from requests handler, rejecting the request",
644 Q_FUNC_INFO, result.DoubleValue());
647 JsState::castFrom(resultJsState),
653void JsStateImpl::setDestroyFromSystemAllowed(QNapi::Object qAbility,
bool destroyAllowed)
655 auto abilityPeer = tryGetQAbilityPeerByInstance(qAbility);
658 "%s: no QAbilityPeer for the given qAbility, flag not set to %s",
662 abilityPeer->destroyAllowedFlag()->store(destroyAllowed);
665void JsStateImpl::visitEachQAbilityPeer(
const std::function<
void(
std::shared_ptr<
QAbilityPeer>)> &visitor)
667 auto qAbilityPeersSnapshot = m_qAbilityPeers;
668 for (
const auto &qAbilityPeerEntry : qAbilityPeersSnapshot)
669 visitor(qAbilityPeerEntry.second);
672void JsStateImpl::startNewQAbilityInstance(
673 QNapi::Object baseQAbility, QObjectThreadSafeRef qwindow,
674 QNapi::Object optStartOptions,
677 m_appFunctions->startQAbilityInstance(
678 baseQAbility, qwindow, optStartOptions, std::move(startupNotifyFunc));
681void JsStateImpl::startAppProcess(
682 const std::string &processId, QNapi::Object requestWant,
683 QNapi::Object optStartOptions, std::function<
void(QOhosJsState &)> continueFunc)
685 auto optBaseQAbility = defaultQAbility();
686 if (optBaseQAbility) {
687 m_appFunctions->startAppProcess(
688 optBaseQAbility.value(), processId, requestWant, optStartOptions, std::move(continueFunc));
690 invokeTask(
std::move(continueFunc));
694std::shared_ptr<
void> JsStateImpl::registerNewWantConsumer(
695 QOhosConsumer<QOhosJsState &, QNapi::Object, QNapi::Object, QNapi::Object> wantConsumer)
697 const auto consumerId = m_nextNewWantConsumerId++;
698 m_newWantConsumers.emplace(consumerId, moveToSharedPtr(std::move(wantConsumer)));
699 return makeDestroyNotifier(
700 [
this, consumerId]() {
701 m_newWantConsumers.erase(consumerId);
705void JsStateImpl::startNoUiChildProcess(
const std::string &libraryName,
const std::vector<std::string> &args)
707 m_appFunctions->startNoUiChildProcess(*
this, libraryName, args);
715template<
typename PeerMatchFunc>
716std::shared_ptr<
QAbilityPeer> JsStateImpl::tryFindMatchingQAbilityPeer(PeerMatchFunc &&matchFunc)
718 auto foundIter =
std::find_if(
719 m_qAbilityPeers.begin(), m_qAbilityPeers.end(),
720 [&](
const auto &entry) {
721 return matchFunc(entry.second);
723 return foundIter != m_qAbilityPeers.end() ? foundIter->second :
nullptr;
726void *JsStateImpl::getAttachedObjectWithLazyCreate(
727 const std::type_info &objectTypeInfo, QOhosSupplier<std::shared_ptr<
void>> objectFactory)
729 auto objectIter = m_attachedObjects.find(objectTypeInfo);
730 if (objectIter == m_attachedObjects.end())
731 std::tie(objectIter, std::ignore) = m_attachedObjects.emplace(objectTypeInfo, objectFactory());
733 return objectIter->second.get();
736std::tuple<QNapi::Object, std::string> JsStateImpl::extractModuleFromEvalExpr(
const std::string &expr)
738 using namespace std::string_literals;
740 std::size_t longestModulePrefixSize = 0;
741 auto considerAsLongestPrefix = [&](
const std::string &name) {
742 if (name.size() > longestModulePrefixSize
743 && expr.compare(0, name.size(), name) == 0
744 && (expr.size() == name.size() || expr[name.size()] ==
'.')) {
745 longestModulePrefixSize = name.size();
748 for (
const auto &moduleFactoryEntry : m_jsModulesFactories)
749 considerAsLongestPrefix(moduleFactoryEntry.first);
751 if (longestModulePrefixSize == 0) {
752 throw QNapi::makeLoggedException(
753 env(),
"global expression doesn't start with known module path: '"s + expr +
"'");
756 return std::make_tuple(
757 getModule(expr.substr(0, longestModulePrefixSize)),
758 longestModulePrefixSize < expr.size()
759 ? expr.substr(longestModulePrefixSize + 1)
763QNapi::Number JsStateImpl::mapOhosEnumToJs(
764 int enumValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
766 using namespace std::string_literals;
768 const auto &enumeratorsValues = getOhosEnumEnumerators(enumTypeInfo, ohosEnumInfoFactory);
770 auto valueIter =
std::find_if(
771 enumeratorsValues.begin(), enumeratorsValues.end(),
772 [&](
const auto &valuePair) {
773 return valuePair.first == enumValue;
776 if (valueIter == enumeratorsValues.end()) {
777 throw QNapi::makeLoggedException(
779 "Illegal C++ value of enumerator for enum '"s + enumTypeInfo.name() +
"': "
780 + std::to_string(enumValue));
783 return QNapi::Number::New(env(), valueIter->second);
786std::optional<
int> JsStateImpl::tryMapOhosEnumFromJs(
787 QNapi::Number enumJsValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
789 double enumDoubleJsValue = enumJsValue.DoubleValue();
791 const auto &enumeratorsValues = getOhosEnumEnumerators(enumTypeInfo, ohosEnumInfoFactory);
793 auto valueIter =
std::find_if(
794 enumeratorsValues.begin(), enumeratorsValues.end(),
795 [&](
const auto &valuePair) {
796 return valuePair.second == enumDoubleJsValue;
799 return valueIter != enumeratorsValues.end()
800 ?
std::optional(valueIter->first)
804int JsStateImpl::mapOhosEnumFromJs(
805 QNapi::Number enumJsValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
807 using namespace std::string_literals;
809 auto optEnumValue = tryMapOhosEnumFromJs(enumJsValue, enumTypeInfo, ohosEnumInfoFactory);
810 if (!optEnumValue.has_value()) {
811 throw QNapi::makeLoggedException(
813 "Illegal Napi value of enumerator for enum '"s + enumTypeInfo.name() +
"': "
814 + std::to_string(enumJsValue.DoubleValue()));
816 return optEnumValue.value();
819QNapi::Symbol JsStateImpl::getJsSymbolForType(
const std::type_info &typeInfo)
821 using namespace std::string_literals;
823 auto jsSymbolRefIter = m_jsSymbolsRefs.find(typeInfo);
824 if (jsSymbolRefIter == m_jsSymbolsRefs.end()) {
825 std::tie(jsSymbolRefIter, std::ignore) = m_jsSymbolsRefs.emplace(
827 QNapi::Reference<>::makePersistentFrom(
828 QNapi::Symbol::New(env(),
"_io_qt_"s + typeInfo.name())));
830 return jsSymbolRefIter->second.Value();
833const std::vector<std::pair<
int,
double>> &JsStateImpl::getOhosEnumEnumerators(
834 const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
836 auto enumeratorsIter = m_ohosEnumsEnumerators.find(enumTypeInfo);
837 if (enumeratorsIter == m_ohosEnumsEnumerators.end()) {
838 std::tie(enumeratorsIter, std::ignore) = m_ohosEnumsEnumerators.emplace(
839 enumTypeInfo, resolveOhosEnumEnumerators(ohosEnumInfoFactory()));
842 return enumeratorsIter->second;
845std::vector<std::pair<
int,
double>> JsStateImpl::resolveOhosEnumEnumerators(
const OhosEnumInfo &enumInfo)
847 using namespace std::string_literals;
849 QNapi::Object enumObj;
851 enumObj = eval<QNapi::Object>(enumInfo.fullTypeName);
852 }
catch (
const Napi::Error &e) {
853 throw QNapi::makeLoggedException(
854 env(),
"JS enum '"s + enumInfo.fullTypeName +
"' not found: "s + e.what());
857 std::vector<std::pair<
int,
double>> enumeratorsValues;
859 for (
const auto &enumNamePair : enumInfo.enumeratorsNames) {
860 const auto *enumeratorName = enumNamePair.second;
862 QNapi::Value jsEnumeratorValue = QNapi::getPropOrUndefined(enumObj, enumeratorName);
864 if (!jsEnumeratorValue.IsNumber()) {
865 throw QNapi::makeLoggedException(
866 env(),
"JS enum '"s + enumInfo.fullTypeName +
"' doesn't contain: "s + enumeratorName);
869 enumeratorsValues.emplace_back(
870 enumNamePair.first, QNapi::checkedCast<QNapi::Number>(jsEnumeratorValue).DoubleValue());
873 return enumeratorsValues;
876JsStateImpl &getJsStateImpl()
878 static JsStateImpl jsStateImpl;
884 static auto qtJsBlockingCallsGateway = QOhosMtBlockingCallsGateway<JsState>::makeInstance(
886 [](std::function<
void(JsState &)> task) {
887 getJsStateImpl().invokeTask(std::move(task));
889 return *qtJsBlockingCallsGateway;
892class JsThreadOpsImpl :
public ::QOhosJsThreadOps
895 QOhosJsState &jsState()
override;
897 void invoke(std::function<
void(QOhosJsState &)> &&task)
override;
898 void invokeAndWaitForContinue(
899 std::function<
void(QOhosJsState &, QOhosTaskPromise<>)> &&task,
900 std::string callerContextName)
override;
902 const std::function<
void(QOhosJsState &)> &task,
903 std::string callerContextName)
override;
906QOhosJsState &JsThreadOpsImpl::jsState()
908 return getJsStateImpl();
911void JsThreadOpsImpl::invoke(std::function<
void(QOhosJsState &)> &&task)
913 getJsStateImpl().invokeTask(
914 [sharedTask = takeFunctionToSharedPtr(task)](
JsState &jsState) {
915 const auto takenTask = std::exchange(*sharedTask,
nullptr);
920void JsThreadOpsImpl::invokeAndWaitForContinue(
921 std::function<
void(QOhosJsState &, QOhosTaskPromise<>)> &&task,
922 std::string callerContextName)
924 const auto funcInfo = Q_FUNC_INFO;
926 struct JsTaskCompletionState
929 bool outerTaskPromiseSettled =
false;
930 std::optional<
std::string> jsThreadExceptionMsg;
932 auto completionState =
std::make_shared<JsTaskCompletionState>();
934 auto catchingTaskWrapper =
935 [task = std::move(task), completionState, callerContextName, funcInfo](
936 JsState &jsState, QOhosTaskPromise<> outerTaskPromise) {
937 auto sharedOuterTaskPromise =
938 std::make_shared<QOhosTaskPromise<>>(std::move(outerTaskPromise));
940 auto settleOuterTaskPromise =
941 [completionState, sharedOuterTaskPromise](
std::optional<
std::string> exceptionMsg) {
943 std::lock_guard<
std::mutex> lock(completionState->mutex);
944 if (completionState->outerTaskPromiseSettled)
946 completionState->outerTaskPromiseSettled =
true;
947 completionState->jsThreadExceptionMsg =
std::move(exceptionMsg);
949 (*sharedOuterTaskPromise)();
952 auto innerTaskPromise = QOhosTaskPromise<>(
953 [settleOuterTaskPromise]() {
954 settleOuterTaskPromise({});
956 [funcInfo, callerContextName]() {
957 if (
std::uncaught_exceptions() == 0) {
958 qOhosReportFatalErrorAndAbort(
959 "%s: promise destroyed without notifying the caller: %s",
960 funcInfo, callerContextName.c_str());
966 task(jsState,
std::move(innerTaskPromise));
967 }
catch (
const std::exception &e) {
968 settleOuterTaskPromise(std::optional<std::string>(e.what()));
970 settleOuterTaskPromise(std::optional<std::string>(
"<unknown exception>"));
974 if (getQtState().isQtThread()) {
975 getQtJsBlockingCallsGateway().runInSlaveThreadAndWaitForContinue(
976 std::move(catchingTaskWrapper), callerContextName);
978 auto taskReadyPromise =
std::make_shared<
std::promise<
void>>();
979 auto taskReadyFuture = taskReadyPromise->get_future();
981 auto sharedTaskPromise = std::make_shared<QOhosTaskPromise<>>(
982 [taskReadyPromise]() {
983 taskReadyPromise->set_value();
985 [funcInfo, callerContextName]() {
986 qOhosReportFatalErrorAndAbort(
987 "%s: promise destroyed without notifying the caller: %s",
988 funcInfo, callerContextName.c_str());
992 getJsStateImpl().invokeTask(
993 [catchingTaskWrapper = std::move(catchingTaskWrapper), sharedTaskPromise](
JsState &jsState) {
994 catchingTaskWrapper(jsState,
std::move(*sharedTaskPromise));
996 taskReadyFuture.wait();
999 if (completionState->jsThreadExceptionMsg) {
1000 qOhosReportFatalErrorAndAbort(
1001 "%s: exception from task invoked in JS thread (caller: \"%s\"): %s",
1002 Q_FUNC_INFO, callerContextName.c_str(),
1003 completionState->jsThreadExceptionMsg.value().c_str());
1007void JsThreadOpsImpl::runAndWait(
1008 const std::function<
void(QOhosJsState &)> &task,
1009 std::string callerContextName)
1011 if (getJsStateImpl().isJsThread()) {
1012 task(getJsStateImpl());
1014 invokeAndWaitForContinue(
1015 [&](
auto &jsState,
auto taskPromise) {
1019 std::move(callerContextName));
1039 return matchTypeIdObject == &typeIdObject ?
this :
nullptr;
1049 qAbilityPeer->tryCastWithTypeIdObject(&typeIdObject));
1051 return qUiAbilityPeer !=
nullptr
1052 ? qUiAbilityPeer->shared_from_this()
1062 if (&jsState != &getJsStateImpl())
1063 qOhosReportFatalErrorAndAbort(
"%s: jsState is not the plugin's JsState", Q_FUNC_INFO);
1065 return static_cast<
JsState &>(jsState);
1070 return getJsStateImpl();
1075 qOhosPrintfDebug(
"Tagging JS Window as closing (from: %s)", logContext);
1076 Napi::HandleScope funcScope{jsWindow.Env()};
1078 getJsWindowsTrackerIsClosingPropSymbol(getJsStateImpl()),
1079 QNapi::Boolean::New(jsWindow.Env(),
true));
1084 Napi::HandleScope funcScope{jsWindow.Env()};
1085 auto isClosingPropSymbol = getJsWindowsTrackerIsClosingPropSymbol(getJsStateImpl());
1086 auto isClosingValue = jsWindow.Has(isClosingPropSymbol)
1087 ? jsWindow.Get(isClosingPropSymbol)
1089 return isClosingValue.IsBoolean() && QNapi::checkedCast<QNapi::Boolean>(isClosingValue).Value();
1096 static JsThreadOpsImpl jsThreadOpsImpl;
1097 QOhosJsThreadOps::registerInstance(&jsThreadOpsImpl);
1099 getJsStateImpl().initInJsThread(env,
std::move(jsModulesFactories), appFunctions, qtRunMode);
1101 qOhosEnableMemoizingJsThreadFetchers(getJsStateImpl());
1106 return getJsStateImpl().isJsThread();
1111 getJsStateImpl().addQAbilityPeerInJsThread(qAbilityPeer);
1116 getJsStateImpl().removeMatchingQAbilityPeerInJsThread(qAbility);
1119void dispatchNewWant(QNapi::Object qAbility, QNapi::Object want, QNapi::Object launchParam)
1121 getJsStateImpl().dispatchNewWantInJsThread(qAbility, want, launchParam);
1125 std::function<
void(
std::function<
void()>)> &&task,
1126 std::chrono::nanoseconds timeout)
1128 if (getJsStateImpl().isJsThread()) {
1129 auto result = getQtJsBlockingCallsGateway().tryInvokeInMasterThreadAndTryWaitForContinue(
std::move(task), timeout);
1130 return result == QOhosMtBlockingCallsGateway<JsState>::MasterThreadTaskResult::Finished;
1132 auto taskReadyPromise =
std::make_shared<
std::promise<
void>>();
1133 auto taskReadyFuture = taskReadyPromise->get_future();
1135 auto continueFunc = [taskReadyPromise]() {
1136 taskReadyPromise->set_value();
1140 [task = std::move(task), continueFunc = std::move(continueFunc)]()
mutable {
1141 task(
std::move(continueFunc));
1143 auto waitResult = taskReadyFuture.wait_for(timeout);
1145 return waitResult ==
std::future_status::ready;
JsState & jsState() const
void * tryCastWithTypeIdObject(const void *matchTypeIdObject) final
static std::shared_ptr< QUiAbilityPeer > tryCastFromQAbilityPeerOrNull(std::shared_ptr< QAbilityPeer > qAbilityPeer)
~QUiAbilityPeer() override
std::string const char * mapBoolToTrueFalseStr(bool value)
void removeMatchingJsQAbilityPeer(QNapi::Object qAbility)
void initJsThreadState(napi_env env, std::map< std::string, QNapi::Reference< QNapi::Function > > &&jsModulesFactories, std::shared_ptr< AppFunctions > appFunctions, QtRunMode qtRunMode)
Q_REQUIRED_RESULT bool tryInvokeInQtThreadAndTryWaitForContinue(std::function< void(std::function< void()>)> &&task, std::chrono::nanoseconds timeout)
QtOhosQpa::enums::ohos::app::ability::AbilityConstant::OnContinueResult QOhosAbilityOnContinueResult
void addJsQAbilityPeer(std::shared_ptr< QAbilityPeer > qAbilityPeer)
void dispatchNewWant(QNapi::Object qAbility, QNapi::Object want, QNapi::Object launchParam)