Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qohosplugincore.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qohosplugincore.h>
5
7#include <QtCore/private/qohoslogger_p.h>
8#include <algorithm>
9#include <deque>
10#include <exception>
11#include <future>
12#include <mutex>
13#include <napi.h>
14#include <napi/native_api.h>
15#include <optional>
16#include <stdexcept>
17#include <tuple>
18#include <typeindex>
19#include <utility>
20#include <vector>
21
22QT_BEGIN_NAMESPACE
23
24namespace QtOhos {
25
26namespace {
27
28constexpr const char *forceLoadJsModulesEnvVariableName = "IO__QT__OHOS__FORCE_LOAD_JS_MODULES";
29
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",
44 "@ohos.deviceInfo",
45 "@ohos.display",
46 "@ohos.file.fileuri",
47 "@ohos.file.picker",
48 "@ohos.font",
49 "@ohos.geoLocationManager",
50 "@ohos.graphics.text",
51 "@ohos.i18n",
52 "@ohos.inputMethod",
53 "@ohos.intl",
54 "@ohos.multimedia.image",
55 "@ohos.multimedia.media",
56 "@ohos.multimodalInput.inputDevice",
57 "@ohos.multimodalInput.pointer",
58 "@ohos.net.connection",
59 "@ohos.notificationManager",
60 "@ohos.pasteboard",
61 "@ohos.settings",
62 "@ohos.web.webview",
63 "@ohos.wifiManager",
64 "@ohos.window",
65};
66
67class DummyQAbilityPeer : public QAbilityPeer
68{
69public:
70 DummyQAbilityPeer() = default;
71
72 std::string instanceId() override;
73 QNapi::Object uiContext() override;
74 QNapi::Object qAbility() override;
75 QNapi::Object launchWant() override;
76 QObjectThreadSafeRef qWindowRef() override;
77 QOhosOptional<QNapi::Promise> qWindowDestroyPromise() override;
78 void forceResolveQWindowDestroyPromiseIfPresent(Napi::Env env) override;
79 std::shared_ptr<std::atomic_bool> destroyAllowedFlag() override;
80 bool isTerminating() override;
81
82 void setQWindow(Napi::Env env, QObjectThreadSafeRef) override;
83
84 void *tryCastWithTypeIdObject(const void *matchTypeIdObject) override;
85};
86
87std::string DummyQAbilityPeer::instanceId()
88{
89 return {};
90}
91
92QNapi::Object DummyQAbilityPeer::uiContext()
93{
94 return QNapi::Object();
95}
96
97QNapi::Object DummyQAbilityPeer::qAbility()
98{
99 return QNapi::Object();
100}
101
102QNapi::Object DummyQAbilityPeer::launchWant()
103{
104 return QNapi::Object();
105}
106
107QObjectThreadSafeRef DummyQAbilityPeer::qWindowRef()
108{
109 return {};
110}
111
112QOhosOptional<QNapi::Promise> DummyQAbilityPeer::qWindowDestroyPromise()
113{
114 return {};
115}
116
117void DummyQAbilityPeer::forceResolveQWindowDestroyPromiseIfPresent(Napi::Env)
118{
119}
120
121std::shared_ptr<std::atomic_bool> DummyQAbilityPeer::destroyAllowedFlag()
122{
123 return std::make_shared<std::atomic_bool>(false);
124}
125
126bool DummyQAbilityPeer::isTerminating()
127{
128 return false;
129}
130
131void DummyQAbilityPeer::setQWindow(Napi::Env, QObjectThreadSafeRef)
132{
133}
134
135void *DummyQAbilityPeer::tryCastWithTypeIdObject(const void *)
136{
137 return nullptr;
138}
139
140template<typename Task>
141class PreQueuingTasksExecutor
142{
143public:
144 void setUnderlyingExecutor(
145 std::function<void(Task)> executor,
146 std::function<void(Task)> optSyncFlushExecutor = nullptr);
147 void invokeTask(Task task);
148
149private:
150 std::recursive_mutex m_executorMutex;
151 std::function<void(Task)> m_optUnderlyingExecutor;
152 std::deque<Task> m_pendingTasks;
153};
154
155template<typename Task>
156void PreQueuingTasksExecutor<Task>::setUnderlyingExecutor(
157 std::function<void(Task)> executor, std::function<void(Task)> optSyncFlushExecutor)
158{
159 std::lock_guard<std::recursive_mutex> executorLock(m_executorMutex);
160 auto &flushExecutor = optSyncFlushExecutor ? optSyncFlushExecutor : executor;
161 while (!m_pendingTasks.empty()) {
162 auto task = std::move(m_pendingTasks.front());
163 m_pendingTasks.pop_front();
164 flushExecutor(std::move(task));
165 }
166 m_optUnderlyingExecutor = std::move(executor);
167}
168
169template<typename Task>
170void PreQueuingTasksExecutor<Task>::invokeTask(Task task)
171{
172 std::lock_guard<std::recursive_mutex> executorLock(m_executorMutex);
173 if (m_optUnderlyingExecutor)
174 m_optUnderlyingExecutor(std::move(task));
175 else
176 m_pendingTasks.push_back(std::move(task));
177}
178
179using PreQueuingJsTasksExecutor = PreQueuingTasksExecutor<std::function<void(JsState &)>>;
180
181std::function<void(std::function<void(JsState &)>)> makeJsThreadTasksExecutor(JsState *jsState)
182{
183 struct Context {
184 std::mutex pendingTasksMutex;
185 bool pendingTasksRunnerActive = false;
186 std::vector<std::function<void(JsState &)>> pendingTasks;
187 Napi::ThreadSafeFunction executorThreadSafeFunc;
188 };
189 auto ctx = std::make_shared<Context>();
190
191 ctx->executorThreadSafeFunc = Napi::ThreadSafeFunction::New(
192 jsState->env(),
193 QNapi::Function::New(
194 jsState->env(),
195 [ctx = ctx.get()](const CallbackInfo &cbInfo) {
196 while (true) {
197 std::vector<std::function<void(JsState &)>> pendingTasks;
198 {
199 std::lock_guard<std::mutex> pendingTasksLock(ctx->pendingTasksMutex);
200 pendingTasks = std::exchange(ctx->pendingTasks, {});
201 if (pendingTasks.empty()) {
202 ctx->pendingTasksRunnerActive = false;
203 break;
204 }
205 }
206 for (auto &task : pendingTasks)
207 task(cbInfo.jsState());
208 }
209 },
210 "QCoreOhosTasksExecutorFunc"),
211 "QCoreOhosTasksExecutor", 0, 1);
212
213 return [ctx](std::function<void(JsState &)> task) {
214 std::lock_guard<std::mutex> pendingTasksLock(ctx->pendingTasksMutex);
215 ctx->pendingTasks.push_back(std::move(task));
216 if (!ctx->pendingTasksRunnerActive) {
217 ctx->executorThreadSafeFunc.NonBlockingCall();
218 ctx->pendingTasksRunnerActive = true;
219 }
220 };
221}
222
223QNapi::Symbol getJsWindowsTrackerIsClosingPropSymbol(JsState &jsState)
224{
225 struct SymbolTag
226 {
227 };
228
229 return jsState.getJsSymbolForType<SymbolTag>();
230}
231
232QNapi::Object loadJsModuleViaNapiOrFail(napi_env env, const std::string &moduleName)
233{
234 qOhosPrintfDebug("%s: loading napi module '%s' via napi_load_module()", Q_FUNC_INFO, moduleName.c_str());
235
236 napi_value result = nullptr;
237 auto status = napi_load_module(env, moduleName.c_str(), &result);
238 if (status != napi_ok) {
239 qOhosReportFatalErrorAndAbort(
240 "%s: napi_load_module failed for module '%s' (status=%d)",
241 Q_FUNC_INFO, moduleName.c_str(), static_cast<int>(status));
242 }
243
244 return QNapi::Object(env, result);
245}
246
247QNapi::Object loadJsModuleViaEtsFactoryOrFail(
248 const QNapi::Function &etsModuleFactory, const std::string &moduleName)
249{
250 qOhosPrintfDebug("%s: loading napi module '%s' via ets factory", Q_FUNC_INFO, moduleName.c_str());
251
252 auto result = etsModuleFactory.Call({});
253 if (!result.IsObject()) {
254 qOhosReportFatalErrorAndAbort(
255 "%s: ets factory for module '%s' did not return an object",
256 Q_FUNC_INFO, moduleName.c_str());
257 }
258
259 return QNapi::checkedCast<QNapi::Object>(result);
260}
261
262template<typename T>
263std::function<T(JsState &)> makeMemoizingJsValueSupplier(std::function<T(JsState &)> baseJsSupplier)
264{
265 auto memoizedValue = moveToSharedPtr(QNapi::Reference<T>());
266 return [baseJsSupplier = std::move(baseJsSupplier), memoizedValue](JsState &jsState) mutable {
267 if (memoizedValue->IsEmpty()) {
268 *memoizedValue = QNapi::Reference<T>::makePersistentFrom(baseJsSupplier(jsState));
269 baseJsSupplier = nullptr;
270 }
271 return memoizedValue->Value();
272 };
273}
274
275class JsStateImpl : public JsState
276{
277public:
278 JsStateImpl() = default;
279
280 void initInJsThread(
281 napi_env env, std::map<std::string, QNapi::Reference<QNapi::Function>> &&etsModulesFactories,
282 std::shared_ptr<AppFunctions> appFunctions, QtRunMode qtRunMode);
283
284 bool isJsThread();
285
286 void addQAbilityPeerInJsThread(std::shared_ptr<QAbilityPeer> qAbilityPeer);
287 void removeMatchingQAbilityPeerInJsThread(QNapi::Object qAbility);
288
289 void dispatchNewWantInJsThread(QNapi::Object want, QNapi::Object launchParam);
290
291 void invokeTask(std::function<void(JsState &)> &&task);
292
293 napi_env env() override;
294 QNapi::Object getModule(const std::string &moduleName) override;
295
296 QNapi::Object appLaunchWant() override;
297 QOhosOptional<QNapi::Object> optAppLaunchParam() override;
298
299 QNapi::Object defaultWindowStageOrEmpty() override;
300 QNapi::Object defaultUiContextOrEmpty() override;
301
302 std::shared_ptr<QAbilityPeer> defaultQAbilityPeer() override;
303 std::shared_ptr<QAbilityPeer> tryGetQAbilityPeerByInstanceId(const std::string &instanceId) override;
304 std::shared_ptr<QAbilityPeer> tryGetQAbilityPeerByInstance(QNapi::Object qAbility) override;
305 std::shared_ptr<QAbilityPeer> tryGetQAbilityPeerByQWindow(QObjectThreadSafeRef qwindow) override;
306
307 void visitEachQAbilityPeer(const std::function<void(std::shared_ptr<QAbilityPeer>)> &visitor) override;
308
309 void startNewQAbilityInstance(
310 std::shared_ptr<QAbilityPeer> baseQAbilityPeer, QObjectThreadSafeRef qwindow,
311 QNapi::Object optStartOptions,
312 std::function<void(JsState &, std::shared_ptr<QAbilityPeer>)> startupNotifyFunc) override;
313
314 void startAppProcess(
315 const std::string &processId, QNapi::Object requestWant,
316 QNapi::Object optStartOptions) override;
317
318 void startAppProcess(
319 const std::string &processId, QNapi::Object requestWant,
320 QNapi::Object optStartOptions, std::function<void(JsState &)> continueFunc) override;
321
322 void addNewWantConsumer(QOhosConsumer<JsState &, QNapi::Object, QNapi::Object> wantConsumer) override;
323
324 void startNoUiChildProcess(const std::string &libraryName, const std::vector<std::string> &args) override;
325
326 QtRunMode qtRunMode() override;
327
328private:
329 template<typename PeerMatchFunc>
330 std::shared_ptr<QAbilityPeer> tryFindMatchingQAbilityPeer(PeerMatchFunc &&matchFunc);
331
332 void *getAttachedObjectWithLazyCreate(
333 const std::type_info &objectTypeInfo, QOhosSupplier<std::shared_ptr<void>> objectFactory) override;
334
335 std::tuple<QNapi::Object, std::string> extractModuleFromEvalExpr(const std::string &expr) override;
336 QNapi::Number mapOhosEnumToJs(int enumValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) override;
337 std::optional<int> tryMapOhosEnumFromJs(QNapi::Number enumJsValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) override;
338 int mapOhosEnumFromJs(QNapi::Number enumJsValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) override;
339 QNapi::Symbol getJsSymbolForType(const std::type_info &typeInfo) override;
340
341 const std::vector<std::pair<int, double>> &getOhosEnumEnumerators(
342 const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)());
343 std::vector<std::pair<int, double>> resolveOhosEnumEnumerators(const OhosEnumInfo &enumInfo);
344
345 void forceLoadAllJsModulesIfRequested();
346
347 pthread_t m_jsThread = {};
348 napi_env m_env = nullptr;
349 QNapi::Reference<QNapi::Object> m_appLaunchWant;
350 QNapi::Reference<QNapi::Object> m_optAppLaunchParam;
351 std::shared_ptr<QAbilityPeer> m_defaultQAbilityPeer;
352 std::map<std::string, std::shared_ptr<QAbilityPeer>> m_qAbilityPeers;
353 std::map<std::string, std::function<QNapi::Object(JsState &)>> m_jsModulesFactories;
354 std::shared_ptr<AppFunctions> m_appFunctions;
355 PreQueuingJsTasksExecutor m_tasksExecutor;
356 std::vector<QOhosConsumer<JsState &, QNapi::Object, QNapi::Object>> m_newWantConsumers;
357 std::map<std::type_index, std::vector<std::pair<int, double>>> m_ohosEnumsEnumerators;
358 std::map<std::type_index, QNapi::Reference<QNapi::Symbol>> m_jsSymbolsRefs;
359 QtRunMode m_qtRunMode;
360 std::map<std::type_index, std::shared_ptr<void>> m_attachedObjects;
361};
362
363bool JsStateImpl::isJsThread()
364{
365 return pthread_self() == m_jsThread;
366}
367
368void JsStateImpl::initInJsThread(
369 napi_env env, std::map<std::string, QNapi::Reference<QNapi::Function>> &&etsModulesFactories,
370 std::shared_ptr<AppFunctions> appFunctions, QtRunMode qtRunMode)
371{
372 m_jsThread = pthread_self();
373 m_env = env;
374 m_defaultQAbilityPeer = std::make_shared<DummyQAbilityPeer>();
375 m_appFunctions = appFunctions;
376 m_qtRunMode = qtRunMode;
377
378 for (const char *moduleName : napiLoadableModules) {
379 m_jsModulesFactories.emplace(
380 moduleName,
381 makeMemoizingJsValueSupplier<QNapi::Object>(
382 [moduleName](JsState &jsState) {
383 return loadJsModuleViaNapiOrFail(jsState.env(), moduleName);
384 }));
385 }
386 for (auto &etsModulesFactoryEntry : etsModulesFactories) {
387 const std::string &moduleName = etsModulesFactoryEntry.first;
388 auto etsModuleFactory = moveToSharedPtr(std::move(etsModulesFactoryEntry.second));
389 m_jsModulesFactories.emplace(
390 moduleName,
391 makeMemoizingJsValueSupplier<QNapi::Object>(
392 [moduleName, etsModuleFactory](JsState &) {
393 return loadJsModuleViaEtsFactoryOrFail(etsModuleFactory->Value(), moduleName);
394 }));
395 }
396 m_jsModulesFactories.emplace(
397 "Global",
398 [](JsState &jsState) -> QNapi::Object {
399 return Napi::Env(jsState.env()).Global();
400 });
401
402 m_tasksExecutor.setUnderlyingExecutor(makeJsThreadTasksExecutor(this));
403}
404
405void JsStateImpl::addQAbilityPeerInJsThread(std::shared_ptr<QAbilityPeer> qAbilityPeer)
406{
407 forceLoadAllJsModulesIfRequested();
408
409 if (m_appLaunchWant.IsEmpty())
410 m_appLaunchWant = Napi::Persistent(qAbilityPeer->launchWant());
411
412 auto optQUiAbilityPeer = QUiAbilityPeer::tryCastFromQAbilityPeerOrNull(qAbilityPeer);
413 if (m_optAppLaunchParam.IsEmpty() && optQUiAbilityPeer) {
414 m_optAppLaunchParam = QNapi::Reference<>::makePersistentFrom(optQUiAbilityPeer->launchParam());
415 qOhosPrintfInfo(
416 "JsStateImpl: appLaunchParam set from Ability with instanceId='%s'",
417 qAbilityPeer->instanceId().c_str());
418 }
419
420 if (m_qAbilityPeers.empty())
421 m_defaultQAbilityPeer = qAbilityPeer;
422
423 m_qAbilityPeers.emplace(qAbilityPeer->instanceId(), qAbilityPeer);
424
425 qOhosPrintfDebug(
426 "JsStateImpl: added QAbilityPeer, instanceId: %s, qwindow: %s",
427 qAbilityPeer->instanceId().c_str(), qAbilityPeer->qWindowRef().refName().c_str());
428}
429
430void JsStateImpl::removeMatchingQAbilityPeerInJsThread(QNapi::Object qAbility)
431{
432 Napi::HandleScope funcScope{qAbility.Env()};
433
434 auto foundPeerIter = std::find_if(
435 m_qAbilityPeers.begin(), m_qAbilityPeers.end(),
436 [&](const auto &entry) {
437 return entry.second->qAbility() == qAbility;
438 });
439
440 if (foundPeerIter != m_qAbilityPeers.end()) {
441 auto removedPeer = foundPeerIter->second;
442 m_qAbilityPeers.erase(foundPeerIter);
443 if (m_defaultQAbilityPeer == removedPeer) {
444 m_defaultQAbilityPeer =
445 !m_qAbilityPeers.empty()
446 ? m_qAbilityPeers.begin()->second
447 : std::make_shared<DummyQAbilityPeer>();
448 }
449 qOhosPrintfDebug(
450 "JsStateImpl: removed QAbilityPeer, instanceId: %s, qwindow: %s",
451 removedPeer->instanceId().c_str(), removedPeer->qWindowRef().refName().c_str());
452 }
453}
454
455void JsStateImpl::dispatchNewWantInJsThread(QNapi::Object want, QNapi::Object launchParam)
456{
457 const auto consumersCount = m_newWantConsumers.size();
458 for (std::size_t i = 0; i < consumersCount; ++i)
459 m_newWantConsumers[i](*this, want, launchParam);
460}
461
462void JsStateImpl::invokeTask(std::function<void(JsState &)> &&task)
463{
464 m_tasksExecutor.invokeTask(std::move(task));
465}
466
467napi_env JsStateImpl::env()
468{
469 return m_env;
470}
471
472void JsStateImpl::forceLoadAllJsModulesIfRequested()
473{
474 if (qEnvironmentVariableIntValue(forceLoadJsModulesEnvVariableName) == 0)
475 return;
476
477 qOhosPrintfDebug("%s: forcibly loading all JS modules", Q_FUNC_INFO);
478
479 for (const auto &moduleFactoryEntry : m_jsModulesFactories)
480 getModule(moduleFactoryEntry.first);
481}
482
483QNapi::Object JsStateImpl::getModule(const std::string &moduleName)
484{
485 using namespace std::string_literals;
486
487 auto moduleFactoryIter = m_jsModulesFactories.find(moduleName);
488 if (moduleFactoryIter == m_jsModulesFactories.end())
489 throw QNapi::makeLoggedException(m_env, "JS module not found: "s + moduleName);
490
491 return moduleFactoryIter->second(*this);
492}
493
494QNapi::Object JsStateImpl::appLaunchWant()
495{
496 return m_appLaunchWant.Value();
497}
498
499QOhosOptional<QNapi::Object> JsStateImpl::optAppLaunchParam()
500{
501 return !m_optAppLaunchParam.IsEmpty()
502 ? makeQOhosOptional(m_optAppLaunchParam.Value())
503 : makeEmptyQOhosOptional();
504}
505
506QNapi::Object JsStateImpl::defaultWindowStageOrEmpty()
507{
508 auto qUiAbilityPeer = QUiAbilityPeer::tryCastFromQAbilityPeerOrNull(m_defaultQAbilityPeer);
509 if (!qUiAbilityPeer)
510 return QNapi::Object();
511
512 return qUiAbilityPeer->windowStage();
513}
514
515QNapi::Object JsStateImpl::defaultUiContextOrEmpty()
516{
517 return m_defaultQAbilityPeer->uiContext();
518}
519
520std::shared_ptr<QAbilityPeer> JsStateImpl::defaultQAbilityPeer()
521{
522 return m_defaultQAbilityPeer;
523}
524
525std::shared_ptr<QAbilityPeer> JsStateImpl::tryGetQAbilityPeerByInstanceId(const std::string &instanceId)
526{
527 auto foundIter = m_qAbilityPeers.find(instanceId);
528 return foundIter != m_qAbilityPeers.end() ? foundIter->second : nullptr;
529}
530
531std::shared_ptr<QAbilityPeer> JsStateImpl::tryGetQAbilityPeerByInstance(QNapi::Object qAbility)
532{
533 Napi::HandleScope findPeerScope(qAbility.Env());
534 return tryFindMatchingQAbilityPeer(
535 [&](const auto &peer) {
536 return peer->qAbility() == qAbility;
537 });
538}
539
540std::shared_ptr<QAbilityPeer> JsStateImpl::tryGetQAbilityPeerByQWindow(QObjectThreadSafeRef qwindow)
541{
542 return tryFindMatchingQAbilityPeer(
543 [&](const auto &peer) {
544 return peer->qWindowRef() == qwindow;
545 });
546}
547
548void JsStateImpl::visitEachQAbilityPeer(const std::function<void(std::shared_ptr<QAbilityPeer>)> &visitor)
549{
550 for (const auto &qAbilityPeerEntry : m_qAbilityPeers)
551 visitor(qAbilityPeerEntry.second);
552}
553
554void JsStateImpl::startNewQAbilityInstance(
555 std::shared_ptr<QAbilityPeer> baseQAbilityPeer, QObjectThreadSafeRef qwindow,
556 QNapi::Object optStartOptions,
557 std::function<void(JsState &, std::shared_ptr<QAbilityPeer>)> startupNotifyFunc)
558{
559 auto baseQAbility = baseQAbilityPeer->qAbility();
560 if (!baseQAbility.IsEmpty()) {
561 m_appFunctions->startQAbilityInstance(
562 baseQAbility, qwindow, optStartOptions, std::move(startupNotifyFunc));
563 }
564}
565
566void JsStateImpl::startAppProcess(
567 const std::string &processId, QNapi::Object requestWant, QNapi::Object optStartOptions)
568{
569 auto baseQAbility = defaultQAbilityPeer()->qAbility();
570 if (!baseQAbility.IsEmpty())
571 m_appFunctions->startAppProcess(baseQAbility, processId, requestWant, optStartOptions);
572}
573
574void JsStateImpl::startAppProcess(
575 const std::string &processId, QNapi::Object requestWant,
576 QNapi::Object optStartOptions, std::function<void(JsState &)> continueFunc)
577{
578 auto baseQAbility = defaultQAbilityPeer()->qAbility();
579 if (!baseQAbility.IsEmpty()) {
580 m_appFunctions->startAppProcess(
581 baseQAbility, processId, requestWant, optStartOptions, std::move(continueFunc));
582 } else {
583 invokeTask(std::move(continueFunc));
584 }
585}
586
587void JsStateImpl::addNewWantConsumer(QOhosConsumer<JsState &, QNapi::Object, QNapi::Object> wantConsumer)
588{
589 m_newWantConsumers.push_back(std::move(wantConsumer));
590}
591
592void JsStateImpl::startNoUiChildProcess(const std::string &libraryName, const std::vector<std::string> &args)
593{
594 m_appFunctions->startNoUiChildProcess(*this, libraryName, args);
595}
596
597QtRunMode JsStateImpl::qtRunMode()
598{
599 return m_qtRunMode;
600}
601
602template<typename PeerMatchFunc>
603std::shared_ptr<QAbilityPeer> JsStateImpl::tryFindMatchingQAbilityPeer(PeerMatchFunc &&matchFunc)
604{
605 auto foundIter = std::find_if(
606 m_qAbilityPeers.begin(), m_qAbilityPeers.end(),
607 [&](const auto &entry) {
608 return matchFunc(entry.second);
609 });
610 return foundIter != m_qAbilityPeers.end() ? foundIter->second : nullptr;
611}
612
613void *JsStateImpl::getAttachedObjectWithLazyCreate(
614 const std::type_info &objectTypeInfo, QOhosSupplier<std::shared_ptr<void>> objectFactory)
615{
616 auto objectIter = m_attachedObjects.find(objectTypeInfo);
617 if (objectIter == m_attachedObjects.end())
618 std::tie(objectIter, std::ignore) = m_attachedObjects.emplace(objectTypeInfo, objectFactory());
619
620 return objectIter->second.get();
621}
622
623std::tuple<QNapi::Object, std::string> JsStateImpl::extractModuleFromEvalExpr(const std::string &expr)
624{
625 using namespace std::string_literals;
626
627 std::size_t longestModulePrefixSize = 0;
628 auto considerAsLongestPrefix = [&](const std::string &name) {
629 if (name.size() > longestModulePrefixSize
630 && expr.compare(0, name.size(), name) == 0
631 && (expr.size() == name.size() || expr[name.size()] == '.')) {
632 longestModulePrefixSize = name.size();
633 }
634 };
635 for (const auto &moduleFactoryEntry : m_jsModulesFactories)
636 considerAsLongestPrefix(moduleFactoryEntry.first);
637
638 if (longestModulePrefixSize == 0) {
639 throw QNapi::makeLoggedException(
640 env(), "global expression doesn't start with known module path: '"s + expr + "'");
641 }
642
643 return std::make_tuple(
644 getModule(expr.substr(0, longestModulePrefixSize)),
645 longestModulePrefixSize < expr.size()
646 ? expr.substr(longestModulePrefixSize + 1)
647 : "");
648}
649
650QNapi::Number JsStateImpl::mapOhosEnumToJs(
651 int enumValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
652{
653 using namespace std::string_literals;
654
655 const auto &enumeratorsValues = getOhosEnumEnumerators(enumTypeInfo, ohosEnumInfoFactory);
656
657 auto valueIter = std::find_if(
658 enumeratorsValues.begin(), enumeratorsValues.end(),
659 [&](const auto &valuePair) {
660 return valuePair.first == enumValue;
661 });
662
663 if (valueIter == enumeratorsValues.end()) {
664 throw QNapi::makeLoggedException(
665 env(),
666 "Illegal C++ value of enumerator for enum '"s + enumTypeInfo.name() + "': "
667 + std::to_string(enumValue));
668 }
669
670 return QNapi::Number::New(env(), valueIter->second);
671}
672
673std::optional<int> JsStateImpl::tryMapOhosEnumFromJs(
674 QNapi::Number enumJsValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
675{
676 double enumDoubleJsValue = enumJsValue.DoubleValue();
677
678 const auto &enumeratorsValues = getOhosEnumEnumerators(enumTypeInfo, ohosEnumInfoFactory);
679
680 auto valueIter = std::find_if(
681 enumeratorsValues.begin(), enumeratorsValues.end(),
682 [&](const auto &valuePair) {
683 return valuePair.second == enumDoubleJsValue;
684 });
685
686 return valueIter != enumeratorsValues.end()
687 ? std::optional(valueIter->first)
688 : std::nullopt;
689}
690
691int JsStateImpl::mapOhosEnumFromJs(
692 QNapi::Number enumJsValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
693{
694 using namespace std::string_literals;
695
696 auto optEnumValue = tryMapOhosEnumFromJs(enumJsValue, enumTypeInfo, ohosEnumInfoFactory);
697 if (!optEnumValue.has_value()) {
698 throw QNapi::makeLoggedException(
699 enumJsValue.Env(),
700 "Illegal Napi value of enumerator for enum '"s + enumTypeInfo.name() + "': "
701 + std::to_string(enumJsValue.DoubleValue()));
702 }
703 return optEnumValue.value();
704}
705
706QNapi::Symbol JsStateImpl::getJsSymbolForType(const std::type_info &typeInfo)
707{
708 using namespace std::string_literals;
709
710 auto jsSymbolRefIter = m_jsSymbolsRefs.find(typeInfo);
711 if (jsSymbolRefIter == m_jsSymbolsRefs.end()) {
712 std::tie(jsSymbolRefIter, std::ignore) = m_jsSymbolsRefs.emplace(
713 typeInfo,
714 QNapi::Reference<>::makePersistentFrom(
715 QNapi::Symbol::New(env(), "_io_qt_"s + typeInfo.name())));
716 }
717 return jsSymbolRefIter->second.Value();
718}
719
720const std::vector<std::pair<int, double>> &JsStateImpl::getOhosEnumEnumerators(
721 const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)())
722{
723 auto enumeratorsIter = m_ohosEnumsEnumerators.find(enumTypeInfo);
724 if (enumeratorsIter == m_ohosEnumsEnumerators.end()) {
725 std::tie(enumeratorsIter, std::ignore) = m_ohosEnumsEnumerators.emplace(
726 enumTypeInfo, resolveOhosEnumEnumerators(ohosEnumInfoFactory()));
727 }
728
729 return enumeratorsIter->second;
730}
731
732std::vector<std::pair<int, double>> JsStateImpl::resolveOhosEnumEnumerators(const OhosEnumInfo &enumInfo)
733{
734 using namespace std::string_literals;
735
736 QNapi::Object enumObj;
737 try {
738 enumObj = eval<QNapi::Object>(enumInfo.fullTypeName);
739 } catch (const Napi::Error &e) {
740 throw QNapi::makeLoggedException(
741 env(), "JS enum '"s + enumInfo.fullTypeName + "' not found: "s + e.what());
742 }
743
744 std::vector<std::pair<int, double>> enumeratorsValues;
745
746 for (const auto &enumNamePair : enumInfo.enumeratorsNames) {
747 const auto *enumeratorName = enumNamePair.second;
748
749 QNapi::Value jsEnumeratorValue = QNapi::getPropOrUndefined(enumObj, enumeratorName);
750
751 if (!jsEnumeratorValue.IsNumber()) {
752 throw QNapi::makeLoggedException(
753 env(), "JS enum '"s + enumInfo.fullTypeName + "' doesn't contain: "s + enumeratorName);
754 }
755
756 enumeratorsValues.emplace_back(
757 enumNamePair.first, QNapi::checkedCast<QNapi::Number>(jsEnumeratorValue).DoubleValue());
758 }
759
760 return enumeratorsValues;
761}
762
763JsStateImpl &getJsStateImpl()
764{
765 static JsStateImpl jsStateImpl;
766 return jsStateImpl;
767}
768
769QOhosMtBlockingCallsGateway<JsState> &getQtJsBlockingCallsGateway()
770{
771 static auto qtJsBlockingCallsGateway = QOhosMtBlockingCallsGateway<JsState>::makeInstance(
772 invokeInQtThread, invokeInJsThread);
773 return *qtJsBlockingCallsGateway;
774}
775
776class JsThreadOpsImpl : public ::QOhosJsThreadOps
777{
778public:
779 QOhosJsState &jsState() override;
780
781 void invoke(std::function<void(QOhosJsState &)> task) override;
782 void invokeAndWaitForContinue(
783 std::function<void(QOhosJsState &, std::function<void()>)> &&task) override;
784 void runAndWait(const std::function<void(QOhosJsState &)> &task) override;
785};
786
787QOhosJsState &JsThreadOpsImpl::jsState()
788{
789 return getJsStateImpl();
790}
791
792void JsThreadOpsImpl::invoke(std::function<void(QOhosJsState &)> task)
793{
795}
796
797void JsThreadOpsImpl::invokeAndWaitForContinue(
798 std::function<void(QOhosJsState &, std::function<void()>)> &&task)
799{
801}
802
803void JsThreadOpsImpl::runAndWait(const std::function<void(QOhosJsState &)> &task)
804{
806}
807
808}
809
810AppFunctions::~AppFunctions() = default;
811
812QAbilityPeer::~QAbilityPeer() = default;
813
814QAbilityPeer::QAbilityPeer() = default;
815
816QUiAbilityPeer::QUiAbilityPeer() = default;
817
818QUiAbilityPeer::~QUiAbilityPeer() = default;
819
820const nullptr_t QUiAbilityPeer::typeIdObject = {};
821
822void *QUiAbilityPeer::tryCastWithTypeIdObject(const void *matchTypeIdObject)
823{
824 return matchTypeIdObject == &typeIdObject ? this : nullptr;
825}
826
828 std::shared_ptr<QAbilityPeer> qAbilityPeer)
829{
830 if (!qAbilityPeer)
831 return nullptr;
832
833 auto qUiAbilityPeer = reinterpret_cast<QUiAbilityPeer *>(
834 qAbilityPeer->tryCastWithTypeIdObject(&typeIdObject));
835
836 return qUiAbilityPeer != nullptr
837 ? qUiAbilityPeer->shared_from_this()
838 : nullptr;
839}
840
841QAbilityEngine::~QAbilityEngine() = default;
842
843QAbilityEngine::QAbilityEngine() = default;
844
845JsState::~JsState() = default;
846
847JsState::JsState() = default;
848
850{
851 return getJsStateImpl();
852}
853
854void JsWindowsTracker::tagWindowAsClosing(QNapi::Object jsWindow, const char *logContext)
855{
856 qOhosPrintfDebug("Tagging JS Window as closing (from: %s)", logContext);
857 Napi::HandleScope funcScope{jsWindow.Env()};
858 jsWindow.Set(
859 getJsWindowsTrackerIsClosingPropSymbol(getJsStateImpl()),
860 QNapi::Boolean::New(jsWindow.Env(), true));
861}
862
863bool JsWindowsTracker::isWindowClosing(QNapi::Object jsWindow)
864{
865 Napi::HandleScope funcScope{jsWindow.Env()};
866 auto isClosingPropSymbol = getJsWindowsTrackerIsClosingPropSymbol(getJsStateImpl());
867 auto isClosingValue = jsWindow.Has(isClosingPropSymbol)
868 ? jsWindow.Get(isClosingPropSymbol)
869 : QNapi::Value();
870 return isClosingValue.IsBoolean() && QNapi::checkedCast<QNapi::Boolean>(isClosingValue).Value();
871}
872
876{
877 static JsThreadOpsImpl jsThreadOpsImpl;
878 QOhosJsThreadOps::registerInstance(&jsThreadOpsImpl);
879
880 getJsStateImpl().initInJsThread(env, std::move(jsModulesFactories), appFunctions, qtRunMode);
881}
882
883void addJsQAbilityPeer(std::shared_ptr<QAbilityPeer> qAbilityPeer)
884{
885 getJsStateImpl().addQAbilityPeerInJsThread(qAbilityPeer);
886}
887
888void removeMatchingJsQAbilityPeer(QNapi::Object qAbility)
889{
890 getJsStateImpl().removeMatchingQAbilityPeerInJsThread(qAbility);
891}
892
893void dispatchNewWant(QNapi::Object want, QNapi::Object launchParam)
894{
895 getJsStateImpl().dispatchNewWantInJsThread(want, launchParam);
896}
897
898void invokeInJsThread(std::function<void(JsState &)> task)
899{
900 getJsStateImpl().invokeTask(std::move(task));
901}
902
904 std::function<void(JsState &, QOhosTaskPromise<>)> &&task,
905 std::string callerContextName)
906{
907 using namespace std::string_literals;
908
909 const auto funcInfo = Q_FUNC_INFO;
910
911 struct JsTaskCompletionState
912 {
913 std::mutex mutex;
914 bool outerTaskPromiseSettled = false;
915 QOhosOptional<std::string> jsThreadExceptionMsg;
916 };
917 auto completionState = std::make_shared<JsTaskCompletionState>();
918
919 auto catchingTaskWrapper =
920 [task = std::move(task), completionState, callerContextName, funcInfo](
921 JsState &jsState, QOhosTaskPromise<> outerTaskPromise) {
922 auto sharedOuterTaskPromise =
923 std::make_shared<QOhosTaskPromise<>>(std::move(outerTaskPromise));
924
925 auto settleOuterTaskPromise =
926 [completionState, sharedOuterTaskPromise](QOhosOptional<std::string> exceptionMsg) {
927 {
928 std::lock_guard<std::mutex> lock(completionState->mutex);
929 if (completionState->outerTaskPromiseSettled)
930 return;
931 completionState->outerTaskPromiseSettled = true;
932 completionState->jsThreadExceptionMsg = std::move(exceptionMsg);
933 }
934 (*sharedOuterTaskPromise)();
935 };
936
937 auto innerTaskPromise = QOhosTaskPromise<>(
938 [settleOuterTaskPromise]() {
939 settleOuterTaskPromise({});
940 },
941 [funcInfo, callerContextName]() {
942 if (!std::uncaught_exception()) {
943 qOhosReportFatalErrorAndAbort(
944 "%s: promise destroyed without notifying the caller: %s",
945 funcInfo, callerContextName.c_str());
946 }
947 },
948 callerContextName);
949
950 try {
951 task(jsState, std::move(innerTaskPromise));
952 } catch (const std::exception &e) {
953 settleOuterTaskPromise(QOhosOptional<std::string>(e.what()));
954 } catch (...) {
955 settleOuterTaskPromise(QOhosOptional<std::string>("<unknown exception>"));
956 }
957 };
958
959 if (getQtState().isQtThread()) {
960 getQtJsBlockingCallsGateway().runInSlaveThreadAndWaitForContinue(
961 std::move(catchingTaskWrapper), callerContextName);
962 } else {
963 auto taskReadyPromise = std::make_shared<std::promise<void>>();
964 auto taskReadyFuture = taskReadyPromise->get_future();
965
966 auto sharedTaskPromise = std::make_shared<QOhosTaskPromise<>>(
967 [taskReadyPromise]() {
968 taskReadyPromise->set_value();
969 },
970 [funcInfo, callerContextName]() {
971 qOhosReportFatalErrorAndAbort(
972 "%s: promise destroyed without notifying the caller: %s",
973 funcInfo, callerContextName.c_str());
974 },
975 callerContextName);
976
977 invokeInJsThread(
978 [catchingTaskWrapper = std::move(catchingTaskWrapper), sharedTaskPromise](JsState &jsState) {
979 catchingTaskWrapper(jsState, std::move(*sharedTaskPromise));
980 });
981 taskReadyFuture.wait();
982 }
983
984 if (completionState->jsThreadExceptionMsg.has_value()) {
985 throw std::runtime_error(
986 "Got exception from task invoked in JS thread"s
987 + " (caller: \""s + callerContextName + "\"): "
988 + completionState->jsThreadExceptionMsg.value());
989 }
990}
991
993 const std::function<void(JsState &)> &task, std::string callerContextName)
994{
995 if (getJsStateImpl().isJsThread()) {
996 task(getJsStateImpl());
997 } else {
998 invokeInJsThreadAndWaitForContinue(
999 [&](auto &jsState, auto taskPromise) {
1000 task(jsState);
1001 taskPromise();
1002 },
1003 std::move(callerContextName));
1004 }
1005}
1006
1008 std::function<void(std::function<void()>)> &&task,
1009 std::chrono::nanoseconds timeout)
1010{
1011 if (getJsStateImpl().isJsThread()) {
1012 auto result = getQtJsBlockingCallsGateway().tryInvokeInMasterThreadAndTryWaitForContinue(std::move(task), timeout);
1013 return result == QOhosMtBlockingCallsGateway<JsState>::MasterThreadTaskResult::Finished;
1014 } else {
1015 auto taskReadyPromise = std::make_shared<std::promise<void>>();
1016 auto taskReadyFuture = taskReadyPromise->get_future();
1017
1018 auto continueFunc = [taskReadyPromise]() {
1019 taskReadyPromise->set_value();
1020 };
1021
1022 invokeInQtThread(
1023 [task = std::move(task), continueFunc = std::move(continueFunc)]() mutable {
1024 task(std::move(continueFunc));
1025 });
1026 auto waitResult = taskReadyFuture.wait_for(timeout);
1027
1028 return waitResult == std::future_status::ready;
1029 }
1030}
1031
1032}
1033
1034QT_END_NAMESPACE
JsState & jsState() const
~JsState() override
void * tryCastWithTypeIdObject(const void *matchTypeIdObject) final
static std::shared_ptr< QUiAbilityPeer > tryCastFromQAbilityPeerOrNull(std::shared_ptr< QAbilityPeer > qAbilityPeer)
~QUiAbilityPeer() override
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)