Bug 1879401 - Make clipboard DataTransfer use sync version of nsIAsyncGetClipboardData r=edgar,ipc-reviewers,nika

Differential Revision: https://phabricator.services.mozilla.com/D224843
This commit is contained in:
Greg Stoll
2024-10-28 22:37:56 +00:00
parent aa4eca1c8a
commit 43938109b2
12 changed files with 270 additions and 26 deletions

View File

@@ -531,9 +531,8 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
// drag and drop.
mozilla::Maybe<nsIClipboard::ClipboardType> mClipboardType;
// The nsIClipboardDataSnapshot that is used for getting clipboard formats.
// XXXedgar we should get the actual data from this in the future, see bug
// 1879401.
// The nsIClipboardDataSnapshot that is used for getting clipboard formats and
// data.
nsCOMPtr<nsIClipboardDataSnapshot> mClipboardDataSnapshot;
// The items contained with the DataTransfer

View File

@@ -172,21 +172,12 @@ void DataTransferItem::FillInExternalData() {
return;
}
nsCOMPtr<nsIClipboard> clipboard =
do_GetService("@mozilla.org/widget/clipboard;1");
if (!clipboard) {
nsCOMPtr<nsIClipboardDataSnapshot> clipboardDataSnapshot =
mDataTransfer->GetClipboardDataSnapshot();
if (!clipboardDataSnapshot) {
return;
}
nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
WindowContext* windowContext = nullptr;
if (global) {
const auto* innerWindow = global->GetAsInnerWindow();
windowContext = innerWindow ? innerWindow->GetWindowContext() : nullptr;
}
MOZ_ASSERT(windowContext);
nsresult rv = clipboard->GetData(trans, *mDataTransfer->ClipboardType(),
windowContext);
nsresult rv = clipboardDataSnapshot->GetDataSync(trans);
if (NS_WARN_IF(NS_FAILED(rv))) {
if (rv == NS_ERROR_CONTENT_BLOCKED) {
// If the load of this content was blocked by Content Analysis,

View File

@@ -97,7 +97,9 @@ description = Only used by gtests
# Clipboard
[PClipboardContentAnalysis::GetClipboard]
description = Legacy synchronous clipboard API
description = Called from PContent::GetClipboard which is a legacy synchronous clipboard API
[PClipboardReadRequest::GetDataSync]
description = Called from synchronous DataTransfer constructor
[PContent::GetClipboard]
description = Legacy synchronous clipboard API
[PContent::ClipboardHasType]

View File

@@ -112,4 +112,37 @@ IPCResult ClipboardReadRequestParent::RecvGetData(
return IPC_OK();
}
IPCResult ClipboardReadRequestParent::RecvGetDataSync(
const nsTArray<nsCString>& aFlavors,
dom::IPCTransferableDataOrError* aTransferableDataOrError) {
bool valid = false;
if (NS_FAILED(mClipboardDataSnapshot->GetValid(&valid)) || !valid) {
Unused << PClipboardReadRequestParent::Send__delete__(this);
*aTransferableDataOrError = NS_ERROR_FAILURE;
return IPC_OK();
}
// Create transferable
auto result = CreateTransferable(aFlavors);
if (result.isErr()) {
*aTransferableDataOrError = result.unwrapErr();
return IPC_OK();
}
nsCOMPtr<nsITransferable> trans = result.unwrap();
nsresult rv = mClipboardDataSnapshot->GetDataSync(trans);
if (NS_FAILED(rv)) {
*aTransferableDataOrError = rv;
if (NS_FAILED(mClipboardDataSnapshot->GetValid(&valid)) || !valid) {
Unused << PClipboardReadRequestParent::Send__delete__(this);
}
return IPC_OK();
}
dom::IPCTransferableData ipcTransferableData;
nsContentUtils::TransferableToIPCTransferableData(
trans, &ipcTransferableData, true /* aInSyncMessage */, mManager);
*aTransferableDataOrError = std::move(ipcTransferableData);
return IPC_OK();
}
} // namespace mozilla

View File

@@ -26,6 +26,9 @@ class ClipboardReadRequestParent final : public PClipboardReadRequestParent {
// PClipboardReadRequestParent
IPCResult RecvGetData(const nsTArray<nsCString>& aFlavors,
GetDataResolver&& aResolver);
IPCResult RecvGetDataSync(
const nsTArray<nsCString>& aFlavors,
dom::IPCTransferableDataOrError* aTransferableDataOrError);
private:
~ClipboardReadRequestParent() = default;

View File

@@ -12,11 +12,12 @@ using nsContentPolicyType from "nsIContentPolicy.h";
namespace mozilla {
protocol PClipboardReadRequest {
sync protocol PClipboardReadRequest {
manager PContent;
parent:
async GetData(nsCString[] aFlavors) returns (IPCTransferableDataOrError aTransferableData);
sync GetDataSync(nsCString[] aFlavors) returns (IPCTransferableDataOrError aTransferableData);
both:
async __delete__();

View File

@@ -1080,6 +1080,77 @@ NS_IMETHODIMP nsBaseClipboard::ClipboardDataSnapshot::GetData(
return NS_OK;
}
NS_IMETHODIMP nsBaseClipboard::ClipboardDataSnapshot::GetDataSync(
nsITransferable* aTransferable) {
MOZ_CLIPBOARD_LOG("ClipboardDataSnapshot::GetDataSync: %p", this);
if (!aTransferable) {
return NS_ERROR_INVALID_ARG;
}
nsTArray<nsCString> flavors;
nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
if (NS_FAILED(rv)) {
return rv;
}
// If the requested flavor is not in the list, throw an error.
for (const auto& flavor : flavors) {
if (!mFlavors.Contains(flavor)) {
return NS_ERROR_FAILURE;
}
}
if (!IsValid()) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(mClipboard);
if (mFromCache) {
const auto* clipboardCache =
mClipboard->GetClipboardCacheIfValid(mClipboardType);
// `IsValid()` above ensures we should get a valid cache and matched
// sequence number here.
MOZ_DIAGNOSTIC_ASSERT(clipboardCache);
MOZ_DIAGNOSTIC_ASSERT(clipboardCache->GetSequenceNumber() ==
mSequenceNumber);
if (NS_SUCCEEDED(clipboardCache->GetData(aTransferable))) {
bool shouldAllowContent = mozilla::contentanalysis::ContentAnalysis::
CheckClipboardContentAnalysisSync(
mClipboard,
mRequestingWindowContext ? mRequestingWindowContext->Canonical()
: nullptr,
aTransferable, mClipboardType);
if (shouldAllowContent) {
return NS_OK;
}
aTransferable->ClearAllData();
return NS_ERROR_CONTENT_BLOCKED;
}
// At this point we can't satisfy the request from cache data so let's look
// for things other people put on the system clipboard.
}
rv = mClipboard->GetNativeClipboardData(aTransferable, mClipboardType);
if (NS_FAILED(rv)) {
return rv;
}
bool shouldAllowContent = mozilla::contentanalysis::ContentAnalysis::
CheckClipboardContentAnalysisSync(
mClipboard,
mRequestingWindowContext ? mRequestingWindowContext->Canonical()
: nullptr,
aTransferable, mClipboardType);
if (shouldAllowContent) {
return NS_OK;
}
aTransferable->ClearAllData();
return NS_ERROR_CONTENT_BLOCKED;
}
bool nsBaseClipboard::ClipboardDataSnapshot::IsValid() {
if (!mClipboard) {
return false;

View File

@@ -204,6 +204,52 @@ NS_IMETHODIMP ClipboardDataSnapshotProxy::GetData(
return NS_OK;
}
NS_IMETHODIMP ClipboardDataSnapshotProxy::GetDataSync(
nsITransferable* aTransferable) {
if (!aTransferable) {
return NS_ERROR_INVALID_ARG;
}
// Get a list of flavors this transferable can import
nsTArray<nsCString> flavors;
nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
if (NS_FAILED(rv)) {
return rv;
}
MOZ_ASSERT(mActor);
// If the requested flavor is not in the list, throw an error.
for (const auto& flavor : flavors) {
if (!mActor->FlavorList().Contains(flavor)) {
return NS_ERROR_FAILURE;
}
}
if (!mActor->CanSend()) {
return NS_ERROR_FAILURE;
}
IPCTransferableDataOrError ipcTransferableDataOrError;
bool success = mActor->SendGetDataSync(flavors, &ipcTransferableDataOrError);
if (!success) {
return NS_ERROR_FAILURE;
}
if (ipcTransferableDataOrError.type() ==
IPCTransferableDataOrError::Tnsresult) {
MOZ_ASSERT(NS_FAILED(ipcTransferableDataOrError.get_nsresult()));
return ipcTransferableDataOrError.get_nsresult();
}
rv = nsContentUtils::IPCTransferableDataToTransferable(
ipcTransferableDataOrError.get_IPCTransferableData(),
false /* aAddDataFlavor */, aTransferable,
false /* aFilterUnknownFlavors */);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
static Result<RefPtr<ClipboardDataSnapshotProxy>, nsresult>
CreateClipboardDataSnapshotProxy(
ClipboardReadRequestOrError&& aClipboardReadRequestOrError) {

View File

@@ -77,6 +77,19 @@ interface nsIClipboardDataSnapshot : nsISupports {
*/
void getData(in nsITransferable aTransferable,
in nsIAsyncClipboardRequestCallback aCallback);
/**
* Filters the flavors that `aTransferable` can import (see
* `nsITransferable::flavorsTransferableCanImport`). Every specified flavors
* must exist in `flavorList`, or the request will be rejected. If the request
* remains valid, it retrieves the data for the first flavor. The data is then
* set for `aTransferable`.
*
* @param aTransferable
* The transferable which contains the flavors to be read.
* @result NS_OK if no errors
*/
void getDataSync(in nsITransferable aTransferable);
};
[scriptable, uuid(ce23c1c4-58fd-4c33-8579-fa0796d9652c)]

View File

@@ -238,3 +238,32 @@ function asyncClipboardRequestGetData(aRequest, aFlavor, aThrows = false) {
}
});
}
function syncClipboardRequestGetData(aRequest, aFlavor, aThrows = false) {
var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
Ci.nsITransferable
);
trans.init(null);
trans.addDataFlavor(aFlavor);
let error = undefined;
try {
aRequest.getDataSync(trans);
try {
var data = SpecialPowers.createBlankObject();
trans.getTransferData(aFlavor, data);
return data.value.QueryInterface(Ci.nsISupportsString).data;
} catch (ex) {
// should widget set empty string to transferable when there no
// data in system clipboard?
return "";
}
} catch (e) {
error = e;
return error;
} finally {
ok(
aThrows === (error !== undefined),
`nsIAsyncGetClipboardData.getData should ${aThrows ? "throw" : "success"}`
);
}
}

View File

@@ -68,6 +68,7 @@ clipboardTypes.forEach(function (type) {
await asyncClipboardRequestGetData(request, "text/plain", true).catch(
() => {}
);
syncClipboardRequestGetData(request, "text/plain", true);
});
add_task(async function test_clipboard_getDataSnapshot_after_write() {
@@ -82,11 +83,19 @@ clipboardTypes.forEach(function (type) {
"Check data"
);
ok(request.valid, "request should still be valid");
is(
syncClipboardRequestGetData(request, "text/plain"),
str,
"Check data (sync)"
);
ok(request.valid, "request should still be valid");
// Requesting a flavor that is not in the list should throw error.
await asyncClipboardRequestGetData(request, "text/html", true).catch(
() => {}
);
ok(request.valid, "request should still be valid");
syncClipboardRequestGetData(request, "text/html", true);
ok(request.valid, "request should still be valid");
// Writing a new data should invalid existing get request.
str = writeRandomStringToClipboard("text/plain", type);
@@ -99,6 +108,8 @@ clipboardTypes.forEach(function (type) {
}
);
ok(!request.valid, "request should no longer be valid");
syncClipboardRequestGetData(request, "text/plain", true);
ok(!request.valid, "request should no longer be valid");
info(`check clipboard data again`);
request = await getClipboardDataSnapshot(type);
@@ -108,6 +119,11 @@ clipboardTypes.forEach(function (type) {
str,
"Check data"
);
is(
syncClipboardRequestGetData(request, "text/plain"),
str,
"Check data (sync)"
);
cleanupAllClipboard();
});
@@ -124,6 +140,12 @@ clipboardTypes.forEach(function (type) {
"Check data"
);
ok(request.valid, "request should still be valid");
is(
syncClipboardRequestGetData(request, "text/plain"),
str,
"Check data (sync)"
);
ok(request.valid, "request should still be valid");
// Empty clipboard data
emptyClipboardData(type);
@@ -155,16 +177,25 @@ add_task(async function test_html_data() {
let request = await getClipboardDataSnapshot(clipboard.kGlobalClipboard);
isDeeply(request.flavorList, ["text/html"], "Check flavorList");
// On Windows, widget adds extra data into HTML clipboard.
let expectedData = navigator.platform.includes("Win")
? `<html><body>\n<!--StartFragment-->${html_str}<!--EndFragment-->\n</body>\n</html>`
: html_str;
is(
await asyncClipboardRequestGetData(request, "text/html"),
// On Windows, widget adds extra data into HTML clipboard.
navigator.platform.includes("Win")
? `<html><body>\n<!--StartFragment-->${html_str}<!--EndFragment-->\n</body>\n</html>`
: html_str,
expectedData,
"Check data"
);
// Requesting a flavor that is not in the list should throw error.
await asyncClipboardRequestGetData(request, "text/plain", true).catch(
() => {}
);
is(
syncClipboardRequestGetData(request, "text/html"),
expectedData,
"Check data (sync)"
);
// Requesting a flavor that is not in the list should throw error.
syncClipboardRequestGetData(request, "text/plain", true);
});

View File

@@ -51,6 +51,7 @@ clipboardTypes.forEach(function (type) {
await asyncClipboardRequestGetData(request, "text/plain", true).catch(
() => {}
);
syncClipboardRequestGetData(request, "text/plain", true);
});
add_task(async function test_clipboard_getDataSnapshotSync_after_write() {
@@ -65,11 +66,19 @@ clipboardTypes.forEach(function (type) {
"Check data"
);
ok(request.valid, "request should still be valid");
is(
syncClipboardRequestGetData(request, "text/plain"),
str,
"Check data (sync)"
);
ok(request.valid, "request should still be valid");
// Requesting a flavor that is not in the list should throw error.
await asyncClipboardRequestGetData(request, "text/html", true).catch(
() => {}
);
ok(request.valid, "request should still be valid");
syncClipboardRequestGetData(request, "text/html", true);
ok(request.valid, "request should still be valid");
// Writing a new data should invalid existing get request.
str = writeRandomStringToClipboard("text/plain", type);
@@ -82,6 +91,8 @@ clipboardTypes.forEach(function (type) {
}
);
ok(!request.valid, "request should no longer be valid");
syncClipboardRequestGetData(request, "text/plain", true);
ok(!request.valid, "request should no longer be valid");
info(`check clipboard data again`);
request = getClipboardDataSnapshotSync(type);
@@ -91,6 +102,11 @@ clipboardTypes.forEach(function (type) {
str,
"Check data"
);
is(
syncClipboardRequestGetData(request, "text/plain"),
str,
"Check data (sync)"
);
cleanupAllClipboard();
});
@@ -138,16 +154,25 @@ add_task(async function test_clipboard_getDataSnapshotSync_html_data() {
let request = getClipboardDataSnapshotSync(clipboard.kGlobalClipboard);
isDeeply(request.flavorList, ["text/html"], "Check flavorList");
// On Windows, widget adds extra data into HTML clipboard.
let expectedData = navigator.platform.includes("Win")
? `<html><body>\n<!--StartFragment-->${html_str}<!--EndFragment-->\n</body>\n</html>`
: html_str;
is(
await asyncClipboardRequestGetData(request, "text/html"),
// On Windows, widget adds extra data into HTML clipboard.
navigator.platform.includes("Win")
? `<html><body>\n<!--StartFragment-->${html_str}<!--EndFragment-->\n</body>\n</html>`
: html_str,
expectedData,
"Check data"
);
// Requesting a flavor that is not in the list should throw error.
await asyncClipboardRequestGetData(request, "text/plain", true).catch(
() => {}
);
is(
syncClipboardRequestGetData(request, "text/html"),
expectedData,
"Check data (sync)"
);
// Requesting a flavor that is not in the list should throw error.
syncClipboardRequestGetData(request, "text/plain", true);
});