cd /news/developer-tools/a-game-portal-rejected-my-game-with-… · home › topics › developer-tools › article
[ARTICLE · art-144224] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

A game portal rejected my game with one line - "The WASD keys aren't working". They were right, and all my tests were green.

A developer whose browser game Void Parry was rejected by a game portal with the single line "The WASD keys aren't working" traced the failure to a day-one `e.preventDefault()` call on `pointerdown` that cancelled the browser's default focus transfer, leaving the game's iframe without keyboard focus on portal pages. The fix adds `window.focus()` on load, on pointer movement, and after ad overlays, plus a test harness that loads the game inside one and two nested iframes and presses WASD without clicking first — a path the original tests never exercised.

read3 min views1 publishedOct 3, 2026

My browser game Void Parry went to a portal's moderation with dozens of automated browser tests passing. The answer came back a few days later:

The WASD keys aren't working in the game.

That was the whole review. On my machine the keys worked. In every test the keys worked. On the portal's page they did nothing. It took one real key press on the real page to see why, and the cause was a line I wrote on day one.

canvas.addEventListener("pointerdown", (e) => {
  e.preventDefault();          // no text selection, no touch scrolling, no long-press menu
  handlePress(e);
});

Calling preventDefault() on pointerdown is common in canvas games. It also cancels the default action of the press - and one of those defaults is moving keyboard focus to what you clicked.

On my own site that never mattered: the game is the top-level page, the document already has focus, keys arrive.

A portal does not load your game as the top-level page. It puts it in an <iframe> (one portal I checked nests it two frames deep). An iframe only receives key events while it has focus, and it normally gets focus when the player clicks inside it. My handler cancelled exactly that. Pointer events kept arriving - mouse aim and buttons worked, which hid the problem - but keydown went to the portal's page forever.

You can see it in two lines from the devtools console of the game frame:

document.hasFocus()   // false, even right after clicking the game

The obvious repair is to take focus yourself:

canvas.addEventListener("pointerdown", (e) => {
  e.preventDefault();
  window.focus();
  handlePress(e);
});

I verified it on the live page - click, press W A S D, all four arrive - and sent the build back.

Hours later I walked through what the reviewer had actually done, and it was not what I had tested. A new player in my game lands straight in the first boss fight. The mouse only aims; you never have to click. So the reviewer opened the game, pressed W, A, S, D, saw nothing move, and wrote the review. No click ever happened. My fix repaired a path the reporter never took.

A frame is allowed to focus itself without a click, so the real fix has three parts:

window.focus();                                   // when the game loads
function startFight() { window.focus(); /* ... */ }
canvas.addEventListener("pointermove", () => {    // the player is over the game
  if (!document.hasFocus()) window.focus();
});

Plus window.focus() after an ad closes, because an ad overlay takes the keyboard and does not hand it back.

All my tests loaded index.html directly. The fix for that is small: a host page with the game in an iframe, and a test that never clicks.

fs.writeFileSync("host.html", '<iframe src="index.html" style="width:1280px;height:720px"></iframe>');
fs.writeFileSync("host2.html", '<iframe src="host.html" style="width:1280px;height:720px"></iframe>');

for (const host of ["host.html", "host2.html"]) {          // one and two frames deep
  await page.goto(host);
  const game = page.frames().find((f) => /index\.html/.test(f.url()));
  await game.evaluate(() => { window.keys = []; addEventListener("keydown", (e) => keys.push(e.code)); });

  for (const k of ["KeyW", "KeyA", "KeyS", "KeyD"]) await page.keyboard.press(k);   // no click first
  assert.equal(await game.evaluate(() => keys.join()), "KeyW,KeyA,KeyS,KeyD");
}

It has three cases now: no click at all, the portal page has focus and the player only moves the mouse, and the portal page has focus and the player clicks. The old build fails the first two. Then I ran the same check against the live portal page, because a test page I wrote can share my blind spots.

I went back to a second portal that had rejected the game twice, the last time for "overall quality", opened the build they had reviewed in their own preview tool, clicked in the game and pressed the keys. Nothing arrived there either. Two reviews of a keyboard game whose keyboard did not work on their site. I do not know how much of "overall quality" that was. I do know the reviewers could not move.

preventDefault() on pointer events cancels focus too. The game and its tests were written with an AI coding assistant; the bug, the review text and the fix are from the real build.

── more in #developer-tools 4 stories · sorted by recency
── more on @void parry 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-game-portal-reject…] indexed:0 read:3min 2026-10-03 · —