Source code

Revision control

Copy as Markdown

// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qwasmscreen.h"
#include "qwasmcompositor.h"
#include "qwasmcssstyle.h"
#include "qwasmintegration.h"
#include "qwasmkeytranslator.h"
#include "qwasmwindow.h"
#include <emscripten/val.h>
#include <private/qwasmsuspendresumecontrol_p.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/qcoreapplication.h>
#include <QtGui/qguiapplication.h>
#include <tuple>
QT_BEGIN_NAMESPACE
using namespace emscripten;
QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
: m_container(containerOrCanvas),
m_intermediateContainer(emscripten::val::undefined()),
m_shadowContainer(emscripten::val::undefined()),
m_compositor(new QWasmCompositor(this))
{
auto document = m_container["ownerDocument"];
// Create an intermediate container which we can remove during cleanup in ~QWasmScreen().
// This is required due to the attachShadow() call below; there is no corresponding
// "detachShadow()" API to return the container to its previous state.
m_intermediateContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
m_intermediateContainer.set("id", std::string("qt-shadow-container"));
emscripten::val intermediateContainerStyle = m_intermediateContainer["style"];
intermediateContainerStyle.set("width", std::string("100%"));
intermediateContainerStyle.set("height", std::string("100%"));
m_container.call<void>("appendChild", m_intermediateContainer);
// Each screen is represented by a div container. All of the windows exist therein as
// its children. Qt versions < 6.5 used to represent screens as canvas elements; this
// is no longer supported.
if (m_container["tagName"].call<std::string>("toLowerCase") == "canvas")
qFatal() << "Qt does not support using a canvas element as the container element. Use a div element instead";
auto shadowOptions = emscripten::val::object();
shadowOptions.set("mode", "open");
auto shadow = m_intermediateContainer.call<emscripten::val>("attachShadow", shadowOptions);
m_shadowContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
shadow.call<void>("appendChild", QWasmCSSStyle::createStyleElement(m_shadowContainer));
shadow.call<void>("appendChild", m_shadowContainer);
m_shadowContainer.set("id", std::string("qt-screen-") + std::to_string(uintptr_t(this)));
m_shadowContainer["classList"].call<void>("add", std::string("qt-screen"));
// Disable the default context menu; Qt applications typically
// provide custom right-click behavior.
m_onContextMenu = std::make_unique<qstdweb::EventCallback>(
m_shadowContainer, "contextmenu",
[](emscripten::val event) { event.call<void>("preventDefault"); });
// Create "specialHTMLTargets" mapping for the canvas - the element might be unreachable based
// on its id only under some conditions, like the target being embedded in a shadow DOM or a
// subframe.
emscripten::val::module_property("specialHTMLTargets")
.set(outerScreenId().toStdString(), m_container);
updateQScreenSize();
installCanvasResizeObserver();
m_shadowContainer.call<void>("focus");
m_touchDevice = std::make_unique<QPointingDevice>(
"touchscreen", 1, QInputDevice::DeviceType::TouchScreen,
QPointingDevice::PointerType::Finger,
QPointingDevice::Capability::Position | QPointingDevice::Capability::Area
| QPointingDevice::Capability::NormalizedPosition,
10, 0);
m_tabletDevice = std::make_unique<QPointingDevice>(
"stylus", 2, QInputDevice::DeviceType::Stylus,
QPointingDevice::PointerType::Pen,
QPointingDevice::Capability::Position | QPointingDevice::Capability::Pressure
| QPointingDevice::Capability::NormalizedPosition
| QInputDevice::Capability::MouseEmulation
| QInputDevice::Capability::Hover | QInputDevice::Capability::Rotation
| QInputDevice::Capability::XTilt | QInputDevice::Capability::YTilt
| QInputDevice::Capability::TangentialPressure,
0, 0);
QWindowSystemInterface::registerInputDevice(m_touchDevice.get());
}
QWasmScreen::~QWasmScreen()
{
m_resizeObserver.call<void>("disconnect");
QWasmSuspendResumeControl::get()->removeEventHandler(m_resizeObserverHandlerIndex);
m_intermediateContainer.call<void>("remove");
}
void QWasmScreen::deleteScreen()
{
// Deletes |this|!
QWindowSystemInterface::handleScreenRemoved(this);
}
QWasmScreen *QWasmScreen::get(QPlatformScreen *screen)
{
return static_cast<QWasmScreen *>(screen);
}
QWasmScreen *QWasmScreen::get(QScreen *screen)
{
if (!screen)
return nullptr;
return get(screen->handle());
}
QWasmCompositor *QWasmScreen::compositor()
{
return m_compositor.get();
}
emscripten::val QWasmScreen::element() const
{
return m_shadowContainer;
}
QString QWasmScreen::outerScreenId() const
{
return QString("!outerscreen_%1").arg(uintptr_t(this));
}
QRect QWasmScreen::geometry() const
{
return m_geometry;
}
int QWasmScreen::depth() const
{
return m_depth;
}
QImage::Format QWasmScreen::format() const
{
return m_format;
}
QDpi QWasmScreen::logicalDpi() const
{
emscripten::val dpi = emscripten::val::module_property("qtFontDpi");
if (!dpi.isUndefined()) {
qreal dpiValue = dpi.as<qreal>();
return QDpi(dpiValue, dpiValue);
}
const qreal defaultDpi = 96;
return QDpi(defaultDpi, defaultDpi);
}
qreal QWasmScreen::devicePixelRatio() const
{
// window.devicePixelRatio gives us the scale factor between CSS and device pixels.
// This property reflects hardware configuration, and also browser zoom on desktop.
//
// window.visualViewport.scale gives us the zoom factor on mobile. If the html page is
// configured with "<meta name="viewport" content="width=device-width">" then this scale
// factor will be 1. Omitting the viewport configuration typically results on a zoomed-out
// viewport, with a scale factor <1. User pinch-zoom will change the scale factor; an event
// handler is installed in the QWasmIntegration constructor. Changing zoom level on desktop
// does not appear to change visualViewport.scale.
//
// The effective devicePixelRatio is the product of these two scale factors, upper-bounded
// by window.devicePixelRatio in order to avoid e.g. allocating a 10x widget backing store.
double dpr = emscripten::val::global("window")["devicePixelRatio"].as<double>();
emscripten::val visualViewport = emscripten::val::global("window")["visualViewport"];
double scale = visualViewport.isUndefined() ? 1.0 : visualViewport["scale"].as<double>();
double effectiveDevicePixelRatio = std::min(dpr * scale, dpr);
return qreal(effectiveDevicePixelRatio);
}
QString QWasmScreen::name() const
{
return QString::fromEcmaString(m_shadowContainer["id"]);
}
QPlatformCursor *QWasmScreen::cursor() const
{
return const_cast<QWasmCursor *>(&m_cursor);
}
void QWasmScreen::resizeMaximizedWindows()
{
if (!screen())
return;
QPlatformScreen::resizeMaximizedWindows();
}
QWindow *QWasmScreen::topLevelAt(const QPoint &p) const
{
const auto found =
std::find_if(childStack().begin(), childStack().end(), [&p](const QWasmWindow *window) {
const QRect geometry = window->windowFrameGeometry();
return window->isVisible() && geometry.contains(p);
});
return found != childStack().end() ? (*found)->window() : nullptr;
}
QPointF QWasmScreen::mapFromLocal(const QPointF &p) const
{
return geometry().topLeft() + p;
}
QPointF QWasmScreen::clipPoint(const QPointF &p) const
{
const auto geometryF = screen()->geometry().toRectF();
return QPointF(qBound(geometryF.left(), p.x(), geometryF.right()),
qBound(geometryF.top(), p.y(), geometryF.bottom()));
}
void QWasmScreen::invalidateSize()
{
m_geometry = QRect();
}
void QWasmScreen::setGeometry(const QRect &rect)
{
m_geometry = rect;
QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(),
availableGeometry());
resizeMaximizedWindows();
}
void QWasmScreen::updateQScreenSize()
{
double css_width;
double css_height;
emscripten_get_element_css_size(outerScreenId().toUtf8().constData(), &css_width, &css_height);
QSizeF cssSize(css_width, css_height);
// A hidden container reports 0x0, keep the last laid out size.
if (cssSize.isNull())
return;
// Returns the html elements document/body position
auto getElementBodyPosition = [](const emscripten::val &element) -> QPoint {
emscripten::val bodyRect =
element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect");
emscripten::val canvasRect = element.call<emscripten::val>("getBoundingClientRect");
return QPoint(canvasRect["left"].as<int>() - bodyRect["left"].as<int>(),
canvasRect["top"].as<int>() - bodyRect["top"].as<int>());
};
setGeometry(QRect(getElementBodyPosition(m_shadowContainer), cssSize.toSize()));
}
// Tracks container layout changes the window resize event misses, such as the
// container being revealed or sized by the page after Qt has started.
void QWasmScreen::installCanvasResizeObserver()
{
QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
m_resizeObserverHandlerIndex = suspendResume->registerEventHandler(
[this](emscripten::val) { updateQScreenSize(); });
m_resizeObserver = emscripten::val::global("ResizeObserver").new_(
suspendResume->jsEventHandlerAt(m_resizeObserverHandlerIndex));
m_resizeObserver.call<void>("observe", m_shadowContainer);
}
emscripten::val QWasmScreen::containerElement()
{
return m_shadowContainer;
}
QWasmWindowTreeNode<> *QWasmScreen::parentNode()
{
return nullptr;
}
QList<QWasmWindow *> QWasmScreen::allWindows() const
{
QList<QWasmWindow *> currentChildren;
QList<QWasmWindow *> result;
for (auto *w : childStack())
currentChildren << w;
while (!currentChildren.empty()) {
result << currentChildren;
QList<QWasmWindow *> toIterate;
currentChildren.swap(toIterate);
for (auto child : toIterate) {
for (auto *w : child->childStack())
currentChildren << w;
}
}
return result;
}
QT_END_NAMESPACE