-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Description
Summary
When entering real fullscreen via emscripten_request_fullscreen_strategy, the fullscreen CSS size is derived from screen.width/height. On macOS with a notch, screen.* includes the reserved area, while the actual fullscreen viewport (innerWidth/innerHeight or visualViewport) is smaller. The canvas ends up oversized and the browser downscales it, causing letterboxing/blurriness and click coordinate mismatch.
Expected
Canvas CSS size should match the fullscreen viewport (prefer visualViewport, fallback to innerWidth/innerHeight) so aspect-fit occurs against the visible area without additional browser downscale.
Actual
Canvas CSS uses screen.*. Example from repro below: screen=1512x982 while inner=1200x905, so CSS ends up at 1309.33x982 even though the viewport is 1200x905.
Repro
- Build:
emcc main.c -O0 -g -sEXPORTED_FUNCTIONS=_main,_enter_fullscreen --post-js post.js -o index.html
python3 -m http.server 8000- Open
http://localhost:8000in Chrome on macOS with a notch. - Click the
Fullscreenbutton.
main.c
#include <emscripten.h>
#include <emscripten/html5.h>
#include <stdio.h>
#include <string.h>
static void dump_metrics(const char *tag) {
EM_ASM({
var tag = UTF8ToString($0);
var canvas = Module['canvas'];
var rect = canvas.getBoundingClientRect();
var vv = window.visualViewport;
var payload = {};
payload.screen = Array(screen.width, screen.height);
payload.avail = Array(screen.availWidth, screen.availHeight);
payload.inner = Array(innerWidth, innerHeight);
payload.outer = Array(outerWidth, outerHeight);
payload.visualViewport = vv ? Array(vv.width, vv.height, vv.offsetLeft, vv.offsetTop, vv.scale) : null;
payload.canvasCss = Array(canvas.clientWidth, canvas.clientHeight);
payload.canvasStyle = Array(canvas.style.width || String(), canvas.style.height || String());
payload.canvasRect = Array(rect.width, rect.height, rect.left, rect.top);
payload.canvasPx = Array(canvas.width, canvas.height);
payload.dpr = devicePixelRatio;
payload.fullscreen = !!document.fullscreenElement;
payload.client = Array(document.documentElement.clientWidth, document.documentElement.clientHeight);
console.log('[metrics]', tag, JSON.stringify(payload));
}, tag);
}
static EM_BOOL fullscreen_change(int eventType, const EmscriptenFullscreenChangeEvent *e, void *userData) {
(void)eventType;
(void)userData;
printf("[fschange] isFullscreen=%d element=%dx%d screen=%dx%d\n",
e->isFullscreen, e->elementWidth, e->elementHeight, e->screenWidth, e->screenHeight);
dump_metrics("fullscreenchange");
return 0;
}
static void delayed_dump(void *arg) {
(void)arg;
dump_metrics("after request");
}
EMSCRIPTEN_KEEPALIVE void enter_fullscreen(void) {
EmscriptenFullscreenStrategy strategy;
memset(&strategy, 0, sizeof(strategy));
strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT;
strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE;
strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
dump_metrics("before request");
EMSCRIPTEN_RESULT res = emscripten_request_fullscreen_strategy("#canvas", true, &strategy);
printf("[request] res=%d\n", res);
emscripten_async_call(delayed_dump, NULL, 200);
}
int main() {
emscripten_set_canvas_element_size("#canvas", 800, 600);
dump_metrics("startup");
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, true, fullscreen_change);
puts("Click fullscreen button to enter fullscreen.");
return 0;
}post.js
if (typeof document !== 'undefined') {
const btn = document.createElement('button');
btn.id = 'fullscreen';
btn.textContent = 'Fullscreen';
btn.style.cssText = 'position:fixed;top:10px;left:10px;z-index:1000;';
btn.addEventListener('click', () => {
if (Module && Module._enter_fullscreen) {
Module._enter_fullscreen();
} else {
console.warn('enter_fullscreen not exported');
}
});
document.body.appendChild(btn);
}Logs (excerpt)
[metrics] before request {"screen":[1512,982],"avail":[1512,949],"inner":[1200,818],"outer":[1200,905],"visualViewport":[1200,818,0,0,1],"canvasCss":[800,600],"canvasStyle":["",""],"canvasRect":[800,600,200,79.7265625],"canvasPx":[800,600],"dpr":2,"fullscreen":false,"client":[1200,818]}
[fschange] isFullscreen=1 element=1200x905 screen=1512x982
[metrics] fullscreenchange {"screen":[1512,982],"avail":[1512,949],"inner":[1200,905],"outer":[1200,905],"visualViewport":[1200,905,0,0,1],"canvasCss":[1200,905],"canvasStyle":["1309.33px","982px"],"canvasRect":[1200,905,0,0],"canvasPx":[800,600],"dpr":2,"fullscreen":true,"client":[1200,905]}
[metrics] after request {"screen":[1512,982],"avail":[1512,949],"inner":[1200,905],"outer":[1200,905],"visualViewport":[1200,905,0,0,1],"canvasCss":[1200,905],"canvasStyle":["1200px","900px"],"canvasRect":[1200,905,0,0],"canvasPx":[800,600],"dpr":2,"fullscreen":true,"client":[1200,905]}
Environment
- macOS 26.2 (build 25C56), MacBook Pro (14-inch, 2021)
- Chrome 143.0.7499.193
- Emscripten 4.0.24-git