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
qohoswindowproxydatafactory.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 <render/qohoswindowproxydatafactory.h>
5
6#include <QtCore/qscopeguard.h>
7#include <cstdint>
8#include <qarkui/qxcomponentregistry.h>
9#include <qohosutils.h>
10#include <render/qohoswindowproxy.h>
11#include <render/qxcomponent.h>
12
13QT_BEGIN_NAMESPACE
14
15namespace {
16
18{
19 static std::uint64_t uniqueIdSuffixCounter = 0;
20 auto result = QStringLiteral("QtWindow_%1_%2")
21 .arg(internalWindowId.toString())
22 .arg(QString::number(uniqueIdSuffixCounter));
23 ++uniqueIdSuffixCounter;
24 return result.toStdString();
25}
26
33
41
52
62
64 QtOhos::JsState &jsState, const std::string &qAbilityInstanceId)
65{
66 auto qAbilityPeer = jsState.tryGetQAbilityPeerByInstanceId(qAbilityInstanceId);
67 if (!qAbilityPeer) {
68 qOhosReportFatalErrorAndAbort(
69 "Failed to find QAbilityPeer for qAbilityInstanceId: %s", qAbilityInstanceId.c_str());
70 }
71 return qAbilityPeer;
72}
73
75{
77 auto nativeNodeXComponentOpt = registry.tryTakeNodeByXComponentId(xComponentId);
78 if (!nativeNodeXComponentOpt.has_value()) {
79 qOhosReportFatalErrorAndAbort(
80 "Failed to fetch native node xcomponent with id: %s",
81 xComponentId.stringId().c_str());
82 }
83 return nativeNodeXComponentOpt.value();
84}
85
87{
88 return jsState.eval<QNapi::Object>("LocalStorage.makeNewLocalStorage()");
89}
90
91QNapi::Object makeRectObject(Napi::Env env, const QRect &rect)
92{
93 return QNapi::makeObject(
94 env,
95 {
96 {"left", rect.x()},
97 {"top", rect.top()},
98 {"width", rect.width()},
99 {"height", rect.height()},
100 });
101}
102
104 QNapi::Object windowStageOrWindowObject,
105 const std::string &windowName, const SubWindowOptions &subWindowOptions)
106{
107 auto subWindowOptionsObject =
108 QNapi::makeObject(
109 windowStageOrWindowObject.Env(),
110 {
111 {"title", subWindowOptions.windowTitle},
112 {"decorEnabled", subWindowOptions.decorEnabled},
113 {"isModal", subWindowOptions.isModal},
114 {
115 "windowRect", makeRectObject(windowStageOrWindowObject.Env(), subWindowOptions.windowRect)
116 }
117 });
118
119 return windowStageOrWindowObject.evalToPromiseOrRejectOnThrow(
120 "createSubWindowWithOptions(*)",
121 {windowName, subWindowOptionsObject});
122}
123
124// Optionless sub window creation, used on phones where the SessionManager-only
125// createSubWindowWithOptions() is unavailable. WindowStage.createSubWindow() is @crossplatform.
127 QNapi::Object windowStageOrWindowObject, const std::string &windowName)
128{
129 return windowStageOrWindowObject.evalToPromiseOrRejectOnThrow(
130 "createSubWindow(*)", {windowName});
131}
132
133// createSubWindow() exists only on the WindowStage, not on a Window.
135 const std::shared_ptr<QtOhos::QAbilityPeer> &qAbilityPeer, const std::string &windowName)
136{
137 auto optQUiAbilityPeer = QtOhos::QUiAbilityPeer::tryCastFromQAbilityPeerOrNull(qAbilityPeer);
138 if (!optQUiAbilityPeer) {
139 qOhosReportFatalErrorAndAbort(
140 "%s sub window creation requires an ability with a windowStage. Aborting...",
141 Q_FUNC_INFO);
142 }
143 return createSubWindow(optQUiAbilityPeer->windowStage(), windowName);
144}
145
146// Query SystemCapability.Window.SessionManager (needed by createSubWindowWithOptions())
147// directly rather than inferring it from the device type.
149{
150 try {
151 return jsState.eval<QNapi::Boolean>(
152 "Global.canIUse(*)",
153 {std::string("SystemCapability.Window.SessionManager")}).Value();
154 } catch (const Napi::Error &error) {
155 qOhosPrintfWarning(
156 "canIUse('SystemCapability.Window.SessionManager') query failed (%s); assuming the "
157 "capability is unavailable and using createSubWindow()",
158 error.what());
159 return false;
160 }
161}
162
163std::function<void(const QtOhos::CallbackInfo &)>
165{
166 auto sharedContext = QtOhos::moveToSharedPtr(std::move(subWindowOnAppearCbCtx));
167 return [sharedContext](const QtOhos::CallbackInfo &cbInfo) mutable {
168 if (!sharedContext)
169 return;
170
171 auto xComponent = takeNodeXComponentFromRegistryOrFail(sharedContext->xComponentId);
172
173 sharedContext->resultConsumer(
174 cbInfo.jsState(),
175 QOhosWindowProxyData {
176 .qAbilityPeer = sharedContext->qAbilityPeer,
177 .jsWindow = std::move(sharedContext->windowObject),
178 .windowProxyType = sharedContext->windowProxyType,
179 .nodeXComponent = std::make_shared<QXComponentNode>(xComponent),
180 .jsKeepAliveData = QtOhos::moveToSharedPtr(std::move(sharedContext->localStorageObj)),
181 .owningQWindowRef = sharedContext->owningQWindowRef,
182 });
183
184 sharedContext.reset();
185 };
186}
187
188QNapi::Promise loadWindowContents(QNapi::Object window, QNapi::Object localStorage, const std::string &contentPagePath)
189{
190 return window.evalToPromiseOrRejectOnThrow("loadContent(*)", {contentPagePath, localStorage});
191}
192
194 QtOhos::JsState &jsState, LocalStorageForWindowCreateInfo &&createInfo)
195{
196 auto *env = jsState.env();
197
198 auto localStorage = makeLocalStorage(jsState);
199 auto subWindowNativeNodeCreateInfo = QNapi::makeObject(
200 env,
201 {
202 {"xComponentId", createInfo.xComponentId.toNapiValue(env)},
203 {
204 "onDisAppear",
205 [xComponentId = createInfo.xComponentId.stringId()]() {
206 qOhosPrintfDebug(
207 "WindowNativeNodeCreateInfo.onDisAppear() called from JS for xComponentId='%s'",
208 xComponentId.c_str());
209 }
210 },
211 {
212 "onAppear",
213 makeSubWindowOnAppearCallbackHandler(SubWindowOnAppearContext {
214 .xComponentId = createInfo.xComponentId,
215 .localStorageObj = Napi::Persistent(localStorage),
216 .windowObject = Napi::Persistent(createInfo.windowObject),
217 .resultConsumer = std::move(createInfo.resultConsumer),
218 .qAbilityPeer = std::move(createInfo.qAbilityPeer),
219 .windowProxyType = createInfo.windowProxyType,
220 .owningQWindowRef = createInfo.owningQWindowRef,
221 }),
222 }
223 });
224
225 localStorage.eval("setOrCreate(*)", {"createInfo", subWindowNativeNodeCreateInfo});
226 return localStorage;
227}
228
230 QtOhos::JsState &, const QNapi::Object &windowObject,
232{
233 struct LoadWindowContentsArgs
234 {
235 QNapi::Reference<QNapi::Object> window;
236 QNapi::Reference<QNapi::Object> localStorage;
237 std::string contentPagePath;
238 };
239
240 if (!context.disableWindowFocusableBeforeLoadContentHack)
241 return loadWindowContents(windowObject, context.localStorage, context.contentPagePath);
242
243 auto loadWindowContentsArgs = LoadWindowContentsArgs {
244 .window = Napi::Persistent(windowObject),
245 .localStorage = Napi::Persistent(context.localStorage),
246 .contentPagePath = context.contentPagePath,
247 };
248
249 return windowObject.evalToPromiseOrRejectOnThrow("setWindowFocusable(*)", {false})
250 .onThen([loadWindowContentsArgs = std::move(loadWindowContentsArgs)]() {
251 return loadWindowContents(
252 loadWindowContentsArgs.window.Value(),
253 loadWindowContentsArgs.localStorage.Value(),
254 loadWindowContentsArgs.contentPagePath);
255 });
256}
257
258}
259
261 QtOhos::JsState &jsState,
262 const QOhosWindowProxyMainWindowCreateInfo &createInfo,
263 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
264{
265 auto optBaseQAbility = jsState.defaultQAbility();
266 if (!optBaseQAbility)
267 qOhosReportFatalErrorAndAbort("%s: can't create window without the default Ability instance", Q_FUNC_INFO);
268
269 auto abilityStartupOptions =
270 QNapi::makeObject(
271 jsState.env(),
272 {
273 {"windowLeft", createInfo.frameGeometry.x()},
274 {"windowTop", createInfo.frameGeometry.y()},
275 {"windowWidth", createInfo.frameGeometry.width()},
276 {"windowHeight", createInfo.frameGeometry.height()},
277 });
278
279 if (createInfo.fullscreen) {
280 auto fullscreenWindowMode = jsState.eval<QNapi::Number>(
281 "@ohos.app.ability.AbilityConstant.WindowMode.WINDOW_MODE_FULLSCREEN");
282 abilityStartupOptions.set("windowMode", fullscreenWindowMode);
283 }
284
285 jsState.startNewQAbilityInstance(
286 optBaseQAbility.value(), createInfo.qWindowRef,
287 abilityStartupOptions,
288 [qWindowRef = createInfo.qWindowRef,
289 windowId = createInfo.windowId,
290 resultConsumer = std::move(resultConsumer)](QtOhos::JsState &jsState, std::shared_ptr<QtOhos::QAbilityPeer> qAbilityPeer) {
291 auto createInfo = QOhosWindowProxyExistingMainWindowCreateInfo {
292 .qWindowRef = qWindowRef,
293 .qAbilityInstanceId = qAbilityPeer->instanceId(),
294 .windowId = windowId,
295 };
296
297 makeWindowProxyDataForExistingMainWindowInJsThread(
298 jsState, createInfo, std::move(resultConsumer));
299 });
300}
301
303 QtOhos::JsState &jsState,
305 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
306{
308 getQAbilityPeerByInstanceIdOrFail(jsState, createInfo.qAbilityInstanceId));
309 if (!optQUiAbilityPeer) {
310 qOhosReportFatalErrorAndAbort(
311 "%s Attempting to make window proxy for main window for ability without windowStage. This is most likely a programming error. Aborting...",
312 Q_FUNC_INFO);
313 }
314 optQUiAbilityPeer->setQWindow(jsState.env(), createInfo.qWindowRef);
315
316 auto window = optQUiAbilityPeer->windowStage().eval<QNapi::Object>("getMainWindowSync()");
317 auto nativeNodeXComponentId = QXComponentId::createForNativeNodeMainWindow(createInfo.qAbilityInstanceId);
318 auto nodeXComponent = takeNodeXComponentFromRegistryOrFail(nativeNodeXComponentId);
319
320 resultConsumer(
321 jsState,
322 QOhosWindowProxyData {
323 .qAbilityPeer = optQUiAbilityPeer,
324 .jsWindow = Napi::Persistent(window),
325 .windowProxyType = WindowProxyType::MainWindow,
326 .nodeXComponent = std::make_shared<QXComponentNode>(nodeXComponent),
327 .jsKeepAliveData = nullptr,
328 .owningQWindowRef = createInfo.qWindowRef,
329 });
330}
331
333 QtOhos::JsState &jsState,
334 const QOhosWindowProxySubWindowCreateInfo &createInfo,
335 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
336{
337 auto qAbilityPeer = getQAbilityPeerByInstanceIdOrFail(jsState, createInfo.qAbilityInstanceId);
338 auto optQUiAbilityPeer = QtOhos::QUiAbilityPeer::tryCastFromQAbilityPeerOrNull(qAbilityPeer);
339 if (!optQUiAbilityPeer) {
340 qOhosReportFatalErrorAndAbort(
341 "%s Attempting to make window proxy for sub window for ability without windowStage. This is most likely a programming error. Aborting...",
342 Q_FUNC_INFO);
343 }
344 makeWindowProxyDataForSubWindowInJsThread(
345 jsState, optQUiAbilityPeer->windowStage(), createInfo, std::move(resultConsumer));
346}
347
349 QtOhos::JsState &jsState,
350 QNapi::Object windowStageOrWindowObject,
351 const QOhosWindowProxySubWindowCreateInfo &createInfo,
352 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
353{
354 auto xComponentId = QXComponentId::createForNativeNodeSubWindow(createInfo.windowId);
355 struct Context
356 {
357 bool disableWindowFocusableBeforeLoadContentHack;
358 QXComponentId xComponentId;
359 std::shared_ptr<QtOhos::QAbilityPeer> qAbilityPeer;
360 QtOhos::QObjectThreadSafeRef owningQWindowRef;
361 };
362
363 auto qAbilityPeer = getQAbilityPeerByInstanceIdOrFail(jsState, createInfo.qAbilityInstanceId);
364
365 const auto windowName = makeOhosUniqueSystemWindowName(createInfo.windowId);
366
367 // Prefer createSubWindowWithOptions() when the capability is present, but fall back to the
368 // @crossplatform createSubWindow() when it is absent or fails (its dropped options are
369 // applied separately after creation anyway) rather than aborting the application.
370 auto subWindowCreationPromise = [&]() -> QNapi::Promise {
371 if (!isWindowSessionManagerAvailable(jsState))
372 return createSubWindowViaWindowStage(qAbilityPeer, windowName);
373
374 return createSubWindowWithOptions(
375 windowStageOrWindowObject,
376 windowName,
377 SubWindowOptions {
378 .windowTitle = createInfo.windowTitle,
379 .decorEnabled = createInfo.decorEnabled,
380 .isModal = createInfo.modal,
381 .windowRect = createInfo.windowRect,
382 })
383 .onCatch([qAbilityPeer, windowName](const QtOhos::CallbackInfo &) -> QNapi::Promise {
384 // Unsupported in some window contexts even when the capability is advertised.
385 qOhosPrintfWarning(
386 "createSubWindowWithOptions() is unavailable in this context; "
387 "falling back to createSubWindow()");
388 return createSubWindowViaWindowStage(qAbilityPeer, windowName);
389 });
390 }();
391
392 auto context = Context {
393 .disableWindowFocusableBeforeLoadContentHack = createInfo.disableWindowFocusableBeforeLoadContentHack,
394 .xComponentId = xComponentId,
395 .qAbilityPeer = qAbilityPeer,
396 .owningQWindowRef = createInfo.window,
397 };
398
399 subWindowCreationPromise
400 .onThen([resultConsumer = std::move(resultConsumer), context = std::move(context)](const QtOhos::CallbackInfo &cbInfo) mutable {
401 auto windowObject = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
402
403 auto localStorage = makeLocalStorageForWindow(
404 cbInfo.jsState(),
405 LocalStorageForWindowCreateInfo {
406 .xComponentId = context.xComponentId,
407 .windowObject = windowObject,
408 .resultConsumer = std::move(resultConsumer),
409 .qAbilityPeer = context.qAbilityPeer,
410 .windowProxyType = WindowProxyType::SubWindow,
411 .owningQWindowRef = context.owningQWindowRef,
412 });
413
414 return onWindowCreatedLoadWindowContents(
415 cbInfo.jsState(), windowObject,
416 OnWindowCreatedLoadWindowContentsContext {
417 .disableWindowFocusableBeforeLoadContentHack = context.disableWindowFocusableBeforeLoadContentHack,
418 .contentPagePath = "pages/SubWindowNativeNode",
419 .localStorage = localStorage,
420 });
421 })
422 .onCatch([windowId = createInfo.windowId](const QtOhos::CallbackInfo &cbInfo) {
423 QtOhos::logJsCallbackError(cbInfo, "sub window creation failed");
424 qOhosReportFatalErrorAndAbort(
425 "Failed to create subwindow for windowId='%s'",
426 windowId.toStdString().c_str());
427 });
428}
429
431 QtOhos::JsState &jsState, const QOhosWindowProxyFloatWindowCreateInfo &createInfo,
432 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
433{
434 auto xComponentId = QXComponentId::createForNativeNodeFloatWindow(createInfo.internalWindowId);
435 auto qAbilityPeer = jsState.defaultQAbilityPeer();
436
437 if (qAbilityPeer->qAbility().IsEmpty())
438 qOhosReportFatalErrorAndAbort("%s: can't create window without the default Ability instance", Q_FUNC_INFO);
439
440 auto configurationObject = QNapi::makeObject(
441 jsState.env(),
442 {
443 // NOTE - The parameter name is misleading, what it refers to actually is (system) window id
444 {"name", makeOhosUniqueSystemWindowName(createInfo.internalWindowId)},
445 {"windowType", jsState.eval<QNapi::Number>("@ohos.window.WindowType.TYPE_FLOAT")},
446 {"ctx", qAbilityPeer->qAbility().get<QNapi::Object>("context")},
447 });
448
449 struct Context
450 {
451 QXComponentId xComponentId;
452 std::shared_ptr<QtOhos::QAbilityPeer> qAbilityPeer;
453 QtOhos::QObjectThreadSafeRef owningQWindowRef;
454 };
455
456 auto context = Context {
457 .xComponentId = xComponentId,
458 .qAbilityPeer = qAbilityPeer,
459 .owningQWindowRef = createInfo.qWindowRef,
460 };
461
462 jsState
463 .evalToPromiseOrRejectOnThrow("@ohos.window.createWindow(*)", {configurationObject})
464 .onThen([resultConsumer = std::move(resultConsumer), context = std::move(context)](const QtOhos::CallbackInfo &cbInfo) mutable {
465 auto windowObject = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
466
467 auto localStorage = makeLocalStorageForWindow(
468 cbInfo.jsState(),
469 LocalStorageForWindowCreateInfo {
470 .xComponentId = context.xComponentId,
471 .windowObject = windowObject,
472 .resultConsumer = std::move(resultConsumer),
473 .qAbilityPeer = context.qAbilityPeer,
474 .windowProxyType = WindowProxyType::FloatWindow,
475 .owningQWindowRef = context.owningQWindowRef,
476 });
477
478 return onWindowCreatedLoadWindowContents(
479 cbInfo.jsState(), windowObject,
480 OnWindowCreatedLoadWindowContentsContext {
481 .disableWindowFocusableBeforeLoadContentHack = false,
482 .contentPagePath = "pages/FloatWindowNativeNode",
483 .localStorage = localStorage,
484 });
485 })
486 .onCatch([internalWindowId = createInfo.internalWindowId](const QtOhos::CallbackInfo &cbInfo) {
487 QtOhos::logJsCallbackError(cbInfo, "Failed to create TYPE_FLOAT window");
488 qOhosReportFatalErrorAndAbort(
489 "Failed to create TYPE_FLOAT window for windowId='%s'",
490 internalWindowId.toStdString().c_str());
491 });
492}
493
494
495QT_END_NAMESPACE
std::optional< QXComponentNode > tryTakeNodeByXComponentId(const QXComponentId &id)
static QXComponentRegistry & instance()
static QXComponentId createForNativeNodeMainWindow(const std::string &qAbilityInstanceId)
std::string stringId() const
JsState & jsState() const
virtual std::shared_ptr< QAbilityPeer > tryGetQAbilityPeerByInstanceId(const std::string &instanceId)=0
virtual std::shared_ptr< QAbilityPeer > defaultQAbilityPeer()=0
static std::shared_ptr< QUiAbilityPeer > tryCastFromQAbilityPeerOrNull(std::shared_ptr< QAbilityPeer > qAbilityPeer)
std::shared_ptr< QtOhos::QAbilityPeer > getQAbilityPeerByInstanceIdOrFail(QtOhos::JsState &jsState, const std::string &qAbilityInstanceId)
QNapi::Object makeRectObject(Napi::Env env, const QRect &rect)
bool isWindowSessionManagerAvailable(QtOhos::JsState &jsState)
QNapi::Promise createSubWindowWithOptions(QNapi::Object windowStageOrWindowObject, const std::string &windowName, const SubWindowOptions &subWindowOptions)
QXComponentNode takeNodeXComponentFromRegistryOrFail(const QXComponentId &xComponentId)
std::function< void(const QtOhos::CallbackInfo &)> makeSubWindowOnAppearCallbackHandler(SubWindowOnAppearContext subWindowOnAppearCbCtx)
QNapi::Object makeLocalStorage(QtOhos::JsState &jsState)
QNapi::Promise createSubWindowViaWindowStage(const std::shared_ptr< QtOhos::QAbilityPeer > &qAbilityPeer, const std::string &windowName)
QNapi::Promise onWindowCreatedLoadWindowContents(QtOhos::JsState &, const QNapi::Object &windowObject, OnWindowCreatedLoadWindowContentsContext context)
QNapi::Promise loadWindowContents(QNapi::Object window, QNapi::Object localStorage, const std::string &contentPagePath)
std::string makeOhosUniqueSystemWindowName(QtOhos::InternalWindowId internalWindowId)
QNapi::Promise createSubWindow(QNapi::Object windowStageOrWindowObject, const std::string &windowName)
QNapi::Object makeLocalStorageForWindow(QtOhos::JsState &jsState, LocalStorageForWindowCreateInfo &&createInfo)
void makeWindowProxyDataForFloatWindowInJsThread(QtOhos::JsState &jsState, const QOhosWindowProxyFloatWindowCreateInfo &createInfo, QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer)
void makeWindowProxyDataForSubWindowInJsThread(QtOhos::JsState &jsState, QNapi::Object windowStageOrWindowObject, const QOhosWindowProxySubWindowCreateInfo &createInfo, QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer)
void makeWindowProxyDataForSubWindowInJsThread(QtOhos::JsState &jsState, const QOhosWindowProxySubWindowCreateInfo &createInfo, QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer)
void makeWindowProxyDataForExistingMainWindowInJsThread(QtOhos::JsState &jsState, const QOhosWindowProxyExistingMainWindowCreateInfo &createInfo, QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer)
void makeWindowProxyDataForMainWindowInJsThread(QtOhos::JsState &jsState, const QOhosWindowProxyMainWindowCreateInfo &createInfo, QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer)
QXComponent< QXComponentType::Node > QXComponentNode
Definition qxcomponent.h:44
QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer
QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer
std::shared_ptr< QtOhos::QAbilityPeer > qAbilityPeer