mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-19 07:33:20 +00:00
LibWebView: Move headless clipboard management to LibWebView
We only supported headless clipboard management in test-web. So when WPT tests the clipboard APIs, we would blindly try to access the Qt app, which does not exist. Note that the AppKit UI has no such restriction, as the NSPasteboard is accessible even without a GUI.
This commit is contained in:
parent
a4a15b9a1e
commit
e57176b484
Notes:
github-actions[bot]
2025-10-10 19:11:12 +00:00
Author: https://github.com/trflynn89
Commit: e57176b484
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6438
9 changed files with 247 additions and 22 deletions
|
@ -625,6 +625,27 @@ void Application::display_error_dialog(StringView error_message) const
|
|||
warnln("{}", error_message);
|
||||
}
|
||||
|
||||
Utf16String Application::clipboard_text() const
|
||||
{
|
||||
if (!m_clipboard.has_value())
|
||||
return {};
|
||||
if (m_clipboard->mime_type != "text/plain"sv)
|
||||
return {};
|
||||
return Utf16String::from_utf8(m_clipboard->data);
|
||||
}
|
||||
|
||||
Vector<Web::Clipboard::SystemClipboardRepresentation> Application::clipboard_entries() const
|
||||
{
|
||||
if (!m_clipboard.has_value())
|
||||
return {};
|
||||
return { *m_clipboard };
|
||||
}
|
||||
|
||||
void Application::insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation entry)
|
||||
{
|
||||
m_clipboard = move(entry);
|
||||
}
|
||||
|
||||
void Application::initialize_actions()
|
||||
{
|
||||
auto debug_request = [this](auto request) {
|
||||
|
|
|
@ -76,9 +76,10 @@ public:
|
|||
virtual void display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const;
|
||||
virtual void display_error_dialog(StringView error_message) const;
|
||||
|
||||
virtual Utf16String clipboard_text() const { return {}; }
|
||||
virtual Vector<Web::Clipboard::SystemClipboardRepresentation> clipboard_entries() const { return {}; }
|
||||
virtual void insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation) { }
|
||||
// FIXME: We should implement UI-agnostic platform APIs to interact with the system clipboard.
|
||||
virtual Utf16String clipboard_text() const;
|
||||
virtual Vector<Web::Clipboard::SystemClipboardRepresentation> clipboard_entries() const;
|
||||
virtual void insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation);
|
||||
|
||||
Action& reload_action() { return *m_reload_action; }
|
||||
Action& copy_selection_action() { return *m_copy_selection_action; }
|
||||
|
@ -221,6 +222,8 @@ private:
|
|||
StringView m_user_agent_string;
|
||||
StringView m_navigator_compatibility_mode;
|
||||
|
||||
Optional<Web::Clipboard::SystemClipboardRepresentation> m_clipboard;
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
OwnPtr<MachPortServer> m_mach_port_server;
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 17 tests
|
||||
|
||||
14 Pass
|
||||
3 Fail
|
||||
Pass navigator.clipboard exists
|
||||
Pass navigator.clipboard.write([text/plain ClipboardItem]) succeeds
|
||||
Fail navigator.clipboard.write([>1 ClipboardItems]) fails (not implemented)
|
||||
Pass navigator.clipboard.write() fails (expect [ClipboardItem])
|
||||
Pass navigator.clipboard.write(null) fails (expect [ClipboardItem])
|
||||
Pass navigator.clipboard.write(DOMString) fails (expect [ClipboardItem])
|
||||
Pass navigator.clipboard.write(Blob) fails (expect [ClipboardItem])
|
||||
Pass navigator.clipboard.writeText(DOMString) succeeds
|
||||
Pass navigator.clipboard.writeText() fails (expect DOMString)
|
||||
Pass navigator.clipboard.write({string : DOMString}) succeeds
|
||||
Pass navigator.clipboard.write({string : image/png Blob}) succeeds
|
||||
Pass navigator.clipboard.write([text + png] succeeds
|
||||
Fail navigator.clipboard.write(image/png DOMString) fails
|
||||
Pass navigator.clipboard.read() succeeds
|
||||
Fail navigator.clipboard.readText() succeeds
|
||||
Pass navigator.clipboard.write(Promise<Blob>) succeeds
|
||||
Pass navigator.clipboard.write(Promise<Blob>s) succeeds
|
|
@ -0,0 +1,142 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Async Clipboard input type validation tests</title>
|
||||
<link rel="help" href="https://w3c.github.io/clipboard-apis/#async-clipboard-api">
|
||||
<body>Body needed for test_driver.click()</body>
|
||||
<script src="../resources/testharness.js"></script>
|
||||
<script src="../resources/testharnessreport.js"></script>
|
||||
<script src="../resources/testdriver.js"></script>
|
||||
<script src="../resources/testdriver-vendor.js"></script>
|
||||
<script src="resources/user-activation.js"></script>
|
||||
<script>
|
||||
|
||||
// Permissions are required in order to invoke navigator.clipboard functions in
|
||||
// an automated test.
|
||||
async function getPermissions() {
|
||||
await tryGrantReadPermission();
|
||||
await tryGrantWritePermission();
|
||||
await waitForUserActivation();
|
||||
}
|
||||
|
||||
test(() => {
|
||||
assert_not_equals(navigator.clipboard, undefined);
|
||||
assert_true(navigator.clipboard instanceof Clipboard);
|
||||
assert_equals(navigator.clipboard, navigator.clipboard);
|
||||
}, 'navigator.clipboard exists');
|
||||
|
||||
promise_test(async () => {
|
||||
await getPermissions();
|
||||
const blob = new Blob(['hello'], {type: 'text/plain'});
|
||||
const item = new ClipboardItem({'text/plain': blob});
|
||||
|
||||
await navigator.clipboard.write([item]);
|
||||
}, 'navigator.clipboard.write([text/plain ClipboardItem]) succeeds');
|
||||
|
||||
promise_test(async t => {
|
||||
await getPermissions();
|
||||
const blob1 = new Blob(['hello'], {type: 'text/plain'});
|
||||
const blob2 = new Blob(['world'], {type: 'text/plain'});
|
||||
|
||||
const item1 = new ClipboardItem({'text/plain': blob1});
|
||||
const item2 = new ClipboardItem({'text/plain': blob2});
|
||||
|
||||
await promise_rejects_dom(t, "NotAllowedError",
|
||||
navigator.clipboard.write([item1, item2]));
|
||||
}, 'navigator.clipboard.write([>1 ClipboardItems]) fails (not implemented)');
|
||||
|
||||
promise_test(async t => {
|
||||
await getPermissions();
|
||||
await promise_rejects_js(t, TypeError, navigator.clipboard.write());
|
||||
}, 'navigator.clipboard.write() fails (expect [ClipboardItem])');
|
||||
|
||||
promise_test(async t => {
|
||||
await getPermissions();
|
||||
await promise_rejects_js(t, TypeError, navigator.clipboard.write(null));
|
||||
}, 'navigator.clipboard.write(null) fails (expect [ClipboardItem])');
|
||||
|
||||
promise_test(async t => {
|
||||
await getPermissions();
|
||||
await promise_rejects_js(t, TypeError,
|
||||
navigator.clipboard.write('Bad string'));
|
||||
}, 'navigator.clipboard.write(DOMString) fails (expect [ClipboardItem])');
|
||||
|
||||
promise_test(async t => {
|
||||
await getPermissions();
|
||||
const blob = new Blob(['hello'], {type: 'text/plain'});
|
||||
await promise_rejects_js(t, TypeError, navigator.clipboard.write(blob));
|
||||
}, 'navigator.clipboard.write(Blob) fails (expect [ClipboardItem])');
|
||||
|
||||
promise_test(async () => {
|
||||
await getPermissions();
|
||||
await navigator.clipboard.writeText('New clipboard text');
|
||||
}, 'navigator.clipboard.writeText(DOMString) succeeds');
|
||||
|
||||
promise_test(async t => {
|
||||
await getPermissions();
|
||||
await promise_rejects_js(t, TypeError,
|
||||
navigator.clipboard.writeText());
|
||||
}, 'navigator.clipboard.writeText() fails (expect DOMString)');
|
||||
|
||||
promise_test(async () => {
|
||||
await getPermissions();
|
||||
const item = new ClipboardItem({'text/plain': 'test'});
|
||||
await navigator.clipboard.write([item]);
|
||||
}, 'navigator.clipboard.write({string : DOMString}) succeeds');
|
||||
|
||||
promise_test(async () => {
|
||||
await getPermissions();
|
||||
const fetched = await fetch('../clipboard-apis/resources/greenbox.png');
|
||||
const image = await fetched.blob();
|
||||
const item = new ClipboardItem({'image/png': image});
|
||||
|
||||
await navigator.clipboard.write([item]);
|
||||
}, 'navigator.clipboard.write({string : image/png Blob}) succeeds');
|
||||
|
||||
promise_test(async() => {
|
||||
await getPermissions();
|
||||
const fetched = await fetch('../clipboard-apis/resources/greenbox.png');
|
||||
const image = await fetched.blob();
|
||||
const item = new ClipboardItem({
|
||||
'text/plain': new Blob(['first'], {type: 'text/plain'}),
|
||||
'image/png': image});
|
||||
|
||||
await navigator.clipboard.write([item]);
|
||||
}, 'navigator.clipboard.write([text + png] succeeds');
|
||||
|
||||
promise_test(async t => {
|
||||
await getPermissions();
|
||||
const item = new ClipboardItem({'image/png': 'not an image'});
|
||||
await promise_rejects_js(t, TypeError, navigator.clipboard.write([item]));
|
||||
}, 'navigator.clipboard.write(image/png DOMString) fails');
|
||||
|
||||
promise_test(async () => {
|
||||
await getPermissions();
|
||||
const result = await navigator.clipboard.read();
|
||||
assert_true(result instanceof Object);
|
||||
assert_true(result[0] instanceof ClipboardItem);
|
||||
}, 'navigator.clipboard.read() succeeds');
|
||||
|
||||
promise_test(async () => {
|
||||
await getPermissions();
|
||||
const result = await navigator.clipboard.readText();
|
||||
assert_equals(typeof result, 'string');
|
||||
}, 'navigator.clipboard.readText() succeeds');
|
||||
|
||||
promise_test(async () => {
|
||||
await getPermissions();
|
||||
const promise_blob = Promise.resolve(new Blob(['hello'], {type: 'text/plain'}));
|
||||
const item = new ClipboardItem({'text/plain': promise_blob});
|
||||
|
||||
await navigator.clipboard.write([item]);
|
||||
}, 'navigator.clipboard.write(Promise<Blob>) succeeds');
|
||||
|
||||
promise_test(async () => {
|
||||
await getPermissions();
|
||||
const promise_text_blob = Promise.resolve(new Blob(['hello'], {type: 'text/plain'}));
|
||||
const promise_html_blob = Promise.resolve(new Blob(["<p style='color: red; font-style: oblique;'>Test</p>"], {type: 'text/html'}));
|
||||
const item = new ClipboardItem({'text/plain': promise_text_blob, 'text/html': promise_html_blob});
|
||||
|
||||
await navigator.clipboard.write([item]);
|
||||
}, 'navigator.clipboard.write(Promise<Blob>s) succeeds');
|
||||
|
||||
</script>
|
Binary file not shown.
After Width: | Height: | Size: 95 B |
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
// In order to use this function, please import testdriver.js and
|
||||
// testdriver-vendor.js, and include a <body> element.
|
||||
async function waitForUserActivation() {
|
||||
if (window.opener) {
|
||||
throw new Error(
|
||||
"waitForUserActivation() only works in the top-level frame");
|
||||
}
|
||||
const loadedPromise = new Promise(resolve => {
|
||||
if(document.readyState == 'complete') {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
window.addEventListener('load', resolve, {once: true});
|
||||
});
|
||||
await loadedPromise;
|
||||
|
||||
const clickedPromise = new Promise(resolve => {
|
||||
document.body.addEventListener('click', resolve, {once: true});
|
||||
});
|
||||
|
||||
test_driver.click(document.body);
|
||||
await clickedPromise;
|
||||
}
|
||||
|
||||
async function trySetPermission(perm, state) {
|
||||
try {
|
||||
await test_driver.set_permission({ name: perm }, state)
|
||||
} catch {
|
||||
// This is expected, as clipboard permissions are not supported by every engine
|
||||
// and also the set_permission. The permission is not required by such engines as
|
||||
// they require user activation instead.
|
||||
}
|
||||
}
|
||||
|
||||
async function tryGrantReadPermission() {
|
||||
await trySetPermission("clipboard-read", "granted");
|
||||
}
|
||||
|
||||
async function tryGrantWritePermission() {
|
||||
await trySetPermission("clipboard-write", "granted");
|
||||
}
|
||||
|
|
@ -93,16 +93,4 @@ ErrorOr<void> Application::launch_test_fixtures()
|
|||
return {};
|
||||
}
|
||||
|
||||
Vector<Web::Clipboard::SystemClipboardRepresentation> Application::clipboard_entries() const
|
||||
{
|
||||
if (m_clipboard.has_value())
|
||||
return { *m_clipboard };
|
||||
return {};
|
||||
}
|
||||
|
||||
void Application::insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation entry)
|
||||
{
|
||||
m_clipboard = move(entry);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,13 +45,6 @@ public:
|
|||
int per_test_timeout_in_seconds { 30 };
|
||||
|
||||
u8 verbosity { 0 };
|
||||
|
||||
private:
|
||||
virtual Vector<Web::Clipboard::SystemClipboardRepresentation> clipboard_entries() const override;
|
||||
virtual void insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation) override;
|
||||
|
||||
// FIXME: We should implement UI-agnostic platform APIs to interact with the system clipboard.
|
||||
Optional<Web::Clipboard::SystemClipboardRepresentation> m_clipboard;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -177,12 +177,18 @@ void Application::display_error_dialog(StringView error_message) const
|
|||
|
||||
Utf16String Application::clipboard_text() const
|
||||
{
|
||||
if (browser_options().headless_mode.has_value())
|
||||
return WebView::Application::clipboard_text();
|
||||
|
||||
auto const* clipboard = QGuiApplication::clipboard();
|
||||
return utf16_string_from_qstring(clipboard->text());
|
||||
}
|
||||
|
||||
Vector<Web::Clipboard::SystemClipboardRepresentation> Application::clipboard_entries() const
|
||||
{
|
||||
if (browser_options().headless_mode.has_value())
|
||||
return WebView::Application::clipboard_entries();
|
||||
|
||||
Vector<Web::Clipboard::SystemClipboardRepresentation> representations;
|
||||
auto const* clipboard = QGuiApplication::clipboard();
|
||||
|
||||
|
@ -202,6 +208,11 @@ Vector<Web::Clipboard::SystemClipboardRepresentation> Application::clipboard_ent
|
|||
|
||||
void Application::insert_clipboard_entry(Web::Clipboard::SystemClipboardRepresentation entry)
|
||||
{
|
||||
if (browser_options().headless_mode.has_value()) {
|
||||
WebView::Application::insert_clipboard_entry(move(entry));
|
||||
return;
|
||||
}
|
||||
|
||||
auto* mime_data = new QMimeData();
|
||||
mime_data->setData(qstring_from_ak_string(entry.mime_type), qbytearray_from_ak_string(entry.data));
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue