Skip to content

Detection: an HTML page is not a stream because its query string names one - #563

Closed
Nawid3333 wants to merge 1 commit into
Andrews54757:mainfrom
Nawid3333:upstream-pr/embed-page-detection
Closed

Nawid3333 wants to merge 1 commit into
Andrews54757:mainfrom
Nawid3333:upstream-pr/embed-page-detection

Conversation

@Nawid3333

Copy link
Copy Markdown
Contributor

What breaks

Embed pages often carry their stream in the query string:

embed.php?file=https://cdn/.../index.m3u8

onHeadersReceived handled that by looking for a stream extension in the
query parameters of any response whose URL has no stream extension. That is
right for a stream fetched through a proxy (proxy?url=...m3u8), but the
check ran for page loads too, so the embed page itself (an HTML document)
was added to the frame's sources as an HLS stream. Two visible outcomes:

  • with the site on the auto-enable list, the player opened on the page
    before the page's own player had requested the real stream, and nothing
    ever loaded;
  • otherwise, the page's URL sat in the source list next to the real stream.

The fix

A main_frame or sub_frame load answered with text/html or
application/xhtml+xml now skips the query-string check - an HTML page is
not a stream, whatever its query string names. Everything else keeps the
existing behavior:

  • a proxy link opened in a tab or an iframe is answered with the stream
    itself, is not HTML, and is still detected;
  • the real stream the page requests is detected by its own response, as
    before;
  • media-type responses are unaffected.

Reading the Content-Type required one opt-in: the listener now asks for
responseHeaders (webRequestPerms2, previously an empty array). The
Content-Type header is not otherwise exposed, and asking for it adds no
permission (webRequest is already granted).

Diff: 1 file, 24 insertions, 2 deletions - a 10-line isHtmlResponse()
helper, a gated early return, and the opt-in.

Why this can't break anything else

  • Blast radius: background.mjs is the extension's service worker; the
    change is confined to the onHeadersReceived detection callback - three
    statements inside its if (!mode) block plus the helper. Nothing outside
    the file changed.
  • Only the HTML page case is removed. The early return requires all
    of: main_frame/sub_frame type, no extension-based mode, and an HTML
    Content-Type. Every combination outside that keeps the previous path:
    • media responses → unchanged (ACCELERATED_MP4 branch is first);
    • non-HTML frame loads (a proxy link opened in a tab/iframe answering
      with the stream) → falls through to the query-string check as before;
    • XHR/fetch/media requests naming a stream in their query → unchanged;
    • subtitle responses → handled earlier by isSubtitles, untouched;
    • Vimeo patch and CustomSourcePatternsMatcher paths → earlier in the
      listener, untouched.
  • Fallback to normal navigation: a page load that returns early is not
    dropped from FastStream's tracking - it is simply not registered as a
    source. The page's player requests the stream itself, and that response
    is detected exactly as it always was.
  • Error responses already returned earlier (statusCode >= 400 || 204),
    so the new branch never sees them.

Verification

  • Build: npm ci + node localescript.mjs + node build.mjs (this
    repo's own build path) - all three targets build.
  • The reported scenario: embed.php?file=...m3u8 answered with
    text/html → no source added for the page; the stream request from
    inside the page → still detected. A proxy?url=...m3u8 opened directly →
    still detected, HTML check does not apply (not an HTML response).
  • Listener opt-in: verified webRequestPerms2 is passed as the
    extraInfoSpec at the onHeadersReceived.addListener call, so
    details.responseHeaders is populated on both engines; isHtmlResponse
    tolerates a missing header array via optional chaining.
  • Lint: eslint with this repo's .eslintrc.js (google config): no
    problems.
  • Diff shape: git diff --numstat against main:
    24 2 chrome/background/background.mjs.

…s one

Embed pages often carry their stream in the query string:
embed.php?file=https://cdn/.../index.m3u8. For a response whose URL has no
stream extension, onHeadersReceived looks for one in the query parameters.
That is right for a stream fetched through a proxy (proxy?url=...m3u8), but
it did the same for page loads, so the embed page itself (HTML) was added to
the frame's sources as an HLS stream. With the site on the auto-enable list
the player opened on the page before the page's own player had requested the
real stream, and nothing ever loaded; otherwise the page URL sat in the
source list next to the real stream.

A main_frame or sub_frame load answered with text/html or
application/xhtml+xml now skips the query-string check. A proxy link opened
in a tab or iframe, answered with the stream itself, is still detected. The
listener now asks for responseHeaders to read the Content-Type.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Andrews54757

Copy link
Copy Markdown
Owner

I believe that unfortunately Chrome does not allow getting response as per new Manifest V3 changes

@Nawid3333

Copy link
Copy Markdown
Contributor Author

That could be the case. I only target Firefox, so I haven’t looked into Chrome too much.

@Nawid3333 Nawid3333 closed this Oct 9, 2026
@Nawid3333
Nawid3333 deleted the upstream-pr/embed-page-detection branch October 9, 2026 08:31
Nawid3333 referenced this pull request in Nawid3333/FastStream Oct 9, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants