forked from mirrors/gecko-dev
Backed out 4 changesets (bug 1858627) for causing clipboard/paste failures. CLOSED TREE
Backed out changeset ad694926aa45 (bug 1858627) Backed out changeset aca702ea720f (bug 1858627) Backed out changeset 1d5175296606 (bug 1858627) Backed out changeset 05b957fe3079 (bug 1858627)
This commit is contained in:
parent
98a58e605d
commit
f56ca9f231
24 changed files with 174 additions and 876 deletions
|
|
@ -713,33 +713,6 @@ static Element* GetElementOrNearestFlattenedTreeParentElement(nsINode* aNode) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used while processing clipboard paste event.
|
||||
*/
|
||||
class MOZ_RAII AutoHandlingPasteEvent final {
|
||||
public:
|
||||
explicit AutoHandlingPasteEvent(nsPIDOMWindowInner* aWindow,
|
||||
DataTransfer* aDataTransfer,
|
||||
const EventMessage& aEventMessage,
|
||||
const int32_t& aClipboardType) {
|
||||
MOZ_ASSERT(aDataTransfer);
|
||||
if (aWindow && aEventMessage == ePaste &&
|
||||
aClipboardType == nsIClipboard::kGlobalClipboard) {
|
||||
aWindow->SetCurrentPasteDataTransfer(aDataTransfer);
|
||||
mInnerWindow = aWindow;
|
||||
}
|
||||
}
|
||||
|
||||
~AutoHandlingPasteEvent() {
|
||||
if (mInnerWindow) {
|
||||
mInnerWindow->SetCurrentPasteDataTransfer(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<nsPIDOMWindowInner> mInnerWindow;
|
||||
};
|
||||
|
||||
bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
|
||||
int32_t aClipboardType,
|
||||
PresShell* aPresShell,
|
||||
|
|
@ -817,15 +790,9 @@ bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
|
|||
InternalClipboardEvent evt(true, originalEventMessage);
|
||||
evt.mClipboardData = clipboardData;
|
||||
|
||||
{
|
||||
AutoHandlingPasteEvent autoHandlingPasteEvent(
|
||||
doc->GetInnerWindow(), clipboardData, aEventMessage, aClipboardType);
|
||||
|
||||
RefPtr<nsPresContext> presContext = presShell->GetPresContext();
|
||||
EventDispatcher::Dispatch(targetElement, presContext, &evt, nullptr,
|
||||
&status);
|
||||
}
|
||||
|
||||
RefPtr<nsPresContext> presContext = presShell->GetPresContext();
|
||||
EventDispatcher::Dispatch(targetElement, presContext, &evt, nullptr,
|
||||
&status);
|
||||
// If the event was cancelled, don't do the clipboard operation
|
||||
doDefault = (status != nsEventStatus_eConsumeNoDefault);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,7 +224,6 @@
|
|||
#include "nsIBrowserChild.h"
|
||||
#include "nsICancelableRunnable.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIClipboard.h"
|
||||
#include "nsIContentSecurityPolicy.h"
|
||||
#include "nsIControllers.h"
|
||||
#include "nsICookieJarSettings.h"
|
||||
|
|
@ -1433,7 +1432,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner)
|
|||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedElement)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowGlobalChild)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentPasteDataTransfer)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenubar)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mToolbar)
|
||||
|
|
@ -1542,7 +1540,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
|
|||
!tmp->mWindowGlobalChild || tmp->mWindowGlobalChild->IsClosed(),
|
||||
"How are we unlinking a window before its actor has been destroyed?");
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowGlobalChild)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCurrentPasteDataTransfer)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMenubar)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar)
|
||||
|
|
@ -7678,19 +7675,6 @@ nsPIDOMWindowInner::SaveStorageAccessPermissionRevoked() {
|
|||
return nsGlobalWindowInner::Cast(this)->StorageAccessPermissionChanged(false);
|
||||
}
|
||||
|
||||
void nsPIDOMWindowInner::SetCurrentPasteDataTransfer(
|
||||
DataTransfer* aDataTransfer) {
|
||||
MOZ_ASSERT_IF(aDataTransfer, aDataTransfer->GetEventMessage() == ePaste);
|
||||
MOZ_ASSERT_IF(aDataTransfer, aDataTransfer->ClipboardType() ==
|
||||
nsIClipboard::kGlobalClipboard);
|
||||
MOZ_ASSERT_IF(aDataTransfer, aDataTransfer->GetAsyncGetClipboardData());
|
||||
mCurrentPasteDataTransfer = aDataTransfer;
|
||||
}
|
||||
|
||||
DataTransfer* nsPIDOMWindowInner::GetCurrentPasteDataTransfer() const {
|
||||
return mCurrentPasteDataTransfer;
|
||||
}
|
||||
|
||||
bool nsPIDOMWindowInner::UsingStorageAccess() {
|
||||
WindowContext* wc = GetWindowContext();
|
||||
if (!wc) {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ class BrowsingContextGroup;
|
|||
class ClientInfo;
|
||||
class ClientState;
|
||||
class ContentFrameMessageManager;
|
||||
class DataTransfer;
|
||||
class DocGroup;
|
||||
class Document;
|
||||
class Element;
|
||||
|
|
@ -656,9 +655,6 @@ class nsPIDOMWindowInner : public mozIDOMWindow {
|
|||
};
|
||||
bool HasActiveWebTransports() { return mWebTransportCount > 0; }
|
||||
|
||||
void SetCurrentPasteDataTransfer(mozilla::dom::DataTransfer* aDataTransfer);
|
||||
mozilla::dom::DataTransfer* GetCurrentPasteDataTransfer() const;
|
||||
|
||||
protected:
|
||||
void CreatePerformanceObjectIfNeeded();
|
||||
|
||||
|
|
@ -781,10 +777,6 @@ class nsPIDOMWindowInner : public mozIDOMWindow {
|
|||
* workers.
|
||||
*/
|
||||
uint32_t mWebTransportCount = 0;
|
||||
|
||||
// Cache the DataTransfer created for a paste event, this will be reset after
|
||||
// the event is dispatched.
|
||||
RefPtr<mozilla::dom::DataTransfer> mCurrentPasteDataTransfer;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
|
||||
|
|
|
|||
|
|
@ -86,16 +86,6 @@ class ClipboardGetCallback : public nsIAsyncClipboardGetCallback {
|
|||
RefPtr<Promise> mPromise;
|
||||
};
|
||||
|
||||
static nsTArray<nsCString> MandatoryDataTypesAsCStrings() {
|
||||
// Mandatory data types defined in
|
||||
// https://w3c.github.io/clipboard-apis/#mandatory-data-types-x. The types
|
||||
// should be in the same order as kNonPlainTextExternalFormats in
|
||||
// DataTransfer.
|
||||
return nsTArray<nsCString>{nsLiteralCString(kHTMLMime),
|
||||
nsLiteralCString(kTextMime),
|
||||
nsLiteralCString(kPNGImageMime)};
|
||||
}
|
||||
|
||||
class ClipboardGetCallbackForRead final : public ClipboardGetCallback {
|
||||
public:
|
||||
explicit ClipboardGetCallbackForRead(nsIGlobalObject* aGlobal,
|
||||
|
|
@ -120,14 +110,10 @@ class ClipboardGetCallbackForRead final : public ClipboardGetCallback {
|
|||
|
||||
AutoTArray<RefPtr<ClipboardItem::ItemEntry>, 3> entries;
|
||||
for (const auto& format : flavorList) {
|
||||
// We might reuse the request from DataTransfer created for paste event,
|
||||
// which could contain more types that are not in the mandatory list.
|
||||
if (MandatoryDataTypesAsCStrings().Contains(format)) {
|
||||
auto entry = MakeRefPtr<ClipboardItem::ItemEntry>(
|
||||
mGlobal, NS_ConvertUTF8toUTF16(format));
|
||||
entry->LoadDataFromSystemClipboard(aAsyncGetClipboardData);
|
||||
entries.AppendElement(std::move(entry));
|
||||
}
|
||||
auto entry = MakeRefPtr<ClipboardItem::ItemEntry>(
|
||||
mGlobal, NS_ConvertUTF8toUTF16(format));
|
||||
entry->LoadDataFromSystemClipboard(aAsyncGetClipboardData);
|
||||
entries.AppendElement(std::move(entry));
|
||||
}
|
||||
|
||||
RefPtr<Promise> p(std::move(mPromise));
|
||||
|
|
@ -228,36 +214,6 @@ NS_IMPL_ISUPPORTS(ClipboardGetCallbackForReadText, nsIAsyncClipboardGetCallback,
|
|||
|
||||
} // namespace
|
||||
|
||||
void Clipboard::RequestRead(Promise& aPromise, const ReadRequestType& aType,
|
||||
nsPIDOMWindowInner& aOwner,
|
||||
nsIPrincipal& aSubjectPrincipal,
|
||||
nsIAsyncGetClipboardData& aRequest) {
|
||||
#ifdef DEBUG
|
||||
bool isValid = false;
|
||||
MOZ_ASSERT(NS_SUCCEEDED(aRequest.GetValid(&isValid)) && isValid);
|
||||
#endif
|
||||
|
||||
RefPtr<ClipboardGetCallback> callback;
|
||||
switch (aType) {
|
||||
case ReadRequestType::eRead: {
|
||||
callback =
|
||||
MakeRefPtr<ClipboardGetCallbackForRead>(aOwner.AsGlobal(), &aPromise);
|
||||
break;
|
||||
}
|
||||
case ReadRequestType::eReadText: {
|
||||
callback = MakeRefPtr<ClipboardGetCallbackForReadText>(&aPromise);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
MOZ_ASSERT_UNREACHABLE("Unknown read type");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(callback);
|
||||
callback->OnSuccess(&aRequest);
|
||||
}
|
||||
|
||||
void Clipboard::RequestRead(Promise* aPromise, ReadRequestType aType,
|
||||
nsPIDOMWindowInner* aOwner,
|
||||
nsIPrincipal& aPrincipal) {
|
||||
|
|
@ -283,14 +239,19 @@ void Clipboard::RequestRead(Promise* aPromise, ReadRequestType aType,
|
|||
|
||||
callback = MakeRefPtr<ClipboardGetCallbackForRead>(global, std::move(p));
|
||||
rv = clipboardService->AsyncGetData(
|
||||
MandatoryDataTypesAsCStrings(), nsIClipboard::kGlobalClipboard,
|
||||
owner->GetWindowContext(), &aPrincipal, callback);
|
||||
// Mandatory data types defined in
|
||||
// https://w3c.github.io/clipboard-apis/#mandatory-data-types-x
|
||||
AutoTArray<nsCString, 3>{nsDependentCString(kHTMLMime),
|
||||
nsDependentCString(kTextMime),
|
||||
nsDependentCString(kPNGImageMime)},
|
||||
nsIClipboard::kGlobalClipboard, owner->GetWindowContext(),
|
||||
&aPrincipal, callback);
|
||||
break;
|
||||
}
|
||||
case ReadRequestType::eReadText: {
|
||||
callback = MakeRefPtr<ClipboardGetCallbackForReadText>(std::move(p));
|
||||
rv = clipboardService->AsyncGetData(
|
||||
AutoTArray<nsCString, 1>{nsLiteralCString(kTextMime)},
|
||||
AutoTArray<nsCString, 1>{nsDependentCString(kTextMime)},
|
||||
nsIClipboard::kGlobalClipboard, owner->GetWindowContext(),
|
||||
&aPrincipal, callback);
|
||||
break;
|
||||
|
|
@ -327,24 +288,6 @@ already_AddRefed<Promise> Clipboard::ReadHelper(nsIPrincipal& aSubjectPrincipal,
|
|||
return p.forget();
|
||||
}
|
||||
|
||||
// If a "paste" clipboard event is actively being processed, we're
|
||||
// intentionally skipping permission/user-activation checks and giving the
|
||||
// webpage access to the clipboard.
|
||||
if (RefPtr<DataTransfer> dataTransfer =
|
||||
owner->GetCurrentPasteDataTransfer()) {
|
||||
// If there is valid nsIAsyncGetClipboardData, use it directly.
|
||||
if (nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData =
|
||||
dataTransfer->GetAsyncGetClipboardData()) {
|
||||
bool isValid = false;
|
||||
asyncGetClipboardData->GetValid(&isValid);
|
||||
if (isValid) {
|
||||
RequestRead(*p, aType, *owner, aSubjectPrincipal,
|
||||
*asyncGetClipboardData);
|
||||
return p.forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsTestingPrefEnabledOrHasReadPermission(aSubjectPrincipal)) {
|
||||
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
|
||||
("%s: testing pref enabled or has read permission", __FUNCTION__));
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@
|
|||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
class nsIAsyncGetClipboardData;
|
||||
#include "mozilla/dom/DataTransfer.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
|
|
@ -76,10 +75,6 @@ class Clipboard : public DOMEventTargetHelper {
|
|||
|
||||
void RequestRead(Promise* aPromise, ReadRequestType aType,
|
||||
nsPIDOMWindowInner* aOwner, nsIPrincipal& aPrincipal);
|
||||
|
||||
void RequestRead(Promise& aPromise, const ReadRequestType& aType,
|
||||
nsPIDOMWindowInner& aOwner, nsIPrincipal& aSubjectPrincipal,
|
||||
nsIAsyncGetClipboardData& aRequest);
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
|
|
|||
|
|
@ -622,56 +622,52 @@ already_AddRefed<DataTransfer> DataTransfer::MozCloneForEvent(
|
|||
}
|
||||
|
||||
// The order of the types matters. `kFileMime` needs to be one of the first two
|
||||
// types. And the order should be the same as the types order defined in
|
||||
// MandatoryDataTypesAsCStrings() for Clipboard API.
|
||||
static const nsCString kNonPlainTextExternalFormats[] = {
|
||||
nsLiteralCString(kCustomTypesMime), nsLiteralCString(kFileMime),
|
||||
nsLiteralCString(kHTMLMime), nsLiteralCString(kRTFMime),
|
||||
nsLiteralCString(kURLMime), nsLiteralCString(kURLDataMime),
|
||||
nsLiteralCString(kTextMime), nsLiteralCString(kPNGImageMime),
|
||||
nsLiteralCString(kPDFJSMime)};
|
||||
// types.
|
||||
static const char* kNonPlainTextExternalFormats[] = {
|
||||
kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime, kURLMime,
|
||||
kURLDataMime, kTextMime, kPNGImageMime, kPDFJSMime};
|
||||
|
||||
/* static */
|
||||
void DataTransfer::GetExternalClipboardFormats(const int32_t& aWhichClipboard,
|
||||
const bool& aPlainTextOnly,
|
||||
nsTArray<nsCString>* aResult) {
|
||||
MOZ_ASSERT(aResult);
|
||||
|
||||
void DataTransfer::GetExternalClipboardFormats(const bool& aPlainTextOnly,
|
||||
nsTArray<nsCString>& aResult) {
|
||||
// NOTE: When you change this method, you may need to change
|
||||
// GetExternalTransferableFormats() too since those methods should
|
||||
// work similarly.
|
||||
|
||||
MOZ_ASSERT(!mAsyncGetClipboardData);
|
||||
|
||||
RefPtr<WindowContext> wc = GetWindowContext();
|
||||
if (NS_WARN_IF(!wc)) {
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"How could this DataTransfer be created with a non-window global?");
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIClipboard> clipboard =
|
||||
do_GetService("@mozilla.org/widget/clipboard;1");
|
||||
if (!clipboard || mClipboardType < 0) {
|
||||
if (!clipboard || aWhichClipboard < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
nsCOMPtr<nsIAsyncGetClipboardData> asyncGetClipboardData;
|
||||
if (aPlainTextOnly) {
|
||||
rv = clipboard->GetDataSnapshotSync(
|
||||
AutoTArray<nsCString, 1>{nsLiteralCString(kTextMime)}, mClipboardType,
|
||||
wc, getter_AddRefs(asyncGetClipboardData));
|
||||
} else {
|
||||
AutoTArray<nsCString, ArrayLength(kNonPlainTextExternalFormats)> formats;
|
||||
formats.AppendElements(Span<const nsCString>(kNonPlainTextExternalFormats));
|
||||
rv = clipboard->GetDataSnapshotSync(formats, mClipboardType, wc,
|
||||
getter_AddRefs(asyncGetClipboardData));
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv) || !asyncGetClipboardData) {
|
||||
bool hasType;
|
||||
AutoTArray<nsCString, 1> textMime = {nsDependentCString(kTextMime)};
|
||||
nsresult rv =
|
||||
clipboard->HasDataMatchingFlavors(textMime, aWhichClipboard, &hasType);
|
||||
NS_SUCCEEDED(rv);
|
||||
if (hasType) {
|
||||
aResult->AppendElement(kTextMime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
asyncGetClipboardData->GetFlavorList(aResult);
|
||||
|
||||
mAsyncGetClipboardData = asyncGetClipboardData;
|
||||
// If not plain text only, then instead check all the other types
|
||||
for (uint32_t f = 0; f < mozilla::ArrayLength(kNonPlainTextExternalFormats);
|
||||
++f) {
|
||||
bool hasType;
|
||||
AutoTArray<nsCString, 1> format = {
|
||||
nsDependentCString(kNonPlainTextExternalFormats[f])};
|
||||
nsresult rv =
|
||||
clipboard->HasDataMatchingFlavors(format, aWhichClipboard, &hasType);
|
||||
NS_SUCCEEDED(rv);
|
||||
if (hasType) {
|
||||
aResult->AppendElement(kNonPlainTextExternalFormats[f]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
@ -699,10 +695,10 @@ void DataTransfer::GetExternalTransferableFormats(
|
|||
}
|
||||
|
||||
// If not plain text only, then instead check all the other types
|
||||
for (const auto& format : kNonPlainTextExternalFormats) {
|
||||
auto index = flavors.IndexOf(format);
|
||||
for (const char* format : kNonPlainTextExternalFormats) {
|
||||
auto index = flavors.IndexOf(nsCString(format));
|
||||
if (index != flavors.NoIndex) {
|
||||
aResult->AppendElement(format);
|
||||
aResult->AppendElement(nsCString(format));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1196,10 +1192,7 @@ void DataTransfer::Disconnect() {
|
|||
}
|
||||
}
|
||||
|
||||
void DataTransfer::ClearAll() {
|
||||
mItems->ClearAllItems();
|
||||
mAsyncGetClipboardData = nullptr;
|
||||
}
|
||||
void DataTransfer::ClearAll() { mItems->ClearAllItems(); }
|
||||
|
||||
uint32_t DataTransfer::MozItemCount() const { return mItems->MozItemCount(); }
|
||||
|
||||
|
|
@ -1267,24 +1260,6 @@ already_AddRefed<nsIGlobalObject> DataTransfer::GetGlobal() const {
|
|||
return global.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<WindowContext> DataTransfer::GetWindowContext() const {
|
||||
nsCOMPtr<nsIGlobalObject> global = GetGlobal();
|
||||
if (!global) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto* innerWindow = global->GetAsInnerWindow();
|
||||
if (!innerWindow) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return do_AddRef(innerWindow->GetWindowContext());
|
||||
}
|
||||
|
||||
nsIAsyncGetClipboardData* DataTransfer::GetAsyncGetClipboardData() const {
|
||||
return mAsyncGetClipboardData;
|
||||
}
|
||||
|
||||
nsresult DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex,
|
||||
nsIPrincipal* aPrincipal,
|
||||
bool aHidden) {
|
||||
|
|
@ -1382,13 +1357,20 @@ void DataTransfer::CacheExternalClipboardFormats(bool aPlainTextOnly) {
|
|||
"caching clipboard data for invalid event");
|
||||
|
||||
nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
|
||||
|
||||
nsTArray<nsCString> typesArray;
|
||||
GetExternalClipboardFormats(aPlainTextOnly, typesArray);
|
||||
|
||||
if (XRE_IsContentProcess()) {
|
||||
ContentChild::GetSingleton()->SendGetExternalClipboardFormats(
|
||||
mClipboardType, aPlainTextOnly, &typesArray);
|
||||
} else {
|
||||
GetExternalClipboardFormats(mClipboardType, aPlainTextOnly, &typesArray);
|
||||
}
|
||||
|
||||
if (aPlainTextOnly) {
|
||||
// The only thing that will be in types is kTextMime
|
||||
MOZ_ASSERT(typesArray.IsEmpty() || typesArray.Length() == 1);
|
||||
if (typesArray.Length() == 1) {
|
||||
MOZ_ASSERT(typesArray.Contains(kTextMime));
|
||||
CacheExternalData(kTextMime, 0, sysPrincipal, false);
|
||||
}
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
#include "mozilla/dom/DataTransferItemList.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
|
||||
class nsIAsyncGetClipboardData;
|
||||
class nsINode;
|
||||
class nsITransferable;
|
||||
class nsILoadContext;
|
||||
|
|
@ -387,6 +386,14 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
|
|||
}
|
||||
bool MozShowFailAnimation() const { return mShowFailAnimation; }
|
||||
|
||||
// Retrieve a list of clipboard formats supported
|
||||
//
|
||||
// If kFileMime is supported, then it will be placed either at
|
||||
// index 0 or at index 1 in aResult
|
||||
static void GetExternalClipboardFormats(const int32_t& aWhichClipboard,
|
||||
const bool& aPlainTextOnly,
|
||||
nsTArray<nsCString>* aResult);
|
||||
|
||||
// Retrieve a list of supporting formats in aTransferable.
|
||||
//
|
||||
// If kFileMime is supported, then it will be placed either at
|
||||
|
|
@ -421,18 +428,7 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
|
|||
|
||||
already_AddRefed<nsIGlobalObject> GetGlobal() const;
|
||||
|
||||
already_AddRefed<WindowContext> GetWindowContext() const;
|
||||
|
||||
nsIAsyncGetClipboardData* GetAsyncGetClipboardData() const;
|
||||
|
||||
protected:
|
||||
// Retrieve a list of clipboard formats supported
|
||||
//
|
||||
// If kFileMime is supported, then it will be placed either at
|
||||
// index 0 or at index 1 in aResult
|
||||
void GetExternalClipboardFormats(const bool& aPlainTextOnly,
|
||||
nsTArray<nsCString>& aResult);
|
||||
|
||||
// caches text and uri-list data formats that exist in the drag service or
|
||||
// clipboard for retrieval later.
|
||||
nsresult CacheExternalData(const char* aFormat, uint32_t aIndex,
|
||||
|
|
@ -510,11 +506,6 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
|
|||
// drag and drop.
|
||||
int32_t mClipboardType;
|
||||
|
||||
// The nsIAsyncGetClipboardData that is used for getting clipboard formats.
|
||||
// XXXedgar we should get the actual data from this in the future, see bug
|
||||
// 1879401.
|
||||
nsCOMPtr<nsIAsyncGetClipboardData> mAsyncGetClipboardData;
|
||||
|
||||
// The items contained with the DataTransfer
|
||||
RefPtr<DataTransferItemList> mItems;
|
||||
|
||||
|
|
|
|||
|
|
@ -262,158 +262,3 @@ add_task(async function test_context_menu_suppression_image() {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
function testPasteContextMenuSuppressionPasteEvent(
|
||||
aTriggerPasteFun,
|
||||
aSuppress,
|
||||
aMsg
|
||||
) {
|
||||
add_task(async function test_context_menu_suppression_paste_event() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
kContentFileUrl,
|
||||
async function (browser) {
|
||||
info(`Write data by in cross-origin frame`);
|
||||
const clipboardText = "X" + Math.random();
|
||||
await SpecialPowers.spawn(
|
||||
browser.browsingContext.children[1],
|
||||
[clipboardText],
|
||||
async text => {
|
||||
content.document.notifyUserGestureActivation();
|
||||
return content.eval(`navigator.clipboard.writeText("${text}");`);
|
||||
}
|
||||
);
|
||||
|
||||
info("Test read should show contextmenu");
|
||||
let pasteButtonIsShown = waitForPasteContextMenu();
|
||||
let readTextRequest = readText(browser);
|
||||
await pasteButtonIsShown;
|
||||
|
||||
info("Click paste button, request should be resolved");
|
||||
await promiseClickPasteButton();
|
||||
is(await readTextRequest, clipboardText, "Request should be resolved");
|
||||
|
||||
info("Test read in paste event handler");
|
||||
readTextRequest = SpecialPowers.spawn(browser, [], async () => {
|
||||
content.document.notifyUserGestureActivation();
|
||||
return content.eval(`
|
||||
(() => {
|
||||
return new Promise(resolve => {
|
||||
document.addEventListener("paste", function(e) {
|
||||
e.preventDefault();
|
||||
resolve(navigator.clipboard.readText());
|
||||
}, { once: true });
|
||||
});
|
||||
})();
|
||||
`);
|
||||
});
|
||||
|
||||
if (aSuppress) {
|
||||
let listener = function (e) {
|
||||
if (e.target.getAttribute("id") == kPasteMenuPopupId) {
|
||||
ok(!aSuppress, "paste contextmenu should not be shown");
|
||||
}
|
||||
};
|
||||
document.addEventListener("popupshown", listener);
|
||||
info(`Trigger paste event by ${aMsg}`);
|
||||
// trigger paste event
|
||||
await aTriggerPasteFun(browser);
|
||||
is(
|
||||
await readTextRequest,
|
||||
clipboardText,
|
||||
"Request should be resolved"
|
||||
);
|
||||
document.removeEventListener("popupshown", listener);
|
||||
} else {
|
||||
let pasteButtonIsShown = waitForPasteContextMenu();
|
||||
info(
|
||||
`Trigger paste event by ${aMsg}, read should still show contextmenu`
|
||||
);
|
||||
// trigger paste event
|
||||
await aTriggerPasteFun(browser);
|
||||
await pasteButtonIsShown;
|
||||
|
||||
info("Click paste button, request should be resolved");
|
||||
await promiseClickPasteButton();
|
||||
is(
|
||||
await readTextRequest,
|
||||
clipboardText,
|
||||
"Request should be resolved"
|
||||
);
|
||||
}
|
||||
|
||||
info("Test read should still show contextmenu");
|
||||
pasteButtonIsShown = waitForPasteContextMenu();
|
||||
readTextRequest = readText(browser);
|
||||
await pasteButtonIsShown;
|
||||
|
||||
info("Click paste button, request should be resolved");
|
||||
await promiseClickPasteButton();
|
||||
is(await readTextRequest, clipboardText, "Request should be resolved");
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// If platform supports selection clipboard, the middle click paste the content
|
||||
// from selection clipboard instead, in such case, we don't suppress the
|
||||
// contextmenu when access global clipboard via async clipboard API.
|
||||
if (
|
||||
!Services.clipboard.isClipboardTypeSupported(
|
||||
Services.clipboard.kSelectionClipboard
|
||||
)
|
||||
) {
|
||||
testPasteContextMenuSuppressionPasteEvent(
|
||||
async browser => {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["middlemouse.paste", true]],
|
||||
});
|
||||
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
EventUtils.synthesizeMouse(
|
||||
content.document.documentElement,
|
||||
1,
|
||||
1,
|
||||
{ button: 1 },
|
||||
content.window
|
||||
);
|
||||
});
|
||||
},
|
||||
true,
|
||||
"middle click"
|
||||
);
|
||||
}
|
||||
|
||||
testPasteContextMenuSuppressionPasteEvent(
|
||||
async browser => {
|
||||
await EventUtils.synthesizeAndWaitKey(
|
||||
"v",
|
||||
kIsMac ? { accelKey: true } : { ctrlKey: true }
|
||||
);
|
||||
},
|
||||
true,
|
||||
"keyboard shortcut"
|
||||
);
|
||||
|
||||
testPasteContextMenuSuppressionPasteEvent(
|
||||
async browser => {
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
return SpecialPowers.doCommand(content.window, "cmd_paste");
|
||||
});
|
||||
},
|
||||
true,
|
||||
"paste command"
|
||||
);
|
||||
|
||||
testPasteContextMenuSuppressionPasteEvent(
|
||||
async browser => {
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
let div = content.document.createElement("div");
|
||||
div.setAttribute("contenteditable", "true");
|
||||
content.document.documentElement.appendChild(div);
|
||||
div.focus();
|
||||
return SpecialPowers.doCommand(content.window, "cmd_pasteNoFormatting");
|
||||
});
|
||||
},
|
||||
false,
|
||||
"pasteNoFormatting command"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1984,6 +1984,12 @@ PRemotePrintJobChild* ContentChild::AllocPRemotePrintJobChild() {
|
|||
#endif
|
||||
}
|
||||
|
||||
already_AddRefed<PClipboardReadRequestChild>
|
||||
ContentChild::AllocPClipboardReadRequestChild(
|
||||
const nsTArray<nsCString>& aTypes) {
|
||||
return MakeAndAddRef<ClipboardReadRequestChild>(aTypes);
|
||||
}
|
||||
|
||||
media::PMediaChild* ContentChild::AllocPMediaChild() {
|
||||
return media::AllocPMediaChild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,6 +227,9 @@ class ContentChild final : public PContentChild,
|
|||
|
||||
PRemotePrintJobChild* AllocPRemotePrintJobChild();
|
||||
|
||||
already_AddRefed<PClipboardReadRequestChild> AllocPClipboardReadRequestChild(
|
||||
const nsTArray<nsCString>& aTypes);
|
||||
|
||||
PMediaChild* AllocPMediaChild();
|
||||
|
||||
bool DeallocPMediaChild(PMediaChild* aActor);
|
||||
|
|
|
|||
|
|
@ -3521,30 +3521,17 @@ mozilla::ipc::IPCResult ContentParent::RecvClipboardHasType(
|
|||
return IPC_OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static Result<ClipboardReadRequest, nsresult> CreateClipboardReadRequest(
|
||||
ContentParent& aContentParent,
|
||||
nsIAsyncGetClipboardData& aAsyncGetClipboardData) {
|
||||
nsTArray<nsCString> flavors;
|
||||
nsresult rv = aAsyncGetClipboardData.GetFlavorList(flavors);
|
||||
if (NS_FAILED(rv)) {
|
||||
return Err(rv);
|
||||
}
|
||||
|
||||
auto requestParent = MakeNotNull<RefPtr<ClipboardReadRequestParent>>(
|
||||
&aContentParent, &aAsyncGetClipboardData);
|
||||
|
||||
// Open a remote endpoint for our PClipboardReadRequest actor.
|
||||
ManagedEndpoint<PClipboardReadRequestChild> childEndpoint =
|
||||
aContentParent.OpenPClipboardReadRequestEndpoint(requestParent);
|
||||
if (NS_WARN_IF(!childEndpoint.IsValid())) {
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
return ClipboardReadRequest(std::move(childEndpoint), std::move(flavors));
|
||||
mozilla::ipc::IPCResult ContentParent::RecvGetExternalClipboardFormats(
|
||||
const int32_t& aWhichClipboard, const bool& aPlainTextOnly,
|
||||
nsTArray<nsCString>* aTypes) {
|
||||
MOZ_ASSERT(aTypes);
|
||||
DataTransfer::GetExternalClipboardFormats(aWhichClipboard, aPlainTextOnly,
|
||||
aTypes);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class ClipboardGetCallback final : public nsIAsyncClipboardGetCallback {
|
||||
public:
|
||||
ClipboardGetCallback(ContentParent* aContentParent,
|
||||
|
|
@ -3558,16 +3545,20 @@ class ClipboardGetCallback final : public nsIAsyncClipboardGetCallback {
|
|||
// nsIAsyncClipboardGetCallback
|
||||
NS_IMETHOD OnSuccess(
|
||||
nsIAsyncGetClipboardData* aAsyncGetClipboardData) override {
|
||||
MOZ_ASSERT(mContentParent);
|
||||
MOZ_ASSERT(aAsyncGetClipboardData);
|
||||
|
||||
auto result =
|
||||
CreateClipboardReadRequest(*mContentParent, *aAsyncGetClipboardData);
|
||||
if (result.isErr()) {
|
||||
return OnError(result.unwrapErr());
|
||||
nsTArray<nsCString> flavors;
|
||||
nsresult rv = aAsyncGetClipboardData->GetFlavorList(flavors);
|
||||
if (NS_FAILED(rv)) {
|
||||
return OnError(rv);
|
||||
}
|
||||
|
||||
mResolver(result.unwrap());
|
||||
auto requestParent = MakeNotNull<RefPtr<ClipboardReadRequestParent>>(
|
||||
mContentParent, aAsyncGetClipboardData);
|
||||
if (!mContentParent->SendPClipboardReadRequestConstructor(
|
||||
requestParent, std::move(flavors))) {
|
||||
return OnError(NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
mResolver(PClipboardReadRequestOrError(requestParent));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
@ -3630,48 +3621,6 @@ 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) {
|
||||
|
|
|
|||
|
|
@ -964,17 +964,16 @@ class ContentParent final : public PContentParent,
|
|||
const int32_t& aWhichClipboard,
|
||||
bool* aHasType);
|
||||
|
||||
mozilla::ipc::IPCResult RecvGetExternalClipboardFormats(
|
||||
const int32_t& aWhichClipboard, const bool& aPlainTextOnly,
|
||||
nsTArray<nsCString>* aTypes);
|
||||
|
||||
mozilla::ipc::IPCResult RecvGetClipboardAsync(
|
||||
nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard,
|
||||
const MaybeDiscarded<WindowContext>& aRequestingWindowContext,
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -470,13 +470,8 @@ struct IPCImage {
|
|||
ImageIntSize size;
|
||||
};
|
||||
|
||||
struct ClipboardReadRequest {
|
||||
ManagedEndpoint<PClipboardReadRequestChild> childEndpoint;
|
||||
nsCString[] availableTypes;
|
||||
};
|
||||
|
||||
union ClipboardReadRequestOrError {
|
||||
ClipboardReadRequest;
|
||||
union PClipboardReadRequestOrError {
|
||||
PClipboardReadRequest;
|
||||
nsresult;
|
||||
};
|
||||
|
||||
|
|
@ -1062,6 +1057,8 @@ child:
|
|||
|
||||
async PRemotePrintJob();
|
||||
|
||||
async PClipboardReadRequest(nsCString[] aTypes);
|
||||
|
||||
parent:
|
||||
async SynchronizeLayoutHistoryState(MaybeDiscardedBrowsingContext aContext,
|
||||
nullable nsILayoutHistoryState aState);
|
||||
|
|
@ -1229,16 +1226,14 @@ parent:
|
|||
MaybeDiscardedWindowContext aRequestingWindowContext)
|
||||
returns (IPCTransferableData transferableData);
|
||||
|
||||
// Returns a list of formats supported by the clipboard
|
||||
sync GetExternalClipboardFormats(int32_t aWhichClipboard, bool aPlainTextOnly) returns (nsCString[] aTypes);
|
||||
|
||||
// Requests getting data from clipboard.
|
||||
async GetClipboardAsync(nsCString[] aTypes, int32_t aWhichClipboard,
|
||||
MaybeDiscardedWindowContext aRequestingWindowContext,
|
||||
nsIPrincipal aRequestingPrincipal)
|
||||
returns (ClipboardReadRequestOrError aClipboardReadRequestOrError);
|
||||
|
||||
// Requests getting data from clipboard.
|
||||
sync GetClipboardDataSnapshotSync(nsCString[] aTypes, int32_t aWhichClipboard,
|
||||
MaybeDiscardedWindowContext aRequestingWindowContext)
|
||||
returns (ClipboardReadRequestOrError aClipboardReadRequestOrError);
|
||||
returns (PClipboardReadRequestOrError aClipboardReadRequest);
|
||||
|
||||
// Clears the clipboard.
|
||||
async EmptyClipboard(int32_t aWhichClipboard);
|
||||
|
|
|
|||
|
|
@ -19,14 +19,6 @@ 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::GetClipboardDataSnapshotSync]
|
||||
description = Legacy synchronous clipboard API
|
||||
|
||||
# The rest
|
||||
[PHeapSnapshotTempFileHelper::OpenHeapSnapshotTempFile]
|
||||
description = legacy sync IPC - please add detailed description
|
||||
|
|
@ -64,6 +56,12 @@ 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]
|
||||
|
|
|
|||
|
|
@ -812,124 +812,56 @@ NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
already_AddRefed<nsIAsyncGetClipboardData>
|
||||
nsBaseClipboard::MaybeCreateGetRequestFromClipboardCache(
|
||||
const nsTArray<nsCString>& aFlavorList, int32_t aClipboardType,
|
||||
mozilla::dom::WindowContext* aRequestingWindowContext) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -55,10 +55,6 @@ 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,
|
||||
|
|
@ -210,11 +206,6 @@ 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.
|
||||
|
|
|
|||
|
|
@ -174,28 +174,6 @@ NS_IMETHODIMP AsyncGetClipboardDataProxy::GetData(
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
static Result<RefPtr<AsyncGetClipboardDataProxy>, nsresult>
|
||||
CreateAsyncGetClipboardDataProxy(
|
||||
ClipboardReadRequestOrError&& aClipboardReadRequestOrError) {
|
||||
if (aClipboardReadRequestOrError.type() ==
|
||||
ClipboardReadRequestOrError::Tnsresult) {
|
||||
MOZ_ASSERT(NS_FAILED(aClipboardReadRequestOrError.get_nsresult()));
|
||||
return Err(aClipboardReadRequestOrError.get_nsresult());
|
||||
}
|
||||
|
||||
ClipboardReadRequest& request =
|
||||
aClipboardReadRequestOrError.get_ClipboardReadRequest();
|
||||
auto requestChild = MakeRefPtr<ClipboardReadRequestChild>(
|
||||
std::move(request.availableTypes()));
|
||||
if (NS_WARN_IF(
|
||||
!ContentChild::GetSingleton()->BindPClipboardReadRequestEndpoint(
|
||||
std::move(request.childEndpoint()), requestChild))) {
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
return MakeRefPtr<AsyncGetClipboardDataProxy>(requestChild);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NS_IMETHODIMP nsClipboardProxy::AsyncGetData(
|
||||
|
|
@ -220,16 +198,23 @@ NS_IMETHODIMP nsClipboardProxy::AsyncGetData(
|
|||
->Then(
|
||||
GetMainThreadSerialEventTarget(), __func__,
|
||||
/* resolve */
|
||||
[callback = nsCOMPtr{aCallback}](
|
||||
ClipboardReadRequestOrError&& aClipboardReadRequestOrError) {
|
||||
auto result = CreateAsyncGetClipboardDataProxy(
|
||||
std::move(aClipboardReadRequestOrError));
|
||||
if (result.isErr()) {
|
||||
callback->OnError(result.unwrapErr());
|
||||
[callback = nsCOMPtr{aCallback}](const PClipboardReadRequestOrError&
|
||||
aClipboardReadRequestOrError) {
|
||||
if (aClipboardReadRequestOrError.type() ==
|
||||
PClipboardReadRequestOrError::Tnsresult) {
|
||||
MOZ_ASSERT(
|
||||
NS_FAILED(aClipboardReadRequestOrError.get_nsresult()));
|
||||
callback->OnError(aClipboardReadRequestOrError.get_nsresult());
|
||||
return;
|
||||
}
|
||||
|
||||
callback->OnSuccess(result.inspect());
|
||||
auto asyncGetClipboardData = MakeRefPtr<AsyncGetClipboardDataProxy>(
|
||||
static_cast<ClipboardReadRequestChild*>(
|
||||
aClipboardReadRequestOrError.get_PClipboardReadRequest()
|
||||
.AsChild()
|
||||
.get()));
|
||||
|
||||
callback->OnSuccess(asyncGetClipboardData);
|
||||
},
|
||||
/* reject */
|
||||
[callback = nsCOMPtr{aCallback}](
|
||||
|
|
@ -239,35 +224,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -178,31 +178,6 @@ 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.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ support-files = [
|
|||
|
||||
# Privacy relevant
|
||||
|
||||
["test_bug1123480.xhtml"]
|
||||
skip-if = ["win11_2009 && bits == 32"]
|
||||
|
||||
["test_bug343416.xhtml"]
|
||||
skip-if = ["debug"]
|
||||
|
||||
|
|
@ -63,8 +66,8 @@ run-if = ["os == 'mac'"] # Cocoa widget test
|
|||
|
||||
["test_bug760802.xhtml"]
|
||||
|
||||
["test_bug1123480.xhtml"]
|
||||
skip-if = ["win11_2009 && bits == 32"]
|
||||
["test_clipboard_chrome.html"]
|
||||
support-files = "file_test_clipboard.js"
|
||||
|
||||
["test_clipboard_asyncGetData_chrome.html"]
|
||||
support-files = "file_test_clipboard_asyncGetData.js"
|
||||
|
|
@ -74,12 +77,6 @@ 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"]
|
||||
|
|
|
|||
|
|
@ -157,13 +157,6 @@ function getClipboardData(aFlavor, aClipboardType) {
|
|||
}
|
||||
}
|
||||
|
||||
function getClipboardDataSnapshotSync(aClipboardType) {
|
||||
return clipboard.getDataSnapshotSync(
|
||||
["text/plain", "text/html", "image/png"],
|
||||
aClipboardType
|
||||
);
|
||||
}
|
||||
|
||||
function asyncGetClipboardData(aClipboardType) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,153 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* import-globals-from clipboard_helper.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
clipboardTypes.forEach(function (type) {
|
||||
if (!clipboard.isClipboardTypeSupported(type)) {
|
||||
add_task(async function test_clipboard_requestGetData_not_support() {
|
||||
info(`Test getDataSnapshotSync request throwing on ${type}`);
|
||||
SimpleTest.doesThrow(
|
||||
() => clipboard.getDataSnapshotSync(["text/plain"], type),
|
||||
"Passing unsupported clipboard type should throw"
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
add_task(async function test_clipboard_getDataSnapshotSync_throw() {
|
||||
info(`Test getDataSnapshotSync request throwing on ${type}`);
|
||||
SimpleTest.doesThrow(
|
||||
() => clipboard.getDataSnapshotSync([], type),
|
||||
"Passing empty flavor list should throw"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(
|
||||
async function test_clipboard_getDataSnapshotSync_no_matched_flavor() {
|
||||
info(`Test getDataSnapshotSync have no matched flavor on ${type}`);
|
||||
cleanupAllClipboard();
|
||||
is(
|
||||
getClipboardData("text/plain", type),
|
||||
null,
|
||||
"ensure clipboard is empty"
|
||||
);
|
||||
|
||||
writeRandomStringToClipboard("text/plain", type);
|
||||
let request = clipboard.getDataSnapshotSync(["text/html"], type);
|
||||
isDeeply(request.flavorList, [], "Check flavorList");
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function test_empty_data() {
|
||||
info(`Test getDataSnapshotSync request with empty data on ${type}`);
|
||||
cleanupAllClipboard();
|
||||
is(getClipboardData("text/plain", type), null, "ensure clipboard is empty");
|
||||
|
||||
let request = getClipboardDataSnapshotSync(type);
|
||||
isDeeply(request.flavorList, [], "Check flavorList");
|
||||
await asyncClipboardRequestGetData(request, "text/plain", true).catch(
|
||||
() => {}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_clipboard_getDataSnapshotSync_after_write() {
|
||||
info(`Test getDataSnapshotSync request after write on ${type}`);
|
||||
|
||||
let str = writeRandomStringToClipboard("text/plain", type);
|
||||
let request = getClipboardDataSnapshotSync(type);
|
||||
isDeeply(request.flavorList, ["text/plain"], "Check flavorList");
|
||||
is(
|
||||
await asyncClipboardRequestGetData(request, "text/plain"),
|
||||
str,
|
||||
"Check data"
|
||||
);
|
||||
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");
|
||||
|
||||
// Writing a new data should invalid existing get request.
|
||||
str = writeRandomStringToClipboard("text/plain", type);
|
||||
await asyncClipboardRequestGetData(request, "text/plain").then(
|
||||
() => {
|
||||
ok(false, "asyncClipboardRequestGetData should not success");
|
||||
},
|
||||
() => {
|
||||
ok(true, "asyncClipboardRequestGetData should reject");
|
||||
}
|
||||
);
|
||||
ok(!request.valid, "request should no longer be valid");
|
||||
|
||||
info(`check clipboard data again`);
|
||||
request = getClipboardDataSnapshotSync(type);
|
||||
isDeeply(request.flavorList, ["text/plain"], "Check flavorList");
|
||||
is(
|
||||
await asyncClipboardRequestGetData(request, "text/plain"),
|
||||
str,
|
||||
"Check data"
|
||||
);
|
||||
|
||||
cleanupAllClipboard();
|
||||
});
|
||||
|
||||
add_task(async function test_clipboard_getDataSnapshotSync_after_empty() {
|
||||
info(`Test getDataSnapshotSync request after empty on ${type}`);
|
||||
|
||||
let str = writeRandomStringToClipboard("text/plain", type);
|
||||
let request = getClipboardDataSnapshotSync(type);
|
||||
isDeeply(request.flavorList, ["text/plain"], "Check flavorList");
|
||||
is(
|
||||
await asyncClipboardRequestGetData(request, "text/plain"),
|
||||
str,
|
||||
"Check data"
|
||||
);
|
||||
ok(request.valid, "request should still be valid");
|
||||
|
||||
// Empty clipboard data
|
||||
emptyClipboardData(type);
|
||||
is(getClipboardData("text/plain", type), null, "ensure clipboard is empty");
|
||||
|
||||
await asyncClipboardRequestGetData(request, "text/plain").then(
|
||||
() => {
|
||||
ok(false, "asyncClipboardRequestGetData should not success");
|
||||
},
|
||||
() => {
|
||||
ok(true, "asyncClipboardRequestGetData should reject");
|
||||
}
|
||||
);
|
||||
ok(!request.valid, "request should no longer be valid");
|
||||
|
||||
info(`check clipboard data again`);
|
||||
request = getClipboardDataSnapshotSync(type);
|
||||
isDeeply(request.flavorList, [], "Check flavorList");
|
||||
|
||||
cleanupAllClipboard();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_clipboard_getDataSnapshotSync_html_data() {
|
||||
info(`Test getDataSnapshotSync request with html data`);
|
||||
|
||||
const html_str = `<img src="https://example.com/oops">`;
|
||||
writeStringToClipboard(html_str, "text/html", clipboard.kGlobalClipboard);
|
||||
|
||||
let request = getClipboardDataSnapshotSync(clipboard.kGlobalClipboard);
|
||||
isDeeply(request.flavorList, ["text/html"], "Check flavorList");
|
||||
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,
|
||||
"Check data"
|
||||
);
|
||||
// Requesting a flavor that is not in the list should throw error.
|
||||
await asyncClipboardRequestGetData(request, "text/plain", true).catch(
|
||||
() => {}
|
||||
);
|
||||
});
|
||||
|
|
@ -25,16 +25,12 @@ skip-if = [
|
|||
support-files = ["file_test_clipboard.js"]
|
||||
|
||||
["test_clipboard_asyncGetData.html"]
|
||||
skip-if = ["display == 'wayland'"] # Bug 1879835
|
||||
skip-if = ["display == 'wayland'"] # Bug 1864211
|
||||
support-files = ["file_test_clipboard_asyncGetData.js"]
|
||||
|
||||
["test_clipboard_asyncSetData.html"]
|
||||
support-files = ["file_test_clipboard_asyncSetData.js"]
|
||||
|
||||
["test_clipboard_getDataSnapshotSync.html"]
|
||||
skip-if = ["display == 'wayland'"] # Bug 1879835
|
||||
support-files = "file_test_clipboard_getDataSnapshotSync.js"
|
||||
|
||||
["test_contextmenu_by_mouse_on_unix.html"]
|
||||
run-if = ["os == 'linux'"]
|
||||
skip-if = ["headless"] # headless widget doesn't dispatch contextmenu event by mouse event.
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1858627
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1858627</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="clipboard_helper.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<!-- Tests are in file_test_clipboard_getDataSnapshotSync.js -->
|
||||
<script src="file_test_clipboard_getDataSnapshotSync.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1858627
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1858627</title>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="clipboard_helper.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<!-- Tests are in file_test_clipboard_getDataSnapshotSync.js -->
|
||||
<script src="file_test_clipboard_getDataSnapshotSync.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue