4#include <qohosplugincore.h>
7#include <QtCore/private/qohoslogger_p.h>
14#include <napi/native_api.h>
16#include <qohosutils.h>
17#include <render/qohosjswindowregistry.h>
28constexpr const char *forceLoadJsModulesEnvVariableName =
"IO__QT__OHOS__FORCE_LOAD_JS_MODULES";
30constexpr const char *napiLoadableModules[] = {
31 "@ohos.abilityAccessCtrl",
32 "@ohos.accessibility",
33 "@ohos.app.ability.AbilityConstant",
34 "@ohos.app.ability.ConfigurationConstant",
35 "@ohos.app.ability.childProcessManager",
36 "@ohos.app.ability.contextConstant",
37 "@ohos.app.ability.wantConstant",
38 "@ohos.arkui.uiExtension",
39 "@ohos.bluetooth.access",
40 "@ohos.bluetooth.connection",
41 "@ohos.bluetooth.socket",
42 "@ohos.bundle.bundleManager",
43 "@ohos.data.uniformTypeDescriptor",
49 "@ohos.geoLocationManager",
50 "@ohos.graphics.text",
54 "@ohos.multimedia.image",
55 "@ohos.multimedia.media",
56 "@ohos.multimodalInput.inputDevice",
57 "@ohos.multimodalInput.pointer",
58 "@ohos.net.connection",
59 "@ohos.notificationManager",
61 "@ohos.resourceManager",
71 DummyQAbilityPeer() =
default;
73 std::string instanceId()
override;
74 QNapi::Object uiContext()
override;
75 QNapi::Object qAbility()
override;
76 QNapi::Object launchWant()
override;
77 QObjectThreadSafeRef qWindowRef()
override;
78 std::optional<QNapi::Promise> qWindowDestroyPromise()
override;
79 void forceResolveQWindowDestroyPromiseIfPresent(Napi::Env env)
override;
80 std::shared_ptr<
std::atomic_bool> destroyAllowedFlag()
override;
81 bool isTerminating()
override;
83 void setQWindow(Napi::Env env, QObjectThreadSafeRef)
override;
85 void *tryCastWithTypeIdObject(
const void *matchTypeIdObject)
override;
88std::string DummyQAbilityPeer::instanceId()
93QNapi::Object DummyQAbilityPeer::uiContext()
95 return QNapi::Object();
98QNapi::Object DummyQAbilityPeer::qAbility()
100 return QNapi::Object();
103QNapi::Object DummyQAbilityPeer::launchWant()
105 return QNapi::Object();
108QObjectThreadSafeRef DummyQAbilityPeer::qWindowRef()
113std::optional<QNapi::Promise> DummyQAbilityPeer::qWindowDestroyPromise()
118void DummyQAbilityPeer::forceResolveQWindowDestroyPromiseIfPresent(Napi::Env)
122std::shared_ptr<
std::atomic_bool> DummyQAbilityPeer::destroyAllowedFlag()
124 return std::make_shared<
std::atomic_bool>(
false);
127bool DummyQAbilityPeer::isTerminating()
132void DummyQAbilityPeer::setQWindow(Napi::Env, QObjectThreadSafeRef)
136void *DummyQAbilityPeer::tryCastWithTypeIdObject(
const void *)
141template<
typename Task>
142class PreQueuingTasksExecutor
145 void setUnderlyingExecutor(
146 std::function<
void(Task)> executor,
147 std::function<
void(Task)> optSyncFlushExecutor =
nullptr);
148 void invokeTask(Task task);
151 std::recursive_mutex m_executorMutex;
152 std::function<
void(Task)> m_optUnderlyingExecutor;
153 std::deque<Task> m_pendingTasks;
156template<
typename Task>
157void PreQueuingTasksExecutor<Task>::setUnderlyingExecutor(
158 std::function<
void(Task)> executor,
std::function<
void(Task)> optSyncFlushExecutor)
160 std::lock_guard<
std::recursive_mutex> executorLock(m_executorMutex);
161 auto &flushExecutor = optSyncFlushExecutor ? optSyncFlushExecutor : executor;
162 while (!m_pendingTasks.empty()) {
163 auto task =
std::move(m_pendingTasks.front());
164 m_pendingTasks.pop_front();
165 flushExecutor(
std::move(task));
167 m_optUnderlyingExecutor =
std::move(executor);
170template<
typename Task>
171void PreQueuingTasksExecutor<Task>::invokeTask(Task task)
173 std::lock_guard<
std::recursive_mutex> executorLock(m_executorMutex);
174 if (m_optUnderlyingExecutor)
175 m_optUnderlyingExecutor(
std::move(task));
177 m_pendingTasks.push_back(std::move(task));
180using PreQueuingJsTasksExecutor = PreQueuingTasksExecutor<
std::function<
void(
JsState &)>>;
185 std::mutex pendingTasksMutex;
186 bool pendingTasksRunnerActive =
false;
187 std::vector<std::function<
void(JsState &)>> pendingTasks;
188 Napi::ThreadSafeFunction executorThreadSafeFunc;
190 auto ctx =
std::make_shared<Context>();
192 ctx->executorThreadSafeFunc = Napi::ThreadSafeFunction::New(
194 QNapi::Function::New(
196 [ctx = ctx.get()](
const CallbackInfo &cbInfo) {
198 std::vector<std::function<
void(JsState &)>> pendingTasks;
200 std::lock_guard<std::mutex> pendingTasksLock(ctx->pendingTasksMutex);
201 pendingTasks = std::exchange(ctx->pendingTasks, {});
202 if (pendingTasks.empty()) {
203 ctx->pendingTasksRunnerActive =
false;
207 for (
auto &task : pendingTasks)
208 task(cbInfo.jsState());
211 "QCoreOhosTasksExecutorFunc"),
212 "QCoreOhosTasksExecutor", 0, 1);
214 return [ctx](
std::function<
void(
JsState &)> task) {
215 std::lock_guard<
std::mutex> pendingTasksLock(ctx->pendingTasksMutex);
216 ctx->pendingTasks.push_back(
std::move(task));
217 if (!ctx->pendingTasksRunnerActive) {
218 ctx->executorThreadSafeFunc.NonBlockingCall();
219 ctx->pendingTasksRunnerActive =
true;
224QNapi::Symbol getJsWindowsTrackerIsClosingPropSymbol(
JsState &jsState)
230 return jsState.getJsSymbolForType<SymbolTag>();
233QNapi::Object loadJsModuleViaNapiOrFail(napi_env env,
const std::string &moduleName)
235 qOhosPrintfDebug(
"%s: loading napi module '%s' via napi_load_module()", Q_FUNC_INFO, moduleName.c_str());
237 napi_value result =
nullptr;
238 auto status = napi_load_module(env, moduleName.c_str(), &result);
239 if (status != napi_ok) {
240 qOhosReportFatalErrorAndAbort(
241 "%s: napi_load_module failed for module '%s' (status=%d)",
242 Q_FUNC_INFO, moduleName.c_str(),
static_cast<
int>(status));
245 return QNapi::Object(env, result);
248QNapi::Object loadJsModuleViaEtsFactoryOrFail(
249 const QNapi::Function &etsModuleFactory,
const std::string &moduleName)
251 qOhosPrintfDebug(
"%s: loading napi module '%s' via ets factory", Q_FUNC_INFO, moduleName.c_str());
253 auto result = etsModuleFactory.Call({});
254 if (!result.IsObject()) {
255 qOhosReportFatalErrorAndAbort(
256 "%s: ets factory for module '%s' did not return an object",
257 Q_FUNC_INFO, moduleName.c_str());
260 return QNapi::checkedCast<QNapi::Object>(result);
264std::function<T(
JsState &)> makeMemoizingJsValueSupplier(
std::function<T(
JsState &)> baseJsSupplier)
266 auto memoizedValue = moveToSharedPtr(QNapi::Reference<T>());
267 return [baseJsSupplier =
std::move(baseJsSupplier), memoizedValue](
JsState &jsState)
mutable {
268 if (memoizedValue->IsEmpty()) {
269 *memoizedValue = QNapi::Reference<T>::makePersistentFrom(baseJsSupplier(jsState));
270 baseJsSupplier =
nullptr;
272 return memoizedValue->Value();
276class JsStateImpl :
public JsState
279 JsStateImpl() =
default;
282 napi_env env, std::map<std::string, QNapi::Reference<QNapi::Function>> &&etsModulesFactories,
283 std::shared_ptr<AppFunctions> appFunctions, QtRunMode qtRunMode);
287 void addQAbilityPeerInJsThread(
std::shared_ptr<
QAbilityPeer> qAbilityPeer);
288 void removeMatchingQAbilityPeerInJsThread(QNapi::Object qAbility);
290 void dispatchNewWantInJsThread(QNapi::Object want, QNapi::Object launchParam);
292 void invokeTask(
std::function<
void(
JsState &)> &&task);
294 napi_env env()
override;
295 QNapi::Object getModule(
const std::string &moduleName)
override;
297 QNapi::Object appLaunchWant()
override;
298 std::optional<QNapi::Object> optAppLaunchParam()
override;
300 QNapi::Object defaultWindowStageOrEmpty()
override;
301 QNapi::Object defaultUiContextOrEmpty()
override;
304 std::shared_ptr<
QAbilityPeer> tryGetQAbilityPeerByInstanceId(
const std::string &instanceId)
override;
305 std::shared_ptr<
QAbilityPeer> tryGetQAbilityPeerByInstance(QNapi::Object qAbility)
override;
306 std::shared_ptr<
QAbilityPeer> tryGetQAbilityPeerByQWindow(QObjectThreadSafeRef qwindow)
override;
307 std::optional<QNapi::Object> tryGetJsWindowByQWindow(QObjectThreadSafeRef qwindow)
override;
308 std::optional<QNapi::Object> tryGetQAbilityByQWindow(QObjectThreadSafeRef qwindow)
override;
309 std::optional<QNapi::Object> defaultQAbility()
override;
311 void visitEachQAbilityPeer(
const std::function<
void(
std::shared_ptr<
QAbilityPeer>)> &visitor)
override;
313 void startNewQAbilityInstance(
314 QNapi::Object baseQAbility, QObjectThreadSafeRef qwindow,
315 QNapi::Object optStartOptions,
318 void startAppProcess(
319 const std::string &processId, QNapi::Object requestWant,
320 QNapi::Object optStartOptions, std::function<
void(QOhosJsState &)> continueFunc)
override;
322 void addNewWantConsumer(QOhosConsumer<QOhosJsState &, QNapi::Object, QNapi::Object> wantConsumer)
override;
324 void setOnContinueRequestsHandler(
325 QNapi::Object qAbility,
326 std::function<
void(QOhosJsState &, QNapi::Object, QOhosConsumer<QOhosJsState &, QNapi::Number>)> requestsHandler)
override;
328 void setDestroyFromSystemAllowed(QNapi::Object qAbility,
bool destroyAllowed)
override;
330 void startNoUiChildProcess(
const std::string &libraryName,
const std::vector<std::string> &args)
override;
335 template<
typename PeerMatchFunc>
336 std::shared_ptr<
QAbilityPeer> tryFindMatchingQAbilityPeer(PeerMatchFunc &&matchFunc);
338 void *getAttachedObjectWithLazyCreate(
339 const std::type_info &objectTypeInfo, QOhosSupplier<std::shared_ptr<
void>> objectFactory)
override;
341 std::tuple<QNapi::Object, std::string> extractModuleFromEvalExpr(
const std::string &expr)
override;
342 QNapi::Number mapOhosEnumToJs(
int enumValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
override;
343 std::optional<
int> tryMapOhosEnumFromJs(QNapi::Number enumJsValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
override;
344 int mapOhosEnumFromJs(QNapi::Number enumJsValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
override;
345 QNapi::Symbol getJsSymbolForType(
const std::type_info &typeInfo)
override;
347 const std::vector<std::pair<
int,
double>> &getOhosEnumEnumerators(
348 const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)());
349 std::vector<std::pair<
int,
double>> resolveOhosEnumEnumerators(
const OhosEnumInfo &enumInfo);
351 void forceLoadAllJsModulesIfRequested();
353 pthread_t m_jsThread = {};
354 napi_env m_env =
nullptr;
355 QNapi::Reference<QNapi::Object> m_appLaunchWant;
356 QNapi::Reference<QNapi::Object> m_optAppLaunchParam;
358 std::map<std::string, std::shared_ptr<QAbilityPeer>> m_qAbilityPeers;
359 std::map<std::string, std::function<QNapi::Object(JsState &)>> m_jsModulesFactories;
361 PreQueuingJsTasksExecutor m_tasksExecutor;
362 std::vector<QOhosConsumer<JsState &, QNapi::Object, QNapi::Object>> m_newWantConsumers;
363 std::map<std::type_index, std::vector<std::pair<
int,
double>>> m_ohosEnumsEnumerators;
364 std::map<std::type_index, QNapi::Reference<QNapi::Symbol>> m_jsSymbolsRefs;
366 std::map<std::type_index, std::shared_ptr<
void>> m_attachedObjects;
369bool JsStateImpl::isJsThread()
371 return pthread_self() == m_jsThread;
374void JsStateImpl::initInJsThread(
375 napi_env env, std::map<std::string, QNapi::Reference<QNapi::Function>> &&etsModulesFactories,
376 std::shared_ptr<AppFunctions> appFunctions, QtRunMode qtRunMode)
378 m_jsThread = pthread_self();
380 m_defaultQAbilityPeer = std::make_shared<DummyQAbilityPeer>();
381 m_appFunctions = appFunctions;
382 m_qtRunMode = qtRunMode;
384 for (
const char *moduleName : napiLoadableModules) {
385 m_jsModulesFactories.emplace(
387 makeMemoizingJsValueSupplier<QNapi::Object>(
388 [moduleName](JsState &jsState) {
389 return loadJsModuleViaNapiOrFail(jsState.env(), moduleName);
392 for (
auto &etsModulesFactoryEntry : etsModulesFactories) {
393 const std::string &moduleName = etsModulesFactoryEntry.first;
394 auto etsModuleFactory = moveToSharedPtr(std::move(etsModulesFactoryEntry.second));
395 m_jsModulesFactories.emplace(
397 makeMemoizingJsValueSupplier<QNapi::Object>(
398 [moduleName, etsModuleFactory](JsState &) {
399 return loadJsModuleViaEtsFactoryOrFail(etsModuleFactory->Value(), moduleName);
402 m_jsModulesFactories.emplace(
404 [](JsState &jsState) -> QNapi::Object {
405 return Napi::Env(jsState.env()).Global();
408 m_tasksExecutor.setUnderlyingExecutor(makeJsThreadTasksExecutor(
this));
411void JsStateImpl::addQAbilityPeerInJsThread(
std::shared_ptr<
QAbilityPeer> qAbilityPeer)
413 forceLoadAllJsModulesIfRequested();
415 if (m_appLaunchWant.IsEmpty())
416 m_appLaunchWant = Napi::Persistent(qAbilityPeer->launchWant());
419 if (m_optAppLaunchParam.IsEmpty() && optQUiAbilityPeer) {
420 m_optAppLaunchParam = QNapi::Reference<>::makePersistentFrom(optQUiAbilityPeer->launchParam());
422 "JsStateImpl: appLaunchParam set from Ability with instanceId='%s'",
423 qAbilityPeer->instanceId().c_str());
426 if (m_qAbilityPeers.empty())
427 m_defaultQAbilityPeer = qAbilityPeer;
429 m_qAbilityPeers.emplace(qAbilityPeer->instanceId(), qAbilityPeer);
432 "JsStateImpl: added QAbilityPeer, instanceId: %s, qwindow: %s",
433 qAbilityPeer->instanceId().c_str(), qAbilityPeer->qWindowRef().refName().c_str());
436void JsStateImpl::removeMatchingQAbilityPeerInJsThread(QNapi::Object qAbility)
438 Napi::HandleScope funcScope{qAbility.Env()};
440 auto foundPeerIter =
std::find_if(
441 m_qAbilityPeers.begin(), m_qAbilityPeers.end(),
442 [&](
const auto &entry) {
443 return entry.second->qAbility() == qAbility;
446 if (foundPeerIter != m_qAbilityPeers.end()) {
447 auto removedPeer = foundPeerIter->second;
448 m_qAbilityPeers.erase(foundPeerIter);
449 if (m_defaultQAbilityPeer == removedPeer) {
450 m_defaultQAbilityPeer =
451 !m_qAbilityPeers.empty()
452 ? m_qAbilityPeers.begin()->second
453 : std::make_shared<DummyQAbilityPeer>();
456 "JsStateImpl: removed QAbilityPeer, instanceId: %s, qwindow: %s",
457 removedPeer->instanceId().c_str(), removedPeer->qWindowRef().refName().c_str());
461void JsStateImpl::dispatchNewWantInJsThread(QNapi::Object want, QNapi::Object launchParam)
463 const auto consumersCount = m_newWantConsumers.size();
464 for (std::size_t i = 0; i < consumersCount; ++i)
465 m_newWantConsumers[i](*
this, want, launchParam);
468void JsStateImpl::invokeTask(
std::function<
void(
JsState &)> &&task)
470 m_tasksExecutor.invokeTask(std::move(task));
473napi_env JsStateImpl::env()
478void JsStateImpl::forceLoadAllJsModulesIfRequested()
480 if (qEnvironmentVariableIntValue(forceLoadJsModulesEnvVariableName) == 0)
483 qOhosPrintfDebug(
"%s: forcibly loading all JS modules", Q_FUNC_INFO);
485 for (
const auto &moduleFactoryEntry : m_jsModulesFactories)
486 getModule(moduleFactoryEntry.first);
489QNapi::Object JsStateImpl::getModule(
const std::string &moduleName)
491 using namespace std::string_literals;
493 auto moduleFactoryIter = m_jsModulesFactories.find(moduleName);
494 if (moduleFactoryIter == m_jsModulesFactories.end())
495 throw QNapi::makeLoggedException(m_env,
"JS module not found: "s + moduleName);
497 return moduleFactoryIter->second(*
this);
500QNapi::Object JsStateImpl::appLaunchWant()
502 return m_appLaunchWant.Value();
505std::optional<QNapi::Object> JsStateImpl::optAppLaunchParam()
507 return !m_optAppLaunchParam.IsEmpty()
508 ? std::optional(m_optAppLaunchParam.Value())
512QNapi::Object JsStateImpl::defaultWindowStageOrEmpty()
516 return QNapi::Object();
518 return qUiAbilityPeer->windowStage();
521QNapi::Object JsStateImpl::defaultUiContextOrEmpty()
523 return m_defaultQAbilityPeer->uiContext();
528 return m_defaultQAbilityPeer;
531std::shared_ptr<
QAbilityPeer> JsStateImpl::tryGetQAbilityPeerByInstanceId(
const std::string &instanceId)
533 auto foundIter = m_qAbilityPeers.find(instanceId);
534 return foundIter != m_qAbilityPeers.end() ? foundIter->second :
nullptr;
537std::shared_ptr<
QAbilityPeer> JsStateImpl::tryGetQAbilityPeerByInstance(QNapi::Object qAbility)
539 Napi::HandleScope findPeerScope(qAbility.Env());
540 return tryFindMatchingQAbilityPeer(
541 [&](
const auto &peer) {
542 return peer->qAbility() == qAbility;
546std::shared_ptr<
QAbilityPeer> JsStateImpl::tryGetQAbilityPeerByQWindow(QObjectThreadSafeRef qwindow)
548 auto optQAbilityPeer = tryFindMatchingQAbilityPeer(
549 [&](
const auto &peer) {
550 return peer->qWindowRef() == qwindow;
553 return optQAbilityPeer;
556 auto optJsWindowRef = jsWindowRegistry.tryFindJsWindowByQWindowRef(qwindow);
557 return optJsWindowRef
558 ? tryGetQAbilityPeerByInstanceId(optJsWindowRef->owningQAbilityInstanceId())
562std::optional<QNapi::Object> JsStateImpl::tryGetJsWindowByQWindow(QObjectThreadSafeRef qwindow)
565 auto optJsWindowRef = jsWindowRegistry.tryFindJsWindowByQWindowRef(qwindow);
566 return optJsWindowRef ?
std::optional(optJsWindowRef->jsObject()) :
std::nullopt;
569std::optional<QNapi::Object> JsStateImpl::tryGetQAbilityByQWindow(QObjectThreadSafeRef qwindow)
571 auto qAbilityPeer = tryGetQAbilityPeerByQWindow(qwindow);
572 return qAbilityPeer ?
std::optional(qAbilityPeer->qAbility()) :
std::nullopt;
575std::optional<QNapi::Object> JsStateImpl::defaultQAbility()
577 auto qAbility = defaultQAbilityPeer()->qAbility();
578 return !qAbility.IsEmpty() ?
std::optional(qAbility) :
std::nullopt;
581void JsStateImpl::setOnContinueRequestsHandler(
582 QNapi::Object qAbility,
583 std::function<
void(QOhosJsState &, QNapi::Object, QOhosConsumer<QOhosJsState &, QNapi::Number>)> requestsHandler)
586 tryGetQAbilityPeerByInstance(qAbility));
587 if (!uiAbilityPeer) {
589 "%s: no QUiAbilityPeer for the given qAbility, handler not set", Q_FUNC_INFO);
593 uiAbilityPeer->setOnContinueRequestsHandler(
594 [requestsHandler = std::move(requestsHandler)](
595 JsState &jsState, QNapi::Object wantParams,
596 QOhosConsumer<JsState &, QOhosAbilityOnContinueResult> resultConsumer) {
599 [resultConsumer = std::move(resultConsumer)](QOhosJsState &resultJsState, QNapi::Number result) {
600 auto optResultEnum = resultJsState.tryMapOhosEnumFromJs<QOhosAbilityOnContinueResult>(result);
601 if (!optResultEnum) {
603 "%s: got invalid OnContinueResult value (%f) from requests handler, rejecting the request",
604 Q_FUNC_INFO, result.DoubleValue());
607 static_cast<
JsState &>(resultJsState),
608 optResultEnum.value_or(QOhosAbilityOnContinueResult::REJECT));
613void JsStateImpl::setDestroyFromSystemAllowed(QNapi::Object qAbility,
bool destroyAllowed)
615 auto abilityPeer = tryGetQAbilityPeerByInstance(qAbility);
618 "%s: no QAbilityPeer for the given qAbility, flag not set to %s",
622 abilityPeer->destroyAllowedFlag()->store(destroyAllowed);
625void JsStateImpl::visitEachQAbilityPeer(
const std::function<
void(
std::shared_ptr<
QAbilityPeer>)> &visitor)
627 auto qAbilityPeersSnapshot = m_qAbilityPeers;
628 for (
const auto &qAbilityPeerEntry : qAbilityPeersSnapshot)
629 visitor(qAbilityPeerEntry.second);
632void JsStateImpl::startNewQAbilityInstance(
633 QNapi::Object baseQAbility, QObjectThreadSafeRef qwindow,
634 QNapi::Object optStartOptions,
637 m_appFunctions->startQAbilityInstance(
638 baseQAbility, qwindow, optStartOptions, std::move(startupNotifyFunc));
641void JsStateImpl::startAppProcess(
642 const std::string &processId, QNapi::Object requestWant,
643 QNapi::Object optStartOptions, std::function<
void(QOhosJsState &)> continueFunc)
645 auto optBaseQAbility = defaultQAbility();
646 if (optBaseQAbility) {
647 m_appFunctions->startAppProcess(
648 optBaseQAbility.value(), processId, requestWant, optStartOptions, std::move(continueFunc));
650 invokeTask(
std::move(continueFunc));
654void JsStateImpl::addNewWantConsumer(QOhosConsumer<QOhosJsState &, QNapi::Object, QNapi::Object> wantConsumer)
656 m_newWantConsumers.push_back(std::move(wantConsumer));
659void JsStateImpl::startNoUiChildProcess(
const std::string &libraryName,
const std::vector<std::string> &args)
661 m_appFunctions->startNoUiChildProcess(*
this, libraryName, args);
669template<
typename PeerMatchFunc>
670std::shared_ptr<
QAbilityPeer> JsStateImpl::tryFindMatchingQAbilityPeer(PeerMatchFunc &&matchFunc)
672 auto foundIter =
std::find_if(
673 m_qAbilityPeers.begin(), m_qAbilityPeers.end(),
674 [&](
const auto &entry) {
675 return matchFunc(entry.second);
677 return foundIter != m_qAbilityPeers.end() ? foundIter->second :
nullptr;
680void *JsStateImpl::getAttachedObjectWithLazyCreate(
681 const std::type_info &objectTypeInfo, QOhosSupplier<std::shared_ptr<
void>> objectFactory)
683 auto objectIter = m_attachedObjects.find(objectTypeInfo);
684 if (objectIter == m_attachedObjects.end())
685 std::tie(objectIter, std::ignore) = m_attachedObjects.emplace(objectTypeInfo, objectFactory());
687 return objectIter->second.get();
690std::tuple<QNapi::Object, std::string> JsStateImpl::extractModuleFromEvalExpr(
const std::string &expr)
692 using namespace std::string_literals;
694 std::size_t longestModulePrefixSize = 0;
695 auto considerAsLongestPrefix = [&](
const std::string &name) {
696 if (name.size() > longestModulePrefixSize
697 && expr.compare(0, name.size(), name) == 0
698 && (expr.size() == name.size() || expr[name.size()] ==
'.')) {
699 longestModulePrefixSize = name.size();
702 for (
const auto &moduleFactoryEntry : m_jsModulesFactories)
703 considerAsLongestPrefix(moduleFactoryEntry.first);
705 if (longestModulePrefixSize == 0) {
706 throw QNapi::makeLoggedException(
707 env(),
"global expression doesn't start with known module path: '"s + expr +
"'");
710 return std::make_tuple(
711 getModule(expr.substr(0, longestModulePrefixSize)),
712 longestModulePrefixSize < expr.size()
713 ? expr.substr(longestModulePrefixSize + 1)
717QNapi::Number JsStateImpl::mapOhosEnumToJs(
718 int enumValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
720 using namespace std::string_literals;
722 const auto &enumeratorsValues = getOhosEnumEnumerators(enumTypeInfo, ohosEnumInfoFactory);
724 auto valueIter =
std::find_if(
725 enumeratorsValues.begin(), enumeratorsValues.end(),
726 [&](
const auto &valuePair) {
727 return valuePair.first == enumValue;
730 if (valueIter == enumeratorsValues.end()) {
731 throw QNapi::makeLoggedException(
733 "Illegal C++ value of enumerator for enum '"s + enumTypeInfo.name() +
"': "
734 + std::to_string(enumValue));
737 return QNapi::Number::New(env(), valueIter->second);
740std::optional<
int> JsStateImpl::tryMapOhosEnumFromJs(
741 QNapi::Number enumJsValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
743 double enumDoubleJsValue = enumJsValue.DoubleValue();
745 const auto &enumeratorsValues = getOhosEnumEnumerators(enumTypeInfo, ohosEnumInfoFactory);
747 auto valueIter =
std::find_if(
748 enumeratorsValues.begin(), enumeratorsValues.end(),
749 [&](
const auto &valuePair) {
750 return valuePair.second == enumDoubleJsValue;
753 return valueIter != enumeratorsValues.end()
754 ?
std::optional(valueIter->first)
758int JsStateImpl::mapOhosEnumFromJs(
759 QNapi::Number enumJsValue,
const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
761 using namespace std::string_literals;
763 auto optEnumValue = tryMapOhosEnumFromJs(enumJsValue, enumTypeInfo, ohosEnumInfoFactory);
764 if (!optEnumValue.has_value()) {
765 throw QNapi::makeLoggedException(
767 "Illegal Napi value of enumerator for enum '"s + enumTypeInfo.name() +
"': "
768 + std::to_string(enumJsValue.DoubleValue()));
770 return optEnumValue.value();
773QNapi::Symbol JsStateImpl::getJsSymbolForType(
const std::type_info &typeInfo)
775 using namespace std::string_literals;
777 auto jsSymbolRefIter = m_jsSymbolsRefs.find(typeInfo);
778 if (jsSymbolRefIter == m_jsSymbolsRefs.end()) {
779 std::tie(jsSymbolRefIter, std::ignore) = m_jsSymbolsRefs.emplace(
781 QNapi::Reference<>::makePersistentFrom(
782 QNapi::Symbol::New(env(),
"_io_qt_"s + typeInfo.name())));
784 return jsSymbolRefIter->second.Value();
787const std::vector<std::pair<
int,
double>> &JsStateImpl::getOhosEnumEnumerators(
788 const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
790 auto enumeratorsIter = m_ohosEnumsEnumerators.find(enumTypeInfo);
791 if (enumeratorsIter == m_ohosEnumsEnumerators.end()) {
792 std::tie(enumeratorsIter, std::ignore) = m_ohosEnumsEnumerators.emplace(
793 enumTypeInfo, resolveOhosEnumEnumerators(ohosEnumInfoFactory()));
796 return enumeratorsIter->second;
799std::vector<std::pair<
int,
double>> JsStateImpl::resolveOhosEnumEnumerators(
const OhosEnumInfo &enumInfo)
801 using namespace std::string_literals;
803 QNapi::Object enumObj;
805 enumObj = eval<QNapi::Object>(enumInfo.fullTypeName);
806 }
catch (
const Napi::Error &e) {
807 throw QNapi::makeLoggedException(
808 env(),
"JS enum '"s + enumInfo.fullTypeName +
"' not found: "s + e.what());
811 std::vector<std::pair<
int,
double>> enumeratorsValues;
813 for (
const auto &enumNamePair : enumInfo.enumeratorsNames) {
814 const auto *enumeratorName = enumNamePair.second;
816 QNapi::Value jsEnumeratorValue = QNapi::getPropOrUndefined(enumObj, enumeratorName);
818 if (!jsEnumeratorValue.IsNumber()) {
819 throw QNapi::makeLoggedException(
820 env(),
"JS enum '"s + enumInfo.fullTypeName +
"' doesn't contain: "s + enumeratorName);
823 enumeratorsValues.emplace_back(
824 enumNamePair.first, QNapi::checkedCast<QNapi::Number>(jsEnumeratorValue).DoubleValue());
827 return enumeratorsValues;
830JsStateImpl &getJsStateImpl()
832 static JsStateImpl jsStateImpl;
838 static auto qtJsBlockingCallsGateway = QOhosMtBlockingCallsGateway<JsState>::makeInstance(
839 invokeInQtThread, invokeInJsThread);
840 return *qtJsBlockingCallsGateway;
843class JsThreadOpsImpl :
public ::QOhosJsThreadOps
846 QOhosJsState &jsState()
override;
848 void invoke(std::function<
void(QOhosJsState &)> task)
override;
849 void invokeAndWaitForContinue(
850 std::function<
void(QOhosJsState &, QOhosTaskPromise<>)> &&task,
851 std::string callerContextName)
override;
853 const std::function<
void(QOhosJsState &)> &task,
854 std::string callerContextName)
override;
857QOhosJsState &JsThreadOpsImpl::jsState()
859 return getJsStateImpl();
862void JsThreadOpsImpl::invoke(std::function<
void(QOhosJsState &)> task)
867void JsThreadOpsImpl::invokeAndWaitForContinue(
868 std::function<
void(QOhosJsState &, QOhosTaskPromise<>)> &&task,
869 std::string callerContextName)
874void JsThreadOpsImpl::runAndWait(
875 const std::function<
void(QOhosJsState &)> &task,
876 std::string callerContextName)
897 return matchTypeIdObject == &typeIdObject ?
this :
nullptr;
907 qAbilityPeer->tryCastWithTypeIdObject(&typeIdObject));
909 return qUiAbilityPeer !=
nullptr
910 ? qUiAbilityPeer->shared_from_this()
924 return getJsStateImpl();
929 qOhosPrintfDebug(
"Tagging JS Window as closing (from: %s)", logContext);
930 Napi::HandleScope funcScope{jsWindow.Env()};
932 getJsWindowsTrackerIsClosingPropSymbol(getJsStateImpl()),
933 QNapi::Boolean::New(jsWindow.Env(),
true));
938 Napi::HandleScope funcScope{jsWindow.Env()};
939 auto isClosingPropSymbol = getJsWindowsTrackerIsClosingPropSymbol(getJsStateImpl());
940 auto isClosingValue = jsWindow.Has(isClosingPropSymbol)
941 ? jsWindow.Get(isClosingPropSymbol)
943 return isClosingValue.IsBoolean() && QNapi::checkedCast<QNapi::Boolean>(isClosingValue).Value();
950 static JsThreadOpsImpl jsThreadOpsImpl;
951 QOhosJsThreadOps::registerInstance(&jsThreadOpsImpl);
953 getJsStateImpl().initInJsThread(env,
std::move(jsModulesFactories), appFunctions, qtRunMode);
958 getJsStateImpl().addQAbilityPeerInJsThread(qAbilityPeer);
963 getJsStateImpl().removeMatchingQAbilityPeerInJsThread(qAbility);
968 getJsStateImpl().dispatchNewWantInJsThread(want, launchParam);
973 getJsStateImpl().invokeTask(
std::move(task));
977 std::function<
void(
JsState &, QOhosTaskPromise<>)> &&task,
978 std::string callerContextName)
980 const auto funcInfo = Q_FUNC_INFO;
982 struct JsTaskCompletionState
985 bool outerTaskPromiseSettled =
false;
986 std::optional<
std::string> jsThreadExceptionMsg;
988 auto completionState =
std::make_shared<JsTaskCompletionState>();
990 auto catchingTaskWrapper =
991 [task = std::move(task), completionState, callerContextName, funcInfo](
992 JsState &jsState, QOhosTaskPromise<> outerTaskPromise) {
993 auto sharedOuterTaskPromise =
994 std::make_shared<QOhosTaskPromise<>>(std::move(outerTaskPromise));
996 auto settleOuterTaskPromise =
997 [completionState, sharedOuterTaskPromise](
std::optional<
std::string> exceptionMsg) {
999 std::lock_guard<
std::mutex> lock(completionState->mutex);
1000 if (completionState->outerTaskPromiseSettled)
1002 completionState->outerTaskPromiseSettled =
true;
1003 completionState->jsThreadExceptionMsg =
std::move(exceptionMsg);
1005 (*sharedOuterTaskPromise)();
1008 auto innerTaskPromise = QOhosTaskPromise<>(
1009 [settleOuterTaskPromise]() {
1010 settleOuterTaskPromise({});
1012 [funcInfo, callerContextName]() {
1013 if (
std::uncaught_exceptions() == 0) {
1014 qOhosReportFatalErrorAndAbort(
1015 "%s: promise destroyed without notifying the caller: %s",
1016 funcInfo, callerContextName.c_str());
1022 task(jsState,
std::move(innerTaskPromise));
1023 }
catch (
const std::exception &e) {
1024 settleOuterTaskPromise(std::optional<std::string>(e.what()));
1026 settleOuterTaskPromise(std::optional<std::string>(
"<unknown exception>"));
1030 if (getQtState().isQtThread()) {
1031 getQtJsBlockingCallsGateway().runInSlaveThreadAndWaitForContinue(
1032 std::move(catchingTaskWrapper), callerContextName);
1034 auto taskReadyPromise =
std::make_shared<
std::promise<
void>>();
1035 auto taskReadyFuture = taskReadyPromise->get_future();
1037 auto sharedTaskPromise = std::make_shared<QOhosTaskPromise<>>(
1038 [taskReadyPromise]() {
1039 taskReadyPromise->set_value();
1041 [funcInfo, callerContextName]() {
1042 qOhosReportFatalErrorAndAbort(
1043 "%s: promise destroyed without notifying the caller: %s",
1044 funcInfo, callerContextName.c_str());
1049 [catchingTaskWrapper = std::move(catchingTaskWrapper), sharedTaskPromise](
JsState &jsState) {
1050 catchingTaskWrapper(jsState,
std::move(*sharedTaskPromise));
1052 taskReadyFuture.wait();
1055 if (completionState->jsThreadExceptionMsg) {
1056 qOhosReportFatalErrorAndAbort(
1057 "%s: exception from task invoked in JS thread (caller: \"%s\"): %s",
1058 Q_FUNC_INFO, callerContextName.c_str(),
1059 completionState->jsThreadExceptionMsg.value().c_str());
1064 const std::function<
void(
JsState &)> &task,
std::string callerContextName)
1066 if (getJsStateImpl().isJsThread()) {
1067 task(getJsStateImpl());
1069 invokeInJsThreadAndWaitForContinue(
1070 [&](
auto &jsState,
auto taskPromise) {
1074 std::move(callerContextName));
1079 std::function<
void(
std::function<
void()>)> &&task,
1080 std::chrono::nanoseconds timeout)
1082 if (getJsStateImpl().isJsThread()) {
1083 auto result = getQtJsBlockingCallsGateway().tryInvokeInMasterThreadAndTryWaitForContinue(
std::move(task), timeout);
1084 return result == QOhosMtBlockingCallsGateway<JsState>::MasterThreadTaskResult::Finished;
1086 auto taskReadyPromise =
std::make_shared<
std::promise<
void>>();
1087 auto taskReadyFuture = taskReadyPromise->get_future();
1089 auto continueFunc = [taskReadyPromise]() {
1090 taskReadyPromise->set_value();
1094 [task = std::move(task), continueFunc = std::move(continueFunc)]()
mutable {
1095 task(
std::move(continueFunc));
1097 auto waitResult = taskReadyFuture.wait_for(timeout);
1099 return waitResult ==
std::future_status::ready;
JsState & jsState() const
virtual ~QAbilityEngine()
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 invokeInJsThread(std::function< void(JsState &)> task)
void dispatchNewWant(QNapi::Object want, QNapi::Object launchParam)
void removeMatchingJsQAbilityPeer(QNapi::Object qAbility)
void runInJsThreadAndWait(const std::function< void(JsState &)> &task, std::string callerContextName={})
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)
void invokeInJsThreadAndWaitForContinue(std::function< void(JsState &, QOhosTaskPromise<>)> &&task, std::string callerContextName={})
void addJsQAbilityPeer(std::shared_ptr< QAbilityPeer > qAbilityPeer)