I figured out some more stuff, including a workaround for Qt 6.9.0 that doesnt require rebuilding the webassembly module from source. I'm able to reproduce this "bug" using just the calendarwidget example, so it at seems to happen in relatively simple situations.
Hopefully this is helpful to other people with this same problem. I suspect there are easier ways to accomplish all of this...
First off, i discovered that qt-shadow-container is defined in the file qwasmwindow.cpp which you can find in \Src\qtbase\src\plugins\platforms\wasm. From looking at that file I figured out that this div is used to display a "shadow dom" that contains the real Qt content. The importance of this is that when an element in the shadow dom has focus, document.activeElement shows the shadow root (qt-shadow-container) as the activeElement even though it isn't really. So, in fact, it's not true that qt-shadow-container has/needs focus in order for you to receive keyboard events. I discovered that when you press "Tab", the focus ACTUALLY goes to an invisible button element whose class is "hidden-visually-read-by-screen-reader". I think this is actually something of a coincidence: buttons are inherently able to receive tab focus and so this just happens to be what receives the focus when you press tab. And it's part of the shadow dom hierarchy in such a way that, when it has focus, Qt will process the keypress events that it receives.
So, what does this all mean?
Long story short, you can use javascript to programmatically force the "hidden-visually-read-by-screen-reader" to always have focus, which will ensure that Qt processes all of your keypress events.
First problem: when you open your website, no object has focus, so we need an initial action that sets the focus. (Note, i think in Qt 6.10 it DOES initially have focus so you may not need this step) You can do this by pressing "Tab", or you can set it programmatically with javascript. It's a little difficult because the button we want to focus on doesn't exist when you first open the page (it doesnt exist until Qt creates it). So you need to delay focusing on it until you're sure it exists. I solved this by modifying the "onLoaded" callback function in generated html file to look like this:
onLoaded: () => {
showUi(screen); setTimeout(function() {
console.log('setting initial focus');
const shadowContainer = document.getElementById('qt-shadow-container');
if(shadowContainer) {
const shadowRoot = document.getElementById('qt-shadow-container').shadowRoot;
if (shadowRoot) {
console.log('shadow root found');
console.log(document.getElementById('qt-shadow-container'));
const targetElement = shadowRoot.querySelector('.hidden-visually-read-by-screen-reader');
console.log(targetElement);
targetElement.focus();
} else {
console.log('Shadow Root not found or is closed.');
}
}
}, 1000);
},
So, it waits 1 second and then sets the focus. If I dont have it wait then it fails to find the required objects.
Second problem: Restore focus to this object any time no object has focus. For this I added another script to the html file that looks like this:
<script>
document.addEventListener('focusout', function (event) {
console.log('Element lost focus:', event.target);
console.log('Element gaining focus:', event.relatedTarget);
if (event.relatedTarget === null) {
const shadowRoot = document.getElementById('qt-shadow-container').shadowRoot;
if (shadowRoot) {
const targetElement = shadowRoot.querySelector('.hidden-visually-read-by-screen-reader');
console.log(targetElement);
targetElement.focus();
} else {
console.log('Shadow Root not found or is closed.');
}
}
});
</script>
So, basically the same function but this time it runs when an element loses focus and no new element is gaining focus.
Third problem: This one is probably a lot more obscure, but i was running into problems implementing a custom "createEditor" function on QAbstractItemDelegate where the html INPUT object corresponding to the editor never lost focus, even once "destroyEditor" ran (notably, my destroyEditor function did not delete the editor but only hid it). To solve, this, I implemented a javascript function to be called by Qt to force the input to lose focus:
#ifdef Q_OS_WASM
#include "emscripten.h"
EM_JS(void, resetFocus, (), {
console.log('removing focus from INPUT');
document.activeElement.blur(); //This takes focus away from the active element, which may be an INPUT, and returns it to the BODY element
});
#endif
...then you can just call resetFocus() in any situation where you find the INPUT is keeping focus. This will trigger a focusout even and then the above function takes over.
(How do you know if INPUT is keeping focus? FOr debugging i set up a QTimer that runs another javascript function that just does "console.log(document.activeElement)" every few seconds. Then you can watch the console to see what object in the DOM has keyboard focus as you test)