Skip to content

Commit

Permalink
Bug 1858627 - Add nsIClipboard API to return nsIAsyncSetClipboardData…
Browse files Browse the repository at this point in the history
… synchrously; r=ipc-reviewers,nika

This is a sync version of `nsIClipboard.asyncGetData`, which can be used for
synchronous clipboard APIs, e.g. DataTransfer, to support the clipboard seqence
number concept, see bug 1879401.

Depends on D201657

Differential Revision: https://phabricator.services.mozilla.com/D201364
  • Loading branch information
EdgarChen committed Mar 13, 2024
1 parent 5c139ae commit a14b5b8
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 49 deletions.
42 changes: 42 additions & 0 deletions dom/ipc/ContentParent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3647,6 +3647,48 @@ mozilla::ipc::IPCResult ContentParent::RecvGetClipboardAsync(
return IPC_OK();
}

mozilla::ipc::IPCResult ContentParent::RecvGetClipboardDataSnapshotSync(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
ClipboardReadRequestOrError* aRequestOrError) {
// If the requesting context has been discarded, cancel the paste.
if (aRequestingWindowContext.IsDiscarded()) {
*aRequestOrError = NS_ERROR_FAILURE;
return IPC_OK();
}

RefPtr<WindowGlobalParent> requestingWindow =
aRequestingWindowContext.get_canonical();
if (requestingWindow && requestingWindow->GetContentParent() != this) {
return IPC_FAIL(
this, "attempt to paste into WindowContext loaded in another process");
}

nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID));
if (!clipboard) {
*aRequestOrError = NS_ERROR_FAILURE;
return IPC_OK();
}

nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData;
nsresult rv =
clipboard->GetDataSnapshotSync(aTypes, aWhichClipboard, requestingWindow,
getter_AddRefs(asyncGetClipboardData));
if (NS_FAILED(rv)) {
*aRequestOrError = rv;
return IPC_OK();
}

auto result = CreateClipboardReadRequest(*this, *asyncGetClipboardData);
if (result.isErr()) {
*aRequestOrError = result.unwrapErr();
return IPC_OK();
}

*aRequestOrError = result.unwrap();
return IPC_OK();
}

already_AddRefed<PClipboardWriteRequestParent>
ContentParent::AllocPClipboardWriteRequestParent(
const int32_t& aClipboardType) {
Expand Down
5 changes: 5 additions & 0 deletions dom/ipc/ContentParent.h
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,11 @@ class ContentParent final : public PContentParent,
mozilla::NotNull<nsIPrincipal*> aRequestingPrincipal,
GetClipboardAsyncResolver&& aResolver);

mozilla::ipc::IPCResult RecvGetClipboardDataSnapshotSync(
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
ClipboardReadRequestOrError* aRequestOrError);

already_AddRefed<PClipboardWriteRequestParent>
AllocPClipboardWriteRequestParent(const int32_t& aClipboardType);

Expand Down
5 changes: 5 additions & 0 deletions dom/ipc/PContent.ipdl
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,11 @@ parent:
nsIPrincipal aRequestingPrincipal)
returns (ClipboardReadRequestOrError aClipboardReadRequestOrError);

// Requests getting data from clipboard.
sync GetClipboardDataSnapshotSync(nsCString[] aTypes, int32_t aWhichClipboard,
MaybeDiscardedWindowContext aRequestingWindowContext)
returns (ClipboardReadRequestOrError aClipboardReadRequestOrError);

// Clears the clipboard.
async EmptyClipboard(int32_t aWhichClipboard);

Expand Down
16 changes: 10 additions & 6 deletions ipc/ipdl/sync-messages.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ description = Only used by gtests
[PQuotaTest::TryInspect_Success_CustomErr_IpcFail]
description = Only used by gtests

# Clipboard
[PContent::GetClipboard]
description = Legacy synchronous clipboard API
[PContent::ClipboardHasType]
description = Legacy synchronous clipboard API
[PContent::GetExternalClipboardFormats]
description = Retrieve supported clipboard formats synchronously
[PContent::GetClipboardDataSnapshotSync]
description = Legacy synchronous clipboard API

# The rest
[PHeapSnapshotTempFileHelper::OpenHeapSnapshotTempFile]
description = legacy sync IPC - please add detailed description
Expand Down Expand Up @@ -56,12 +66,6 @@ description = JS MessageManager implementation
description = legacy sync IPC - please add detailed description
[PContent::PURLClassifier]
description = legacy sync IPC - please add detailed description
[PContent::GetClipboard]
description = Legacy synchronous clipboard API
[PContent::ClipboardHasType]
description = Legacy synchronous clipboard API
[PContent::GetExternalClipboardFormats]
description = Retrieve supported clipboard formats synchronously
[PContent::GetIconForExtension]
description = legacy sync IPC - please add detailed description
[PContent::BeginDriverCrashGuard]
Expand Down
142 changes: 105 additions & 37 deletions widget/nsBaseClipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -812,56 +812,124 @@ NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
return NS_OK;
}

void nsBaseClipboard::AsyncGetDataInternal(
already_AddRefed<nsIAsyncGetClipboardData>
nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIAsyncClipboardGetCallback* aCallback) {
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
mozilla::dom::WindowContext* aRequestingWindowContext) {
MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));

if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
// If we were the last ones to put something on the native clipboard, then
// just use the cached transferable. Otherwise clear it because it isn't
// relevant any more.
if (auto* clipboardCache = GetClipboardCacheIfValid(aClipboardType)) {
nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
MOZ_ASSERT(cachedTransferable);

nsTArray<nsCString> transferableFlavors;
if (NS_SUCCEEDED(cachedTransferable->FlavorsTransferableCanExport(
transferableFlavors))) {
nsTArray<nsCString> results;
for (const auto& transferableFlavor : transferableFlavors) {
for (const auto& flavor : aFlavorList) {
// XXX We need special check for image as we always put the
// image as "native" on the clipboard.
if (transferableFlavor.Equals(flavor) ||
(transferableFlavor.Equals(kNativeImageMime) &&
nsContentUtils::IsFlavorImage(flavor))) {
MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
results.AppendElement(flavor);
}
}
}
if (!mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
return nullptr;
}

// If we were the last ones to put something on the native clipboard, then
// just use the cached transferable. Otherwise clear it because it isn't
// relevant any more.
ClipboardCache* clipboardCache = GetClipboardCacheIfValid(aClipboardType);
if (!clipboardCache) {
return nullptr;
}

nsITransferable* cachedTransferable = clipboardCache->GetTransferable();
MOZ_ASSERT(cachedTransferable);

nsTArray<nsCString> transferableFlavors;
if (NS_FAILED(cachedTransferable->FlavorsTransferableCanExport(
transferableFlavors))) {
return nullptr;
}

// XXX Do we need to check system clipboard for the flavors that cannot
// be found in cache?
auto asyncGetClipboardData = mozilla::MakeRefPtr<AsyncGetClipboardData>(
aClipboardType, clipboardCache->GetSequenceNumber(),
std::move(results), true, this, aRequestingWindowContext);
aCallback->OnSuccess(asyncGetClipboardData);
return;
nsTArray<nsCString> results;
for (const auto& transferableFlavor : transferableFlavors) {
for (const auto& flavor : aFlavorList) {
// XXX We need special check for image as we always put the
// image as "native" on the clipboard.
if (transferableFlavor.Equals(flavor) ||
(transferableFlavor.Equals(kNativeImageMime) &&
nsContentUtils::IsFlavorImage(flavor))) {
MOZ_CLIPBOARD_LOG(" has %s", flavor.get());
results.AppendElement(flavor);
}
}
}

// 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.
// XXX Do we need to check system clipboard for the flavors that cannot
// be found in cache?
return mozilla::MakeAndAddRef<AsyncGetClipboardData>(
aClipboardType, clipboardCache->GetSequenceNumber(), std::move(results),
true /* aFromCache */, this, aRequestingWindowContext);
}

void nsBaseClipboard::AsyncGetDataInternal(
const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIAsyncClipboardGetCallback* aCallback) {
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));

if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
MaybeCreateGetRequestFromClipboardCache(aFlavorList, aClipboardType,
aRequestingWindowContext)) {
aCallback->OnSuccess(asyncGetClipboardData);
return;
}

// 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.
MaybeRetryGetAvailableFlavors(aFlavorList, aClipboardType, aCallback,
kGetAvailableFlavorsRetryCount,
aRequestingWindowContext);
}

NS_IMETHODIMP nsBaseClipboard::GetDataSnapshotSync(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIAsyncGetClipboardData** _retval) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);

*_retval = nullptr;

if (aFlavorList.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}

if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
aWhichClipboard);
return NS_ERROR_FAILURE;
}

if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
MaybeCreateGetRequestFromClipboardCache(aFlavorList, aWhichClipboard,
aRequestingWindowContext)) {
asyncGetClipboardData.forget(_retval);
return NS_OK;
}

auto sequenceNumberOrError =
GetNativeClipboardSequenceNumber(aWhichClipboard);
if (sequenceNumberOrError.isErr()) {
MOZ_CLIPBOARD_LOG("%s: unable to get sequence number for clipboard %d.",
__FUNCTION__, aWhichClipboard);
return sequenceNumberOrError.unwrapErr();
}

nsTArray<nsCString> results;
for (const auto& flavor : aFlavorList) {
auto resultOrError = HasNativeClipboardDataMatchingFlavors(
AutoTArray<nsCString, 1>{flavor}, aWhichClipboard);
if (resultOrError.isOk() && resultOrError.unwrap()) {
results.AppendElement(flavor);
}
}

*_retval =
mozilla::MakeAndAddRef<AsyncGetClipboardData>(
aWhichClipboard, sequenceNumberOrError.unwrap(), std::move(results),
false /* aFromCache */, this, aRequestingWindowContext)
.take();
return NS_OK;
}

NS_IMETHODIMP nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard) {
MOZ_CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__, aWhichClipboard);

Expand Down
9 changes: 9 additions & 0 deletions widget/nsBaseClipboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ class nsBaseClipboard : public nsIClipboard {
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIPrincipal* aRequestingPrincipal,
nsIAsyncClipboardGetCallback* aCallback) override final;
NS_IMETHOD GetDataSnapshotSync(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIAsyncGetClipboardData** _retval) override final;
NS_IMETHOD EmptyClipboard(int32_t aWhichClipboard) override final;
NS_IMETHOD HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
int32_t aWhichClipboard,
Expand Down Expand Up @@ -206,6 +210,11 @@ class nsBaseClipboard : public nsIClipboard {
nsIPrincipal* aRequestingPrincipal,
nsIAsyncClipboardGetCallback* aCallback);

already_AddRefed<nsIAsyncGetClipboardData>
MaybeCreateGetRequestFromClipboardCache(
const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
mozilla::dom::WindowContext* aRequestingWindowContext);

// Track the pending request for each clipboard type separately. And only need
// to track the latest request for each clipboard type as the prior pending
// request will be canceled when a new request is made.
Expand Down
29 changes: 29 additions & 0 deletions widget/nsClipboardProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,35 @@ NS_IMETHODIMP nsClipboardProxy::AsyncGetData(
return NS_OK;
}

NS_IMETHODIMP nsClipboardProxy::GetDataSnapshotSync(
const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
mozilla::dom::WindowContext* aRequestingWindowContext,
nsIAsyncGetClipboardData** _retval) {
*_retval = nullptr;

if (aFlavorList.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}

if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)) {
MOZ_CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__,
aWhichClipboard);
return NS_ERROR_FAILURE;
}

ContentChild* contentChild = ContentChild::GetSingleton();
ClipboardReadRequestOrError requestOrError;
contentChild->SendGetClipboardDataSnapshotSync(
aFlavorList, aWhichClipboard, aRequestingWindowContext, &requestOrError);
auto result = CreateAsyncGetClipboardDataProxy(std::move(requestOrError));
if (result.isErr()) {
return result.unwrapErr();
}

result.unwrap().forget(_retval);
return NS_OK;
}

NS_IMETHODIMP
nsClipboardProxy::EmptyClipboard(int32_t aWhichClipboard) {
ContentChild::GetSingleton()->SendEmptyClipboard(aWhichClipboard);
Expand Down
25 changes: 25 additions & 0 deletions widget/nsIClipboard.idl
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,31 @@ interface nsIClipboard : nsISupports
in nsIPrincipal aRequestingPrincipal,
in nsIAsyncClipboardGetCallback aCallback);

/**
* Requests getting data from the native clipboard. This does not actually
* retreive the data, but returns a nsIAsyncGetClipboardData contains
* current avaiable data formats. If the native clipboard is updated, either
* by us or other application, the existing nsIAsyncGetClipboardData becomes
* invalid.
*
* @param aFlavorList
* Specific data formats ('flavors') that can be retrieved from the
* clipboard.
* @param aWhichClipboard
* Specifies the clipboard to which this operation applies.
* @param aRequestingWindowContext [optional]
* The window context window that is requesting the clipboard, which is
* used for content analysis. Passing null means that the content is
* exempt from content analysis. (for example, scripted clipboard read by
* system code) This parameter should not be null when calling this from a
* content process.
* @return nsIAsyncSetClipboardData if successful.
* @throws if the request can not be made.
*/
nsIAsyncGetClipboardData getDataSnapshotSync(in Array<ACString> aFlavorList,
in long aWhichClipboard,
[optional] in WindowContext aRequestingWindowContext);

/**
* This empties the clipboard and notifies the clipboard owner.
* This empties the "logical" clipboard. It does not clear the native clipboard.
Expand Down
13 changes: 8 additions & 5 deletions widget/tests/chrome.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ support-files = [

# Privacy relevant

["test_bug1123480.xhtml"]
skip-if = ["win11_2009 && bits == 32"]

["test_bug343416.xhtml"]
skip-if = ["debug"]

Expand Down Expand Up @@ -66,8 +63,8 @@ run-if = ["os == 'mac'"] # Cocoa widget test

["test_bug760802.xhtml"]

["test_clipboard_chrome.html"]
support-files = "file_test_clipboard.js"
["test_bug1123480.xhtml"]
skip-if = ["win11_2009 && bits == 32"]

["test_clipboard_asyncGetData_chrome.html"]
support-files = "file_test_clipboard_asyncGetData.js"
Expand All @@ -77,6 +74,12 @@ support-files = "file_test_clipboard_asyncSetData.js"

["test_clipboard_cache_chrome.html"]

["test_clipboard_chrome.html"]
support-files = "file_test_clipboard.js"

["test_clipboard_getDataSnapshotSync_chrome.html"]
support-files = "file_test_clipboard_getDataSnapshotSync.js"

["test_clipboard_owner_chrome.html"]

["test_composition_text_querycontent.xhtml"]
Expand Down
Loading

0 comments on commit a14b5b8

Please sign in to comment.