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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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__();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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"}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user