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