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
qohosjsutils.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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 "qohosjsutils.h"
5#include <QtCore/private/qohoscommon_p.h>
6#include <qohosutils.h>
7#include <utility>
8
9using namespace std::chrono_literals;
10
11QT_BEGIN_NAMESPACE
12
13namespace QtOhos {
14
15std::shared_ptr<void> registerOnOffMethodsBasedEventHandler(
16 QNapi::Object eventSourceObject, const std::string &eventTypeName,
17 QNapi::CallbackFuncWrapper eventHandler, OnOffMethodsBasedEventHandlerOptions options)
18{
19 struct Context
20 {
21 std::function<QNapi::Value(const CallbackInfo &)> eventHandler;
22 std::function<bool(QNapi::Object)> eventSourceAliveCheckFunc;
23 QNapi::Reference<QNapi::Value> optExtraOnArg;
24 QNapi::Reference<QNapi::Value> optExtraOffArg;
25 };
26
27 auto env = eventSourceObject.Env();
28
29 auto sharedContext = moveToSharedPtr(
30 Context{
31 .eventHandler = std::move(eventHandler.callbackFunc()),
32 .eventSourceAliveCheckFunc = options.optEventSourceAliveCheckFunc
33 ? std::move(options.optEventSourceAliveCheckFunc)
34 : [](QNapi::Object) {
35 return true;
36 },
37 .optExtraOnArg = options.extraOnArg.has_value()
38 ? QNapi::Reference<>::makePersistentFrom(
39 options.extraOnArg.value().mapToValue(env))
40 : QNapi::Reference<>::makeEmpty(),
41 .optExtraOffArg = options.extraOffArg.has_value()
42 ? QNapi::Reference<>::makePersistentFrom(
43 options.extraOffArg.value().mapToValue(env))
44 : QNapi::Reference<>::makeEmpty(),
45 });
46
47 auto jsEventHandlerRef = moveToSharedPtr(
48 QNapi::Reference<>::makePersistentFrom(
49 QNapi::Function::New(
50 eventSourceObject.Env(),
51 [eventTypeName, weakContext = makeWeakPtr(sharedContext)](const CallbackInfo &cbInfo) {
52 auto sharedContext = weakContext.lock();
53 if (sharedContext) {
54 return sharedContext->eventHandler(cbInfo);
55 } else {
56 qOhosPrintfWarning(
57 "%s: got unexpected '%s' event callback call for detached handler",
58 Q_FUNC_INFO, eventTypeName.c_str());
59 return cbInfo.Env().Undefined();
60 }
61 })));
62
63 std::vector<QNapi::ValueWrapper> onCallArgs;
64 onCallArgs.push_back(eventTypeName);
65 if (!sharedContext->optExtraOnArg.IsEmpty())
66 onCallArgs.push_back(sharedContext->optExtraOnArg.Value());
67 onCallArgs.push_back(jsEventHandlerRef->Value());
68 bool onCallSuccessful;
69 try {
70 eventSourceObject.call("on", onCallArgs);
71 onCallSuccessful = true;
72 } catch (const Napi::Error &error) {
73 onCallSuccessful = false;
74 if (options.optOnCallExceptionHandler) {
75 options.optOnCallExceptionHandler(error);
76 } else {
77 throw;
78 }
79 }
80
81 if (!onCallSuccessful)
82 return nullptr;
83
84 auto eventSourceWeakRef = moveToSharedPtr(Napi::Weak(eventSourceObject));
85
86 return makeProxyWithJsThreadDeleter(
87 QtOhos::makeDestroyNotifier(
88 [eventSourceWeakRef, eventTypeName, sharedContext, jsEventHandlerRef]() {
89 auto eventSourceValue = eventSourceWeakRef->Value();
90 if (eventSourceValue.IsObject()) {
91 auto eventSourceObject = QNapi::checkedCast<QNapi::Object>(eventSourceValue);
92 if (sharedContext->eventSourceAliveCheckFunc(eventSourceObject)) {
93 try {
94 std::vector<QNapi::ValueWrapper> offCallArgs;
95 offCallArgs.push_back(eventTypeName);
96 if (!sharedContext->optExtraOffArg.IsEmpty())
97 offCallArgs.push_back(sharedContext->optExtraOffArg.Value());
98 offCallArgs.push_back(jsEventHandlerRef->Value());
99 eventSourceObject.call("off", offCallArgs);
100 } catch (const Napi::Error &e) {
101 qOhosPrintfError(
102 "%s: got exception from off(%s, ...) call (ignoring): %s",
103 Q_FUNC_INFO, eventTypeName.c_str(), e.what());
104 }
105 } else {
106 qOhosPrintfDebug(
107 "%s: not calling off(%s, ...), event source 'considered' not alive",
108 Q_FUNC_INFO, eventTypeName.c_str());
109 }
110 } else {
111 qOhosPrintfDebug(
112 "%s: not calling off(%s, ...), event source not alive",
113 Q_FUNC_INFO, eventTypeName.c_str());
114 }
115 }));
116}
117
118std::shared_ptr<void> startDelayedJsThreadTask(
119 JsState &jsState, std::function<void(JsState &)> task,
120 std::chrono::milliseconds delay)
121{
122 struct Context
123 {
124 std::function<void(JsState &)> task;
125 QOhosOptional<int> timerId;
126 };
127
128 auto context = std::make_shared<Context>();
129 context->task = std::move(task);
130
131 int timerId = jsState.eval<QNapi::Number>(
132 "Global.setTimeout(*)",
133 {
134 [context](const CallbackInfo &cbInfo) {
135 if (context->task) {
136 auto task = std::exchange(context->task, nullptr);
137 context->timerId.reset();
138 task(cbInfo.jsState());
139 }
140 },
141 std::max(delay, std::chrono::milliseconds(0)).count(),
142 });
143 context->timerId = timerId;
144
145 return QtOhos::makeDestroyNotifier(
146 [context]() {
147 if (context->timerId.has_value()) {
148 runInJsThreadAndWait(
149 [&](JsState &jsState) {
150 jsState.eval("Global.clearTimeout(*)", {context->timerId.value()});
151 },
152 Q_FUNC_INFO);
153 context->task = nullptr;
154 context->timerId.reset();
155 }
156 });
157}
158
159int setJsTimeout(
160 JsState &jsState, std::function<void(const CallbackInfo &)> timeoutFunc,
161 std::chrono::milliseconds delay)
162{
163 int timerId = jsState.eval<QNapi::Number>(
164 "Global.setTimeout(*)", {std::move(timeoutFunc), delay.count()});
165 return timerId;
166}
167
168void clearJsTimeout(JsState &jsState, int timerId)
169{
170 jsState.eval("Global.clearTimeout(*)", {timerId});
171}
172
173QNapi::Promise makeResolvedPromise(QNapi::Value valueForResolve)
174{
175 auto promiseDeferred = QNapi::Promise::Deferred::New(valueForResolve.Env());
176 promiseDeferred.Resolve(valueForResolve);
177 return promiseDeferred.Promise();
178}
179
180QOhosOptional<std::uint32_t> tryGetCodeFromJsBusinessError(const Napi::Error &error)
181{
182 if (!error.Value().IsObject())
183 return {};
184
185 auto errorObject = QNapi::checkedCast<QNapi::Object>(error.Value());
186 auto optErrorCode = QNapi::getOptionalPropOrEmpty<QNapi::Number>(errorObject, "code");
187
188 return !optErrorCode.IsEmpty()
189 ? makeQOhosOptional(optErrorCode.Uint32Value())
191}
192
193void rethrowUnlessJsBusinessErrorIs(
194 const Napi::Error &error, std::uint32_t suppressedErrorCode, const char *callerContextName)
195{
196 if (tryGetCodeFromJsBusinessError(error) != suppressedErrorCode)
197 throw;
198
199 qOhosPrintfWarning(
200 "%s: ignored expected JS business error %u",
201 callerContextName, suppressedErrorCode);
202}
203
204bool runIgnoringJsBusinessError(
205 JsState &, std::uint32_t suppressedErrorCode, const char *callerContextName,
206 const std::function<void()> &action)
207{
208 try {
209 action();
210 return true;
211 } catch (const Napi::Error &error) {
212 rethrowUnlessJsBusinessErrorIs(error, suppressedErrorCode, callerContextName);
213 return false;
214 }
215}
216
217}
218
219QT_END_NAMESPACE
std::nullopt_t makeEmptyQOhosOptional()