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