wasm: fix clipboard event handler leaks

Use QWasmEventHandler instead of calling addEventListener()
directly (using QWasmEventHandler also allows supporting
JSPI). The QWasmEventHandler destructor calls removeEventListener(),
which should make sure everything gets cleaned up.

Keep the Chrome-specific global (document) event handler code path,
but register once at startup instead of once per window.

Change-Id: If4314df738afc0dcfdb0f6f1ab9e1f176e1812ac
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
Morten Sørvig 2025-02-28 20:09:00 +01:00
parent d4efce2119
commit f4287c4531
4 changed files with 33 additions and 29 deletions

View File

@ -46,7 +46,7 @@ static void commonCopyEvent(val event)
event.call<void>("preventDefault");
}
static void qClipboardCutTo(val event)
void QWasmClipboard::cut(val event)
{
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
if (wasmInput && wasmInput->usingTextInput())
@ -61,7 +61,7 @@ static void qClipboardCutTo(val event)
commonCopyEvent(event);
}
static void qClipboardCopyTo(val event)
void QWasmClipboard::copy(val event)
{
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
if (wasmInput && wasmInput->usingTextInput())
@ -75,7 +75,7 @@ static void qClipboardCopyTo(val event)
commonCopyEvent(event);
}
static void qClipboardPasteTo(val event)
void QWasmClipboard::paste(val event)
{
QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
if (wasmInput && wasmInput->usingTextInput())
@ -86,12 +86,6 @@ static void qClipboardPasteTo(val event)
QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
}
EMSCRIPTEN_BINDINGS(qtClipboardModule) {
function("qtClipboardCutTo", &qClipboardCutTo);
function("qtClipboardCopyTo", &qClipboardCopyTo);
function("qtClipboardPasteTo", &qClipboardPasteTo);
}
QWasmClipboard::QWasmClipboard()
{
val clipboard = val::global("navigator")["clipboard"];
@ -101,6 +95,13 @@ QWasmClipboard::QWasmClipboard()
if (m_hasClipboardApi && hasPermissionsApi)
initClipboardPermissions();
if (!shouldInstallWindowEventHandlers()) {
val document = val::global("document");
m_documentCut = QWasmEventHandler(document, "cut", QWasmClipboard::cut);
m_documentCopy = QWasmEventHandler(document, "copy", QWasmClipboard::copy);
m_documentPaste = QWasmEventHandler(document, "paste", QWasmClipboard::paste);
}
}
QWasmClipboard::~QWasmClipboard()
@ -167,29 +168,17 @@ void QWasmClipboard::initClipboardPermissions()
})());
}
void QWasmClipboard::installEventHandlers(const emscripten::val &target)
{
emscripten::val cContext = val::undefined();
emscripten::val isChromium = val::global("window")["chrome"];
if (!isChromium.isUndefined()) {
cContext = val::global("document");
} else {
cContext = target;
}
// Fallback path for browsers which do not support direct clipboard access
cContext.call<void>("addEventListener", val("cut"),
val::module_property("qtClipboardCutTo"), true);
cContext.call<void>("addEventListener", val("copy"),
val::module_property("qtClipboardCopyTo"), true);
cContext.call<void>("addEventListener", val("paste"),
val::module_property("qtClipboardPasteTo"), true);
}
bool QWasmClipboard::hasClipboardApi()
{
return m_hasClipboardApi;
}
bool QWasmClipboard::shouldInstallWindowEventHandlers()
{
// Chrome uses global handlers
return val::global("window")["chrome"].isUndefined() == false;
}
void QWasmClipboard::writeToClipboardApi()
{
Q_ASSERT(m_hasClipboardApi);

View File

@ -36,16 +36,23 @@ public:
bool ownsMode(QClipboard::Mode mode) const override;
ProcessKeyboardResult processKeyboard(const KeyEvent &event);
static void installEventHandlers(const emscripten::val &target);
bool hasClipboardApi();
static bool shouldInstallWindowEventHandlers();
void sendClipboardData(emscripten::val event);
static void cut(emscripten::val event);
static void copy(emscripten::val event);
static void paste(emscripten::val event);
private:
void initClipboardPermissions();
void writeToClipboardApi();
void writeToClipboard();
bool m_hasClipboardApi = false;
QWasmEventHandler m_documentCut;
QWasmEventHandler m_documentCopy;
QWasmEventHandler m_documentPaste;
};
QT_END_NAMESPACE

View File

@ -95,7 +95,11 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
m_window.set("contentEditable", std::string("true"));
m_window["style"].set("outline", std::string("none"));
QWasmClipboard::installEventHandlers(m_window);
if (QWasmClipboard::shouldInstallWindowEventHandlers()) {
m_cutCallback = QWasmEventHandler(m_window, "cut", QWasmClipboard::cut);
m_copyCallback = QWasmEventHandler(m_window, "copy", QWasmClipboard::copy);
m_pasteCallback = QWasmEventHandler(m_window, "paste", QWasmClipboard::paste);
}
#endif
// Set inputMode to none to stop the mobile keyboard from opening

View File

@ -173,6 +173,10 @@ private:
QMap<int, QWindowSystemInterface::TouchPoint> m_pointerIdToTouchPoints;
QWasmEventHandler m_cutCallback;
QWasmEventHandler m_copyCallback;
QWasmEventHandler m_pasteCallback;
Qt::WindowStates m_state = Qt::WindowNoState;
Qt::WindowStates m_previousWindowState = Qt::WindowNoState;