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