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