mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-23 01:16:36 +00:00
fix(extension): harden player discovery for Crunchyroll
This commit is contained in:
@@ -2622,10 +2622,46 @@ function executeScriptWithTimeout(options, timeoutMs = SCRIPT_INJECTION_TIMEOUT_
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs as a tiny all-frame beacon before the heavier monitor injection.
|
||||
* Chromium can reject an allFrames result wholesale when one unrelated ad
|
||||
* frame disappears mid-sweep, even though stable frames already executed the
|
||||
* function. Those stable frames announce their ids through the sender metadata,
|
||||
* letting the next step address them individually without webNavigation.
|
||||
*/
|
||||
async function announcePotentialMediaFrame() {
|
||||
let relevant = false;
|
||||
try {
|
||||
const identity = `${window.location.href} ${window.name || ''}`;
|
||||
relevant = !!document.querySelector('video, iframe, frame')
|
||||
|| /player|video|stream|watch|embed|media|xfp/i.test(identity);
|
||||
} catch { /* inaccessible or already-detached document */ }
|
||||
if (!relevant) return false;
|
||||
try {
|
||||
await chrome.runtime.sendMessage({ type: 'MEDIA_FRAME_DISCOVERED' });
|
||||
} catch { /* extension context or document disappeared */ }
|
||||
return true;
|
||||
}
|
||||
|
||||
async function injectMediaFrameMonitors(tabId, contentTarget) {
|
||||
// The sweep is best effort; the known frames are addressed individually so a
|
||||
// rejected sweep cannot leave the deep player frame without a monitor — and
|
||||
// therefore without any way to report itself later.
|
||||
try {
|
||||
const discoveries = await executeScriptWithTimeout({
|
||||
target: { tabId, allFrames: true },
|
||||
func: announcePotentialMediaFrame
|
||||
}, 750);
|
||||
for (const entry of discoveries || []) {
|
||||
if (entry?.result === true) rememberFrameId(tabId, entry.frameId);
|
||||
}
|
||||
} catch {
|
||||
// Stable frames still announce themselves if a disappearing ad frame
|
||||
// makes Chromium reject the aggregate allFrames result.
|
||||
}
|
||||
// Give those sender messages one task boundary to update the registry before
|
||||
// taking the snapshot used for individual monitor injections below.
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
const targets = [
|
||||
...listMediaFrameScriptTargets(tabId),
|
||||
...listKnownFrameIds(tabId)
|
||||
@@ -3756,6 +3792,10 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
||||
|
||||
const senderTabId = normalizeTabId(sender?.tab?.id);
|
||||
if (senderTabId !== null) rememberFrameId(senderTabId, sender?.frameId);
|
||||
if (message.type === 'MEDIA_FRAME_DISCOVERED') {
|
||||
sendResponse({ status: 'ok' });
|
||||
return;
|
||||
}
|
||||
const mediaLifecycleMessage = message.type === 'MEDIA_FRAME_CANDIDATE_CHANGED'
|
||||
|| message.type === 'MEDIA_FRAME_VISIBILITY'
|
||||
|| message.type === 'MEDIA_TARGET_REFRESH';
|
||||
|
||||
@@ -659,7 +659,14 @@
|
||||
|| Number(style.opacity) === 0)) {
|
||||
return false;
|
||||
}
|
||||
if (typeof current.checkVisibility === 'function'
|
||||
// `display: contents` deliberately gives the wrapper no box, so
|
||||
// Chromium reports the wrapper itself as not visible even while
|
||||
// its children are fully rendered. Crunchyroll's player layout
|
||||
// uses exactly that shape. The explicit CSS checks above still
|
||||
// reject genuinely hidden ancestors; skip only this boxless
|
||||
// wrapper case when walking up from the video.
|
||||
if (style?.display !== 'contents'
|
||||
&& typeof current.checkVisibility === 'function'
|
||||
&& !current.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,9 @@ describe('target tab lifecycle', () => {
|
||||
|
||||
it('uses all-frame probing for cross-origin targets without navigation permissions', () => {
|
||||
expect(backgroundSource).toContain("files: ['media-frame-monitor.js']");
|
||||
expect(backgroundSource).toContain('async function announcePotentialMediaFrame()');
|
||||
expect(backgroundSource).toContain("{ type: 'MEDIA_FRAME_DISCOVERED' }");
|
||||
expect(backgroundSource).toContain('func: announcePotentialMediaFrame');
|
||||
// Monitors must reach the frames we know about, not only whatever the
|
||||
// all-frames sweep happens to accept — both on the way in and out.
|
||||
expect(backgroundSource.match(/\.\.\.listMediaFrameScriptTargets\(tabId\),/g)?.length).toBe(2);
|
||||
|
||||
Reference in New Issue
Block a user