wasm: add end-user accessibillty opt-in
We want to make support for screen readers available incrementally to avoid destabilizing existing functionality. Accomplish this by initially adding a single "enable screen reader" button only. When pressed, this button will remove itself and populate the page with the actual accessibility elements. Change-Id: I65036d249c408d4dc1fa75e8c807d9b7300e4722 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io> (cherry picked from commit 42d4619967688e4f313f5508de7594f0c0c976ff) Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io> Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
56cbab3b12
commit
e7c88aa5a8
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "qwasmaccessibility.h"
|
#include "qwasmaccessibility.h"
|
||||||
#include "qwasmscreen.h"
|
#include "qwasmscreen.h"
|
||||||
|
#include "qwasmwindow.h"
|
||||||
|
|
||||||
#include <QtGui/qwindow.h>
|
#include <QtGui/qwindow.h>
|
||||||
|
|
||||||
@ -18,33 +19,105 @@ Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
|
|||||||
// events. In addition or alternatively, we could also walk the accessibility tree
|
// events. In addition or alternatively, we could also walk the accessibility tree
|
||||||
// from setRootObject().
|
// from setRootObject().
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QWasmWindow *asWasmWindow(QWindow *window)
|
||||||
|
{
|
||||||
|
return static_cast<QWasmWindow*>(window->handle());
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
QWasmAccessibility::QWasmAccessibility()
|
QWasmAccessibility::QWasmAccessibility()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
s_instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
QWasmAccessibility::~QWasmAccessibility()
|
QWasmAccessibility::~QWasmAccessibility()
|
||||||
{
|
{
|
||||||
|
s_instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWasmAccessibility *QWasmAccessibility::s_instance = nullptr;
|
||||||
|
|
||||||
|
QWasmAccessibility* QWasmAccessibility::get()
|
||||||
|
{
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWasmAccessibility::addAccessibilityEnableButton(QWindow *window)
|
||||||
|
{
|
||||||
|
get()->addAccessibilityEnableButtonImpl(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWasmAccessibility::removeAccessibilityEnableButton(QWindow *window)
|
||||||
|
{
|
||||||
|
get()->removeAccessibilityEnableButtonImpl(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window)
|
||||||
|
{
|
||||||
|
if (m_accessibilityEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
emscripten::val container = getContainer(window);
|
||||||
|
emscripten::val document = getDocument(container);
|
||||||
|
emscripten::val button = document.call<emscripten::val>("createElement", std::string("button"));
|
||||||
|
button.set("innerText", std::string("Enable Screen Reader"));
|
||||||
|
container.call<void>("appendChild", button);
|
||||||
|
|
||||||
|
emscripten::val style = button["style"];
|
||||||
|
style.set("width", "100%");
|
||||||
|
style.set("height", "100%");
|
||||||
|
|
||||||
|
auto enableContext = std::make_tuple(button, std::make_unique<qstdweb::EventCallback>
|
||||||
|
(button, std::string("click"), [this](emscripten::val) { enableAccessibility(); }));
|
||||||
|
m_enableButtons.insert(std::make_pair(window, std::move(enableContext)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWasmAccessibility::removeAccessibilityEnableButtonImpl(QWindow *window)
|
||||||
|
{
|
||||||
|
auto it = m_enableButtons.find(window);
|
||||||
|
if (it == m_enableButtons.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Remove button
|
||||||
|
auto [element, callback] = it->second;
|
||||||
|
Q_UNUSED(callback);
|
||||||
|
element["parentElement"].call<void>("removeChild", element);
|
||||||
|
m_enableButtons.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWasmAccessibility::enableAccessibility()
|
||||||
|
{
|
||||||
|
// Enable accessibility globally for the applicaton. Remove all "enable"
|
||||||
|
// buttons and populate the accessibility tree, starting from the root object.
|
||||||
|
|
||||||
|
Q_ASSERT(!m_accessibilityEnabled);
|
||||||
|
m_accessibilityEnabled = true;
|
||||||
|
for (const auto& [key, value] : m_enableButtons) {
|
||||||
|
const auto &[element, callback] = value;
|
||||||
|
Q_UNUSED(key);
|
||||||
|
Q_UNUSED(callback);
|
||||||
|
element["parentElement"].call<void>("removeChild", element);
|
||||||
|
}
|
||||||
|
m_enableButtons.clear();
|
||||||
|
populateAccessibilityTree(QAccessible::queryAccessibleInterface(m_rootObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
emscripten::val QWasmAccessibility::getContainer(QWindow *window)
|
||||||
|
{
|
||||||
|
return window ? asWasmWindow(window)->a11yContainer() : emscripten::val::undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
emscripten::val QWasmAccessibility::getContainer(QAccessibleInterface *iface)
|
emscripten::val QWasmAccessibility::getContainer(QAccessibleInterface *iface)
|
||||||
{
|
{
|
||||||
// Get to QWasmScreen::container(), return undefined element if unable to
|
return getContainer(iface->window());
|
||||||
QWindow *window = iface->window();
|
|
||||||
if (!window)
|
|
||||||
return emscripten::val::undefined();
|
|
||||||
QWasmScreen *screen = QWasmScreen::get(window->screen());
|
|
||||||
if (!screen)
|
|
||||||
return emscripten::val::undefined();
|
|
||||||
return screen->element();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container)
|
emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container)
|
||||||
{
|
{
|
||||||
if (container.isUndefined())
|
if (container.isUndefined())
|
||||||
return emscripten::val::undefined();
|
return emscripten::val::global("document");
|
||||||
return container["ownerDocument"];
|
return container["ownerDocument"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +135,7 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
|
|||||||
|
|
||||||
// Get the correct html document for the container, or fall back
|
// Get the correct html document for the container, or fall back
|
||||||
// to the global document. TODO: Does using the correct document actually matter?
|
// to the global document. TODO: Does using the correct document actually matter?
|
||||||
emscripten::val document = container.isUndefined() ? emscripten::val::global("document") : getDocument(container);
|
emscripten::val document = getDocument(container);
|
||||||
|
|
||||||
// Translate the Qt a11y elemen role into html element type + ARIA role.
|
// Translate the Qt a11y elemen role into html element type + ARIA role.
|
||||||
// Here we can either create <div> elements with a spesific ARIA role,
|
// Here we can either create <div> elements with a spesific ARIA role,
|
||||||
@ -136,14 +209,13 @@ void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, b
|
|||||||
void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
|
void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
|
||||||
{
|
{
|
||||||
emscripten::val element = ensureHtmlElement(iface);
|
emscripten::val element = ensureHtmlElement(iface);
|
||||||
setHtmlElementGeometry(iface, element);
|
setHtmlElementGeometry(element, iface->rect());
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface, emscripten::val element)
|
void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
|
||||||
{
|
{
|
||||||
// Position the element using "position: absolute" in order to place
|
// Position the element using "position: absolute" in order to place
|
||||||
// it under the corresponding Qt element in the screen.
|
// it under the corresponding Qt element in the screen.
|
||||||
QRect geometry = iface->rect();
|
|
||||||
emscripten::val style = element["style"];
|
emscripten::val style = element["style"];
|
||||||
style.set("position", std::string("absolute"));
|
style.set("position", std::string("absolute"));
|
||||||
style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the
|
style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the
|
||||||
@ -190,8 +262,27 @@ void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
|
||||||
|
{
|
||||||
|
if (!iface)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Create html element for the interface, sync up properties.
|
||||||
|
ensureHtmlElement(iface);
|
||||||
|
const bool visible = !iface->state().invisible;
|
||||||
|
setHtmlElementVisibility(iface, visible);
|
||||||
|
setHtmlElementGeometry(iface);
|
||||||
|
setHtmlElementTextName(iface);
|
||||||
|
|
||||||
|
for (int i = 0; i < iface->childCount(); ++i)
|
||||||
|
populateAccessibilityTree(iface->child(i));
|
||||||
|
}
|
||||||
|
|
||||||
void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
|
void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
|
||||||
{
|
{
|
||||||
|
if (!m_accessibilityEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
QAccessibleInterface *iface = event->accessibleInterface();
|
QAccessibleInterface *iface = event->accessibleInterface();
|
||||||
if (!iface) {
|
if (!iface) {
|
||||||
qWarning() << "notifyAccessibilityUpdate with null a11y interface" ;
|
qWarning() << "notifyAccessibilityUpdate with null a11y interface" ;
|
||||||
@ -237,11 +328,9 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWasmAccessibility::setRootObject(QObject *o)
|
void QWasmAccessibility::setRootObject(QObject *root)
|
||||||
{
|
{
|
||||||
qCDebug(lcQpaAccessibility) << "setRootObject" << o;
|
m_rootObject = root;
|
||||||
QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(o);
|
|
||||||
Q_UNUSED(iface)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWasmAccessibility::initialize()
|
void QWasmAccessibility::initialize()
|
||||||
|
@ -5,11 +5,14 @@
|
|||||||
#define QWASMACCESIBILITY_H
|
#define QWASMACCESIBILITY_H
|
||||||
|
|
||||||
#include <QtCore/qhash.h>
|
#include <QtCore/qhash.h>
|
||||||
|
#include <private/qstdweb_p.h>
|
||||||
#include <qpa/qplatformaccessibility.h>
|
#include <qpa/qplatformaccessibility.h>
|
||||||
|
|
||||||
#include <emscripten/val.h>
|
#include <emscripten/val.h>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility)
|
Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility)
|
||||||
|
|
||||||
class QWasmAccessibility : public QPlatformAccessibility
|
class QWasmAccessibility : public QPlatformAccessibility
|
||||||
@ -18,6 +21,17 @@ public:
|
|||||||
QWasmAccessibility();
|
QWasmAccessibility();
|
||||||
~QWasmAccessibility();
|
~QWasmAccessibility();
|
||||||
|
|
||||||
|
static QWasmAccessibility* get();
|
||||||
|
|
||||||
|
static void addAccessibilityEnableButton(QWindow *window);
|
||||||
|
static void removeAccessibilityEnableButton(QWindow *window);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void addAccessibilityEnableButtonImpl(QWindow *window);
|
||||||
|
void removeAccessibilityEnableButtonImpl(QWindow *window);
|
||||||
|
void enableAccessibility();
|
||||||
|
|
||||||
|
static emscripten::val getContainer(QWindow *window);
|
||||||
static emscripten::val getContainer(QAccessibleInterface *iface);
|
static emscripten::val getContainer(QAccessibleInterface *iface);
|
||||||
static emscripten::val getDocument(const emscripten::val &container);
|
static emscripten::val getDocument(const emscripten::val &container);
|
||||||
static emscripten::val getDocument(QAccessibleInterface *iface);
|
static emscripten::val getDocument(QAccessibleInterface *iface);
|
||||||
@ -27,19 +41,24 @@ public:
|
|||||||
emscripten::val ensureHtmlElement(QAccessibleInterface *iface);
|
emscripten::val ensureHtmlElement(QAccessibleInterface *iface);
|
||||||
void setHtmlElementVisibility(QAccessibleInterface *iface, bool visible);
|
void setHtmlElementVisibility(QAccessibleInterface *iface, bool visible);
|
||||||
void setHtmlElementGeometry(QAccessibleInterface *iface);
|
void setHtmlElementGeometry(QAccessibleInterface *iface);
|
||||||
void setHtmlElementGeometry(QAccessibleInterface *iface, emscripten::val element);
|
void setHtmlElementGeometry(emscripten::val element, QRect geometry);
|
||||||
void setHtmlElementTextName(QAccessibleInterface *iface);
|
void setHtmlElementTextName(QAccessibleInterface *iface);
|
||||||
|
|
||||||
void handleStaticTextUpdate(QAccessibleEvent *event);
|
void handleStaticTextUpdate(QAccessibleEvent *event);
|
||||||
void handleButtonUpdate(QAccessibleEvent *event);
|
void handleButtonUpdate(QAccessibleEvent *event);
|
||||||
void handleCheckBoxUpdate(QAccessibleEvent *event);
|
void handleCheckBoxUpdate(QAccessibleEvent *event);
|
||||||
|
|
||||||
|
void populateAccessibilityTree(QAccessibleInterface *iface);
|
||||||
void notifyAccessibilityUpdate(QAccessibleEvent *event) override;
|
void notifyAccessibilityUpdate(QAccessibleEvent *event) override;
|
||||||
void setRootObject(QObject *o) override;
|
void setRootObject(QObject *o) override;
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
void cleanup() override;
|
void cleanup() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static QWasmAccessibility *s_instance;
|
||||||
|
QObject *m_rootObject = nullptr;
|
||||||
|
bool m_accessibilityEnabled = false;
|
||||||
|
std::map<QWindow *, std::tuple<emscripten::val, std::shared_ptr<qstdweb::EventCallback>>> m_enableButtons;
|
||||||
QHash<QAccessibleInterface *, emscripten::val> m_elements;
|
QHash<QAccessibleInterface *, emscripten::val> m_elements;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "qwasmevent.h"
|
#include "qwasmevent.h"
|
||||||
#include "qwasmeventdispatcher.h"
|
#include "qwasmeventdispatcher.h"
|
||||||
#include "qwasmstring.h"
|
#include "qwasmstring.h"
|
||||||
|
#include "qwasmaccessibility.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <emscripten/val.h>
|
#include <emscripten/val.h>
|
||||||
@ -470,6 +471,7 @@ QWasmWindow::~QWasmWindow()
|
|||||||
m_compositor->removeWindow(this);
|
m_compositor->removeWindow(this);
|
||||||
if (m_requestAnimationFrameId > -1)
|
if (m_requestAnimationFrameId > -1)
|
||||||
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
|
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
|
||||||
|
QWasmAccessibility::removeAccessibilityEnableButton(window());
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWasmWindow::destroy()
|
void QWasmWindow::destroy()
|
||||||
@ -505,6 +507,11 @@ void QWasmWindow::initialize()
|
|||||||
setWindowIcon(window()->icon());
|
setWindowIcon(window()->icon());
|
||||||
m_normalGeometry = rect;
|
m_normalGeometry = rect;
|
||||||
QPlatformWindow::setGeometry(m_normalGeometry);
|
QPlatformWindow::setGeometry(m_normalGeometry);
|
||||||
|
|
||||||
|
// Add accessibility-enable button. The user can activate this
|
||||||
|
// button to opt-in to accessibility.
|
||||||
|
if (window()->isTopLevel())
|
||||||
|
QWasmAccessibility::addAccessibilityEnableButton(window());
|
||||||
}
|
}
|
||||||
|
|
||||||
QWasmScreen *QWasmWindow::platformScreen() const
|
QWasmScreen *QWasmWindow::platformScreen() const
|
||||||
|
Loading…
x
Reference in New Issue
Block a user