Skip to content

EJS synthetic globalThis.location causes real navigation in WKWebView JavaScript runtime #76

Description

@homeautomator

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:

globalThis === window

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions