mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-29 12:07:09 +00:00
fix(extension): bound frame injection and retry failures
This commit is contained in:
+70
-11
@@ -80,6 +80,8 @@ let currentTargetHasVideo = false;
|
|||||||
// popup look as if the user never selected a tab.
|
// popup look as if the user never selected a tab.
|
||||||
let requestedTargetTabId = null;
|
let requestedTargetTabId = null;
|
||||||
let requestedTargetTitle = null;
|
let requestedTargetTitle = null;
|
||||||
|
let requestedTargetRetryBlockedTabId = null;
|
||||||
|
let requestedTargetRetryBlockedMessage = null;
|
||||||
let pendingRequestedActivationCount = 0;
|
let pendingRequestedActivationCount = 0;
|
||||||
let targetActivationGeneration = 0;
|
let targetActivationGeneration = 0;
|
||||||
let activeTargetActivation = null;
|
let activeTargetActivation = null;
|
||||||
@@ -224,6 +226,7 @@ function ensureState() {
|
|||||||
'forceSyncDeadline', 'reconnectFailed', 'reconnectStartTime', 'reconnectAttempts', 'currentTabId', 'currentTabTitle',
|
'forceSyncDeadline', 'reconnectFailed', 'reconnectStartTime', 'reconnectAttempts', 'currentTabId', 'currentTabTitle',
|
||||||
'currentTargetFrameId', 'currentTargetDocumentId', 'currentTargetHasVideo',
|
'currentTargetFrameId', 'currentTargetDocumentId', 'currentTargetHasVideo',
|
||||||
'requestedTargetTabId', 'requestedTargetTitle',
|
'requestedTargetTabId', 'requestedTargetTitle',
|
||||||
|
'requestedTargetRetryBlockedTabId', 'requestedTargetRetryBlockedMessage',
|
||||||
'episodeLobby', 'localSeq', 'lastSeqBySender', 'expectedAcksCount', 'roomIdleSince', 'lastContentHeartbeatAt',
|
'episodeLobby', 'localSeq', 'lastSeqBySender', 'expectedAcksCount', 'roomIdleSince', 'lastContentHeartbeatAt',
|
||||||
'hcmDesynced', 'chatActivityTimeline'
|
'hcmDesynced', 'chatActivityTimeline'
|
||||||
], (data) => {
|
], (data) => {
|
||||||
@@ -246,6 +249,11 @@ function ensureState() {
|
|||||||
&& typeof data.requestedTargetTitle === 'string'
|
&& typeof data.requestedTargetTitle === 'string'
|
||||||
? data.requestedTargetTitle
|
? data.requestedTargetTitle
|
||||||
: null;
|
: null;
|
||||||
|
requestedTargetRetryBlockedTabId = normalizeTabId(data.requestedTargetRetryBlockedTabId);
|
||||||
|
requestedTargetRetryBlockedMessage = requestedTargetRetryBlockedTabId !== null
|
||||||
|
&& typeof data.requestedTargetRetryBlockedMessage === 'string'
|
||||||
|
? data.requestedTargetRetryBlockedMessage
|
||||||
|
: null;
|
||||||
if (data.currentTabTitle !== undefined) {
|
if (data.currentTabTitle !== undefined) {
|
||||||
currentTabTitle = currentTabId !== null && typeof data.currentTabTitle === 'string'
|
currentTabTitle = currentTabId !== null && typeof data.currentTabTitle === 'string'
|
||||||
? data.currentTabTitle
|
? data.currentTabTitle
|
||||||
@@ -2340,6 +2348,31 @@ function createTargetActivationSupersededError() {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SCRIPT_INJECTION_TIMEOUT_MS = 5000;
|
||||||
|
|
||||||
|
function executeScriptWithTimeout(options, timeoutMs = SCRIPT_INJECTION_TIMEOUT_MS) {
|
||||||
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
||||||
|
return chrome.scripting.executeScript(options);
|
||||||
|
}
|
||||||
|
let timeoutId = null;
|
||||||
|
const label = Array.isArray(options?.files) && options.files.length > 0
|
||||||
|
? options.files.join(', ')
|
||||||
|
: 'function injection';
|
||||||
|
const timeout = new Promise((_, reject) => {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
const error = new Error(`Script injection timed out after ${timeoutMs}ms (${label})`);
|
||||||
|
error.code = 'script_injection_timeout';
|
||||||
|
reject(error);
|
||||||
|
}, timeoutMs);
|
||||||
|
});
|
||||||
|
return Promise.race([
|
||||||
|
chrome.scripting.executeScript(options),
|
||||||
|
timeout
|
||||||
|
]).finally(() => {
|
||||||
|
if (timeoutId !== null) clearTimeout(timeoutId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function injectMediaFrameMonitors(tabId, contentTarget) {
|
async function injectMediaFrameMonitors(tabId, contentTarget) {
|
||||||
// The all-frames target is only a best-effort sweep: one inaccessible
|
// The all-frames target is only a best-effort sweep: one inaccessible
|
||||||
// frame can make Chromium reject the entire sweep. Always include the
|
// frame can make Chromium reject the entire sweep. Always include the
|
||||||
@@ -2353,12 +2386,15 @@ async function injectMediaFrameMonitors(tabId, contentTarget) {
|
|||||||
let injectedCount = 0;
|
let injectedCount = 0;
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
try {
|
try {
|
||||||
await chrome.scripting.executeScript({
|
await executeScriptWithTimeout({
|
||||||
target,
|
target,
|
||||||
files: ['media-frame-monitor.js']
|
files: ['media-frame-monitor.js']
|
||||||
});
|
}, 2000);
|
||||||
injectedCount++;
|
injectedCount++;
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
if (error?.code === 'script_injection_timeout') {
|
||||||
|
addLog(`Media-frame monitor injection timed out for ${JSON.stringify(target)}`, 'warn');
|
||||||
|
}
|
||||||
// One denied widget frame must not block the selected player.
|
// One denied widget frame must not block the selected player.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2424,12 +2460,12 @@ async function injectContentScript(tabId, {
|
|||||||
}
|
}
|
||||||
if (needsPageApiSeek) {
|
if (needsPageApiSeek) {
|
||||||
try {
|
try {
|
||||||
await chrome.scripting.executeScript({
|
await executeScriptWithTimeout({
|
||||||
target: scriptTarget,
|
target: scriptTarget,
|
||||||
world: 'MAIN',
|
world: 'MAIN',
|
||||||
files: ['page-api-seek-overrides.js']
|
files: ['page-api-seek-overrides.js']
|
||||||
});
|
});
|
||||||
await chrome.scripting.executeScript({
|
await executeScriptWithTimeout({
|
||||||
target: scriptTarget,
|
target: scriptTarget,
|
||||||
world: 'MAIN',
|
world: 'MAIN',
|
||||||
func: installPageApiSeekBridge
|
func: installPageApiSeekBridge
|
||||||
@@ -2440,16 +2476,16 @@ async function injectContentScript(tabId, {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await chrome.scripting.executeScript({
|
await executeScriptWithTimeout({
|
||||||
target: scriptTarget,
|
target: scriptTarget,
|
||||||
files: ['page-api-seek-overrides.js']
|
files: ['page-api-seek-overrides.js']
|
||||||
});
|
});
|
||||||
await chrome.scripting.executeScript({
|
await executeScriptWithTimeout({
|
||||||
target: scriptTarget,
|
target: scriptTarget,
|
||||||
func: setPageApiSeekEnabled,
|
func: setPageApiSeekEnabled,
|
||||||
args: [pageApiSeekReady]
|
args: [pageApiSeekReady]
|
||||||
});
|
});
|
||||||
const injectionResults = await chrome.scripting.executeScript({
|
const injectionResults = await executeScriptWithTimeout({
|
||||||
target: scriptTarget,
|
target: scriptTarget,
|
||||||
files: ['chat-format.js', 'chat-overlay.js', 'content.js']
|
files: ['chat-format.js', 'chat-overlay.js', 'content.js']
|
||||||
});
|
});
|
||||||
@@ -2652,9 +2688,13 @@ async function rememberRequestedTarget(tabId, tabTitle) {
|
|||||||
if (normalizedTabId === null) return false;
|
if (normalizedTabId === null) return false;
|
||||||
requestedTargetTabId = normalizedTabId;
|
requestedTargetTabId = normalizedTabId;
|
||||||
requestedTargetTitle = typeof tabTitle === 'string' ? tabTitle : null;
|
requestedTargetTitle = typeof tabTitle === 'string' ? tabTitle : null;
|
||||||
|
requestedTargetRetryBlockedTabId = null;
|
||||||
|
requestedTargetRetryBlockedMessage = null;
|
||||||
await chrome.storage.session.set({
|
await chrome.storage.session.set({
|
||||||
requestedTargetTabId,
|
requestedTargetTabId,
|
||||||
requestedTargetTitle
|
requestedTargetTitle,
|
||||||
|
requestedTargetRetryBlockedTabId: null,
|
||||||
|
requestedTargetRetryBlockedMessage: null
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2666,9 +2706,13 @@ async function clearRequestedTarget(expectedTabId = null) {
|
|||||||
}
|
}
|
||||||
requestedTargetTabId = null;
|
requestedTargetTabId = null;
|
||||||
requestedTargetTitle = null;
|
requestedTargetTitle = null;
|
||||||
|
requestedTargetRetryBlockedTabId = null;
|
||||||
|
requestedTargetRetryBlockedMessage = null;
|
||||||
await chrome.storage.session.set({
|
await chrome.storage.session.set({
|
||||||
requestedTargetTabId: null,
|
requestedTargetTabId: null,
|
||||||
requestedTargetTitle: null
|
requestedTargetTitle: null,
|
||||||
|
requestedTargetRetryBlockedTabId: null,
|
||||||
|
requestedTargetRetryBlockedMessage: null
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2678,7 +2722,8 @@ async function retryRequestedTarget() {
|
|||||||
if (selectedTabId === null
|
if (selectedTabId === null
|
||||||
|| normalizeTabId(currentTabId) === selectedTabId
|
|| normalizeTabId(currentTabId) === selectedTabId
|
||||||
|| pendingRequestedActivationCount > 0
|
|| pendingRequestedActivationCount > 0
|
||||||
|| activeTargetActivation) {
|
|| activeTargetActivation
|
||||||
|
|| normalizeTabId(requestedTargetRetryBlockedTabId) === selectedTabId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2702,6 +2747,14 @@ async function retryRequestedTarget() {
|
|||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error?.code !== HOST_ACCESS_REQUIRED_STATUS) {
|
||||||
|
requestedTargetRetryBlockedTabId = selectedTabId;
|
||||||
|
requestedTargetRetryBlockedMessage = error?.message || 'Script injection failed';
|
||||||
|
await chrome.storage.session.set({
|
||||||
|
requestedTargetRetryBlockedTabId,
|
||||||
|
requestedTargetRetryBlockedMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
addLog(`Requested target retry failed: ${error.message}`, 'warn');
|
addLog(`Requested target retry failed: ${error.message}`, 'warn');
|
||||||
return injectionFailureResponse(error);
|
return injectionFailureResponse(error);
|
||||||
}
|
}
|
||||||
@@ -3391,7 +3444,12 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
|||||||
? 'ready'
|
? 'ready'
|
||||||
: pendingTarget?.tabId === targetTabId
|
: pendingTarget?.tabId === targetTabId
|
||||||
? 'access_required'
|
? 'access_required'
|
||||||
|
: normalizeTabId(requestedTargetRetryBlockedTabId) === targetTabId
|
||||||
|
? 'error'
|
||||||
: 'activating';
|
: 'activating';
|
||||||
|
const targetActivationError = normalizeTabId(requestedTargetRetryBlockedTabId) === targetTabId
|
||||||
|
? requestedTargetRetryBlockedMessage
|
||||||
|
: null;
|
||||||
sendResponse({
|
sendResponse({
|
||||||
status,
|
status,
|
||||||
peerId,
|
peerId,
|
||||||
@@ -3403,6 +3461,7 @@ async function handleAsyncMessage(message, sender, sendResponse) {
|
|||||||
targetHasVideo: currentTargetHasVideo,
|
targetHasVideo: currentTargetHasVideo,
|
||||||
targetReady,
|
targetReady,
|
||||||
targetActivationState,
|
targetActivationState,
|
||||||
|
targetActivationError,
|
||||||
pendingTargetTabId: pendingTarget?.tabId ?? null,
|
pendingTargetTabId: pendingTarget?.tabId ?? null,
|
||||||
pendingTargetHost: pendingTarget?.host ?? null,
|
pendingTargetHost: pendingTarget?.host ?? null,
|
||||||
pendingTargetOriginPattern: pendingTarget?.originPattern ?? null,
|
pendingTargetOriginPattern: pendingTarget?.originPattern ?? null,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export const MEDIA_FRAME_ACCESS_REQUIRED = 'media_frame_access_required';
|
|||||||
const MIN_PLAYER_FRAME_AREA = 320 * 180;
|
const MIN_PLAYER_FRAME_AREA = 320 * 180;
|
||||||
const MIN_PLAYER_ASPECT_RATIO = 1.15;
|
const MIN_PLAYER_ASPECT_RATIO = 1.15;
|
||||||
const MAX_PLAYER_ASPECT_RATIO = 2.6;
|
const MAX_PLAYER_ASPECT_RATIO = 2.6;
|
||||||
|
const DEFAULT_PROBE_TIMEOUT_MS = 1500;
|
||||||
|
|
||||||
function normalizeFrameId(value) {
|
function normalizeFrameId(value) {
|
||||||
return Number.isInteger(value) && value >= 0 ? value : 0;
|
return Number.isInteger(value) && value >= 0 ? value : 0;
|
||||||
@@ -463,10 +464,29 @@ function mergeFrameResults(...groups) {
|
|||||||
return Array.from(merged.values());
|
return Array.from(merged.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeInAccessibleFrames(chromeApi, targets, func, args) {
|
function executeWithTimeout(task, timeoutMs, label) {
|
||||||
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return task();
|
||||||
|
let timeoutId = null;
|
||||||
|
const timeout = new Promise((_, reject) => {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
const error = new Error(`${label} timed out after ${timeoutMs}ms`);
|
||||||
|
error.code = 'media_frame_probe_timeout';
|
||||||
|
reject(error);
|
||||||
|
}, timeoutMs);
|
||||||
|
});
|
||||||
|
return Promise.race([task(), timeout]).finally(() => {
|
||||||
|
if (timeoutId !== null) clearTimeout(timeoutId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeInAccessibleFrames(chromeApi, targets, func, args, timeoutMs = DEFAULT_PROBE_TIMEOUT_MS) {
|
||||||
const settled = await Promise.all(targets.map(async target => {
|
const settled = await Promise.all(targets.map(async target => {
|
||||||
try {
|
try {
|
||||||
const result = await chromeApi.scripting.executeScript({ target, func, args });
|
const result = await executeWithTimeout(
|
||||||
|
() => chromeApi.scripting.executeScript({ target, func, args }),
|
||||||
|
timeoutMs,
|
||||||
|
`Frame probe for ${JSON.stringify(target)}`
|
||||||
|
);
|
||||||
return Array.isArray(result) ? result : [];
|
return Array.isArray(result) ? result : [];
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
@@ -478,7 +498,8 @@ async function executeInAccessibleFrames(chromeApi, targets, func, args) {
|
|||||||
export async function resolveMediaContentTarget(chromeApi, tabId, {
|
export async function resolveMediaContentTarget(chromeApi, tabId, {
|
||||||
attempts = 8,
|
attempts = 8,
|
||||||
retryDelayMs = 200,
|
retryDelayMs = 200,
|
||||||
probeDelayMs = 60
|
probeDelayMs = 60,
|
||||||
|
probeTimeoutMs = DEFAULT_PROBE_TIMEOUT_MS
|
||||||
} = {}) {
|
} = {}) {
|
||||||
let fallback = null;
|
let fallback = null;
|
||||||
let missingAccess = null;
|
let missingAccess = null;
|
||||||
@@ -489,13 +510,15 @@ export async function resolveMediaContentTarget(chromeApi, tabId, {
|
|||||||
chromeApi,
|
chromeApi,
|
||||||
[{ tabId, frameIds: [0] }],
|
[{ tabId, frameIds: [0] }],
|
||||||
inspectMediaFrame,
|
inspectMediaFrame,
|
||||||
[null]
|
[null],
|
||||||
|
probeTimeoutMs
|
||||||
);
|
);
|
||||||
const allFrameResults = await executeInAccessibleFrames(
|
const allFrameResults = await executeInAccessibleFrames(
|
||||||
chromeApi,
|
chromeApi,
|
||||||
listMediaFrameScriptTargets(tabId),
|
listMediaFrameScriptTargets(tabId),
|
||||||
inspectMediaFrame,
|
inspectMediaFrame,
|
||||||
[null]
|
[null],
|
||||||
|
probeTimeoutMs
|
||||||
);
|
);
|
||||||
let results = mergeFrameResults(topResults, allFrameResults);
|
let results = mergeFrameResults(topResults, allFrameResults);
|
||||||
const embeddedFrameCount = topResults.reduce(
|
const embeddedFrameCount = topResults.reduce(
|
||||||
@@ -507,7 +530,8 @@ export async function resolveMediaContentTarget(chromeApi, tabId, {
|
|||||||
chromeApi,
|
chromeApi,
|
||||||
listFrameProbeTargets(tabId, embeddedFrameCount),
|
listFrameProbeTargets(tabId, embeddedFrameCount),
|
||||||
inspectMediaFrame,
|
inspectMediaFrame,
|
||||||
[null]
|
[null],
|
||||||
|
probeTimeoutMs
|
||||||
);
|
);
|
||||||
results = mergeFrameResults(results, individuallyProbed);
|
results = mergeFrameResults(results, individuallyProbed);
|
||||||
}
|
}
|
||||||
@@ -521,7 +545,8 @@ export async function resolveMediaContentTarget(chromeApi, tabId, {
|
|||||||
chromeApi,
|
chromeApi,
|
||||||
frameTargets,
|
frameTargets,
|
||||||
installParentFrameVisibilityProbe,
|
installParentFrameVisibilityProbe,
|
||||||
[token]
|
[token],
|
||||||
|
probeTimeoutMs
|
||||||
);
|
);
|
||||||
// Four passes match the maximum same-origin recursion depth.
|
// Four passes match the maximum same-origin recursion depth.
|
||||||
for (let pass = 0; pass < 4; pass++) {
|
for (let pass = 0; pass < 4; pass++) {
|
||||||
@@ -529,7 +554,8 @@ export async function resolveMediaContentTarget(chromeApi, tabId, {
|
|||||||
chromeApi,
|
chromeApi,
|
||||||
frameTargets,
|
frameTargets,
|
||||||
dispatchParentFrameVisibilityProbe,
|
dispatchParentFrameVisibilityProbe,
|
||||||
[token]
|
[token],
|
||||||
|
probeTimeoutMs
|
||||||
);
|
);
|
||||||
await new Promise(resolve => setTimeout(resolve, probeDelayMs));
|
await new Promise(resolve => setTimeout(resolve, probeDelayMs));
|
||||||
}
|
}
|
||||||
@@ -537,7 +563,8 @@ export async function resolveMediaContentTarget(chromeApi, tabId, {
|
|||||||
chromeApi,
|
chromeApi,
|
||||||
frameTargets,
|
frameTargets,
|
||||||
inspectMediaFrame,
|
inspectMediaFrame,
|
||||||
[token]
|
[token],
|
||||||
|
probeTimeoutMs
|
||||||
);
|
);
|
||||||
if (inspected.length > 0) results = mergeFrameResults(results, inspected);
|
if (inspected.length > 0) results = mergeFrameResults(results, inspected);
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
+11
-1
@@ -2808,13 +2808,23 @@ function refreshDebugInfo() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (res?.targetActivationState === 'error') {
|
||||||
|
if (elements.videoDebug) {
|
||||||
|
elements.videoDebug.textContent = res.targetActivationError
|
||||||
|
? `Injection fehlgeschlagen: ${res.targetActivationError}`
|
||||||
|
: 'Video-Injection fehlgeschlagen.';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (elements.videoDebug) elements.videoDebug.textContent = getMessage('DEBUG_NO_TAB');
|
if (elements.videoDebug) elements.videoDebug.textContent = getMessage('DEBUG_NO_TAB');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.targetReady !== true) {
|
if (res.targetReady !== true) {
|
||||||
if (elements.videoDebug) {
|
if (elements.videoDebug) {
|
||||||
elements.videoDebug.textContent = getMessage('DEBUG_TARGET_ACTIVATING');
|
elements.videoDebug.textContent = res.targetActivationState === 'error'
|
||||||
|
? `Injection fehlgeschlagen: ${res.targetActivationError || 'unbekannter Fehler'}`
|
||||||
|
: getMessage('DEBUG_TARGET_ACTIVATING');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user