F1 BLOCKING — walk() cycle detection (src/viewer/index.js:161)
CI / test (pull_request) Successful in 5m41s
CI / test (pull_request) Successful in 5m41s
Added a per-tree WeakSet visited-guard so the probe terminates. The depth cap alone didn't stop the infinite recursion: window/_parent/_top/frames/_globalObject/_globalProxy are mutually-reachable circular Proxies, so Strategy H's window scan re-walked the same identities along exponentially many paths. The set is declared inside walk (not the outer function) so the separate player trees stay independent. This is what was hanging npm test (exit 124); it now completes in ~15s.
F2 SHOULD-FIX — doc/behavior mismatch (src/background/background.js:299-322)
Reworded the referer-capture comment block. It claimed the referer was read "via onBeforeSendHeaders, which sees the request headers," but the code uses onBeforeRequest + chrome.tabs.get (deriving the referer from the source tab's URL). Now states the actual mechanism and documents why onBeforeSendHeaders+extraHeaders was avoided (extraInfoSpec is rejected by some Chromium builds and fails the whole SW).
F3 SHOULD-FIX — leaked settings file
git rm --cached .claude/settings.local.json and added it to .gitignore. It carried machine-specific absolute paths (/Users/kyle/.claude/...).
F4 FYI — package-lock churn (package-lock.json)
Removed the spurious root "dependencies": { @rollup/rollup-darwin-arm64 } and restored the dev: true / optional: true flags on that platform package so it matches the rest of the @rollup/rollup-* siblings.
F5 FYI — real provider hostnames in test fixtures
Swapped phncdn.com→neutral example.net (registrable-domain sibling test) and thisvid.com→example.com in data_flow/hls_qa/simple_video/referer_proxy test files, rewording comments that named the real sites. src/ and __tests__/ now have zero phncdn/thisvid/pornhub references.
This commit is contained in:
1 parent
ff463c107a
commit
3e02a6ae74
9 files changed
+70
-75
No files matched your search
@@ -193,8 +193,8 @@ describe('Data flow: popup -> storage -> viewer', () => {
|
||||
// explicit property or storage-layer validation would reject the entry.
|
||||
const scrapedEntry = {
|
||||
url: 'https://pageA.example.com/watch',
|
||||
src: 'https://hv-h.phncdn.com/hls/live/master.m3u8',
|
||||
hlsManifest: 'https://hv-h.phncdn.com/hls/live/master.m3u8',
|
||||
src: 'https://cdn.example.com/hls/live/master.m3u8',
|
||||
hlsManifest: 'https://cdn.example.com/hls/live/master.m3u8',
|
||||
mediaType: 'hls',
|
||||
poster: 'https://pageA.example.com/thumb.jpg',
|
||||
tab: 'Referer-protected stream',
|
||||
|
||||
+13
-13
@@ -1262,24 +1262,24 @@ describe('HLS QA: route-template / nav URL rejection', () => {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reproduces thisvid.com: pageContext.langUrls holds server-side route
|
||||
// Reproduces a provider whose pageContext.langUrls holds server-side route
|
||||
// TEMPLATES / nav links (e.g. /playlists/, /playlist/%1%/video/%2%/) that the
|
||||
// old /playlist|/manifest substring check misclassified as "manifests".
|
||||
const TEMPLATE_URLS = [
|
||||
'https://thisvid.com/playlists/',
|
||||
'https://thisvid.com/playlists/liked/',
|
||||
'https://thisvid.com/playlists/popular/',
|
||||
'https://thisvid.com/playlists/commented/',
|
||||
'https://thisvid.com/playlists/updated/',
|
||||
'https://thisvid.com/playlists/largest/',
|
||||
'https://thisvid.com/playlist/%1%/video/%2%/',
|
||||
'https://thisvid.com/playlists/?category=%DIR%',
|
||||
'https://thisvid.com/members/%ID%/playlists/',
|
||||
'https://thisvid.com/my/playlists/%ID%/',
|
||||
'https://example.com/playlists/',
|
||||
'https://example.com/playlists/liked/',
|
||||
'https://example.com/playlists/popular/',
|
||||
'https://example.com/playlists/commented/',
|
||||
'https://example.com/playlists/updated/',
|
||||
'https://example.com/playlists/largest/',
|
||||
'https://example.com/playlist/%1%/video/%2%/',
|
||||
'https://example.com/playlists/?category=%DIR%',
|
||||
'https://example.com/members/%ID%/playlists/',
|
||||
'https://example.com/my/playlists/%ID%/',
|
||||
];
|
||||
|
||||
it('route templates / nav links are NOT reported as manifests', () => {
|
||||
const result = runProbeWin('https://thisvid.com/videos/redneck/', {
|
||||
const result = runProbeWin('https://example.com/videos/redneck/', {
|
||||
pageContext: { langUrls: TEMPLATE_URLS },
|
||||
kvsplayer: { config: { langUrls: TEMPLATE_URLS } },
|
||||
__goon_plugin_data: { manifests: [], sources: [] },
|
||||
@@ -1294,7 +1294,7 @@ describe('HLS QA: route-template / nav URL rejection', () => {
|
||||
it('a real .m3u8 with a %2B token is still found (no over-rejection)', () => {
|
||||
// %2B is a valid percent-encoded token, not a placeholder — it must survive.
|
||||
const real = 'https://cdn.example.com/live/master.m3u8?h=a%2Bb%2Fc&e=1234';
|
||||
const result = runProbeWin('https://thisvid.com/videos/redneck/', {
|
||||
const result = runProbeWin('https://example.com/videos/redneck/', {
|
||||
hls: { url: real },
|
||||
pageContext: { langUrls: TEMPLATE_URLS },
|
||||
});
|
||||
|
||||
@@ -252,26 +252,26 @@ describe('PR#5: per-host referer capture + proxy race', () => {
|
||||
});
|
||||
await loadBackground(env);
|
||||
|
||||
// Capture the provider referer for hv-h.phncdn.com (one edge subdomain).
|
||||
await capture(env, 'https://hv-h.phncdn.com/hls/live/seg-1.ts', 'https://provider.example.com/watch');
|
||||
// Capture the provider referer for hv-h.example.net (one edge subdomain).
|
||||
await capture(env, 'https://hv-h.example.net/hls/live/seg-1.ts', 'https://provider.example.com/watch');
|
||||
|
||||
// Proxy a SIBLING subdomain ev-h.phncdn.com with NO referer in the message:
|
||||
// Proxy a SIBLING subdomain ev-h.example.net with NO referer in the message:
|
||||
// the SW must resolve it from the domain-keyed store.
|
||||
const handler = env.handlers.onMessage[env.handlers.onMessage.length - 1];
|
||||
let resp;
|
||||
handler({ type: 'GOON_PROXY_FETCH', url: 'https://ev-h.phncdn.com/hls/live/seg-1.ts' }, {}, (r) => { resp = r; });
|
||||
handler({ type: 'GOON_PROXY_FETCH', url: 'https://ev-h.example.net/hls/live/seg-1.ts' }, {}, (r) => { resp = r; });
|
||||
await new Promise(r => setTimeout(r, 20));
|
||||
|
||||
if (!resp || !resp.ok) throw new Error('sibling proxy failed: ' + JSON.stringify(resp));
|
||||
const rules = env.getSessionRules();
|
||||
const rule = Object.values(rules).find(r => r.condition?.requestDomains?.includes('phncdn.com'));
|
||||
if (!rule) throw new Error('no domain-scoped DNR rule for phncdn.com; rules=' + JSON.stringify(rules));
|
||||
const rule = Object.values(rules).find(r => r.condition?.requestDomains?.includes('example.net'));
|
||||
if (!rule) throw new Error('no domain-scoped DNR rule for example.net; rules=' + JSON.stringify(rules));
|
||||
const ref = rule.action.requestHeaders?.find(h => h.header.toLowerCase() === 'referer')?.value;
|
||||
if (ref !== 'https://provider.example.com/watch') {
|
||||
throw new Error('sibling edge served wrong referer: ' + ref);
|
||||
}
|
||||
// The rule is scoped to the registrable domain, NOT the exact subdomain.
|
||||
if (rule.condition.requestDomains?.includes('ev-h.phncdn.com')) {
|
||||
if (rule.condition.requestDomains?.includes('ev-h.example.net')) {
|
||||
throw new Error('rule scoped to exact subdomain instead of registrable domain');
|
||||
}
|
||||
});
|
||||
@@ -313,23 +313,23 @@ describe('PR#5: per-host referer capture + proxy race', () => {
|
||||
global.chrome = env.chrome;
|
||||
await loadBackground(env);
|
||||
|
||||
// Capture for hv-h.phncdn.com only.
|
||||
await capture(env, 'https://hv-h.phncdn.com/hls/live/seg-1.ts', 'https://provider.example.com/watch');
|
||||
// Capture for hv-h.example.net only.
|
||||
await capture(env, 'https://hv-h.example.net/hls/live/seg-1.ts', 'https://provider.example.com/watch');
|
||||
|
||||
const handler = env.handlers.onMessage[env.handlers.onMessage.length - 1];
|
||||
let resp;
|
||||
handler(
|
||||
{ type: 'GOON_GET_REFERERS', hosts: ['ev-h.phncdn.com', 'hv-h.phncdn.com', 'cdnA.example.com'] },
|
||||
{ type: 'GOON_GET_REFERERS', hosts: ['ev-h.example.net', 'hv-h.example.net', 'cdnA.example.com'] },
|
||||
{},
|
||||
(r) => { resp = r; });
|
||||
await new Promise(r => setTimeout(r, 15));
|
||||
|
||||
if (!resp || !resp.ok) throw new Error('GOON_GET_REFERERS failed: ' + JSON.stringify(resp));
|
||||
const referers = resp.referers || {};
|
||||
if (referers['ev-h.phncdn.com'] !== 'https://provider.example.com/watch') {
|
||||
if (referers['ev-h.example.net'] !== 'https://provider.example.com/watch') {
|
||||
throw new Error('sibling subdomain did not resolve the captured referer: ' + JSON.stringify(referers));
|
||||
}
|
||||
if (referers['hv-h.phncdn.com'] !== 'https://provider.example.com/watch') {
|
||||
if (referers['hv-h.example.net'] !== 'https://provider.example.com/watch') {
|
||||
throw new Error('captured subdomain did not resolve its own referer: ' + JSON.stringify(referers));
|
||||
}
|
||||
if ('cdnA.example.com' in referers) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Regression tests: simple <video> / direct-media detection.
|
||||
//
|
||||
// The feature branch broke detection of pages that expose a plain <video>
|
||||
// (e.g. thisvid.com, which serves a direct MP4 with a TRAILING SLASH and a
|
||||
// (e.g. a site that serves a direct MP4 with a TRAILING SLASH and a
|
||||
// ?rnd= query: .../14644093/14644093.mp4/?rnd=...). Two site-agnostic fixes
|
||||
// are guarded here:
|
||||
// 1) The URL predicate (isValidManifestUrl) must let concrete media signals
|
||||
@@ -9,7 +9,7 @@
|
||||
// slash rejections, so a real direct-media URL is not mistaken for a
|
||||
// route template.
|
||||
// 2) scrapeTab must fall back to an ISOLATED-world scrape when the MAIN-world
|
||||
// scrape yields nothing — the runtime cause of "detects nothing" on thisvid
|
||||
// scrape yields nothing — the runtime cause of "detects nothing" on such pages
|
||||
// is a MAIN-world + allFrames rejection on its ad-iframe-heavy page.
|
||||
import { describe, it } from './runner.mjs';
|
||||
import { JSDOM } from 'jsdom';
|
||||
@@ -40,12 +40,12 @@ function extractFn(src, name) {
|
||||
throw new Error('unbalanced: ' + name);
|
||||
}
|
||||
|
||||
// The thisvid direct-media URL, exactly as served (note the trailing slash +
|
||||
// The direct-media URL, shaped exactly like the rejected case (a trailing slash +
|
||||
// ?rnd= query). This is the concrete case the predicate used to reject.
|
||||
const THISVID_MP4 =
|
||||
'https://thisvid.com/get_file/5/6cae22f8b3ba9d74f10cc2f8d060af738c8d30ddf5/14644000/14644093/14644093.mp4/?rnd=1787811205406';
|
||||
const DIRECT_MP4 =
|
||||
'https://example.com/get_file/5/6cae22f8b3ba9d74f10cc2f8d060af738c8d30ddf5/14644000/14644093/14644093.mp4/?rnd=1787811205406';
|
||||
|
||||
describe('HLS QA: simple <video> direct-media predicate (thisvid regression)', () => {
|
||||
describe('HLS QA: simple <video> direct-media predicate', () => {
|
||||
// Extract and evaluate the REAL isValidManifestUrl from index.js in a browser
|
||||
// window so its `new URL(...)` / string behavior matches production.
|
||||
const isValidSrc = extractFn(indexSource, 'isValidManifestUrl');
|
||||
@@ -53,7 +53,7 @@ describe('HLS QA: simple <video> direct-media predicate (thisvid regression)', (
|
||||
function assertValid(url, expectValid, label) {
|
||||
const dom = new JSDOM(
|
||||
'<!DOCTYPE html><html><body></body></html>',
|
||||
{ url: 'https://thisvid.com/videos/x/', runScripts: 'dangerously' }
|
||||
{ url: 'https://example.com/videos/x/', runScripts: 'dangerously' }
|
||||
);
|
||||
const got = dom.window.eval('(' + isValidSrc + ')(' + JSON.stringify(url) + ')');
|
||||
if (got !== expectValid) {
|
||||
@@ -63,9 +63,9 @@ describe('HLS QA: simple <video> direct-media predicate (thisvid regression)', (
|
||||
}
|
||||
}
|
||||
|
||||
it('accepts thisvid\'s real direct-MP4 URL WITH a trailing slash + ?rnd=', () => {
|
||||
it('accepts a real direct-MP4 URL WITH a trailing slash + ?rnd=', () => {
|
||||
// Before the fix, `path.endsWith('/')` rejected this real media source.
|
||||
assertValid(THISVID_MP4, true, 'thisvid trailing-slash MP4');
|
||||
assertValid(DIRECT_MP4, true, 'trailing-slash MP4');
|
||||
});
|
||||
|
||||
it('accepts a direct .mp4 with no trailing slash', () => {
|
||||
@@ -86,18 +86,18 @@ describe('HLS QA: simple <video> direct-media predicate (thisvid regression)', (
|
||||
assertValid('https://cdn.example.com/live/master.m3u8?h=a%2Bb%2Fc&e=1234', true, '%2B token');
|
||||
});
|
||||
|
||||
it('still rejects thisvid route-template / nav URLs (no over-acceptance)', () => {
|
||||
it('still rejects route-template / nav URLs (no over-acceptance)', () => {
|
||||
// The fix must not trade one regression for the other: the route templates
|
||||
// thisvid\'s pageContext.langUrls leaks must still be rejected.
|
||||
assertValid('https://thisvid.com/playlists/', false, '/playlists/');
|
||||
assertValid('https://thisvid.com/playlists/liked/', false, '/playlists/liked/');
|
||||
assertValid('https://thisvid.com/playlist/%1%/video/%2%/', false, '/playlist/%1%/');
|
||||
assertValid('https://thisvid.com/members/%ID%/playlists/', false, '/members/%ID%/playlists/');
|
||||
assertValid('https://thisvid.com/videos/%DIR%/', false, '/videos/%DIR%/');
|
||||
// a provider's pageContext.langUrls leaks must still be rejected.
|
||||
assertValid('https://example.com/playlists/', false, '/playlists/');
|
||||
assertValid('https://example.com/playlists/liked/', false, '/playlists/liked/');
|
||||
assertValid('https://example.com/playlist/%1%/video/%2%/', false, '/playlist/%1%/');
|
||||
assertValid('https://example.com/members/%ID%/playlists/', false, '/members/%ID%/playlists/');
|
||||
assertValid('https://example.com/videos/%DIR%/', false, '/videos/%DIR%/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('HLS QA: scrapeTab isolated-world fallback (thisvid regression)', () => {
|
||||
describe('HLS QA: scrapeTab isolated-world fallback', () => {
|
||||
// Extract the whole scrapeTab function (balanced braces, indentation-agnostic).
|
||||
const scrapeSrc = extractFn(indexSource, 'scrapeTab');
|
||||
|
||||
|
||||
Reference in new issue
Block a user