Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Your ruby.wasm Doesn't Work on Mobile. Here's W...

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Your ruby.wasm Doesn't Work on Mobile. Here's Why, #rubykaigi 2026 LT

You ship a ruby.wasm app. It works on your MacBook. You bring it to a conference booth, hand it to a stranger on their iPhone — it crashes. The next person's Android freezes with no error. A third player comes back from a break and their quiz progress is gone. I hit all three at SITCON 2026 which is the only open source conference for students, running a Ruby quiz game powered by Ruby 4.0 WASM in-browser, designed to introduce Ruby to people who had never written a line of it. Each failure has a root cause and a fix under 10 lines. I'll show you all three.

Avatar for Ryudo Awaru

Ryudo Awaru

April 22, 2026

More Decks by Ryudo Awaru

Other Decks in Programming

Transcript

  1. Your ruby.wasm Doesn’t Work on Mobile. Here’s Why. RubyKaigi 2026

    · Lightning Talk Mu-Fan Teng (Owaru Ryudo) · Ruby Taiwan
  2. Who Am I Mu-Fan Teng 🏢 Chief Organizer, Ruby Taiwan

    💎 Rubyist for 19 years 🚀 Rails by day, pushing Ruby everywhere else by night 💎 🎤 ❤️ RubyKaraoke lover 𝕏 @ryudoawaru 🇹🇼 𝕏 @rubytaiwan a.k.a. Owaru Ryudo Ruby Evangelist of Taiwan credit by @headius
  3. The Problem with Conference Booths 😴 Another Google Form. Fill

    it out, get a sticker. 🏷️ I run Ruby booths at Taiwanese dev conferences constantly 📋 Booth activity = a Google Form 🏃 Fill out your name → get a sticker → leave ❌ Attendees never actually touch Ruby
  4. Then came SITCON 2026 📅 March 28, 2026 👥 ~1,000

    attendees 💡 What if they could write real Ruby on their own phone, right now? Taiwan's largest student tech conference mostly students who had never written Ruby
  5. What We Built sitcon2026-booth.ruby.tw ✅ Jekyll static site — zero

    server ✅ Ruby 4.0 WASM running in-browser ✅ Players type Ruby one-liners; WASM executes live ✅ 10 questions, difficulty gradient (easy → hard) ✅ Shareable scorecard (Web Share API) ✅ Completion certificate with proof code ✅ No install. No account. Just open your phone. github.com/rubytaiwan/sitcon2026-booth
  6. Game Flow ① Quiz Phase ② Result Phase ③ Form

    Phase ④ Proof Phase 10 questions (easy → hard) Max 3 attempts each Answer revealed on failure Verified by rubyVM.eval() 1080×1080 Canvas scorecard Web Share API Share to IG or download Name, email, phone, school POST via Cloudflare Worker → self-hosted Listmonk Non-blocking on failure QR code + proof code RUBY-{score}-{hash8} Show staff → claim prize + SITCON city game check-in
  7. 3 Bugs. 3 Root Causes. 📱 💥 iOS 17: crashes

    on first eval 🤖 ⏳ Android: hangs forever 📱 🔄 iOS mid-quiz: state gone
  8. Bug 1 — iOS Safari 18.x RangeError: Maximum call stack

    size exceeded Symptom: Crashes on the very first eval. Desktop Safari ✅ · iOS Chrome ✅ · iOS Safari 18 💥 Root cause: Ruby WASM is compiled with Asyncify (Binaryen transform for async I/O). The first eval that triggers a lazy require of stdlib ( stringio , strscan ) pushes the Asyncify-instrumented stack past Safari’s limit. It’s the module loading, not your code, that blows the stack. // Part 1: Pre-warm during initRuby() — force lazy stdlib into memory now try { rubyVM.eval('require "stringio"; require "strscan"'); } catch(_) {} // Part 2: In runRuby() — catch overflow, retry after one macrotask break try { result = rubyVM.eval(code).toString(); } catch (e) { if (e instanceof RangeError || e.message?.includes('call stack')) { await new Promise(r => setTimeout(r, 50)); // unwind Asyncify frames result = rubyVM.eval(code).toString(); // succeeds } }
  9. Bug 2 — Android Chrome / Firefox Silent WASM streaming

    compile hang Symptom: Loading spinner runs forever. compileStreaming never resolves, never rejects. Zero console errors. Root cause: Some Android browser/OS combinations silently cancel the streaming pipe for the 31 MB .wasm binary — even when the server sends the correct application/wasm MIME type. Bonus: Added a two-step progress indicator: 1/2 compiling… → 2/2 initializing… so players know the page is alive. try { module = await WebAssembly.compileStreaming(fetch(wasmUrl)); } catch (_) { // Android browsers sometimes silently fail streaming — fall back const buf = await (await fetch(wasmUrl)).arrayBuffer(); module = await WebAssembly.compile(buf); // universally supported ✅ }
  10. Bug 3 — iOS Safari Page Eviction Mid-session state wipe

    Symptom: Mid-quiz, the page silently reloads — progress gone. Some players never left the browser; others triggered it by switching apps. Not pinned to a specific iOS version. Root cause: iOS Safari aggressively reclaims memory by evicting tabs and triggering a full page reload. With a 31 MB WASM binary in memory, even active tabs are at risk. Key detail: Store {defIndex, raw} pairs (index into task bank + generated secret value), not rendered HTML — rebuild-safe after reload. function saveState() { sessionStorage.setItem('quizState', JSON.stringify({ questionIndex, score, questions // questions = [{defIndex, raw}] })); } // On init() — before starting fresh, check for saved state: const saved = sessionStorage.getItem('quizState'); if (saved) restoreFrom(JSON.parse(saved)); // Clear state only when the player reaches the proof phase (game complete)
  11. ruby.wasm Is Ready for Community Outreach 🍎 iOS Safari 18

    🤖 Android Chrome 💾 iOS Eviction ⚠️ None of these appear in the ruby.wasm documentation. Pre-warm stdlib + macrotask retry arrayBuffer fallback for compileStreaming sessionStorage every state transition
  12. Go Ship It. 📱 SOURCE CODE github.com/rubytaiwan/sitcon2026-booth TRY IT NOW

    · 中文 / EN / JA sitcon2026-booth.ruby.tw Fork it · customize the questions · bring it to your community 🎉