summaryrefslogtreecommitdiffstats
path: root/tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp')
-rw-r--r--tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp696
1 files changed, 696 insertions, 0 deletions
diff --git a/tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp b/tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp
new file mode 100644
index 0000000000..365fc74a34
--- /dev/null
+++ b/tests/auto/wasm/selenium/tst_qwasmwindow_harness.cpp
@@ -0,0 +1,696 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/QEvent>
+#include <QtWidgets/qwidget.h>
+
+#include <QtGui/qevent.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qregularexpression.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qrasterwindow.h>
+#include <QtGui/qscreen.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/qguiapplication.h>
+#include <QtWidgets/qlineedit.h>
+#include <QApplication>
+#include <QDialog>
+#include <QSysInfo>
+
+#include <QOpenGLWindow>
+#include <QOpenGLFunctions>
+#include <QOpenGLShaderProgram>
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/val.h>
+
+#include <memory>
+#include <sstream>
+#include <vector>
+
+class TestWindowBase
+{
+public:
+ virtual ~TestWindowBase() {}
+ virtual void setBackgroundColor(int r, int g, int b) = 0;
+ virtual void setVisible(bool visible) = 0;
+ virtual void setParent(QWindow *parent) = 0;
+ virtual bool close() = 0;
+ virtual QWindow *qWindow() = 0;
+ virtual void opengl_color_at_0_0(int *r, int *g, int *b) = 0;
+};
+
+class TestWidget : public QDialog
+{
+ Q_OBJECT
+};
+
+class TestWindow : public QRasterWindow, public TestWindowBase
+{
+ Q_OBJECT
+
+public:
+ virtual void setBackgroundColor(int r, int g, int b) override final
+ {
+ m_backgroundColor = QColor::fromRgb(r, g, b);
+ update();
+ }
+ virtual void setVisible(bool visible) override final
+ {
+ QRasterWindow::setVisible(visible);
+ }
+ virtual void setParent(QWindow *parent) override final
+ {
+ QRasterWindow::setParent(parent);
+ }
+ virtual bool close() override final
+ {
+ return QRasterWindow::close();
+ }
+ virtual QWindow *qWindow() override final
+ {
+ return static_cast<QRasterWindow *>(this);
+ }
+ virtual void opengl_color_at_0_0(int *r, int *g, int *b) override final
+ {
+ *r = 0;
+ *g = 0;
+ *b = 0;
+ }
+
+private:
+ void closeEvent(QCloseEvent *ev) override final
+ {
+ Q_UNUSED(ev);
+ delete this;
+ }
+
+ void keyPressEvent(QKeyEvent *event) final
+ {
+ auto data = emscripten::val::object();
+ data.set("type", emscripten::val("keyPress"));
+ data.set("windowId", emscripten::val(winId()));
+ data.set("windowTitle", emscripten::val(title().toStdString()));
+ data.set("key", emscripten::val(event->text().toStdString()));
+ emscripten::val::global("window")["testSupport"].call<void>("reportEvent", std::move(data));
+ }
+
+ void keyReleaseEvent(QKeyEvent *event) final
+ {
+ auto data = emscripten::val::object();
+ data.set("type", emscripten::val("keyRelease"));
+ data.set("windowId", emscripten::val(winId()));
+ data.set("windowTitle", emscripten::val(title().toStdString()));
+ data.set("key", emscripten::val(event->text().toStdString()));
+ emscripten::val::global("window")["testSupport"].call<void>("reportEvent", std::move(data));
+ }
+
+ void paintEvent(QPaintEvent *e) final
+ {
+ QPainter painter(this);
+ painter.fillRect(e->rect(), m_backgroundColor);
+ }
+
+ QColor m_backgroundColor = Qt::white;
+};
+
+class ContextGuard
+{
+public:
+ ContextGuard(QOpenGLContext *context, QSurface *surface) : m_context(context)
+ {
+ m_contextMutex.lock();
+ m_context->makeCurrent(surface);
+ }
+
+ ~ContextGuard()
+ {
+ m_context->doneCurrent();
+ m_contextMutex.unlock();
+ }
+
+private:
+ QOpenGLContext *m_context = nullptr;
+ static std::mutex m_contextMutex;
+};
+
+std::mutex ContextGuard::m_contextMutex;
+
+class TestOpenGLWindow : public QWindow, QOpenGLFunctions, public TestWindowBase
+{
+ Q_OBJECT
+
+public:
+ TestOpenGLWindow()
+ {
+ setSurfaceType(OpenGLSurface);
+ create();
+
+ //
+ // Create the texture in the share context
+ //
+ m_shareContext = std::make_shared<QOpenGLContext>();
+ m_shareContext->create();
+
+ {
+ ContextGuard guard(m_shareContext.get(), this);
+ initializeOpenGLFunctions();
+
+ m_shaderProgram = std::make_shared<QOpenGLShaderProgram>();
+
+ if (!m_shaderProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vshader.glsl")
+ || !m_shaderProgram->addShaderFromSourceFile(QOpenGLShader::Fragment,
+ ":/fshader.glsl")
+ || !m_shaderProgram->link() || !m_shaderProgram->bind()) {
+
+ qDebug() << " Build problem";
+ qDebug() << "Log " << m_shaderProgram->log();
+
+ m_shaderProgram = nullptr;
+ } else {
+ m_shaderProgram->setUniformValue("texture", 0);
+ }
+
+ //
+ // Texture
+ //
+ glGenTextures(1, &m_TextureId);
+ glBindTexture(GL_TEXTURE_2D, m_TextureId);
+
+ uint8_t pixel[4] = { 255, 255, 255, 128 };
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+ const GLfloat triangleData[] = { -1.0, -1.0, 0.0, 0.5, 0.5, 1.0, -1.0, 0.0,
+ 0.5, 0.5, -1.0, 1.0, 0.0, 0.5, 0.5 };
+ const GLushort indices[] = { 0, 1, 2 };
+
+ glGenBuffers(1, &m_vertexBufferId);
+ glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float[5]) * 3, &triangleData, GL_STATIC_DRAW);
+
+ glGenBuffers(1, &m_indexBufferId);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferId);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * 3, indices, GL_STATIC_DRAW);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
+
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float[5]), 0);
+
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float[5]), (void *)(12));
+ }
+
+ //
+ // We will use the texture in this context
+ //
+ m_context = std::make_shared<QOpenGLContext>();
+ m_context->setShareContext(m_shareContext.get());
+ m_context->create();
+
+ {
+ ContextGuard guard(m_context.get(), this);
+ initializeOpenGLFunctions();
+
+ glBindTexture(GL_TEXTURE_2D, m_TextureId);
+ glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferId);
+ m_shaderProgram->bind();
+
+ // Tell OpenGL programmable pipeline how to locate vertex position data
+ const int vertexLocation = m_shaderProgram->attributeLocation("a_position");
+ m_shaderProgram->enableAttributeArray(vertexLocation);
+ m_shaderProgram->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, sizeof(float[5]));
+
+ // Tell OpenGL programmable pipeline how to locate vertex texture coordinate data
+ const int texcoordLocation = m_shaderProgram->attributeLocation("a_texcoord");
+ m_shaderProgram->enableAttributeArray(texcoordLocation);
+ m_shaderProgram->setAttributeBuffer(texcoordLocation, GL_FLOAT, sizeof(float[3]), 2,
+ sizeof(float[5]));
+ }
+
+ renderLater();
+ }
+
+public:
+ virtual void setBackgroundColor(int red, int green, int blue) override final
+ {
+ {
+ ContextGuard guard(m_shareContext.get(), this);
+
+ //
+ // Update texture
+ //
+ const uint8_t pixel[4] = { (uint8_t)red, (uint8_t)green, (uint8_t)blue, 128 };
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
+ }
+
+ renderLater();
+ }
+ virtual void setVisible(bool visible) override final { QWindow::setVisible(visible); }
+ virtual void setParent(QWindow *parent) override final { QWindow::setParent(parent); }
+ virtual bool close() override final { return QWindow::close(); }
+ virtual QWindow *qWindow() override final { return static_cast<QWindow *>(this); }
+ virtual void opengl_color_at_0_0(int *r, int *g, int *b) override final
+ {
+ ContextGuard guard(m_context.get(), this);
+
+ *r = m_rgba[0];
+ *g = m_rgba[1];
+ *b = m_rgba[2];
+ }
+
+private:
+ bool event(QEvent *event) override final
+ {
+ switch (event->type()) {
+ case QEvent::UpdateRequest:
+ renderNow();
+ return true;
+ default:
+ return QWindow::event(event);
+ }
+ }
+
+ void exposeEvent(QExposeEvent *event) override final
+ {
+ Q_UNUSED(event);
+
+ if (isExposed())
+ renderNow();
+ }
+
+ void closeEvent(QCloseEvent *ev) override final
+ {
+ Q_UNUSED(ev);
+ delete this;
+ }
+
+ void keyPressEvent(QKeyEvent *event) override final
+ {
+ auto data = emscripten::val::object();
+ data.set("type", emscripten::val("keyPress"));
+ data.set("windowId", emscripten::val(winId()));
+ data.set("windowTitle", emscripten::val(title().toStdString()));
+ data.set("key", emscripten::val(event->text().toStdString()));
+ emscripten::val::global("window")["testSupport"].call<void>("reportEvent", std::move(data));
+ }
+
+ void keyReleaseEvent(QKeyEvent *event) override final
+ {
+ auto data = emscripten::val::object();
+ data.set("type", emscripten::val("keyRelease"));
+ data.set("windowId", emscripten::val(winId()));
+ data.set("windowTitle", emscripten::val(title().toStdString()));
+ data.set("key", emscripten::val(event->text().toStdString()));
+ emscripten::val::global("window")["testSupport"].call<void>("reportEvent", std::move(data));
+ }
+ void renderLater() { requestUpdate(); }
+ void renderNow()
+ {
+ qDebug() << " Render now";
+ ContextGuard guard(m_context.get(), this);
+ const auto sz = size();
+ glViewport(0, 0, sz.width(), sz.height());
+
+ glClearColor(1, 1, 1, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Draw triangle using indices from VBO
+ glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr);
+
+ glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, m_rgba);
+ m_context->swapBuffers(this);
+ }
+
+private:
+ std::shared_ptr<QOpenGLShaderProgram> m_shaderProgram;
+ GLuint m_vertexBufferId = 0;
+ GLuint m_indexBufferId = 0;
+ GLuint m_TextureId = 0;
+
+ std::shared_ptr<QOpenGLContext> m_shareContext;
+ std::shared_ptr<QOpenGLContext> m_context;
+ uint8_t m_rgba[4]; // Color at location(0, 0)
+};
+
+namespace {
+TestWindowBase *findWindowByTitle(const std::string &title)
+{
+ auto windows = qGuiApp->allWindows();
+ auto window_it = std::find_if(windows.begin(), windows.end(), [&title](QWindow *window) {
+ return window->title() == QString::fromLatin1(title);
+ });
+ return window_it == windows.end() ? nullptr : dynamic_cast<TestWindowBase *>(*window_it);
+}
+
+class WidgetStorage
+{
+private:
+ ~WidgetStorage()
+ {
+ }
+public:
+ static WidgetStorage *getInstance()
+ {
+ if (!s_instance)
+ {
+ s_instance = new WidgetStorage();
+ }
+ return s_instance;
+ }
+ static void clearInstance()
+ {
+ delete s_instance;
+ s_instance = nullptr;
+ }
+
+ TestWidget *findWidget(const std::string &name)
+ {
+ auto it = m_widgets.find(name);
+ if (it != m_widgets.end())
+ return it->second.get();
+ return nullptr;
+ }
+
+ QLineEdit *findEdit(const std::string &name)
+ {
+ auto it = m_lineEdits.find(name);
+ if (it != m_lineEdits.end())
+ return it->second;
+ return nullptr;
+ }
+
+ void make(const std::string &name)
+ {
+ auto widget = std::make_shared<TestWidget>();
+ auto *lineEdit = new QLineEdit(widget.get());
+
+ 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;
+ }
+
+private:
+ using TestWidgetPtr = std::shared_ptr<TestWidget>;
+
+ static WidgetStorage * s_instance;
+ std::map<std::string, TestWidgetPtr> m_widgets;
+ std::map<std::string, QLineEdit *> m_lineEdits;
+ int m_widgetY = 0;
+};
+
+WidgetStorage *WidgetStorage::s_instance = nullptr;
+
+} // namespace
+
+using namespace emscripten;
+
+std::string toJSArray(const std::vector<std::string> &elements)
+{
+ std::ostringstream out;
+ out << "[";
+ bool comma = false;
+ for (const auto &element : elements) {
+ out << (comma ? "," : "");
+ out << element;
+ comma = true;
+ }
+ out << "]";
+ return out.str();
+}
+
+std::string toJSString(const QString &qstring)
+{
+ Q_ASSERT_X(([qstring]() {
+ static QRegularExpression unescapedQuoteRegex(R"re((?:^|[^\\])')re");
+ return qstring.indexOf(unescapedQuoteRegex) == -1;
+ })(),
+ Q_FUNC_INFO, "Unescaped single quotes found");
+ return "'" + qstring.toStdString() + "'";
+}
+
+std::string rectToJSObject(const QRect &rect)
+{
+ std::ostringstream out;
+ out << "{"
+ << " x: " << std::to_string(rect.x()) << ","
+ << " y: " << std::to_string(rect.y()) << ","
+ << " width: " << std::to_string(rect.width()) << ","
+ << " height: " << std::to_string(rect.height()) << "}";
+ return out.str();
+}
+
+std::string screenToJSObject(const QScreen &screen)
+{
+ std::ostringstream out;
+ out << "{"
+ << " name: " << toJSString(screen.name()) << ","
+ << " geometry: " << rectToJSObject(screen.geometry()) << "}";
+ return out.str();
+}
+
+std::string windowToJSObject(const QWindow &window)
+{
+ std::ostringstream out;
+ out << "{"
+ << " id: " << std::to_string(window.winId()) << ","
+ << " geometry: " << rectToJSObject(window.geometry()) << ","
+ << " frameGeometry: " << rectToJSObject(window.frameGeometry()) << ","
+ << " screen: " << screenToJSObject(*window.screen()) << ","
+ << " title: '" << window.title().toStdString() << "' }";
+ return out.str();
+}
+
+void windowInformation()
+{
+ auto windows = qGuiApp->allWindows();
+
+ std::vector<std::string> windowsAsJsObjects;
+ windowsAsJsObjects.reserve(windows.size());
+ std::transform(windows.begin(), windows.end(), std::back_inserter(windowsAsJsObjects),
+ [](const QWindow *window) { return windowToJSObject(*window); });
+
+ emscripten::val::global("window").call<void>("windowInformationCallback",
+ emscripten::val(toJSArray(windowsAsJsObjects)));
+}
+
+void screenInformation()
+{
+ auto screens = qGuiApp->screens();
+
+ std::vector<std::string> screensAsJsObjects;
+ screensAsJsObjects.reserve(screens.size());
+ std::transform(screens.begin(), screens.end(), std::back_inserter(screensAsJsObjects),
+ [](const QScreen *screen) { return screenToJSObject(*screen); });
+ emscripten::val::global("window").call<void>("screenInformationCallback",
+ emscripten::val(toJSArray(screensAsJsObjects)));
+}
+
+void createWidget(const std::string &name)
+{
+ WidgetStorage::getInstance()->make(name);
+}
+
+void setWidgetNoFocusShow(const std::string &name)
+{
+ auto w = WidgetStorage::getInstance()->findWidget(name);
+ if (w)
+ w->setAttribute(Qt::WA_ShowWithoutActivating);
+}
+
+void showWidget(const std::string &name)
+{
+ auto w = WidgetStorage::getInstance()->findWidget(name);
+ if (w)
+ w->show();
+}
+
+void hasWidgetFocus(const std::string &name)
+{
+ bool focus = false;
+ auto le = WidgetStorage::getInstance()->findEdit(name);
+ if (le)
+ focus = le->hasFocus();
+
+ emscripten::val::global("window").call<void>("hasWidgetFocusCallback",
+ emscripten::val(focus));
+}
+
+void activateWidget(const std::string &name)
+{
+ auto w = WidgetStorage::getInstance()->findWidget(name);
+ if (w)
+ w->activateWindow();
+}
+
+void clearWidgets()
+{
+ WidgetStorage::clearInstance();
+}
+
+void createWindow(int x, int y, int w, int h, const std::string &parentType, const std::string &parentId,
+ const std::string &title, bool opengl)
+{
+ QScreen *parentScreen = nullptr;
+ QWindow *parentWindow = nullptr;
+ if (parentType == "screen") {
+ auto screens = qGuiApp->screens();
+ auto screen_it = std::find_if(screens.begin(), screens.end(), [&parentId](QScreen *screen) {
+ return screen->name() == QString::fromLatin1(parentId);
+ });
+ if (screen_it == screens.end()) {
+ qWarning() << "No such screen: " << parentId;
+ return;
+ }
+ parentScreen = *screen_it;
+ } else if (parentType == "window") {
+ auto testWindow = findWindowByTitle(parentId);
+
+ if (!testWindow) {
+ qWarning() << "No parent window: " << parentId;
+ return;
+ }
+ parentWindow = testWindow->qWindow();
+ parentScreen = parentWindow->screen();
+ } else {
+ qWarning() << "Wrong parent type " << parentType;
+ return;
+ }
+
+ if (opengl) {
+ qDebug() << "Making OpenGL window";
+ auto window = new TestOpenGLWindow;
+ window->setFlag(Qt::WindowTitleHint);
+ window->setFlag(Qt::WindowMaximizeButtonHint);
+ window->setTitle(QString::fromLatin1(title));
+ window->setGeometry(x, y, w, h);
+ window->setScreen(parentScreen);
+ window->setParent(parentWindow);
+ } else {
+ qDebug() << "Making Raster window";
+ auto window = new TestWindow;
+ window->setFlag(Qt::WindowTitleHint);
+ window->setFlag(Qt::WindowMaximizeButtonHint);
+ window->setTitle(QString::fromLatin1(title));
+ window->setGeometry(x, y, w, h);
+ window->setScreen(parentScreen);
+ window->setParent(parentWindow);
+ }
+}
+
+void setWindowBackgroundColor(const std::string &title, int r, int g, int b)
+{
+ auto *window = findWindowByTitle(title);
+ if (!window) {
+ qWarning() << "No such window: " << title;
+ return;
+ }
+ window->setBackgroundColor(r, g, b);
+}
+
+void setWindowVisible(int windowId, bool visible)
+{
+ auto windows = qGuiApp->allWindows();
+ auto window_it = std::find_if(windows.begin(), windows.end(), [windowId](QWindow *window) {
+ return window->winId() == WId(windowId);
+ });
+ if (window_it == windows.end()) {
+ qWarning() << "No such window: " << windowId;
+ return;
+ }
+
+ (*window_it)->setVisible(visible);
+}
+
+void setWindowParent(const std::string &windowTitle, const std::string &parentTitle)
+{
+ TestWindowBase *window = findWindowByTitle(windowTitle);
+ if (!window) {
+ qWarning() << "Window could not be found " << windowTitle;
+ return;
+ }
+ TestWindowBase *parent = nullptr;
+ if (parentTitle != "none") {
+ if ((parent = findWindowByTitle(parentTitle)) == nullptr) {
+ qWarning() << "Parent window could not be found " << parentTitle;
+ return;
+ }
+ }
+ window->setParent(parent ? parent->qWindow() : nullptr);
+}
+
+bool closeWindow(const std::string &title)
+{
+ TestWindowBase *window = findWindowByTitle(title);
+ return window ? window->close() : false;
+}
+
+std::string colorToJs(int r, int g, int b)
+{
+ return
+ "[{"
+ " r: " + std::to_string(r) + ","
+ " g: " + std::to_string(g) + ","
+ " b: " + std::to_string(b) + ""
+ "}]";
+}
+
+void getOpenGLColorAt_0_0(const std::string &windowTitle)
+{
+ TestWindowBase *window = findWindowByTitle(windowTitle);
+ int r = 0;
+ int g = 0;
+ int b = 0;
+
+ if (!window) {
+ qWarning() << "Window could not be found " << windowTitle;
+ } else {
+ window->opengl_color_at_0_0(&r, &g, &b);
+ }
+
+ emscripten::val::global("window").call<void>("getOpenGLColorAt_0_0Callback",
+ emscripten::val(colorToJs(r, g, b)));
+}
+
+EMSCRIPTEN_BINDINGS(qwasmwindow)
+{
+ emscripten::function("screenInformation", &screenInformation);
+ emscripten::function("windowInformation", &windowInformation);
+
+ emscripten::function("createWindow", &createWindow);
+ emscripten::function("setWindowVisible", &setWindowVisible);
+ emscripten::function("setWindowParent", &setWindowParent);
+ emscripten::function("closeWindow", &closeWindow);
+ emscripten::function("setWindowBackgroundColor", &setWindowBackgroundColor);
+
+ emscripten::function("getOpenGLColorAt_0_0", &getOpenGLColorAt_0_0);
+
+ emscripten::function("createWidget", &createWidget);
+ emscripten::function("setWidgetNoFocusShow", &setWidgetNoFocusShow);
+ emscripten::function("showWidget", &showWidget);
+ emscripten::function("activateWidget", &activateWidget);
+ emscripten::function("hasWidgetFocus", &hasWidgetFocus);
+ emscripten::function("clearWidgets", &clearWidgets);
+}
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+ app.exec();
+ return 0;
+}
+
+#include "tst_qwasmwindow_harness.moc"