mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-16 22:38:20 +00:00
fix: harden frame observation and unblock supported Drive tabs
Audit follow-ups on the same-origin frame walk: - prune detached frames from the load-hook set so ad/SPA frame churn no longer grows it for the page's lifetime - hook load on nested frames, not just top-level ones - re-observe from scratch after a frame reload instead of leaving the replaced document's tree registered - reset the frame registry when the heartbeat error path disconnects Also: a broad parent domain in the blacklist no longer hides a host with its own supported player path (drive.google.com behind google.com), while an exact user entry for that host still filters it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,14 @@ All notable changes to the KoalaSync browser extension and relay server.
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
- **Extension: Same-origin frame video detection** — Finds and controls players that live inside a first-party iframe instead of the top document, so sites that wrap their player in their own frame sync without site-specific workarounds. Frame documents are watched for late-loading players and re-scanned after a frame reload; cross-origin frames stay out of reach by design.
|
||||
- **Extension: Editable Hide-Clutter list** — Adds a validated, deduplicated domain editor in Settings, prefilled with the shipped blacklist, with a defaults reset and per-device persistence in `chrome.storage.local`.
|
||||
- **Extension: Independent default audio boost** — Adds a configurable `0–20 dB` output gain in half-decibel steps. The boost works with or without the compressor and applies live to the selected video tab.
|
||||
|
||||
### Fixed
|
||||
- **Extension: Google Drive tab selection** — A broad parent domain in the Hide-Clutter list no longer hides a host that has its own supported player path, so Drive videos stay selectable while the filter is on. An exact entry for the host itself still filters it.
|
||||
- **Extension: Debug report frame visibility** — Video counts now include players inside same-origin frames and the report states whether the selected video sits in a frame.
|
||||
|
||||
## [v3.0.2] — 2026-07-31
|
||||
|
||||
This release adds three focused chat improvements: encrypted quick-reaction emojis,
|
||||
|
||||
+4
-4
@@ -49,13 +49,13 @@
|
||||
|
||||
*Ideas and feature requests under evaluation.*
|
||||
|
||||
### Cross-frame video detection and control
|
||||
### Cross-origin frame video detection and control
|
||||
|
||||
- **Priority:** P3
|
||||
- **Category:** Compatibility / Embedded Players
|
||||
- **Background:** KoalaSync currently injects on demand into the selected tab's top frame. This works for normal top-frame players, including current Emby/Jellyfin usage, but does not cover cases where the real `<video>` lives inside a cross-origin iframe or an `about:blank`/`srcdoc` player frame.
|
||||
- **Possible approach:** Add an opt-in frame bridge where child frames announce detected videos to the top frame, and the top frame routes remote play/pause/seek commands to the active child video.
|
||||
- **Status:** Future compatibility work, not needed for current Emby behavior.
|
||||
- **Background:** KoalaSync injects on demand into the selected tab's top frame. Since the same-origin frame walk shipped, the top-frame script also reaches players inside first-party iframes (`jkanime.net`-style `/jkplayer/` frames, `srcdoc` and `about:blank` frames that inherit the parent origin). What remains uncovered is the real `<video>` living inside a **cross-origin** iframe, where `contentDocument` is unreachable by design.
|
||||
- **Possible approach:** Add an opt-in frame bridge (`allFrames: true` injection) where child frames announce detected videos to the top frame, and the top frame routes remote play/pause/seek commands to the active child video. Needs a frame-election rule so ad frames cannot claim the session.
|
||||
- **Status:** Same-origin part completed; cross-origin frame bridge still open. Not needed for current Emby behavior.
|
||||
|
||||
### Local extension E2E smoke tests
|
||||
|
||||
|
||||
+21
-6
@@ -1697,23 +1697,37 @@
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// --- Helper: Wait until video is ready for playback (buffered & seeked) ---
|
||||
|
||||
function pollSeekReady(targetTime, timeoutMs = 8000) {
|
||||
return new Promise((resolve) => {
|
||||
const interval = 150;
|
||||
const interval = 150;
|
||||
let elapsed = 0;
|
||||
const timer = setInterval(() => {
|
||||
if (destroyed) {
|
||||
let elapsed = 0;
|
||||
const timer = setInterval(() => {
|
||||
if (destroyed) {
|
||||
clearInterval(timer);
|
||||
seekPollTimers.delete(timer);
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
const video = findVideo(); // Re-query DOM on every iteration
|
||||
if (!video) {
|
||||
clearInterval(timer);
|
||||
seekPollTimers.delete(timer);
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const video = findVideo(); // Re-query DOM on every iteration
|
||||
elapsed += interval;
|
||||
|
||||
const current = getSyncCurrentTime(video);
|
||||
const timeDiff = current !== null ? Math.abs(current - targetTime) : Infinity;
|
||||
const ready = video.readyState >= 3 && timeDiff < 2.0;
|
||||
if (ready) {
|
||||
clearInterval(timer);
|
||||
seekPollTimers.delete(timer);
|
||||
resolve(true);
|
||||
@@ -1764,6 +1778,7 @@
|
||||
|
||||
|
||||
if (message.action === 'RESET_AUDIO_PROCESSING') {
|
||||
|
||||
_audioProcessingAllowed = false;
|
||||
|
||||
bypassCurrentAudioProcessing();
|
||||
|
||||
@@ -39,6 +39,13 @@ assert.match(popupSource, /chrome\.storage\.local\.set\(\{ \[CUSTOM_BLACKLIST_ST
|
||||
assert.doesNotMatch(popupSource, /chrome\.storage\.sync\.set\(\{ \[CUSTOM_BLACKLIST_STORAGE_KEY\]/, 'custom list is never synced');
|
||||
assert.match(popupSource, /isUrlBlacklisted\(tab\.url, blacklistDomains\)/, 'tab filtering uses the effective custom list');
|
||||
|
||||
// A broad parent domain must not hide a host with a dedicated player path,
|
||||
// but an exact user entry for that host still filters it.
|
||||
assert.equal(isUrlBlacklisted('https://drive.google.com/file/d/x/view', BLACKLIST_DOMAINS), false);
|
||||
assert.equal(isUrlBlacklisted('https://drive.google.com/file/d/x/view', ['drive.google.com']), true);
|
||||
assert.equal(isUrlBlacklisted('https://docs.google.com/document/d/x', BLACKLIST_DOMAINS), true);
|
||||
assert.equal(isUrlBlacklisted('https://mail.google.com/mail/u/0', BLACKLIST_DOMAINS), true);
|
||||
|
||||
const popupHtml = fs.readFileSync(path.join(repoRoot, 'extension/popup.html'), 'utf8');
|
||||
assert.match(popupHtml, /id="blacklistDomains"/, 'settings UI contains the editable domain list');
|
||||
assert.match(popupHtml, /id="blacklistReset"/, 'settings UI contains a defaults reset');
|
||||
|
||||
+14
-1
@@ -179,6 +179,15 @@ export const BLACKLIST_DOMAINS = [
|
||||
'skribbl.io'
|
||||
];
|
||||
|
||||
/**
|
||||
* Hosts KoalaSync supports through a site-specific player path. A broad parent
|
||||
* domain in the list (e.g. 'google.com') must not hide them from tab selection.
|
||||
* An exact entry for the host itself still filters it, so users stay in control.
|
||||
*/
|
||||
export const BLACKLIST_SUFFIX_EXCEPTIONS = [
|
||||
'drive.google.com'
|
||||
];
|
||||
|
||||
export const CUSTOM_BLACKLIST_STORAGE_KEY = 'customBlacklistDomains';
|
||||
export const MAX_BLACKLIST_DOMAINS = 500;
|
||||
|
||||
@@ -246,5 +255,9 @@ export function isUrlBlacklisted(rawUrl, domains = BLACKLIST_DOMAINS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return domains.some(domain => hostname === domain || hostname.endsWith(`.${domain}`));
|
||||
return domains.some(domain => {
|
||||
if (hostname === domain) return true;
|
||||
if (!hostname.endsWith(`.${domain}`)) return false;
|
||||
return !BLACKLIST_SUFFIX_EXCEPTIONS.includes(hostname);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user