TypeError: session.requestAnimationFrame is not a function when re-entering XR session after exit
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 17.6k
- Forks
- 4.4k
- PR merge metrics
- No merged PRs in 30d
Description
Title: TypeError: session.requestAnimationFrame is not a function when re-entering XR session after exit
Description:
When an A-Frame WebXR application exits XR session and then re-enters, the old rAF (requestAnimationFrame) callback still references the ended session. When the callback executes, it attempts to call session.requestAnimationFrame on an invalid session object, causing a TypeError. This affects A-Frame versions 1.4.2 and 1.5.0.
Steps to Reproduce:
- Open an A-Frame WebXR application (e.g., a-blast, a-painter)
- Enter VR session (click "Enter VR" button)
- Exit VR session (click "Exit VR" button)
- Re-enter VR session (click "Enter VR" again)
- Check browser console (on Quest 3 or Pico 4 native browser)
Expected Behavior:
Re-entry should create a new session with valid rAF callback. The old callback should be cleaned up before session end.
Actual Behavior:
TypeError: session.requestAnimationFrame is not a function
at AFRAME.sys.render (system.js:352)
at <anonymous>
Minimized Reproducible Example:
<!-- A-Frame app with re-entry -->
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-entity camera look-controls wasd-controls></a-entity>
<a-box position="0 1 -3" rotation="0 45 0" color="red"></a-box>
</a-scene>
<button onclick="document.querySelector('a-scene').enterVR()">Enter VR</button>
<button onclick="document.querySelector('a-scene').exitVR()">Exit VR</button>
</body>
</html>
Root Cause Analysis:
In A-Frame's render system (system.js), the requestAnimationFrame callback is not properly cleaned up when the XR session ends. When re-entering, the old callback still holds a reference to the ended session.
Problematic code pattern in A-Frame:
// In A-Frame's render system (system.js)
AFRAME.registerSystem('render', {
init: function() {
this.rAFId = null;
this.session = null;
},
startXR: function(session) {
this.session = session;
this._bindRender = this.render.bind(this);
this.session.requestAnimationFrame(this._bindRender);
},
// BUG: endXR does not cancel rAF callback
endXR: function() {
// MISSING: this.session.cancelAnimationFrame(this.rAFId);
this.session = null; // But old callback still references this.session
},
render: function(timestamp, frame) {
// BUG: When re-entering, this.session may be invalid
this.rAFId = this.session.requestAnimationFrame(this._bindRender); // Fails!
// ... render code ...
}
});
Corrected Code:
AFRAME.registerSystem('render', {
init: function() {
this.rAFId = null;
this.session = null;
this._bindRender = this.render.bind(this);
},
startXR: function(session) {
this.session = session;
this.rAFId = this.session.requestAnimationFrame(this._bindRender);
},
endXR: function() {
// FIX: Cancel rAF callback before clearing session
if (this.session && this.rAFId) {
this.session.cancelAnimationFrame(this.rAFId);
this.rAFId = null;
}
this.session = null;
},
render: function(timestamp, frame) {
// FIX: Check session validity before calling rAF
if (!this.session) {
console.warn('Render: session is null, skipping rAF');
return;
}
if (this.session && this.session.requestAnimationFrame) {
this.rAFId = this.session.requestAnimationFrame(this._bindRender);
}
// ... render code ...
},
// Also add cleanup on scene destroy
remove: function() {
if (this.session && this.rAFId) {
this.session.cancelAnimationFrame(this.rAFId);
}
this.rAFId = null;
this.session = null;
}
});
Impact:
- Application crash on re-entry: Users cannot exit and re-enter VR session
- Poor user experience: Error disrupts XR experience, may require page reload
- Affects 26 applications: Detected in 72 instances across 65 traces and 26 A-Frame applications
- Device-specific: Occurs on real devices (Quest 3, Pico 4) but not on simulators (IWE polyfill handles rAF differently)
Experimental Evidence (from WebXRBench ICSE 2027 submission):
- Pattern ID: D7
- Divergence type:
runtime_error_mismatch - Occurrences: 72 instances across 65 traces
- Affected applications: 26 A-Frame WebXR applications
- A-Frame versions: 1.4.2, 1.5.0
- Environment: Quest 3, Pico 4 native browsers (true devices, not simulators)
- Witness + target evidence: Available
Environment:
- A-Frame versions: 1.4.2, 1.5.0
- Browsers: Quest Browser (Quest 3), Pico Browser (Pico 4)
- Devices: True devices (not simulators)
- WebXR mode: immersive-vr
Related Bugs:
- Bug #1: Null cAF handle (similar cleanup issue with
cancelAnimationFrame) - Both bugs are related to A-Frame's XR session lifecycle management
Proposed Fix (patch for A-Frame):
Apply the corrected code above to A-Frame's render system. Additionally, add defensive programming:
// Defensive: wrap rAF call in try-catch
render: function(timestamp, frame) {
try {
if (this.session && typeof this.session.requestAnimationFrame === 'function') {
this.rAFId = this.session.requestAnimationFrame(this._bindRender);
} else {
console.warn('Render: session.requestAnimationFrame is not available');
// Fallback to non-XR rendering
requestAnimationFrame(this._bindRender);
}
} catch (e) {
console.error('Render error:', e);
}
// ... render code ...
}
Testing Recommendations:
- Test XR session re-entry on real devices (Quest 3, Pico 4)
- Add unit tests for
endXR()to verify rAF cleanup - Add integration tests for exit-re-enter flow
- Test with multiple A-Frame components that use XR session
References:
- A-Frame documentation: https://aframe.io/docs/
- WebXR Device API (requestAnimationFrame): https://www.w3.org/TR/webxr/#dom-xrsession-requestanimationframe
- Related issue in A-Frame: [search for "requestAnimationFrame" in issues]
Additional Context:
This bug was discovered through systematic cross-host testing using WebXRBench framework (ICSE 2027 submission). The IDC (Incremental Delta Consistency) checker detected this runtime error by comparing runtime errors across host configurations. The error occurs consistently on real devices but not on simulators, suggesting that IWE polyfill's rAF handling masks the bug in simulation environments.
Priority: High (affects core XR session lifecycle, 26 applications impacted)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in the render system in system.js, focusing on the startXR, endXR, and render lifecycle described in the report. Reproduce the exit-and-re-enter flow on a Quest 3 or Pico 4 if available, then add coverage for rAF cleanup and session re-entry. Done means re-entering XR creates a valid callback without the reported TypeError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- ar-vr-xr
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100