The issue
When I open an AR session via Needle Go from a Safari-hosted Needle Engine page, pressing “Back” or “Quit AR” in Needle Go’s context menu always results in a completely black screen in Safari. The original page is visible in the iOS app switcher (the tab is still open and appears correct), but Safari displays a black page once it comes back to the foreground.
The AR session itself works perfectly, the issue only occurs on return from Needle Go to Safari.
Reproduction steps
-
Open the Needle Engine project in Safari on iOS (iPhone, Needle Go 1.7 installed)
-
Tap the AR button on the page
-
Needle Go opens, AR session starts and works correctly (3D model overlaid on real environment)
-
Open Needle Go’s context menu (long press), shows “Back”, “Quit AR”, “Reload Page”, “Send Feedback”, “Needle Go 1.7”
→ Before tap on “Back” OR “Quit AR”, Safari still have the pages loaded perfectly in background -
Tap “Back” OR “Quit AR”
-
→ Safari comes back to the foreground and displays a black screen
The original Safari tab is visible in the iOS multitasker with the correct page content before step 5.
Technical investigation
Fix attempt 1 — pageshow + bfcache reload
Hypothesis: When navigateNeedleAppClip() runs window.location.href = appclipUrl, Safari navigates away from the page. iOS intercepts and opens Needle Go. The original page is stored in the bfcache. When Needle Go closes, Safari returns but pageshow.persisted = true fires without a handler → WebGL context is suspended → black canvas.
Fix: Added pageshow listener in index.html to force location.reload() when persisted = true.
Result: âś— No change. Black screen persists.
Fix attempt 2 — window.open(_blank) in navigateNeedleAppClip()
Hypothesis: Using location.href navigates Safari’s current tab to the appclip URL. After Needle Go, Safari is left on the blank appclip URL page. Using window.open(_blank) should preserve the current tab.
Fix: Changed navigateNeedleAppClip() to use window.open(urlStr, "_blank", "noopener,noreferrer") with location.href fallback.
Result: ✗ No change. Black screen persists, sometimes with a “◄ Needle Go” indicator visible in Safari’s status bar (meaning Needle Go opened Safari, not the other way around).
Fix attempt 3 — Needle Go Full App user-agent detection
Discovery: DeviceUtilities.isNeedleAppClip() in Needle Engine checks for /NeedleAppClip\//i in the user-agent. Needle Go Full App appears to use NeedleGo/x.x in its user-agent (not NeedleAppClip/), so isNeedleAppClip() returns false even when running inside Needle Go.
This means our patched NeedleXRSession.start was calling navigateNeedleAppClip() even when already running inside Needle Go’s WKWebView — causing Needle Go to open Safari with the appclip URL.
Fix: Added isInAnyNeedleGoContext() helper that checks for both NeedleAppClip/ and NeedleGo/ in the user-agent:
function isInAnyNeedleGoContext(DeviceUtilities: { isNeedleAppClip(): boolean }): boolean {
if (DeviceUtilities.isNeedleAppClip()) return true;
if (typeof navigator === “undefined”) return false;
return /NeedleGo\//i.test(navigator.userAgent);
}
And replaced !DeviceUtilities.isNeedleAppClip() with !isInAnyNeedleGoContext() in the condition.
Result: âś“ AR session now starts correctly in Needle Go Full App. âś— Black screen on return still persists.
Fix attempt 4 — Revert to location.href + visibilitychange resize
Hypothesis: window.open(_blank) creates a new empty Safari tab. When Needle Go closes, iOS focuses this new empty tab (appclip URL → black) rather than the original tab.
Fixes:
-
Reverted
navigateNeedleAppClip()back tolocation.href(same tab, no new tab created) -
Improved
pageshow.persistedhandler to show the loading screen beforelocation.reload() -
Added
visibilitychange→resizeto let Needle Engine recalculate renderer dimensions after resume
Result: ✗ Black screen still persists after pressing “Back” or “Quit AR”.
Current state / open questions
After extensive investigation, it appears that:
-
The Needle Engine
DeviceUtilities.isNeedleAppClip()check fails for Needle Go Full App (only detects the App Clip variant, not the full app). This is the root cause ofnavigateNeedleAppClip()being called from inside Needle Go, but we worked around it in our own code. -
Even with the correct AR session starting in Needle Go, pressing “Back” or “Quit AR” consistently results in a black Safari page.
-
The original Safari tab is intact and visible in the multitasker with the correct content — but it displays black when active.
Questions for the Needle team:
-
What is the exact user-agent string set by Needle Go Full App (not App Clip)?
DeviceUtilities.isNeedleAppClip()should ideally detect both variants. -
When pressing “Quit AR” in Needle Go’s context menu, does Needle Go close the App Clip / WKWebView entirely, or does it just end the XR session and stay in the WKWebView?
-
When Needle Go is launched from Safari via
location.href = "https://appclip.apple.com/id?p=tools.needle.launch-app.Clip&url=..."— does iOS navigate Safari’s tab to the appclip URL, or does iOS intercept the navigation and leave Safari on the original page? This determines whether the black screen is Safari showing the appclip URL (no content) or Safari restoring our page from bfcache with a broken WebGL context. -
Is there a recommended pattern in Needle Engine projects for graceful recovery when Safari returns from background after a Needle Go AR session?
