Files
KoalaSync/extension/media-frame-target.js
T

263 lines
10 KiB
JavaScript

const GOOGLE_DRIVE_HOST = 'drive.google.com';
const GOOGLE_DRIVE_PLAYER_HOST = 'youtube.googleapis.com';
export const GOOGLE_DRIVE_PLAYER_ORIGIN_PATTERN = 'https://youtube.googleapis.com/*';
export const GOOGLE_DRIVE_PLAYER_ACCESS_REQUIRED = 'google_drive_player_access_required';
export const GOOGLE_DRIVE_PLAYER_AMBIGUOUS = 'google_drive_player_ambiguous';
export function isGoogleDriveUrl(value) {
try {
return new URL(value).hostname.toLowerCase() === GOOGLE_DRIVE_HOST;
} catch {
return false;
}
}
export function inspectMediaFrame() {
const videos = Array.from(document.querySelectorAll('video'));
const drivePlayerIframes = Array.from(document.querySelectorAll('iframe')).filter((frame) => {
try {
const url = new URL(frame.src);
if (url.hostname.toLowerCase() !== 'youtube.googleapis.com') return false;
const origin = url.searchParams.get('origin') || url.searchParams.get('post_message_origin');
return origin === 'https://drive.google.com';
} catch {
return false;
}
});
let bestVideoScore = 0;
for (const video of videos) {
const width = video.videoWidth || video.offsetWidth || 0;
const height = video.videoHeight || video.offsetHeight || 0;
const duration = Number.isFinite(video.duration) ? video.duration : 0;
bestVideoScore = Math.max(bestVideoScore, (width * height) + (duration * 100));
}
return {
href: window.location.href,
videoCount: videos.length,
videoScore: bestVideoScore,
frameArea: Math.max(0, window.innerWidth) * Math.max(0, window.innerHeight),
drivePlayerIframeCount: drivePlayerIframes.length,
parentFrameVisible: window.__koalaParentFrameVisibility?.visible ?? null,
parentFrameArea: window.__koalaParentFrameVisibility?.area ?? null
};
}
export function installParentFrameVisibilityProbe(token) {
const handler = (event) => {
if (event.source !== window.parent
|| event.data?.type !== 'KOALASYNC_FRAME_VISIBILITY'
|| event.data?.token !== token) {
return;
}
window.__koalaParentFrameVisibility = {
visible: event.data.visible === true,
area: Number.isFinite(event.data.area) ? event.data.area : 0
};
window.removeEventListener('message', handler);
};
window.addEventListener('message', handler);
}
export async function dispatchParentFrameVisibilityProbe(token) {
let count = 0;
for (const frame of document.querySelectorAll('iframe')) {
try {
const url = new URL(frame.src);
const origin = url.searchParams.get('origin') || url.searchParams.get('post_message_origin');
if (url.hostname.toLowerCase() !== 'youtube.googleapis.com'
|| origin !== 'https://drive.google.com') {
continue;
}
const rect = frame.getBoundingClientRect();
const style = window.getComputedStyle(frame);
const area = Math.max(0, rect.width) * Math.max(0, rect.height);
const intersectsViewport = rect.bottom > 0
&& rect.right > 0
&& rect.top < window.innerHeight
&& rect.left < window.innerWidth;
const browserReportsVisible = typeof frame.checkVisibility === 'function'
? frame.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })
: true;
const visible = area > 0
&& intersectsViewport
&& browserReportsVisible
&& style.display !== 'none'
&& style.visibility !== 'hidden'
&& Number(style.opacity) !== 0;
frame.contentWindow?.postMessage({
type: 'KOALASYNC_FRAME_VISIBILITY',
token,
visible,
area
}, 'https://youtube.googleapis.com');
count++;
} catch {
// Ignore unrelated or not-yet-initialized frames.
}
}
await new Promise(resolve => setTimeout(resolve, 100));
return count;
}
function isGoogleDrivePlayerFrameUrl(value) {
try {
const url = new URL(value);
if (url.hostname.toLowerCase() !== GOOGLE_DRIVE_PLAYER_HOST || url.pathname !== '/embed/') {
return false;
}
const origin = url.searchParams.get('origin') || url.searchParams.get('post_message_origin');
return origin === 'https://drive.google.com';
} catch {
return false;
}
}
function createGoogleDrivePlayerAccessError() {
const error = new Error('Google Drive player frame access is required');
error.code = GOOGLE_DRIVE_PLAYER_ACCESS_REQUIRED;
error.originPattern = GOOGLE_DRIVE_PLAYER_ORIGIN_PATTERN;
return error;
}
function createGoogleDrivePlayerAmbiguousError() {
const error = new Error('Google Drive player frame could not be identified safely');
error.code = GOOGLE_DRIVE_PLAYER_AMBIGUOUS;
return error;
}
export function selectGoogleDrivePlayerFrame(injectionResults) {
let candidates = (Array.isArray(injectionResults) ? injectionResults : [])
.filter(entry => Number.isInteger(entry?.frameId) && entry?.result)
.filter(entry => isGoogleDrivePlayerFrameUrl(entry.result.href));
if (candidates.length > 1) {
const confirmedVisible = candidates.filter(entry => entry.result.parentFrameVisible === true);
if (confirmedVisible.length > 0) {
candidates = confirmedVisible;
} else {
const notConfirmedHidden = candidates.filter(entry =>
entry.result.parentFrameVisible !== false
);
if (notConfirmedHidden.length !== 1) return null;
candidates = notConfirmedHidden;
}
}
candidates.sort((left, right) => {
const visibilityRank = result => result.parentFrameVisible === true
? 2
: result.parentFrameVisible === false
? 0
: 1;
const leftVisible = visibilityRank(left.result);
const rightVisible = visibilityRank(right.result);
if (leftVisible !== rightVisible) return rightVisible - leftVisible;
const leftParentArea = Number.isFinite(left.result.parentFrameArea)
? left.result.parentFrameArea
: left.result.frameArea;
const rightParentArea = Number.isFinite(right.result.parentFrameArea)
? right.result.parentFrameArea
: right.result.frameArea;
if (leftParentArea !== rightParentArea) return rightParentArea - leftParentArea;
const leftHasVideo = left.result.videoCount > 0 ? 1 : 0;
const rightHasVideo = right.result.videoCount > 0 ? 1 : 0;
if (leftHasVideo !== rightHasVideo) return rightHasVideo - leftHasVideo;
if (left.result.videoScore !== right.result.videoScore) {
return right.result.videoScore - left.result.videoScore;
}
return right.result.frameArea - left.result.frameArea;
});
return candidates[0] || null;
}
export async function resolveMediaScriptTarget(chromeApi, tabId, tabUrl, {
attempts = 8,
retryDelayMs = 200
} = {}) {
const topFrameTarget = { tabId };
if (!isGoogleDriveUrl(tabUrl)) return topFrameTarget;
let fallbackFrame = null;
let drivePlayerIframeDetected = false;
let ambiguousPlayerFramesDetected = false;
for (let attempt = 0; attempt < attempts; attempt++) {
let results;
try {
results = await chromeApi.scripting.executeScript({
target: { tabId, allFrames: true },
func: inspectMediaFrame
});
} catch {
let topFrameResult = null;
try {
const topResults = await chromeApi.scripting.executeScript({
target: { tabId },
func: inspectMediaFrame
});
topFrameResult = Array.isArray(topResults) ? topResults[0]?.result : null;
} catch {
// The generic injection path below remains the final source of truth.
}
if (topFrameResult?.drivePlayerIframeCount > 0) {
throw createGoogleDrivePlayerAccessError();
}
return topFrameTarget;
}
const topFrame = results.find(entry => entry?.frameId === 0);
if (topFrame?.result?.drivePlayerIframeCount > 0) {
drivePlayerIframeDetected = true;
}
const playerFrameCount = results.filter(entry =>
isGoogleDrivePlayerFrameUrl(entry?.result?.href)
).length;
if (playerFrameCount > 1) {
const token = `${tabId}:${attempt}:${Date.now()}:${Math.random()}`;
try {
await chromeApi.scripting.executeScript({
target: { tabId, allFrames: true },
func: installParentFrameVisibilityProbe,
args: [token]
});
await chromeApi.scripting.executeScript({
target: { tabId },
func: dispatchParentFrameVisibilityProbe,
args: [token]
});
results = await chromeApi.scripting.executeScript({
target: { tabId, allFrames: true },
func: inspectMediaFrame
});
} catch {
// Ambiguous frames are retried below; never guess a stale player.
}
}
const selectedFrame = selectGoogleDrivePlayerFrame(results);
if (!selectedFrame && playerFrameCount > 1) {
ambiguousPlayerFramesDetected = true;
}
if (selectedFrame?.result?.videoCount > 0) {
return { tabId, frameIds: [selectedFrame.frameId] };
}
if (selectedFrame) fallbackFrame = selectedFrame;
if (attempt < attempts - 1) {
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
}
}
if (!fallbackFrame && ambiguousPlayerFramesDetected) {
throw createGoogleDrivePlayerAmbiguousError();
}
if (!fallbackFrame && drivePlayerIframeDetected) {
throw createGoogleDrivePlayerAccessError();
}
return fallbackFrame
? { tabId, frameIds: [fallbackFrame.frameId] }
: topFrameTarget;
}