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