Bug 1915351 part 3 - add entry point to get all clipboard to actor r=dlp-reviewers,ipc-reviewers,nika,edgar,handyman

Add an entry point to ClipboardContentAnalysisParent to get all the
data on the clipboard, which also analyzes all the clipboard contents.

Differential Revision: https://phabricator.services.mozilla.com/D223412
This commit is contained in:
Greg Stoll 2024-10-28 22:37:57 +00:00
parent d836de2847
commit c12829c03e
7 changed files with 253 additions and 35 deletions

View File

@ -98,6 +98,8 @@ description = Only used by gtests
# Clipboard
[PClipboardContentAnalysis::GetClipboard]
description = Called from PContent::GetClipboard which is a legacy synchronous clipboard API
[PClipboardContentAnalysis::GetAllClipboardDataSync]
description = Called from synchronous DataTransfer constructor
[PClipboardReadRequest::GetDataSync]
description = Called from synchronous DataTransfer constructor
[PContent::GetClipboard]

View File

@ -5,7 +5,9 @@
#include "ContentAnalysis.h"
#include "mozilla/ClipboardContentAnalysisParent.h"
#include "mozilla/ClipboardReadRequestParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
@ -23,7 +25,7 @@ using ClipboardResultPromise =
static RefPtr<ClipboardResultPromise> GetClipboardImpl(
const nsTArray<nsCString>& aTypes,
nsIClipboard::ClipboardType aWhichClipboard,
uint64_t aRequestingWindowContextId,
uint64_t aRequestingWindowContextId, bool aCheckAllContent,
dom::ThreadsafeContentParentHandle* aRequestingContentParent) {
AssertIsOnMainThread();
@ -65,20 +67,55 @@ static RefPtr<ClipboardResultPromise> GetClipboardImpl(
return ClipboardResultPromise::CreateAndReject(
transferableToCheck.unwrapErr(), __func__);
}
nsCOMPtr<nsITransferable> transferable = transferableToCheck.unwrap();
if (aCheckAllContent) {
for (const auto& type : aTypes) {
AutoTArray<nsCString, 1> singleTypeArray{type};
auto singleTransferableToCheck =
dom::ContentParent::CreateClipboardTransferable(singleTypeArray);
if (singleTransferableToCheck.isErr()) {
return ClipboardResultPromise::CreateAndReject(
singleTransferableToCheck.unwrapErr(), __func__);
}
// Pass nullptr for the window here because we will be doing
// content analysis ourselves asynchronously (so it doesn't block
// main thread we're running on now)
nsCOMPtr transferable = transferableToCheck.unwrap();
// Ideally we would be calling GetDataSnapshot() here to avoid blocking the
// main thread (and this would mean we could also pass in the window here so
// we wouldn't have to duplicate the Content Analysis code below). See
// bug 1908280.
nsresult rv = clipboard->GetData(transferable, aWhichClipboard, nullptr);
if (NS_FAILED(rv)) {
return ClipboardResultPromise::CreateAndReject(rv, __func__);
// Pass nullptr for the window here because we will be doing
// content analysis ourselves asynchronously (so it doesn't block
// main thread we're running on now)
nsCOMPtr singleTransferable = singleTransferableToCheck.unwrap();
// Ideally we would be calling GetDataSnapshot() here to avoid blocking
// the main thread (and this would mean we could also pass in the window
// here so we wouldn't have to duplicate the Content Analysis code below).
// See bug 1908280.
nsresult rv =
clipboard->GetData(singleTransferable, aWhichClipboard, nullptr);
if (NS_FAILED(rv)) {
return ClipboardResultPromise::CreateAndReject(rv, __func__);
}
nsCOMPtr<nsISupports> data;
rv =
singleTransferable->GetTransferData(type.get(), getter_AddRefs(data));
// This call will fail if the data is null
if (NS_SUCCEEDED(rv)) {
rv = transferable->SetTransferData(type.get(), data);
if (NS_FAILED(rv)) {
return ClipboardResultPromise::CreateAndReject(rv, __func__);
}
}
}
} else {
// Pass nullptr for the window here because we will be doing
// content analysis ourselves asynchronously (so it doesn't block
// main thread we're running on now)
//
// Ideally we would be calling GetDataSnapshot() here to avoid blocking the
// main thread (and this would mean we could also pass in the window here so
// we wouldn't have to duplicate the Content Analysis code below). See
// bug 1908280.
nsresult rv = clipboard->GetData(transferable, aWhichClipboard, nullptr);
if (NS_FAILED(rv)) {
return ClipboardResultPromise::CreateAndReject(rv, __func__);
}
}
auto resultPromise = MakeRefPtr<ClipboardResultPromise::Private>(__func__);
auto contentAnalysisCallback =
@ -106,41 +143,51 @@ static RefPtr<ClipboardResultPromise> GetClipboardImpl(
contentanalysis::ContentAnalysis::CheckClipboardContentAnalysis(
static_cast<nsBaseClipboard*>(clipboard.get()), window, transferable,
aWhichClipboard, contentAnalysisCallback);
aWhichClipboard, contentAnalysisCallback, aCheckAllContent);
return resultPromise;
}
} // namespace
ipc::IPCResult ClipboardContentAnalysisParent::RecvGetClipboard(
ipc::IPCResult ClipboardContentAnalysisParent::GetSomeClipboardData(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const uint64_t& aRequestingWindowContextId,
const uint64_t& aRequestingWindowContextId, bool aCheckAllContent,
IPCTransferableDataOrError* aTransferableDataOrError) {
// The whole point of having this actor is that it runs on a background thread
// and so waiting for the content analysis result won't cause the main thread
// to use SpinEventLoopUntil() which can cause a shutdownhang per bug 1901197.
MOZ_ASSERT(!NS_IsMainThread());
Monitor mon("ClipboardContentAnalysisParent::RecvGetClipboard");
Monitor mon("ClipboardContentAnalysisParent::GetSomeClipboardData");
InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
[&]() {
return GetClipboardImpl(aTypes, aWhichClipboard,
aRequestingWindowContextId,
mThreadsafeContentParentHandle);
return GetClipboardImpl(
aTypes, aWhichClipboard, aRequestingWindowContextId,
aCheckAllContent, mThreadsafeContentParentHandle);
})
->Then(GetMainThreadSerialEventTarget(), __func__,
[&](ClipboardResultPromise::ResolveOrRejectValue&& aResult) {
AssertIsOnMainThread();
// Acquire the lock, pass the data back to the background
// thread, and notify the background thread that work is
// complete.
MonitorAutoLock lock(mon);
if (aResult.IsResolve()) {
*aTransferableDataOrError = std::move(aResult.ResolveValue());
} else {
auto monitor = MakeScopeExit([&]() { mon.Notify(); });
if (aResult.IsReject()) {
*aTransferableDataOrError = aResult.RejectValue();
return;
}
if (aCheckAllContent) {
// Content Analysis succeeded on everything
// Just return the flavors that were asked for
IPCTransferableData analyzedData =
std::move(aResult.ResolveValue());
nsTArray<IPCTransferableDataItem> dataItems;
for (auto& item : analyzedData.items()) {
if (aTypes.Contains(item.flavor())) {
dataItems.AppendElement(std::move(item));
}
}
IPCTransferableData data(std::move(dataItems));
*aTransferableDataOrError = std::move(data);
} else {
*aTransferableDataOrError = std::move(aResult.ResolveValue());
}
mon.Notify();
});
{
@ -155,11 +202,38 @@ ipc::IPCResult ClipboardContentAnalysisParent::RecvGetClipboard(
IPCTransferableDataOrError::Tnsresult) {
NS_WARNING(nsPrintfCString(
"ClipboardContentAnalysisParent::"
"RecvGetClipboard got error %x",
"GetSomeClipboardData got error %x",
static_cast<int>(aTransferableDataOrError->get_nsresult()))
.get());
}
return IPC_OK();
}
ipc::IPCResult ClipboardContentAnalysisParent::RecvGetClipboard(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const uint64_t& aRequestingWindowContextId,
IPCTransferableDataOrError* aTransferableDataOrError) {
// The whole point of having this actor is that it runs on a background thread
// and so waiting for the content analysis result won't cause the main thread
// to use SpinEventLoopUntil() which can cause a shutdownhang per bug 1901197.
MOZ_ASSERT(!NS_IsMainThread());
return GetSomeClipboardData(
std::move(aTypes), aWhichClipboard, aRequestingWindowContextId,
/* aCheckAllContent */ false, aTransferableDataOrError);
}
ipc::IPCResult ClipboardContentAnalysisParent::RecvGetAllClipboardDataSync(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const uint64_t& aRequestingWindowContextId,
IPCTransferableDataOrError* aTransferableDataOrError) {
MOZ_ASSERT(!NS_IsMainThread());
return GetSomeClipboardData(
std::move(aTypes), aWhichClipboard, aRequestingWindowContextId,
/* aCheckAllContent */ true, aTransferableDataOrError);
}
} // namespace mozilla

View File

@ -25,6 +25,11 @@ class ClipboardContentAnalysisParent final
private:
~ClipboardContentAnalysisParent() = default;
RefPtr<dom::ThreadsafeContentParentHandle> mThreadsafeContentParentHandle;
ipc::IPCResult GetSomeClipboardData(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const uint64_t& aRequestingWindowContextId, bool aCheckAllContent,
IPCTransferableDataOrError* aTransferableDataOrError);
public:
ipc::IPCResult RecvGetClipboard(
@ -32,6 +37,11 @@ class ClipboardContentAnalysisParent final
const nsIClipboard::ClipboardType& aWhichClipboard,
const uint64_t& aRequestingWindowContextId,
IPCTransferableDataOrError* aTransferableDataOrError);
ipc::IPCResult RecvGetAllClipboardDataSync(
nsTArray<nsCString>&& aTypes,
const nsIClipboard::ClipboardType& aWhichClipboard,
const uint64_t& aRequestingWindowContextId,
IPCTransferableDataOrError* aTransferableDataOrError);
};
} // namespace mozilla

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include IPCTransferable;
include protocol PContent;
include "mozilla/widget/WidgetMessageUtils.h";
@ -18,11 +19,18 @@ sync protocol PClipboardContentAnalysis
parent:
// Given a list of supported types, returns the clipboard data for the
// first type that matches.
// aRequestingWindowContext is the window that is requesting the clipboard,
// which is used for content analysis.
// aRequestingWindowContextId is the ID of the window that is requesting
// the clipboard, which is used for content analysis.
sync GetClipboard(nsCString[] aTypes, ClipboardType aWhichClipboard,
uint64_t aRequestingWindowContextId)
returns (IPCTransferableDataOrError transferableDataOrError);
// Requests getting data from clipboard.
// aRequestingWindowContextId is the ID of the window that is requesting
// the clipboard, which is used for content analysis.
sync GetAllClipboardDataSync(nsCString[] aTypes, ClipboardType aWhichClipboard,
uint64_t aRequestingWindowContextId)
returns (IPCTransferableDataOrError transferableDataOrError);
};
}

View File

@ -1184,6 +1184,87 @@ bool nsBaseClipboard::ClipboardDataSnapshot::IsValid() {
return true;
}
NS_IMPL_ISUPPORTS(nsBaseClipboard::ClipboardPopulatedDataSnapshot,
nsIClipboardDataSnapshot)
nsBaseClipboard::ClipboardPopulatedDataSnapshot::ClipboardPopulatedDataSnapshot(
nsITransferable* aTransferable)
: mTransferable(aTransferable) {
MOZ_ASSERT(mTransferable);
aTransferable->FlavorsTransferableCanExport(mFlavors);
}
NS_IMETHODIMP nsBaseClipboard::ClipboardPopulatedDataSnapshot::GetValid(
bool* aOutResult) {
// Since this is a snapshot of what the clipboard data was, this is always
// valid
*aOutResult = true;
return NS_OK;
}
NS_IMETHODIMP nsBaseClipboard::ClipboardPopulatedDataSnapshot::GetFlavorList(
nsTArray<nsCString>& aFlavors) {
aFlavors.AppendElements(mFlavors);
return NS_OK;
}
NS_IMETHODIMP nsBaseClipboard::ClipboardPopulatedDataSnapshot::GetData(
nsITransferable* aTransferable,
nsIAsyncClipboardRequestCallback* aCallback) {
if (!aTransferable || !aCallback) {
return NS_ERROR_INVALID_ARG;
}
NS_DispatchToMainThread(NS_NewRunnableFunction(
"ClipboardPopulatedDataSnapshot::GetData",
[self = RefPtr{this}, transferable = RefPtr{aTransferable},
callback = RefPtr{aCallback}]() {
nsresult rv = self->GetDataSync(transferable);
callback->OnComplete(rv);
}));
return NS_OK;
}
NS_IMETHODIMP nsBaseClipboard::ClipboardPopulatedDataSnapshot::GetDataSync(
nsITransferable* aTransferable) {
MOZ_CLIPBOARD_LOG("ClipboardPopulatedDataSnapshot::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;
}
}
// This method only fills in the data for the first flavor passed in. This
// seems weird but matches the IDL documentation and behavior.
if (!flavors.IsEmpty()) {
nsCOMPtr<nsISupports> data;
rv = mTransferable->GetTransferData(flavors[0].get(), getter_AddRefs(data));
if (NS_FAILED(rv)) {
aTransferable->ClearAllData();
return rv;
}
rv = aTransferable->SetTransferData(flavors[0].get(), data);
if (NS_FAILED(rv)) {
aTransferable->ClearAllData();
return rv;
}
}
return NS_OK;
}
mozilla::Maybe<uint64_t> nsBaseClipboard::GetClipboardCacheInnerWindowId(
ClipboardType aClipboardType) {
auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType);

View File

@ -85,6 +85,19 @@ class nsBaseClipboard : public nsIClipboard {
virtual mozilla::Result<int32_t, nsresult> GetNativeClipboardSequenceNumber(
ClipboardType aWhichClipboard) = 0;
class ClipboardPopulatedDataSnapshot final : public nsIClipboardDataSnapshot {
public:
explicit ClipboardPopulatedDataSnapshot(nsITransferable* aTransferable);
NS_DECL_ISUPPORTS
NS_DECL_NSICLIPBOARDDATASNAPSHOT
private:
virtual ~ClipboardPopulatedDataSnapshot() = default;
nsCOMPtr<nsITransferable> mTransferable;
// List of available data types for clipboard content.
nsTArray<nsCString> mFlavors;
};
protected:
virtual ~nsBaseClipboard();

View File

@ -332,9 +332,39 @@ NS_IMETHODIMP nsClipboardProxy::GetDataSnapshotSync(
aWhichClipboard);
return NS_ERROR_FAILURE;
}
ContentChild* contentChild = ContentChild::GetSingleton();
if (MOZ_UNLIKELY(nsIContentAnalysis::MightBeActive())) {
// If Content Analysis is active we want to fetch all the clipboard data
// up front since we need to analyze it anyway.
RefPtr<ClipboardContentAnalysisChild> contentAnalysis =
ClipboardContentAnalysisChild::GetOrCreate();
IPCTransferableDataOrError ipcTransferableDataOrError;
bool result = contentAnalysis->SendGetAllClipboardDataSync(
aFlavorList, aWhichClipboard, aRequestingWindowContext->InnerWindowId(),
&ipcTransferableDataOrError);
if (!result) {
return NS_ERROR_FAILURE;
}
if (ipcTransferableDataOrError.type() ==
IPCTransferableDataOrError::Tnsresult) {
return ipcTransferableDataOrError.get_nsresult();
}
nsresult rv;
nsCOMPtr<nsITransferable> trans =
do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
trans->Init(nullptr);
rv = nsContentUtils::IPCTransferableDataToTransferable(
ipcTransferableDataOrError.get_IPCTransferableData(),
true /* aAddDataFlavor */, trans, false /* aFilterUnknownFlavors */);
NS_ENSURE_SUCCESS(rv, rv);
auto snapshot =
mozilla::MakeRefPtr<nsBaseClipboard::ClipboardPopulatedDataSnapshot>(
trans);
snapshot.forget(_retval);
return NS_OK;
}
ClipboardReadRequestOrError requestOrError;
ContentChild* contentChild = ContentChild::GetSingleton();
contentChild->SendGetClipboardDataSnapshotSync(
aFlavorList, aWhichClipboard, aRequestingWindowContext, &requestOrError);
auto result = CreateClipboardDataSnapshotProxy(std::move(requestOrError));