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 return !context.disableWindowFocusableBeforeLoadContentHack
241 ? loadWindowContents(windowObject, context.localStorage, context.contentPagePath)
242 : windowObject.evalToPromiseOrRejectOnThrow("setWindowFocusable(*)", {false})
243 .withContext(LoadWindowContentsArgs {
244 .window = Napi::Persistent(windowObject),
245 .localStorage = Napi::Persistent(context.localStorage),
246 .contentPagePath = context.contentPagePath,
247 })
248 .onThenWithContext([](LoadWindowContentsArgs &windowLocalStoragePair) {
249 return loadWindowContents(
250 windowLocalStoragePair.window.Value(),
251 windowLocalStoragePair.localStorage.Value(),
252 windowLocalStoragePair.contentPagePath);
253 });
254}
255
256}
257
259 QtOhos::JsState &jsState,
260 const QOhosWindowProxyMainWindowCreateInfo &createInfo,
261 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
262{
263 auto optBaseQAbility = jsState.defaultQAbility();
264 if (!optBaseQAbility)
265 qOhosReportFatalErrorAndAbort("%s: can't create window without the default Ability instance", Q_FUNC_INFO);
266
267 auto abilityStartupOptions =
268 QNapi::makeObject(
269 jsState.env(),
270 {
271 {"windowLeft", createInfo.frameGeometry.x()},
272 {"windowTop", createInfo.frameGeometry.y()},
273 {"windowWidth", createInfo.frameGeometry.width()},
274 {"windowHeight", createInfo.frameGeometry.height()},
275 });
276
277 if (createInfo.fullscreen) {
278 auto fullscreenWindowMode = jsState.eval<QNapi::Number>(
279 "@ohos.app.ability.AbilityConstant.WindowMode.WINDOW_MODE_FULLSCREEN");
280 abilityStartupOptions.set("windowMode", fullscreenWindowMode);
281 }
282
283 jsState.startNewQAbilityInstance(
284 optBaseQAbility.value(), createInfo.qWindowRef,
285 abilityStartupOptions,
286 [qWindowRef = createInfo.qWindowRef,
287 windowId = createInfo.windowId,
288 resultConsumer = std::move(resultConsumer)](QtOhos::JsState &jsState, std::shared_ptr<QtOhos::QAbilityPeer> qAbilityPeer) {
289 auto createInfo = QOhosWindowProxyExistingMainWindowCreateInfo {
290 .qWindowRef = qWindowRef,
291 .qAbilityInstanceId = qAbilityPeer->instanceId(),
292 .windowId = windowId,
293 };
294
295 makeWindowProxyDataForExistingMainWindowInJsThread(
296 jsState, createInfo, std::move(resultConsumer));
297 });
298}
299
301 QtOhos::JsState &jsState,
303 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
304{
306 getQAbilityPeerByInstanceIdOrFail(jsState, createInfo.qAbilityInstanceId));
307 if (!optQUiAbilityPeer) {
308 qOhosReportFatalErrorAndAbort(
309 "%s Attempting to make window proxy for main window for ability without windowStage. This is most likely a programming error. Aborting...",
310 Q_FUNC_INFO);
311 }
312 optQUiAbilityPeer->setQWindow(jsState.env(), createInfo.qWindowRef);
313
314 auto window = optQUiAbilityPeer->windowStage().eval<QNapi::Object>("getMainWindowSync()");
315 auto nativeNodeXComponentId = QXComponentId::createForNativeNodeMainWindow(optQUiAbilityPeer->instanceId());
316 auto nodeXComponent = takeNodeXComponentFromRegistryOrFail(nativeNodeXComponentId);
317
318 resultConsumer(
319 jsState,
320 QOhosWindowProxyData {
321 .qAbilityPeer = optQUiAbilityPeer,
322 .jsWindow = Napi::Persistent(window),
323 .windowProxyType = WindowProxyType::MainWindow,
324 .nodeXComponent = std::make_shared<QXComponentNode>(nodeXComponent),
325 .jsKeepAliveData = nullptr,
326 .owningQWindowRef = createInfo.qWindowRef,
327 });
328}
329
331 QtOhos::JsState &jsState,
332 const QOhosWindowProxySubWindowCreateInfo &createInfo,
333 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
334{
335 auto qAbilityPeer = getQAbilityPeerByInstanceIdOrFail(jsState, createInfo.qAbilityInstanceId);
336 auto optQUiAbilityPeer = QtOhos::QUiAbilityPeer::tryCastFromQAbilityPeerOrNull(qAbilityPeer);
337 if (!optQUiAbilityPeer) {
338 qOhosReportFatalErrorAndAbort(
339 "%s Attempting to make window proxy for sub window for ability without windowStage. This is most likely a programming error. Aborting...",
340 Q_FUNC_INFO);
341 }
342 makeWindowProxyDataForSubWindowInJsThread(
343 jsState, optQUiAbilityPeer->windowStage(), createInfo, std::move(resultConsumer));
344}
345
347 QtOhos::JsState &jsState,
348 QNapi::Object windowStageOrWindowObject,
349 const QOhosWindowProxySubWindowCreateInfo &createInfo,
350 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
351{
352 auto xComponentId = QXComponentId::createForNativeNodeSubWindow(createInfo.windowId);
353 struct Context
354 {
355 bool disableWindowFocusableBeforeLoadContentHack;
356 QXComponentId xComponentId;
357 std::shared_ptr<QtOhos::QAbilityPeer> qAbilityPeer;
358 QtOhos::QObjectThreadSafeRef owningQWindowRef;
359 };
360
361 auto qAbilityPeer = getQAbilityPeerByInstanceIdOrFail(jsState, createInfo.qAbilityInstanceId);
362
363 const auto windowName = makeOhosUniqueSystemWindowName(createInfo.windowId);
364
365 // Prefer createSubWindowWithOptions() when the capability is present, but fall back to the
366 // @crossplatform createSubWindow() when it is absent or fails (its dropped options are
367 // applied separately after creation anyway) rather than aborting the application.
368 auto subWindowCreationPromise = [&]() -> QNapi::Promise {
369 if (!isWindowSessionManagerAvailable(jsState))
370 return createSubWindowViaWindowStage(qAbilityPeer, windowName);
371
372 return createSubWindowWithOptions(
373 windowStageOrWindowObject,
374 windowName,
375 SubWindowOptions {
376 .windowTitle = createInfo.windowTitle,
377 .decorEnabled = createInfo.decorEnabled,
378 .isModal = createInfo.modal,
379 .windowRect = createInfo.windowRect,
380 })
381 .onCatch([qAbilityPeer, windowName](const QtOhos::CallbackInfo &) -> QNapi::Promise {
382 // Unsupported in some window contexts even when the capability is advertised.
383 qOhosPrintfWarning(
384 "createSubWindowWithOptions() is unavailable in this context; "
385 "falling back to createSubWindow()");
386 return createSubWindowViaWindowStage(qAbilityPeer, windowName);
387 });
388 }();
389
390 subWindowCreationPromise
391 .withContext(Context {
392 .disableWindowFocusableBeforeLoadContentHack = createInfo.disableWindowFocusableBeforeLoadContentHack,
393 .xComponentId = xComponentId,
394 .qAbilityPeer = qAbilityPeer,
395 .owningQWindowRef = createInfo.window,
396 })
397 .onThenWithContext([resultConsumer = std::move(resultConsumer)](const QtOhos::CallbackInfo &cbInfo, Context &context) mutable {
398 auto windowObject = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
399
400 auto localStorage = makeLocalStorageForWindow(
401 cbInfo.jsState(),
402 LocalStorageForWindowCreateInfo {
403 .xComponentId = context.xComponentId,
404 .windowObject = windowObject,
405 .resultConsumer = std::move(resultConsumer),
406 .qAbilityPeer = context.qAbilityPeer,
407 .windowProxyType = WindowProxyType::SubWindow,
408 .owningQWindowRef = context.owningQWindowRef,
409 });
410
411 return onWindowCreatedLoadWindowContents(
412 cbInfo.jsState(), windowObject,
413 OnWindowCreatedLoadWindowContentsContext {
414 .disableWindowFocusableBeforeLoadContentHack = context.disableWindowFocusableBeforeLoadContentHack,
415 .contentPagePath = "pages/SubWindowNativeNode",
416 .localStorage = localStorage,
417 });
418 })
419 .onCatch([windowId = createInfo.windowId](const QtOhos::CallbackInfo &cbInfo) {
420 QtOhos::logJsCallbackError(cbInfo, "sub window creation failed");
421 qOhosReportFatalErrorAndAbort(
422 "Failed to create subwindow for windowId='%s'",
423 windowId.toStdString().c_str());
424 });
425}
426
428 QtOhos::JsState &jsState, const QOhosWindowProxyFloatWindowCreateInfo &createInfo,
429 QOhosConsumer<QtOhos::JsState &, QOhosWindowProxyData> resultConsumer)
430{
431 auto xComponentId = QXComponentId::createForNativeNodeFloatWindow(createInfo.internalWindowId);
432 auto qAbilityPeer = jsState.defaultQAbilityPeer();
433
434 if (qAbilityPeer->qAbility().IsEmpty())
435 qOhosReportFatalErrorAndAbort("%s: can't create window without the default Ability instance", Q_FUNC_INFO);
436
437 auto configurationObject = QNapi::makeObject(
438 jsState.env(),
439 {
440 // NOTE - The parameter name is misleading, what it refers to actually is (system) window id
441 {"name", makeOhosUniqueSystemWindowName(createInfo.internalWindowId)},
442 {"windowType", jsState.eval<QNapi::Number>("@ohos.window.WindowType.TYPE_FLOAT")},
443 {"ctx", qAbilityPeer->qAbility().get<QNapi::Object>("context")},
444 });
445
446 struct Context
447 {
448 QXComponentId xComponentId;
449 std::shared_ptr<QtOhos::QAbilityPeer> qAbilityPeer;
450 QtOhos::QObjectThreadSafeRef owningQWindowRef;
451 };
452
453 jsState
454 .evalToPromiseOrRejectOnThrow("@ohos.window.createWindow(*)", {configurationObject})
455 .withContext(Context {
456 .xComponentId = xComponentId,
457 .qAbilityPeer = qAbilityPeer,
458 .owningQWindowRef = createInfo.qWindowRef,
459 })
460 .onThenWithContext([resultConsumer = std::move(resultConsumer)](const QtOhos::CallbackInfo &cbInfo, Context &context) mutable {
461 auto windowObject = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
462
463 auto localStorage = makeLocalStorageForWindow(
464 cbInfo.jsState(),
465 LocalStorageForWindowCreateInfo {
466 .xComponentId = context.xComponentId,
467 .windowObject = windowObject,
468 .resultConsumer = std::move(resultConsumer),
469 .qAbilityPeer = context.qAbilityPeer,
470 .windowProxyType = WindowProxyType::FloatWindow,
471 .owningQWindowRef = context.owningQWindowRef,
472 });
473
474 return onWindowCreatedLoadWindowContents(
475 cbInfo.jsState(), windowObject,
476 OnWindowCreatedLoadWindowContentsContext {
477 .disableWindowFocusableBeforeLoadContentHack = false,
478 .contentPagePath = "pages/FloatWindowNativeNode",
479 .localStorage = localStorage,
480 });
481 })
482 .onCatch([internalWindowId = createInfo.internalWindowId](const QtOhos::CallbackInfo &cbInfo) {
483 QtOhos::logJsCallbackError(cbInfo, "Failed to create TYPE_FLOAT window");
484 qOhosReportFatalErrorAndAbort(
485 "Failed to create TYPE_FLOAT window for windowId='%s'",
486 internalWindowId.toStdString().c_str());
487 });
488}
489
490
491QT_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:45
QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer
QOhosConsumer< QtOhos::JsState &, QOhosWindowProxyData > resultConsumer
std::shared_ptr< QtOhos::QAbilityPeer > qAbilityPeer