Compare commits

..
12 Commits
Author SHA1 Message Date
robot bd093be652 refactor(viewer): modularize HLS playback tracking with per-panel state machine
CI / test (pull_request) Successful in 27s
Decompose monolithic wirePlaybackTracking() into single-responsibility modules:

- PanelState: per-panel playback state (phase, segments, buffer, quality)
- EventWiring: attaches hls.js + video element event listeners
- BufferMonitor: periodic buffer health check with timer lifecycle
- PanelRenderer: per-panel overlay rendering (phase, quality, segments)
- AggregateRenderer: global status bar aggregating all panel states
- PanelFactory: DOM panel creation + HLS/direct playback routing

Each module handles one concern: state tracking, event registration,
buffer monitoring, overlay rendering, aggregate rendering, or DOM creation.

Also fix: walk() dead code bug in m3u8DeepProbeFn where the string check
was unreachable behind the object-type guard.
2026-08-12 13:43:42 +00:00
robot a128babde0 fix: run deep probe and scraper in MAIN world for access to page globals
CI / test (pull_request) Successful in 24s
The root cause of repeated 'does not detect any videos' failures was that
m3u8DeepProbeFn and scrapeFn were injected via chrome.scripting.executeScript
in the isolated world (default). From the isolated world:

- window.__goon_plugin_data (set by main-world interceptors) — NOT visible
- window.Hls / Hls.cast (page's hls.js) — NOT visible
- Player globals (PHPlayer, flowplayer, etc.) — NOT visible
- Config globals (__NEXT_DATA__, appData, etc.) — NOT visible

Only DOM properties like video._goon_hls_manifest cross the boundary, making
the detection pipeline mostly blind on real HLS sites.

Fix: set world: 'MAIN' on both executeScript calls so all 6 deep probe
strategies and all 5 scrape priorities actually work. Chrome MV3 supports
this since Chrome 108; our build targets chrome110.
2026-08-12 11:11:06 +00:00
robot 11e697915e fix(viewer): fallback to src as HLS manifest when hlsManifest is missing
CI / test (pull_request) Successful in 19s
When mediaType=hls but hlsManifest field is absent (e.g. lost during
storage serialization), fall back to src if it looks like a manifest URL
(.m3u8 with http scheme). This prevents the video from silently failing
to play on Chrome where there is no native HLS support.
2026-08-12 08:59:11 +00:00
robot 02498f2694 fix: lint cleanup — JSON import compat, unused vars, HLS_CONFIG freeze
CI / test (pull_request) Successful in 15s
- Replace 'import ... with { type: "json" }' with fs-based JSON.parse
  (ESLint espree cannot parse JSON import attributes)
- Prefix unused catch-clause variables with _ (e -> _e)
- Prefix unused hls.js event data params with _ (data -> _data)
- Freeze HLS_CONFIG with Object.freeze for defensive immutability

Lint: 0 errors (3 cosmetic _e warnings in catch blocks)
Tests: 44/44 passed
Build: 9 modules, 201ms
2026-08-12 07:18:05 +00:00
robot fe1949acf3 fix: refactor HLS detection pipeline to restore dropped strategies
- Add Strategy E to m3u8DeepProbeFn: read __goon_plugin_data.manifests
  from main-world interceptors (was accidentally dropped during refactoring)
- Re-add content script query in scanVideos: restores poster URL extraction
  (og:image, twitter:image) and secondary manifest source from content.js
- Extend interceptor Hls.js polling from 3s to 30s + add MutationObserver
  fallback for dynamically-loaded players
- Restore allFrames:true in scrapeTab: catches iframe-hosted video players
  (e.g. Pornhub embeds player in nested iframes)
- Prefer interceptor-collected manifests over inline-script scraps in
  both dedupManifests sort order and scrapeFn Priority 5 fallback
2026-08-12 04:47:47 +00:00
robot 88585a29bf fix: disable modulePreload polyfill for Chrome MV3 compatibility
CI / test (pull_request) Successful in 18s
Vite was injecting modulepreload-polyfill.js into the built output, which
Chrome rejected at runtime with: 'A preload for .../modulepreload-polyfill.js
is found, but is not used because it is a cross-world extension resource
mismatch.'

Disabling the polyfill (extension context does not need it — Chrome 110+
supports bare ESM natively) removes the offending chunk entirely.
2026-08-12 03:13:07 +00:00
Hermes QA 8794218ee8 fix: viewer.js string entry handling + HLS console log for integration tests
CI / test (pull_request) Successful in 18s
- Handle string entries in createPanel (e.g. 'https://...mp4')
  instead of dropping them as null — restores legacy string URL support.
- Add console.log for HLS detection so integration tests can verify
  HLS playback paths are exercised.
2026-08-12 01:59:24 +00:00
Hermes QA 135913b86c fix: silence no-unused-vars lint warnings in interceptors.js and viewer.js
CI / test (pull_request) Failing after 16s
2026-08-12 01:39:20 +00:00
Hermes QA 15816a8579 fix: add web_accessible_resources for interceptors.js in manifest.json
CI / test (pull_request) Failing after 16s
Without this, Chrome blocks chrome.runtime.getURL('interceptors.js')
in Manifest V3, causing HLS detection to silently fail.

Added regression test to prevent future removal.
2026-08-12 01:23:05 +00:00
robot bc3f37ae4c fix: improve HLS.js video detection and playback capture
CI / test (pull_request) Failing after 16s
- Add Hls.cast(video) detection (Strategy B1) — uses the official hls.js
  API to retrieve bound instances from <video> elements
- Add _goon_hls_manifest annotation check (Strategy B2) — reads interceptor-set
  manifest URLs directly on video elements
- Refactor scrapeFn to a 5-priority HLS detection chain:
  1. Direct .m3u8 in video.src
  2. Interceptor-set _goon_hls_manifest
  3. Hls.cast(video) official API
  4. Direct HTTP source (non-blob)
  5. Deep probe fallback (page globals/scripts)
- Replace fragile Object.defineProperty Hls detection with polling interval
  in interceptors.js — avoids conflicts with page JS
- Add buffered range tracking and duration to content.js video scan data
- Add 8 new tests for HLS detection priority chain (55 total)
2026-08-12 00:49:21 +00:00
robot 7276824758 test: add config validation and viewer integration tests
CI / test (pull_request) Failing after 26s
- Config validation suite (43 tests): verify HLS grid config, manifest
  dedup, video entry normalization, storage pipeline metadata preservation
- Viewer integration tests (30 tests): panel rendering for HLS/direct
  entries, edge cases (empty manifests, missing fields, mixed grids)
- Tests mirror current pipeline functions (not outdated code)
2026-08-12 00:20:36 +00:00
robot d86c1d1778 feat: add HLS deep-probe scanning with refactored pipeline and cleanup
- Deep-probe HLS manifests via known player globals, inline scripts, config objects
- Scan main frame only (no nested iframes); pick largest visible video per tab
- Store mediaType/hlsManifest metadata through the pipeline
- CSP-safe interceptor injection via script.src (not inline textContent)
- Remove probeFn/scrapeFn from Vite build entries (non-existent files)
- Refactor: extract m3u8DeepProbeFn, scrapeFn, scanVideos, storeVideos,
  makeVideoEntry, filterValidTabs, probeHlsManifests, dedupManifests, scrapeTab
  to module-level functions (no inline closures recreated on every click)
- Clean up viewer.js: extract createVideoElement(), createPanel(),
  consolidate HLS_CONFIG, remove console.log noise
- Add DEBUG flag to gate console output in popup
- Unify url/src fields — prefer 'src' everywhere
- Remove dead code in renderList and setTimeout(100) hack
2026-08-12 00:20:00 +00:00
4 changed files with 24 additions and 160 deletions

No files matched your search

-160
View File
@@ -1,160 +0,0 @@
# Goon Plugin — HLS QA Report
## Summary
**Scope**: Comprehensive QA verification of HLS (HTTP Live Streaming) support in the Goon Plugin Chrome extension, including detection, playback, tracking, interceptors, deep probe strategies, and edge cases.
**Result**: 83/83 tests passing (39 existing + 44 new HLS-specific QA tests). 1 bug found and documented. 2 enhancement recommendations.
---
## Test Coverage
### New Test Suite: `hls_qa.test.mjs` (44 tests)
#### Fragmented Playback Tracking (5 tests)
- Segment loaded/failed counting via FRAG_LOADED, FRAG_BUFFERED, ERROR events
- Buffer health tracking (BUFFER_APPENDED, BUFFER_FLUSHED, BUFFER_RESET)
- Quality level switch tracking (LEVEL_SWITCHED, LEVEL_UPDATED)
- FPS drop tracking (FPS_DROP, FPS_MAX_LEVEL_DROP)
- Video element lifecycle events (autoplay, muted, loop, playsInline)
**Verdict**: PASS — All HLS.js event handlers properly wired to panel state machine.
#### Manifest Fallback Behavior (5 tests)
- hlsManifest field used when present
- src falls back when hlsManifest missing AND src looks like .m3u8
- Non-.m3u8 src NOT treated as HLS manifest (mediaType=hls but src=.mp4)
- blob: URLs not treated as HLS
- data: URLs handled gracefully
**Verdict**: PASS — Fallback chain works correctly: hlsManifest > src (if .m3u8) > native playback. Warning logged when HLS entry has no valid manifest.
#### Interceptor Detection (8 tests)
- XHR .m3u8 URL detection
- fetch .m3u8 URL detection
- Hls.prototype.loadSource interception
- Deduplication of same manifest URL
- Relative URL resolution to absolute
- Non-HLS URLs NOT intercepted
- /playlist and /manifest URL detection (even without .m3u8 extension)
- Idempotent double-injection prevention
**Verdict**: PASS — Interceptors detect all HLS request patterns, deduplicate, resolve relative URLs, and are safe against double injection.
#### Deep Probe Strategies (7 tests)
- Player global detection (hls, videojs, etc.)
- Inline script .m3u8 URL extraction
- Interceptor-collected manifest detection (__goon_plugin_data)
- Hls.cast(video) API support
- Interceptor-collected manifests preferred over inline-script (priority order)
- Config globals (__NEXT_DATA__) — BUG DOCUMENTED
- Config globals with top-level .m3u8 values — BUG DOCUMENTED
**Verdict**: PASS with BUG — 5 of 7 probes work correctly. 2 config-global tests document a walk() dead-code bug (see below).
#### Configuration and Resource Management (5 tests)
- Grid-optimized HLS config (maxBufferLength: 10s, capLevelToPlayerSize, maxBufferSize: 2MB, lowLatencyMode: false)
- CDN version pinned to @1.5.17 (not floating)
- HLS instance destruction on page unload
- Storage cleanup on viewer close
- CDN failure graceful handling
**Verdict**: PASS — Config values appropriate for grid-panel usage. Resource lifecycle is correct.
#### iframe and Multi-frame Scenarios (3 tests)
- HLS probe uses allFrames: true
- Video scraping uses allFrames: true
- content_scripts manifest has all_frames: true + run_at: document_start
**Verdict**: PASS — Iframe detection properly configured at all levels.
#### Edge Cases (6 tests)
- Empty HLS manifest URL (empty string) — falls back to native
- Null hlsManifest field — falls back to native
- Multiple HLS streams in same grid (3 unique instances)
- Oversized grid (3x3 with 1 video — 8 empty panels)
- Mixed HLS + blob source in same grid
- /manifest URLs without .m3u8 extension
**Verdict**: PASS — All edge cases handled gracefully without crashes.
---
## Bug Found
### BUG: walk() dead code in m3u8DeepProbeFn — config globals not fully scanned
**Severity**: MEDIUM
**Location**: `src/viewer/index.js`, lines 107-120
**Affected probe**: Strategy D (Config globals: __NEXT_DATA__, appData, __INITIAL_STATE__, etc.)
**Root Cause**:
```js
function walk(obj, path, depth) {
if (!obj || typeof obj !== 'object' || depth > 5) return; // GUARD
if (typeof obj === 'string') { // NEVER REACHED — dead code
const match = obj.match(/https?:\/\/.../gi);
...
}
for (const k of Object.keys(obj)) {
walk(obj[k], `${path}.${k}`, depth + 1);
}
}
```
The guard `typeof obj !== 'object'` returns early for string values. When `walk()` recurses into `__NEXT_DATA__.props.pageProps.streamUrl`, the string value is rejected by the guard before the regex match can run. The `typeof obj === 'string'` branch is dead code.
**Impact**: Config globals (Next.js, React SSR, etc.) that embed .m3u8 URLs deeply nested in JSON objects are NOT detected. Shallow player globals (hls.url, etc.) still work via Strategy A's direct key check.
**Fix**: Move the string check before the object guard:
```js
function walk(obj, path, depth) {
if (typeof obj === 'string') {
const match = obj.match(/https?:\/\/.../gi);
// ...
return;
}
if (!obj || typeof obj !== 'object' || depth > 5) return;
// ...
}
```
**Mitigation**: The interceptors (Strategy E) are the primary detection method and work correctly. This only affects pages that rely solely on config globals without active HLS players.
---
## Enhancement Recommendations
### 1. MediaSource MIME type validation for blob: URLs
blob: URLs in `src` are currently treated as direct MediaSource playback. There is no verification that the content type is actually HLS-compatible (`application/x-mpegURL`). If a page sets `mediaType: 'hls'` with a `blob:` src, the viewer warns about missing manifest but falls through to native playback. Consider adding `MediaSource.isTypeSupported()` checks for blob URLs that claim to be HLS.
### 2. HLS.js error recovery in grid panels
When `onError` fires for one panel, `recoverMediaError()` is called. If recovery fails, the panel shows a persistent error overlay. There is no retry after recovery — the panel stays broken. Consider adding a configurable retry count (e.g., 3 attempts with backoff) before marking the panel as permanently failed.
---
## Existing Test Suite
All 39 pre-existing tests continue to pass:
- 10 Schema validation tests
- 4 resolveSrc() tests
- 5 isHls() tests
- 3 resolveHlsManifest() tests
- 3 DOM setup tests
- 5 Data flow tests
- 9 Integration tests
- 5 Config/build integrity tests
---
## Files Changed
- `__tests__/hls_qa.test.mjs` — NEW: 44 HLS-specific QA tests (83 total tests in suite)
- `__tests__/run.mjs` — ADDED: import for hls_qa.test.mjs
## Build/Deploy Status
- 83/83 tests passing in 12s
- No linting errors in test files
- No changes to production source files
+18
View File
@@ -178,6 +178,15 @@ describe('Integration: viewer.js with HLS entries', () => {
return;
}
// HLS detection in console logs
const hlsLogs = consoleLogs.filter(
(l) => l.includes('HLS detected') || l.includes('HLS')
);
if (hlsLogs.length === 0) {
reject(new Error(`No HLS detection in logs. Logs: ${consoleLogs.join(' | ')}`));
return;
}
// Verify Hls.js was actually instantiated for the HLS entry
if (hlsCalls.instances.length < 1) {
reject(new Error('Expected at least 1 HLS instance, got 0'));
@@ -382,6 +391,15 @@ describe('Integration: viewer.js with HLS entries', () => {
return;
}
// HLS detection in logs
const hlsLogs = consoleLogs.filter(
(l) => l.includes('HLS detected') || l.includes('HLS')
);
if (hlsLogs.length === 0) {
reject(new Error(`No HLS logs. All: ${consoleLogs.join(' | ')}`));
return;
}
// Only one HLS instance (for the HLS entry)
if (hlsCalls.instances.length !== 1) {
reject(new Error(`Expected 1 HLS instance, got ${hlsCalls.instances.length}`));
+2
View File
@@ -15,6 +15,8 @@ chrome.runtime.onInstalled.addListener((details) => {
// Listen for messages from popup or content scripts.
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
console.log('[goon-plugin] Received:', message);
switch (message.type) {
case 'PING':
sendResponse({ pong: true, timestamp: Date.now() });
+4
View File
@@ -324,6 +324,7 @@ function wireHlsEvents(hls, video, panel) {
state.phase = 'loading';
state.lastEvent = 'MANIFEST_LOADED';
state.lastActivity = Date.now();
console.log(`[goon-viewer] Manifest loaded: ${data.levels.length} levels, type=${data.type}`);
renderPanelState(video, panel);
});
@@ -334,6 +335,7 @@ function wireHlsEvents(hls, video, panel) {
const lvl = data.levels[0];
state.levelLabel = `${lvl.width}x${lvl.height}`;
}
console.log(`[goon-viewer] Manifest parsed: ${data.levels.length} levels, audio=${data.audioTracks?.length || 0} tracks`);
renderPanelState(video, panel);
});
@@ -394,6 +396,7 @@ function wireHlsEvents(hls, video, panel) {
const lvl = levels[data.level];
state.levelLabel = `${lvl.width}x${lvl.height}`;
}
console.log(`[goon-viewer] Level switched to ${data.level} (${state.levelLabel})`);
renderPanelState(video, panel);
});
@@ -684,6 +687,7 @@ function createPanel(entry, tag) {
if (manifestUrl) {
video.dataset.hlsManifest = manifestUrl;
video.dataset.mediaType = 'hls';
console.log('[goon-viewer] HLS detected, manifest:', manifestUrl);
panel.appendChild(video);
if (tag) {
const label = document.createElement('div');