wasm: Fix handling of native windows

There are two problems
1) internal (not toplevel) windows needs to have the frameLess attribute
set. Without this attribute the window is not visible

2) The backingstore needs to use the correct window, if not the
symptoms are that the display is not always correctly updated.

Seen in the qtdoc/examples/demos/documentviewer demo

Fixes: QTBUG-125856
Change-Id: I040d963c0c130214cc70a607090faa006c02f981
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Even Oscar Andersen 2024-06-06 14:57:14 +02:00
parent 1943183282
commit 1b220e1db8
6 changed files with 130 additions and 29 deletions

View File

@ -39,11 +39,12 @@ QPaintDevice *QWasmBackingStore::paintDevice()
void QWasmBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
{
Q_UNUSED(window);
Q_UNUSED(region);
Q_UNUSED(offset);
m_dirty |= region;
m_compositor->handleBackingStoreFlush(window);
QRect updateRect = region.boundingRect();
updateRect.translate(offset);
m_compositor->handleBackingStoreFlush(this->window(), updateRect);
}
void QWasmBackingStore::updateTexture(QWasmWindow *window)

View File

@ -55,16 +55,23 @@ bool QWasmCompositor::releaseRequestUpdateHold()
return wasEnabled;
}
void QWasmCompositor::requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType)
void QWasmCompositor::requestUpdateWindow(QWasmWindow *window, const QRect &updateRect, UpdateRequestDeliveryType updateType)
{
auto it = m_requestUpdateWindows.find(window);
if (it == m_requestUpdateWindows.end()) {
m_requestUpdateWindows.insert(window, updateType);
m_requestUpdateWindows.insert(window, std::make_tuple(updateRect, updateType));
} else {
// Already registered, but upgrade ExposeEventDeliveryType to UpdateRequestDeliveryType.
// if needed, to make sure QWindow::updateRequest's are matched.
if (it.value() == ExposeEventDelivery && updateType == UpdateRequestDelivery)
it.value() = UpdateRequestDelivery;
if (std::get<0>(it.value()) != updateRect) {
QRegion region;
region |= std::get<0>(it.value());
region |= updateRect;
std::get<0>(it.value()) = region.boundingRect();
}
if (std::get<1>(it.value()) == ExposeEventDelivery &&
updateType == UpdateRequestDelivery)
std::get<1>(it.value()) = UpdateRequestDelivery;
}
requestUpdate();
@ -106,15 +113,20 @@ void QWasmCompositor::deliverUpdateRequests()
m_inDeliverUpdateRequest = true;
for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) {
auto *window = it.key();
UpdateRequestDeliveryType updateType = it.value();
deliverUpdateRequest(window, updateType);
const QRect updateRect = std::get<0>(it.value());
const UpdateRequestDeliveryType updateType = std::get<1>(it.value());
deliverUpdateRequest(window, updateRect, updateType);
}
m_inDeliverUpdateRequest = false;
frame(requestUpdateWindows.keys());
}
void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType)
void QWasmCompositor::deliverUpdateRequest(
QWasmWindow *window,
const QRect &updateRect,
UpdateRequestDeliveryType updateType)
{
QWindow *qwindow = window->window();
@ -126,7 +138,6 @@ void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDel
// type. If the window has not yet been exposed then we must expose it first regardless
// of update type. The deliverUpdateRequest must still be sent in this case in order
// to maintain correct window update state.
QRect updateRect(QPoint(0, 0), qwindow->geometry().size());
if (updateType == UpdateRequestDelivery) {
if (qwindow->isExposed() == false)
QWindowSystemInterface::handleExposeEvent(qwindow, updateRect);
@ -136,12 +147,12 @@ void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDel
}
}
void QWasmCompositor::handleBackingStoreFlush(QWindow *window)
void QWasmCompositor::handleBackingStoreFlush(QWindow *window, const QRect &updateRect)
{
// Request update to flush the updated backing store content, unless we are currently
// processing an update, in which case the new content will flushed as a part of that update.
if (!m_inDeliverUpdateRequest)
requestUpdateWindow(static_cast<QWasmWindow *>(window->handle()));
requestUpdateWindow(static_cast<QWasmWindow *>(window->handle()), updateRect);
}
void QWasmCompositor::frame(const QList<QWasmWindow *> &windows)

View File

@ -9,6 +9,7 @@
#include <qpa/qplatformwindow.h>
#include <QMap>
#include <tuple>
QT_BEGIN_NAMESPACE
@ -35,9 +36,9 @@ public:
void requestUpdate();
enum UpdateRequestDeliveryType { ExposeEventDelivery, UpdateRequestDelivery };
void requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType = ExposeEventDelivery);
void requestUpdateWindow(QWasmWindow *window, const QRect &updateRect, UpdateRequestDeliveryType updateType = ExposeEventDelivery);
void handleBackingStoreFlush(QWindow *window);
void handleBackingStoreFlush(QWindow *window, const QRect &updateRect);
void onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindow *window);
private:
@ -46,10 +47,10 @@ private:
void deregisterEventHandlers();
void deliverUpdateRequests();
void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType);
void deliverUpdateRequest(QWasmWindow *window, const QRect &updateRect, UpdateRequestDeliveryType updateType);
bool m_isEnabled = true;
QMap<QWasmWindow *, UpdateRequestDeliveryType> m_requestUpdateWindows;
QMap<QWasmWindow *, std::tuple<QRect, UpdateRequestDeliveryType>> m_requestUpdateWindows;
int m_requestAnimationFrameId = -1;
bool m_inDeliverUpdateRequest = false;
static bool m_requestUpdateHoldEnabled;

View File

@ -317,7 +317,7 @@ void QWasmWindow::setGeometry(const QRect &rect)
if (shouldInvalidate)
invalidate();
else
m_compositor->requestUpdateWindow(this);
m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size()));
}
void QWasmWindow::setVisible(bool visible)
@ -327,7 +327,7 @@ void QWasmWindow::setVisible(bool visible)
if (visible == nowVisible)
return;
m_compositor->requestUpdateWindow(this, QWasmCompositor::ExposeEventDelivery);
m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size()), QWasmCompositor::ExposeEventDelivery);
m_qtWindow["style"].set("display", visible ? "block" : "none");
if (window()->isActive())
m_canvas.call<void>("focus");
@ -385,7 +385,7 @@ void QWasmWindow::setOpacity(qreal level)
void QWasmWindow::invalidate()
{
m_compositor->requestUpdateWindow(this);
m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size()));
}
void QWasmWindow::onActivationChanged(bool active)
@ -401,7 +401,7 @@ void QWasmWindow::setWindowFlags(Qt::WindowFlags flags)
onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags));
}
m_flags = flags;
dom::syncCSSClassWith(m_qtWindow, "frameless", !hasFrame());
dom::syncCSSClassWith(m_qtWindow, "frameless", !hasFrame() || !window()->isTopLevel());
dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
dom::syncCSSClassWith(m_qtWindow, "has-shadow", hasShadow());
dom::syncCSSClassWith(m_qtWindow, "has-title", hasTitleBar());
@ -566,7 +566,7 @@ qreal QWasmWindow::devicePixelRatio() const
void QWasmWindow::requestUpdate()
{
m_compositor->requestUpdateWindow(this, QWasmCompositor::UpdateRequestDelivery);
m_compositor->requestUpdateWindow(this, QRect(QPoint(0, 0), geometry().size()), QWasmCompositor::UpdateRequestDelivery);
}
bool QWasmWindow::hasFrame() const

View File

@ -28,6 +28,24 @@ class WidgetTestCase(unittest.TestCase):
self.addTypeEqualityFunc(Color, assert_colors_equal)
self.addTypeEqualityFunc(Rect, assert_rects_equal)
#
# This is a manual test
# The reason is that the color readback works
# even if the display is incorrect
#
def test_native_widgets(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=600, height=1200)
w0 = Widget(self._driver, "w0", 1)
w0.show()
#time.sleep(3600)
color = w0.color_at(100, 150)
self.assertEqual(color.r, 255)
self.assertEqual(color.g, 255)
self.assertEqual(color.b, 255)
self.assertEqual(w0.hasFocus(), True)
def test_hasFocus_returnsFalse_whenSetNoFocusShowWasCalled(self):
screen = Screen(self._driver, ScreenPosition.FIXED,
x=0, y=0, width=600, height=1200)
@ -607,15 +625,30 @@ def clearWidgets(driver):
)
class Widget:
def __init__(self, driver, name):
def __init__(self, driver, name, isNative=0):
self.name=name
self.driver=driver
self.driver.execute_script(
f'''
instance.createWidget('{self.name}');
'''
)
if isNative == 0:
self.driver.execute_script(
f'''
instance.createWidget('{self.name}');
'''
)
if isNative == 1:
self.driver.execute_script(
f'''
instance.createNativeWidget('{self.name}');
'''
)
if isNative == 1:
information = self.__window_information()
self.screen = Screen(self.driver, screen_name=information['screen']['name'])
self._window_id = self.__window_information()['id']
self.element = self.screen.find_element(
By.CSS_SELECTOR, f'#qt-window-{self._window_id}')
def setNoFocusShow(self):
self.driver.execute_script(
@ -641,6 +674,18 @@ class Widget:
'''
)
def color_at(self, x, y):
raw = self.driver.execute_script(
f'''
return arguments[0].querySelector('canvas')
.getContext('2d').getImageData({x}, {y}, 1, 1).data;
''', self.element)
return Color(r=raw[0], g=raw[1], b=raw[2])
def __window_information(self):
information = call_instance_function(self.driver, 'windowInformation')
return next(filter(lambda e: e['title'] == "Dialog", information))
class Window:
def __init__(self, parent=None, rect=None, title=None, element=None, visible=True, opengl=0):

View File

@ -16,6 +16,10 @@
#include <QApplication>
#include <QDialog>
#include <QSysInfo>
#include <QTreeView>
#include <QFileSystemModel>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QOpenGLWindow>
#include <QOpenGLFunctions>
@ -389,6 +393,7 @@ public:
void make(const std::string &name)
{
auto widget = std::make_shared<TestWidget>();
widget->setWindowTitle("Dialog");
auto *lineEdit = new QLineEdit(widget.get());
widget->setMinimumSize(200, 200);
@ -401,6 +406,38 @@ public:
m_widgets[name] = widget;
m_lineEdits[name] = lineEdit;
}
void makeNative(const std::string &name)
{
auto widget = std::make_shared<TestWidget>();
widget->setWindowTitle("Dialog");
auto *lineEdit = new QLineEdit();
widget->setMinimumSize(200, 200);
widget->setMaximumSize(200, 200);
widget->setGeometry(0, m_widgetY, 200, 200);
m_widgetY += 200;
lineEdit->setText("Hello world");
m_widgets[name] = widget;
m_lineEdits[name] = lineEdit;
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath());
auto *scrollArea = new QScrollArea();
auto *layout = new QVBoxLayout(widget.get());
auto *treeView = new QTreeView(scrollArea);
treeView->setModel(model);
layout->addWidget(lineEdit);
layout->addWidget(scrollArea);
treeView->setAttribute(Qt::WA_NativeWindow);
scrollArea->setAttribute(Qt::WA_NativeWindow);
lineEdit->setAttribute(Qt::WA_NativeWindow);
widget->setAttribute(Qt::WA_NativeWindow);
}
private:
using TestWidgetPtr = std::shared_ptr<TestWidget>;
@ -503,6 +540,11 @@ void createWidget(const std::string &name)
WidgetStorage::getInstance()->make(name);
}
void createNativeWidget(const std::string &name)
{
WidgetStorage::getInstance()->makeNative(name);
}
void setWidgetNoFocusShow(const std::string &name)
{
auto w = WidgetStorage::getInstance()->findWidget(name);
@ -678,6 +720,7 @@ EMSCRIPTEN_BINDINGS(qwasmwindow)
emscripten::function("getOpenGLColorAt_0_0", &getOpenGLColorAt_0_0);
emscripten::function("createWidget", &createWidget);
emscripten::function("createNativeWidget", &createNativeWidget);
emscripten::function("setWidgetNoFocusShow", &setWidgetNoFocusShow);
emscripten::function("showWidget", &showWidget);
emscripten::function("activateWidget", &activateWidget);