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