78 lines
3.6 KiB
JavaScript
78 lines
3.6 KiB
JavaScript
|
|
// Browser acceptance with synthetic content and intercepted clipboard writes.
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { fileURLToPath } from 'node:url';
|
||
|
|
import { chromium } from 'playwright';
|
||
|
|
|
||
|
|
const browser = await chromium.launch({headless: true});
|
||
|
|
try {
|
||
|
|
const page = await browser.newPage();
|
||
|
|
const script = fileURLToPath(new URL('../coulomb_social/apps/spaces/static/spaces/copy-content.js', import.meta.url));
|
||
|
|
const fixture = `<nav>PRIVATE APP CHROME</nav>
|
||
|
|
<button id="copy-page-content">Copy content to clipboard</button>
|
||
|
|
<p id="copy-content-status" role="status"></p>
|
||
|
|
<textarea id="copy-content-manual" hidden></textarea>
|
||
|
|
<div id="page-content"><h1>Weekly call</h1><ul><li>Open point<ul><li>Nested follow-up</li></ul></li><li>Completed point</li></ul><h2>Protocol</h2><p>New notes with <strong>emphasis</strong>.</p></div>`;
|
||
|
|
// A secure origin enables the browser's native ClipboardItem implementation.
|
||
|
|
await page.route('https://clipboard.test/', route => route.fulfill({
|
||
|
|
contentType: 'text/html', body: fixture,
|
||
|
|
}));
|
||
|
|
await page.goto('https://clipboard.test/');
|
||
|
|
async function setup(mode) {
|
||
|
|
await page.setContent(fixture);
|
||
|
|
await page.evaluate(mode => {
|
||
|
|
window.receipts = [];
|
||
|
|
window.clipboardMode = mode;
|
||
|
|
Object.defineProperty(navigator, 'clipboard', {configurable: true, value: {
|
||
|
|
write: async items => {
|
||
|
|
if (window.clipboardMode !== 'rich') throw new Error('Rich clipboard denied');
|
||
|
|
const item = items[0];
|
||
|
|
window.receipts.push({html: await (await item.getType('text/html')).text(),
|
||
|
|
text: await (await item.getType('text/plain')).text()});
|
||
|
|
},
|
||
|
|
writeText: async text => {
|
||
|
|
if (window.clipboardMode === 'denied') throw new Error('Clipboard denied');
|
||
|
|
window.receipts.push({text});
|
||
|
|
},
|
||
|
|
}});
|
||
|
|
}, mode);
|
||
|
|
await page.addScriptTag({path: script});
|
||
|
|
}
|
||
|
|
for (const mode of ['rich', 'plain', 'denied']) {
|
||
|
|
await setup(mode);
|
||
|
|
await page.locator('#copy-page-content').click();
|
||
|
|
await page.waitForFunction(() => !document.getElementById('copy-page-content').disabled);
|
||
|
|
const result = await page.evaluate(() => ({
|
||
|
|
receipts: window.receipts,
|
||
|
|
status: document.getElementById('copy-content-status').textContent,
|
||
|
|
manual: document.getElementById('copy-content-manual').value,
|
||
|
|
hidden: document.getElementById('copy-content-manual').hidden,
|
||
|
|
selected: document.getElementById('copy-content-manual').selectionEnd,
|
||
|
|
}));
|
||
|
|
const text = mode === 'denied' ? result.manual : result.receipts[0].text;
|
||
|
|
assert.match(text, /Weekly call/);
|
||
|
|
assert.match(text, /- Open point/);
|
||
|
|
assert.match(text, / - Nested follow-up/);
|
||
|
|
assert.match(text, /- Completed point/);
|
||
|
|
assert.match(text, /New notes with emphasis/);
|
||
|
|
assert.doesNotMatch(text, /PRIVATE APP CHROME|Copy content to clipboard/);
|
||
|
|
if (mode === 'denied') {
|
||
|
|
assert.equal(result.receipts.length, 0);
|
||
|
|
assert.equal(result.hidden, false);
|
||
|
|
assert.equal(result.selected, result.manual.length);
|
||
|
|
assert.match(result.status, /Ctrl\+C or Cmd\+C/);
|
||
|
|
assert.doesNotMatch(result.status, /Content copied/);
|
||
|
|
} else {
|
||
|
|
assert.equal(result.receipts.length, 1);
|
||
|
|
assert.match(result.status, /Content copied/);
|
||
|
|
if (mode === 'rich') {
|
||
|
|
assert.match(result.receipts[0].html, /<ul><li>Open point/);
|
||
|
|
assert.match(result.receipts[0].html, /<strong>emphasis<\/strong>/);
|
||
|
|
assert.doesNotMatch(result.receipts[0].html, /PRIVATE APP CHROME/);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
console.log('Clipboard browser checks passed: rich HTML, plain text, blocked/manual fallback.');
|
||
|
|
} finally {
|
||
|
|
await browser.close();
|
||
|
|
}
|