Background
I am using yt-dlp on iOS in a-Shell, with yt-dlp-ejs 0.8.0 and the yt-dlp-apple-webkit-jsi 0.1.1 plugin.
The relevant execution path is:
yt-dlp
→ requires YouTube JS challenge solving
→ yt-dlp-ejs supplies the EJS solver JavaScript
→ apple-webkit-jsi is selected as the JavaScript runtime
→ apple-webkit-jsi creates a WKWebView
→ EJS JavaScript is executed inside that WKWebView
This generally works: the EJS challenge completes and yt-dlp continues the download successfully.
However, when the challenge solver is invoked, iOS can unexpectedly foreground the installed YouTube app, which then displays "This video is unavailable".
I initially investigated this as an apple-webkit-jsi issue, but tracing the exact JavaScript being executed led to the EJS environment setup.
Problem
The EJS solver setup contains:
if (typeof URL === "undefined") {
globalThis.location = {
...
href: "https://www.youtube.com/watch?v=yt-dlp-wins",
...
};
} else {
globalThis.location =
new URL("https://www.youtube.com/watch?v=yt-dlp-wins");
}
I understand the apparent purpose: provide YouTube's JavaScript with a synthetic browser-like location representing a YouTube watch page.
The issue is that apple-webkit-jsi uses a real WKWebView as the JS runtime.
In WKWebView:
and globalThis.location is therefore the real browser window.location.
Since URL also exists, EJS executes:
globalThis.location =
new URL("https://www.youtube.com/watch?v=yt-dlp-wins");
Rather than merely creating synthetic environment data, this causes the WKWebView to navigate to that URL.
On iOS, that YouTube URL is then handled in a way consistent with YouTube Universal Link handling: the installed YouTube app is foregrounded and attempts to open video ID yt-dlp-wins, resulting in "This video is unavailable".
Deterministic reproduction
To remove yt-dlp extraction, YouTube challenge selection, and downloading from the equation, I built a small test harness using the same WKJSE_Factory / WKJSE_Webview implementation from apple-webkit-jsi.
I ran four controlled tests in otherwise identical fresh WKWebViews.
1. Harmless JavaScript
globalThis.__webkit_jsi_harness = "ok";
Result: no app switch
2. Construct the EJS synthetic URL only
new URL("https://www.youtube.com/watch?v=yt-dlp-wins");
Result: no app switch
3. Assign a non-YouTube URL to location
globalThis.location = new URL("https://example.com/");
Result: no app switch
4. Assign the EJS synthetic YouTube URL to location
globalThis.location =
new URL("https://www.youtube.com/watch?v=yt-dlp-wins");
Result: the YouTube app is immediately foregrounded and displays "This video is unavailable"
This reproduces the original yt-dlp symptom without running yt-dlp, without downloading a video, and without requiring YouTube to issue a JS challenge.
It therefore appears that the EJS synthetic location assignment is being interpreted as genuine navigation when EJS is executed by a browser-backed JavaScript runtime such as WKWebView.
Request / question
Could EJS's environment setup be made safe for browser-backed JavaScript runtimes while still providing YouTube's player/challenge JavaScript with the synthetic YouTube location it requires?
Simply avoiding the assignment when globalThis.location already exists may not be sufficient, since a newly-created WKWebView may have a real location such as about:blank, while the solver presumably expects the synthetic YouTube origin/path.
So I would appreciate guidance on the intended solution:
- Should EJS detect browser-backed runtimes and provide the synthetic location differently?
- Is there a supported way for a JSI provider to supply the required location/environment without EJS assigning to the real
window.location?
- Or is the preferred solution for
apple-webkit-jsi to intercept and cancel this specific synthetic navigation?
The challenge itself completes successfully, so I don't think the solver logic is failing. The issue appears to be an unintended side effect of using globalThis.location for environment emulation when globalThis is a real browser window.
I can provide the diagnostic instrumentation, logs, and standalone WKWebView reproduction harness if useful.
Background
I am using
yt-dlpon iOS in a-Shell, withyt-dlp-ejs 0.8.0and theyt-dlp-apple-webkit-jsi 0.1.1plugin.The relevant execution path is:
This generally works: the EJS challenge completes and yt-dlp continues the download successfully.
However, when the challenge solver is invoked, iOS can unexpectedly foreground the installed YouTube app, which then displays "This video is unavailable".
I initially investigated this as an
apple-webkit-jsiissue, but tracing the exact JavaScript being executed led to the EJS environment setup.Problem
The EJS solver setup contains:
I understand the apparent purpose: provide YouTube's JavaScript with a synthetic browser-like
locationrepresenting a YouTube watch page.The issue is that
apple-webkit-jsiuses a real WKWebView as the JS runtime.In WKWebView:
and
globalThis.locationis therefore the real browserwindow.location.Since
URLalso exists, EJS executes:Rather than merely creating synthetic environment data, this causes the WKWebView to navigate to that URL.
On iOS, that YouTube URL is then handled in a way consistent with YouTube Universal Link handling: the installed YouTube app is foregrounded and attempts to open video ID
yt-dlp-wins, resulting in "This video is unavailable".Deterministic reproduction
To remove yt-dlp extraction, YouTube challenge selection, and downloading from the equation, I built a small test harness using the same
WKJSE_Factory/WKJSE_Webviewimplementation fromapple-webkit-jsi.I ran four controlled tests in otherwise identical fresh WKWebViews.
1. Harmless JavaScript
Result: no app switch
2. Construct the EJS synthetic URL only
Result: no app switch
3. Assign a non-YouTube URL to location
Result: no app switch
4. Assign the EJS synthetic YouTube URL to location
Result: the YouTube app is immediately foregrounded and displays "This video is unavailable"
This reproduces the original yt-dlp symptom without running yt-dlp, without downloading a video, and without requiring YouTube to issue a JS challenge.
It therefore appears that the EJS synthetic
locationassignment is being interpreted as genuine navigation when EJS is executed by a browser-backed JavaScript runtime such as WKWebView.Request / question
Could EJS's environment setup be made safe for browser-backed JavaScript runtimes while still providing YouTube's player/challenge JavaScript with the synthetic YouTube
locationit requires?Simply avoiding the assignment when
globalThis.locationalready exists may not be sufficient, since a newly-created WKWebView may have a real location such asabout:blank, while the solver presumably expects the synthetic YouTube origin/path.So I would appreciate guidance on the intended solution:
window.location?apple-webkit-jsito intercept and cancel this specific synthetic navigation?The challenge itself completes successfully, so I don't think the solver logic is failing. The issue appears to be an unintended side effect of using
globalThis.locationfor environment emulation whenglobalThisis a real browserwindow.I can provide the diagnostic instrumentation, logs, and standalone WKWebView reproduction harness if useful.