Run the reference emulator silently when WebKit reports no audio sample rate
After a page process has opened many AudioContexts, WebKit can hand out one whose sampleRate is 0. The adapter passed that rate to binjgb, whose emulator_run_until divides by the audio frequency on every call, so the first frame trapped with "Division by zero". This failed the Day-Care cartridge comparisons on WebKit after earlier tests had created hundreds of emulators, and could crash the reference player in Safari. A context without a usable rate is now closed and the emulator runs silently at the fallback rate, as when no AudioContext can be created. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e49bc6de9a
commit
7a32e689c3
2 files changed
+35
No files matched your search
@@ -59,11 +59,17 @@ export class BinjgbEmulator implements Emulator {
|
|||||||
// Audio is optional. Failure must not block a silent game.
|
// Audio is optional. Failure must not block a silent game.
|
||||||
try {
|
try {
|
||||||
this.audio = new AudioContext();
|
this.audio = new AudioContext();
|
||||||
|
// WebKit can return a context whose sample rate is 0 (seen after a
|
||||||
|
// page process has opened many); binjgb divides by the rate.
|
||||||
|
if (!(this.audio.sampleRate > 0))
|
||||||
|
throw new Error("No audio sample rate.");
|
||||||
this.gain = this.audio.createGain();
|
this.gain = this.audio.createGain();
|
||||||
this.gain.gain.value = this.muted ? 0 : 0.4;
|
this.gain.gain.value = this.muted ? 0 : 0.4;
|
||||||
this.gain.connect(this.audio.destination);
|
this.gain.connect(this.audio.destination);
|
||||||
} catch {
|
} catch {
|
||||||
|
void this.audio?.close().catch(() => {});
|
||||||
this.audio = undefined;
|
this.audio = undefined;
|
||||||
|
this.gain = undefined;
|
||||||
}
|
}
|
||||||
this.romPointer = this.module._malloc(rom.length);
|
this.romPointer = this.module._malloc(rom.length);
|
||||||
if (!this.romPointer) throw new Error("Could not allocate ROM memory.");
|
if (!this.romPointer) throw new Error("Could not allocate ROM memory.");
|
||||||
|
|||||||
@@ -140,6 +140,35 @@ test("actual WASM executes an original cartridge, reads input and round-trips st
|
|||||||
expect(errors).toEqual([]);
|
expect(errors).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("the emulator runs silently when the browser reports no audio sample rate", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto("/");
|
||||||
|
const result = await page.evaluate(async (bytes) => {
|
||||||
|
// WebKit hands out contexts with a sample rate of 0 once a page process
|
||||||
|
// has opened many; binjgb divides by the rate on every frame.
|
||||||
|
const closed: boolean[] = [];
|
||||||
|
window.AudioContext = class extends AudioContext {
|
||||||
|
get sampleRate() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
closed.push(true);
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const { BinjgbEmulator } = await import("/src/emulator/binjgb.ts");
|
||||||
|
const emulator = new BinjgbEmulator();
|
||||||
|
await emulator.load(new Uint8Array(bytes), document.createElement("canvas"));
|
||||||
|
emulator.advanceFrames(12);
|
||||||
|
const battery = emulator.exportBattery()[0];
|
||||||
|
emulator.dispose();
|
||||||
|
return { battery, closed: closed.length };
|
||||||
|
}, Array.from(syntheticRom()));
|
||||||
|
expect(result.battery & 1).toBe(1);
|
||||||
|
expect(result.closed).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test("full player lifecycle with test-only validator replacement", async ({
|
test("full player lifecycle with test-only validator replacement", async ({
|
||||||
page,
|
page,
|
||||||
context,
|
context,
|
||||||
|
|||||||
Reference in new issue
Block a user