mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-20 16:12:16 +00:00
fix(extension): support cross-origin media frames
This commit is contained in:
@@ -27,16 +27,51 @@ async function selectTargetTab(context, extensionId, pageUrl) {
|
||||
|
||||
async function sendServerCommand(context, extensionId, tabId, action, payload) {
|
||||
return withExtensionPage(context, extensionId, page => page.evaluate(async ({ tabId, action, payload }) => {
|
||||
return chrome.tabs.sendMessage(tabId, {
|
||||
type: 'SERVER_COMMAND',
|
||||
return chrome.runtime.sendMessage({
|
||||
type: 'CONTENT_EVENT',
|
||||
action,
|
||||
payload,
|
||||
actionTimestamp: Date.now(),
|
||||
commandSenderId: 'e2e'
|
||||
payload: payload || {},
|
||||
expectedTabId: tabId
|
||||
});
|
||||
}, { tabId, action, payload }));
|
||||
}
|
||||
|
||||
async function sendServerCommandBurst(context, extensionId, tabId, commands) {
|
||||
return withExtensionPage(context, extensionId, page => page.evaluate(async ({ tabId, commands }) => {
|
||||
return Promise.all(commands.map(({ action, payload }) => chrome.runtime.sendMessage({
|
||||
type: 'CONTENT_EVENT',
|
||||
action,
|
||||
payload: payload || {},
|
||||
expectedTabId: tabId
|
||||
})));
|
||||
}, { tabId, commands }));
|
||||
}
|
||||
|
||||
async function getExtensionState(context, extensionId, message) {
|
||||
return withExtensionPage(context, extensionId, page => page.evaluate(
|
||||
request => chrome.runtime.sendMessage(request),
|
||||
message
|
||||
));
|
||||
}
|
||||
|
||||
async function getFrameMonitorState(context, extensionId, pageUrl, frameUrlPart) {
|
||||
return withExtensionPage(context, extensionId, page => page.evaluate(async ({ pageUrl, frameUrlPart }) => {
|
||||
const [tab] = await chrome.tabs.query({ url: pageUrl });
|
||||
if (!tab) throw new Error(`no tab matched ${pageUrl}`);
|
||||
const frames = await chrome.webNavigation.getAllFrames({ tabId: tab.id });
|
||||
const frame = frames.find(candidate => candidate.url.includes(frameUrlPart));
|
||||
if (!frame) throw new Error(`no frame matched ${frameUrlPart}`);
|
||||
const target = frame.documentId
|
||||
? { tabId: tab.id, documentIds: [frame.documentId] }
|
||||
: { tabId: tab.id, frameIds: [frame.frameId] };
|
||||
const [result] = await chrome.scripting.executeScript({
|
||||
target,
|
||||
func: () => typeof window.__koalaMediaFrameMonitorCleanup
|
||||
});
|
||||
return result?.result;
|
||||
}, { pageUrl, frameUrlPart }));
|
||||
}
|
||||
|
||||
test('injects into the target tab and attaches to a same-origin frame player', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/iframe-player.html`;
|
||||
const page = await context.newPage();
|
||||
@@ -67,7 +102,8 @@ test('applies remote play, pause and seek to the framed player', async ({ contex
|
||||
return video ? video.dataset.koalaAttached : null;
|
||||
})).toBe('true');
|
||||
|
||||
await sendServerCommand(context, extensionId, tabId, 'play');
|
||||
const playResponse = await sendServerCommand(context, extensionId, tabId, 'play');
|
||||
expect(playResponse).toMatchObject({ status: 'ok_solo' });
|
||||
await expect.poll(() => page.evaluate(FRAMED_VIDEO_PAUSED), { message: 'remote play should start playback' }).toBe(false);
|
||||
|
||||
await sendServerCommand(context, extensionId, tabId, 'pause');
|
||||
@@ -159,6 +195,271 @@ test('re-attaches when a nested player frame swaps its document', async ({ conte
|
||||
).toBe('true');
|
||||
});
|
||||
|
||||
test('moves local event listeners after a CSS-only player switch', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/player-css-switch.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
await selectTargetTab(context, extensionId, url);
|
||||
|
||||
await expect.poll(() => page.locator('#first').getAttribute('data-koala-attached')).toBe('true');
|
||||
await page.waitForTimeout(250);
|
||||
const beforeBurst = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
await page.evaluate(() => {
|
||||
const first = document.getElementById('first');
|
||||
first.dispatchEvent(new window.Event('play'));
|
||||
first.dispatchEvent(new window.Event('pause'));
|
||||
window.switchPlayer();
|
||||
});
|
||||
await expect.poll(async () => {
|
||||
const status = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
return status.lastActionState.action === 'play'
|
||||
&& status.lastActionState.timestamp > beforeBurst.lastActionState.timestamp;
|
||||
}).toBe(true);
|
||||
const leading = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
await expect.poll(
|
||||
() => page.locator('#second').getAttribute('data-koala-attached'),
|
||||
{ message: 'attribute-only visibility changes must move the active controller' }
|
||||
).toBe('true');
|
||||
expect(await page.locator('#first').getAttribute('data-koala-attached')).toBeNull();
|
||||
const afterSwitch = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
expect(afterSwitch.lastActionState.action).toBe('play');
|
||||
expect(afterSwitch.lastActionState.timestamp).toBe(leading.lastActionState.timestamp);
|
||||
|
||||
const before = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
await page.locator('#first').evaluate(video => video.dispatchEvent(new window.Event('play')));
|
||||
await page.waitForTimeout(250);
|
||||
const afterStale = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
expect(afterStale.lastActionState.timestamp).toBe(before.lastActionState.timestamp);
|
||||
|
||||
await page.locator('#second').evaluate(video => video.dispatchEvent(new window.Event('play')));
|
||||
await expect.poll(async () => {
|
||||
const status = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
return status.lastActionState.timestamp;
|
||||
}).toBeGreaterThan(afterStale.lastActionState.timestamp);
|
||||
});
|
||||
|
||||
test('re-elects after a wrapper-only change inside an unselected cross-origin frame', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/cross-origin-internal-switching.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
const first = page.frames().find(frame => frame.url().includes('slot=first'));
|
||||
const second = page.frames().find(frame => frame.url().includes('slot=second'));
|
||||
await selectTargetTab(context, extensionId, url);
|
||||
|
||||
await expect.poll(() => first.locator('video').getAttribute('data-koala-attached')).toBe('true');
|
||||
expect(await second.locator('video').getAttribute('data-koala-attached')).toBeNull();
|
||||
await page.evaluate(() => window.switchInternalPlayer());
|
||||
await expect.poll(
|
||||
() => second.locator('video').getAttribute('data-koala-attached'),
|
||||
{ message: 'an unselected frame must announce its internally-visible player' }
|
||||
).toBe('true');
|
||||
expect(await first.locator('video').getAttribute('data-koala-attached')).toBeNull();
|
||||
});
|
||||
|
||||
test('targets a visible nested cross-origin player and keeps top-page debug context', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/cross-origin-nested.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
|
||||
const visiblePlayer = () => page.frames().find(frame => frame.url().includes('/frames/player-frame.html?visible=1'));
|
||||
await expect.poll(async () => visiblePlayer()?.locator('video').getAttribute('src')).toContain('player-480p-12s.mp4');
|
||||
|
||||
const { tabId, response } = await selectTargetTab(context, extensionId, url);
|
||||
expect(response).toMatchObject({ status: 'ok' });
|
||||
expect(response.frameId).toBeGreaterThan(0);
|
||||
|
||||
await expect.poll(() => visiblePlayer()?.locator('video').getAttribute('data-koala-attached')).toBe('true');
|
||||
const hiddenPlayer = page.frames().find(frame => frame.url().includes('/frames/player-frame-2.html?hidden=1'));
|
||||
expect(await hiddenPlayer.locator('video').getAttribute('data-koala-attached')).toBeNull();
|
||||
|
||||
const playResponse = await sendServerCommand(context, extensionId, tabId, 'play');
|
||||
expect(playResponse).toMatchObject({ status: 'ok_solo' });
|
||||
await expect.poll(() => visiblePlayer().locator('video').evaluate(video => video.paused)).toBe(false);
|
||||
await sendServerCommand(context, extensionId, tabId, 'pause');
|
||||
await expect.poll(() => visiblePlayer().locator('video').evaluate(video => video.paused)).toBe(true);
|
||||
await sendServerCommand(context, extensionId, tabId, 'seek', { targetTime: 6 });
|
||||
await expect.poll(() => visiblePlayer().locator('video').evaluate(video => video.currentTime)).toBeGreaterThan(5);
|
||||
|
||||
await sendServerCommand(context, extensionId, tabId, 'pause');
|
||||
await sendServerCommandBurst(context, extensionId, tabId, [
|
||||
{ action: 'seek', payload: { targetTime: 8 } },
|
||||
{ action: 'play', payload: { currentTime: 8 } }
|
||||
]);
|
||||
await expect.poll(() => visiblePlayer().locator('video').evaluate(video => ({
|
||||
paused: video.paused,
|
||||
currentTime: video.currentTime
|
||||
}))).toMatchObject({ paused: false, currentTime: expect.any(Number) });
|
||||
await expect.poll(() => visiblePlayer().locator('video').evaluate(video => video.currentTime)).toBeGreaterThan(7);
|
||||
|
||||
const state = await getExtensionState(context, extensionId, { type: 'GET_VIDEO_STATE', tabId });
|
||||
expect(state).toMatchObject({
|
||||
found: true,
|
||||
url,
|
||||
pageTitle: 'Nested cross-origin player',
|
||||
frameOrigin: new URL(baseURL.replace('localhost', '127.0.0.1')).origin,
|
||||
inIframe: true
|
||||
});
|
||||
});
|
||||
|
||||
test('re-elects the visible cross-origin player after an iframe switch', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/cross-origin-switching.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
const first = page.frames().find(frame => frame.url().includes('/frames/player-frame.html?slot=first'));
|
||||
const second = page.frames().find(frame => frame.url().includes('/frames/player-frame-2.html?slot=second'));
|
||||
const { tabId, response } = await selectTargetTab(context, extensionId, url);
|
||||
expect(response).toMatchObject({ status: 'ok' });
|
||||
const firstFrameId = response.frameId;
|
||||
|
||||
await expect.poll(() => first.locator('video').getAttribute('data-koala-attached')).toBe('true');
|
||||
await page.evaluate(() => window.switchPlayer());
|
||||
await expect.poll(() => second.locator('video').getAttribute('data-koala-attached')).toBe('true');
|
||||
await expect.poll(async () => {
|
||||
const status = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
return status.targetFrameId;
|
||||
}).not.toBe(firstFrameId);
|
||||
|
||||
const playResponse = await sendServerCommand(context, extensionId, tabId, 'play');
|
||||
expect(playResponse).toMatchObject({ status: 'ok_solo' });
|
||||
await expect.poll(() => second.locator('video').evaluate(video => video.paused)).toBe(false);
|
||||
expect(await first.locator('video').evaluate(video => video.paused)).toBe(true);
|
||||
});
|
||||
|
||||
test('keeps commands flowing during continuous player-frame geometry changes', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/cross-origin-switching.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
const first = page.frames().find(frame => frame.url().includes('/frames/player-frame.html?slot=first'));
|
||||
const { tabId, response } = await selectTargetTab(context, extensionId, url);
|
||||
expect(response).toMatchObject({ status: 'ok' });
|
||||
await expect.poll(() => first.locator('video').getAttribute('data-koala-attached')).toBe('true');
|
||||
|
||||
await page.evaluate(() => window.startGeometryChurn());
|
||||
try {
|
||||
await page.waitForTimeout(300);
|
||||
await sendServerCommand(context, extensionId, tabId, 'play', { currentTime: 1 });
|
||||
await expect.poll(
|
||||
() => first.locator('video').evaluate(video => video.paused),
|
||||
{ timeout: 3000, message: 'bounded refresh passes must not starve commands' }
|
||||
).toBe(false);
|
||||
} finally {
|
||||
await page.evaluate(() => window.stopGeometryChurn());
|
||||
}
|
||||
});
|
||||
|
||||
test('deactivates media monitors in child frames after a target-tab switch', async ({ context, extensionId, baseURL }) => {
|
||||
const firstUrl = `${baseURL}/pages/cross-origin-nested.html`;
|
||||
const secondUrl = `${baseURL}/pages/simple-player.html`;
|
||||
const firstPage = await context.newPage();
|
||||
const secondPage = await context.newPage();
|
||||
await firstPage.goto(firstUrl);
|
||||
await firstPage.waitForFunction(() => window.__fixtureReady === true);
|
||||
await secondPage.goto(secondUrl);
|
||||
await secondPage.waitForFunction(() => window.__fixtureReady === true);
|
||||
await selectTargetTab(context, extensionId, firstUrl);
|
||||
await expect.poll(() => getFrameMonitorState(
|
||||
context,
|
||||
extensionId,
|
||||
firstUrl,
|
||||
'/frames/player-frame.html?visible=1'
|
||||
)).toBe('function');
|
||||
await selectTargetTab(context, extensionId, secondUrl);
|
||||
await expect.poll(
|
||||
() => getFrameMonitorState(
|
||||
context,
|
||||
extensionId,
|
||||
firstUrl,
|
||||
'/frames/player-frame.html?visible=1'
|
||||
),
|
||||
{ message: 'child-frame monitor should be destroyed with the old target tab' }
|
||||
).toBe('undefined');
|
||||
});
|
||||
|
||||
test('re-attaches after a selected cross-origin frame navigates', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/cross-origin-reloading.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
const first = page.frames().find(frame => frame.url().includes('generation=first'));
|
||||
const { tabId, response } = await selectTargetTab(context, extensionId, url);
|
||||
expect(response).toMatchObject({ status: 'ok' });
|
||||
await expect.poll(() => first.locator('video').getAttribute('data-koala-attached')).toBe('true');
|
||||
const firstDocumentId = (await getExtensionState(context, extensionId, { type: 'GET_STATUS' })).targetDocumentId;
|
||||
|
||||
await page.evaluate(() => window.reloadPlayer());
|
||||
await expect.poll(() => page.frames().find(frame => frame.url().includes('generation=second'))?.locator('video').getAttribute('data-koala-attached')).toBe('true');
|
||||
await expect.poll(async () => {
|
||||
const status = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
return status.targetDocumentId;
|
||||
}).not.toBe(firstDocumentId);
|
||||
|
||||
const state = await getExtensionState(context, extensionId, { type: 'GET_VIDEO_STATE', tabId });
|
||||
expect(state).toMatchObject({ found: true, inIframe: true });
|
||||
});
|
||||
|
||||
test('discovers a video inserted late inside a cross-origin frame', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/cross-origin-late.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
const lateFrame = page.frames().find(frame => frame.url().includes('/frames/late-player-frame.html'));
|
||||
const { response } = await selectTargetTab(context, extensionId, url);
|
||||
expect(response).toMatchObject({ status: 'ok', hasVideo: false });
|
||||
|
||||
await expect.poll(
|
||||
() => lateFrame.locator('#late-player').getAttribute('data-koala-attached'),
|
||||
{ timeout: 12_000, message: 'late cross-origin video should trigger target re-election' }
|
||||
).toBe('true');
|
||||
await expect.poll(async () => {
|
||||
const status = await getExtensionState(context, extensionId, { type: 'GET_STATUS' });
|
||||
return status.targetHasVideo;
|
||||
}).toBe(true);
|
||||
});
|
||||
|
||||
test('rejects a cross-origin player hidden three frame levels deep', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/deep-hidden-cross-origin.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
const hiddenFrame = page.frames().find(frame => frame.url().includes('deep=hidden'));
|
||||
await expect.poll(() => hiddenFrame?.locator('video').getAttribute('src')).toContain('player-1080p-30s.mp4');
|
||||
|
||||
const { response } = await selectTargetTab(context, extensionId, url);
|
||||
expect(response).toMatchObject({ status: 'ok', frameId: 0, hasVideo: false });
|
||||
expect(await hiddenFrame.locator('video').getAttribute('data-koala-attached')).toBeNull();
|
||||
});
|
||||
|
||||
test('rejects a player inside a hidden same-origin frame', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/hidden-same-origin.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
const hiddenFrame = page.frames().find(frame => frame.url().includes('hidden=same-origin'));
|
||||
await expect.poll(() => hiddenFrame?.locator('video').getAttribute('src')).toContain('player-480p-12s.mp4');
|
||||
|
||||
const { response } = await selectTargetTab(context, extensionId, url);
|
||||
expect(response).toMatchObject({ status: 'ok', frameId: 0, hasVideo: false });
|
||||
expect(await hiddenFrame.locator('video').getAttribute('data-koala-attached')).toBeNull();
|
||||
});
|
||||
|
||||
test('rejects a hidden cross-origin player after its iframe URL redirects', async ({ context, extensionId, baseURL }) => {
|
||||
const url = `${baseURL}/pages/hidden-redirect-cross-origin.html`;
|
||||
const page = await context.newPage();
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => window.__fixtureReady === true);
|
||||
const redirectedFrame = page.frames().find(frame => frame.url().includes('redirected=hidden'));
|
||||
await expect.poll(() => redirectedFrame?.locator('video').getAttribute('src')).toContain('player-1080p-30s.mp4');
|
||||
|
||||
const { response } = await selectTargetTab(context, extensionId, url);
|
||||
expect(response).toMatchObject({ status: 'ok', frameId: 0, hasVideo: false });
|
||||
expect(await redirectedFrame.locator('video').getAttribute('data-koala-attached')).toBeNull();
|
||||
});
|
||||
|
||||
function FRAMED_VIDEO_PAUSED() {
|
||||
return document.querySelector('iframe').contentDocument.querySelector('video').paused;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,13 @@ const TYPES = {
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const requested = decodeURIComponent(new URL(req.url, 'http://localhost').pathname);
|
||||
if (requested === '/redirect/hidden-player') {
|
||||
res.writeHead(302, {
|
||||
Location: `http://127.0.0.1:${port}/pages/frames/player-frame-2.html?redirected=hidden`,
|
||||
'Cache-Control': 'no-store'
|
||||
}).end();
|
||||
return;
|
||||
}
|
||||
const filePath = path.resolve(root, `.${requested}`);
|
||||
|
||||
if (!filePath.startsWith(root + path.sep)) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cross-origin internal wrapper switch</title>
|
||||
<style>
|
||||
iframe { width: 640px; height: 360px; border: 0; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="first" allow="autoplay; fullscreen"></iframe>
|
||||
<iframe id="second" allow="autoplay; fullscreen"></iframe>
|
||||
<script>
|
||||
const playerOrigin = `http://127.0.0.1:${location.port}`;
|
||||
const first = document.getElementById('first');
|
||||
const second = document.getElementById('second');
|
||||
first.src = `${playerOrigin}/pages/frames/wrapper-switch-player.html?slot=first&visible=1`;
|
||||
second.src = `${playerOrigin}/pages/frames/wrapper-switch-player.html?slot=second&visible=0`;
|
||||
let loaded = 0;
|
||||
const ready = () => {
|
||||
loaded++;
|
||||
if (loaded === 2) window.__fixtureReady = true;
|
||||
};
|
||||
first.addEventListener('load', ready);
|
||||
second.addEventListener('load', ready);
|
||||
window.switchInternalPlayer = () => {
|
||||
first.contentWindow.postMessage('koala-hide', playerOrigin);
|
||||
second.contentWindow.postMessage('koala-show', playerOrigin);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Late cross-origin player</title>
|
||||
<style>body { margin: 0; } iframe { border: 0; }</style>
|
||||
<iframe id="player" width="860" height="490" allowfullscreen></iframe>
|
||||
<script>
|
||||
const player = document.getElementById('player');
|
||||
player.src = `http://127.0.0.1:${location.port}/pages/frames/late-player-frame.html`;
|
||||
player.addEventListener('load', () => { window.__fixtureReady = true; }, { once: true });
|
||||
</script>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Nested cross-origin player</title>
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
iframe { border: 0; }
|
||||
#hidden-player { display: none; }
|
||||
</style>
|
||||
<iframe id="wrapper" width="870" height="500" src="frames/cross-origin-wrapper.html" allowfullscreen></iframe>
|
||||
<iframe id="hidden-player" width="870" height="500" allowfullscreen></iframe>
|
||||
<script>
|
||||
const hidden = document.getElementById('hidden-player');
|
||||
hidden.src = `http://127.0.0.1:${location.port}/pages/frames/player-frame-2.html?hidden=1`;
|
||||
Promise.all([
|
||||
new Promise(resolve => document.getElementById('wrapper').addEventListener('load', resolve, { once: true })),
|
||||
new Promise(resolve => hidden.addEventListener('load', resolve, { once: true }))
|
||||
]).then(() => { window.__fixtureReady = true; });
|
||||
</script>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Reloading cross-origin player</title>
|
||||
<style>body { margin: 0; } iframe { border: 0; }</style>
|
||||
<iframe id="player" width="860" height="490" allowfullscreen></iframe>
|
||||
<script>
|
||||
const player = document.getElementById('player');
|
||||
player.src = `http://127.0.0.1:${location.port}/pages/frames/player-frame.html?generation=first`;
|
||||
player.addEventListener('load', () => { window.__fixtureReady = true; }, { once: true });
|
||||
window.reloadPlayer = () => {
|
||||
player.src = `http://127.0.0.1:${location.port}/pages/frames/player-frame-2.html?generation=second`;
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,38 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Switching cross-origin players</title>
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
iframe { border: 0; }
|
||||
#second { display: none; }
|
||||
</style>
|
||||
<iframe id="first" width="860" height="490" allowfullscreen></iframe>
|
||||
<iframe id="second" width="860" height="490" allowfullscreen></iframe>
|
||||
<script>
|
||||
const first = document.getElementById('first');
|
||||
const second = document.getElementById('second');
|
||||
first.src = `http://127.0.0.1:${location.port}/pages/frames/player-frame.html?slot=first`;
|
||||
second.src = `http://127.0.0.1:${location.port}/pages/frames/player-frame-2.html?slot=second`;
|
||||
Promise.all([
|
||||
new Promise(resolve => first.addEventListener('load', resolve, { once: true })),
|
||||
new Promise(resolve => second.addEventListener('load', resolve, { once: true }))
|
||||
]).then(() => { window.__fixtureReady = true; });
|
||||
window.switchPlayer = () => {
|
||||
first.style.visibility = 'hidden';
|
||||
first.style.position = 'absolute';
|
||||
first.style.left = '-10000px';
|
||||
second.style.display = 'block';
|
||||
};
|
||||
let geometryChurn = null;
|
||||
window.startGeometryChurn = () => {
|
||||
let wide = false;
|
||||
geometryChurn = setInterval(() => {
|
||||
wide = !wide;
|
||||
first.style.width = wide ? '840px' : '800px';
|
||||
}, 80);
|
||||
};
|
||||
window.stopGeometryChurn = () => {
|
||||
clearInterval(geometryChurn);
|
||||
geometryChurn = null;
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Deep hidden cross-origin player</title>
|
||||
<style>body { margin: 0; } iframe { border: 0; } #hidden-wrapper { visibility: hidden; }</style>
|
||||
<iframe id="hidden-wrapper" width="870" height="500" src="frames/deep-wrapper-1.html" allowfullscreen></iframe>
|
||||
<script>
|
||||
document.getElementById('hidden-wrapper').addEventListener('load', () => {
|
||||
window.__fixtureReady = true;
|
||||
}, { once: true });
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Cross-origin wrapper</title>
|
||||
<style>body { margin: 0; } iframe { border: 0; }</style>
|
||||
<iframe id="inner-player" width="860" height="490" allowfullscreen></iframe>
|
||||
<script>
|
||||
document.getElementById('inner-player').src =
|
||||
`http://127.0.0.1:${location.port}/pages/frames/player-frame.html?visible=1`;
|
||||
</script>
|
||||
@@ -0,0 +1,5 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Deep wrapper 1</title>
|
||||
<style>body { margin: 0; } iframe { border: 0; }</style>
|
||||
<iframe width="860" height="490" src="deep-wrapper-2.html" allowfullscreen></iframe>
|
||||
@@ -0,0 +1,9 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Deep wrapper 2</title>
|
||||
<style>body { margin: 0; } iframe { border: 0; }</style>
|
||||
<iframe id="deep-player" width="850" height="480" allowfullscreen></iframe>
|
||||
<script>
|
||||
document.getElementById('deep-player').src =
|
||||
`http://127.0.0.1:${location.port}/pages/frames/player-frame-2.html?deep=hidden`;
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Late player frame</title>
|
||||
<style>body { margin: 0; }</style>
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
const video = document.createElement('video');
|
||||
video.id = 'late-player';
|
||||
video.width = 854;
|
||||
video.height = 480;
|
||||
video.controls = true;
|
||||
video.src = '../../media/player-480p-12s.mp4';
|
||||
document.body.append(video);
|
||||
}, 8000);
|
||||
</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Wrapper-switch player</title>
|
||||
<style>
|
||||
html, body { margin: 0; }
|
||||
#wrapper.hidden { visibility: hidden; }
|
||||
video { width: 640px; height: 360px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
<video id="player" controls preload="auto"></video>
|
||||
</div>
|
||||
<script>
|
||||
const params = new URLSearchParams(location.search);
|
||||
const wrapper = document.getElementById('wrapper');
|
||||
const player = document.getElementById('player');
|
||||
wrapper.classList.toggle('hidden', params.get('visible') !== '1');
|
||||
player.src = params.get('slot') === 'second'
|
||||
? '../../media/player-1080p-30s.mp4'
|
||||
: '../../media/player-480p-12s.mp4';
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data === 'koala-show') wrapper.classList.remove('hidden');
|
||||
if (event.data === 'koala-hide') wrapper.classList.add('hidden');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Hidden redirected cross-origin player</title>
|
||||
<style>body { margin: 0; } iframe { border: 0; visibility: hidden; }</style>
|
||||
<iframe id="player" width="860" height="490" allowfullscreen></iframe>
|
||||
<script>
|
||||
const player = document.getElementById('player');
|
||||
player.src = `http://localhost:${location.port}/redirect/hidden-player`;
|
||||
player.addEventListener('load', () => { window.__fixtureReady = true; }, { once: true });
|
||||
</script>
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hidden same-origin frame</title>
|
||||
<style>
|
||||
iframe { width: 800px; height: 450px; border: 0; visibility: hidden; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="frames/player-frame.html?hidden=same-origin" allow="autoplay; fullscreen"></iframe>
|
||||
<script>window.__fixtureReady = true;</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CSS player switch</title>
|
||||
<style>
|
||||
video { width: 640px; height: 360px; }
|
||||
#second { visibility: hidden; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<video id="first" controls preload="auto" src="../media/player-480p-12s.mp4"></video>
|
||||
<video id="second" controls preload="auto" src="../media/player-1080p-30s.mp4"></video>
|
||||
<script>
|
||||
window.switchPlayer = () => {
|
||||
document.getElementById('first').style.visibility = 'hidden';
|
||||
document.getElementById('second').style.visibility = 'visible';
|
||||
};
|
||||
window.__fixtureReady = true;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user