Issue #2361 - Base implementation of Navigator.Clipboard

This hard-refuses any reads from clipboard with a promise rejection.
This commit is contained in:
Moonchild 2023-11-15 21:53:16 +01:00 committed by roytam1
commit 3478c01fc8
13 changed files with 327 additions and 11 deletions

View file

@ -30,6 +30,7 @@
#include "nsContentUtils.h"
#include "nsUnicharUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Clipboard.h"
#ifdef MOZ_GAMEPAD
#include "mozilla/dom/GamepadServiceTest.h"
#endif
@ -1510,6 +1511,15 @@ Navigator::GetUserAgent(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
return siteSpecificUA->GetUserAgentForURIAndWindow(aURI, aWindow, aUserAgent);
}
Clipboard*
Navigator::Clipboard()
{
if (!mClipboard) {
mClipboard = new dom::Clipboard(GetWindow());
}
return mClipboard;
}
#ifdef MOZ_EME
static nsCString
ToCString(const nsString& aString)

View file

@ -40,6 +40,7 @@ class WakeLock;
class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
class ServiceWorkerContainer;
class DOMRequest;
class Clipboard;
} // namespace dom
} // namespace mozilla
@ -213,6 +214,8 @@ public:
ErrorResult& aRv);
already_AddRefed<ServiceWorkerContainer> ServiceWorker();
dom::Clipboard* Clipboard();
void GetLanguages(nsTArray<nsString>& aLanguages);
@ -277,6 +280,7 @@ private:
RefPtr<DesktopNotificationCenter> mNotification;
RefPtr<PowerManager> mPowerManager;
RefPtr<network::Connection> mConnection;
RefPtr<dom::Clipboard> mClipboard;
#ifdef MOZ_AUDIO_CHANNEL_MANAGER
RefPtr<system::AudioChannelManager> mAudioChannelManager;
#endif

View file

@ -169,6 +169,10 @@ DOMInterfaces = {
'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClients.h',
},
'Clipboard' : {
'implicitJSContext' : ['write', 'writeText', 'read', 'readText'],
},
'console': {
'nativeType': 'mozilla::dom::Console',
},

202
dom/events/Clipboard.cpp Normal file
View file

@ -0,0 +1,202 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/AbstractThread.h"
#include "mozilla/dom/Clipboard.h"
#include "mozilla/dom/ClipboardBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/DataTransfer.h"
#include "mozilla/dom/DataTransferItemList.h"
#include "mozilla/dom/DataTransferItem.h"
#include "mozilla/dom/ContentChild.h"
#include "nsIClipboard.h"
#include "nsISupportsPrimitives.h"
#include "nsComponentManagerUtils.h"
#include "nsITransferable.h"
#include "nsArrayUtils.h"
static mozilla::LazyLogModule gClipboardLog("Clipboard");
namespace mozilla {
namespace dom {
Clipboard::Clipboard(nsPIDOMWindowInner* aWindow)
: DOMEventTargetHelper(aWindow)
{
}
Clipboard::~Clipboard()
{
}
already_AddRefed<Promise>
Clipboard::ReadHelper(JSContext* aCx, nsIPrincipal& aSubjectPrincipal,
ClipboardReadType aClipboardReadType, ErrorResult& aRv)
{
// Create a new promise
RefPtr<Promise> p = dom::Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
// We always reject reading from clipboard for sec reasons.
// below code kept in place in case we change our minds on that but will require
// permissions.
// e.g. if(!nsContentUtils::IsCallerChrome()) would allow add-ons...
if (true) {
MOZ_LOG(GetClipboardLog(), LogLevel::Debug, ("Clipboard, ReadHelper, "
"Don't have permissions for reading\n"));
p->MaybeRejectWithUndefined();
return p.forget();
}
// Want isExternal = true in order to use the data transfer object to perform a read
RefPtr<DataTransfer> dataTransfer = new DataTransfer(this, ePaste, /* is external */ true,
nsIClipboard::kGlobalClipboard);
// Create a new runnable
RefPtr<nsIRunnable> r = NS_NewRunnableFunction(
[p, dataTransfer, &aSubjectPrincipal, aClipboardReadType]() {
IgnoredErrorResult ier;
switch (aClipboardReadType) {
case eRead:
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("Clipboard, ReadHelper, read case\n"));
dataTransfer->FillAllExternalData();
// If there are items on the clipboard, data transfer will contain those,
// else, data transfer will be empty and we will be resolving with an empty data transfer
p->MaybeResolve(dataTransfer);
break;
case eReadText:
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("Clipboard, ReadHelper, read text case\n"));
nsAutoString str;
dataTransfer->GetData(NS_LITERAL_STRING(kTextMime), str, aSubjectPrincipal, ier);
// Either resolve with a string extracted from data transfer item
// or resolve with an empty string if nothing was found
p->MaybeResolve(str);
break;
}
});
// Dispatch the runnable
NS_DispatchToCurrentThread(r.forget());
return p.forget();
}
already_AddRefed<Promise>
Clipboard::Read(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
{
return ReadHelper(aCx, aSubjectPrincipal, eRead, aRv);
}
already_AddRefed<Promise>
Clipboard::ReadText(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
{
return ReadHelper(aCx, aSubjectPrincipal, eReadText, aRv);
}
already_AddRefed<Promise>
Clipboard::Write(JSContext* aCx, DataTransfer& aData, nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv)
{
// Create a promise
RefPtr<Promise> p = dom::Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
// We want to disable this if dom.allow_cut_copy is false, or
// it doesn't meet the other requirements for copying to clipboard
if (!nsContentUtils::IsCutCopyAllowed()) {
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
("Clipboard, Write, Not allowed to write to clipboard\n"));
p->MaybeRejectWithUndefined();
return p.forget();
}
// Get the clipboard service
nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1"));
if (!clipboard) {
p->MaybeRejectWithUndefined();
return p.forget();
}
nsPIDOMWindowInner* owner = GetOwner();
nsIDocument* doc = owner ? owner->GetDoc() : nullptr;
nsILoadContext* context = doc ? doc->GetLoadContext() : nullptr;
if (!context) {
p->MaybeRejectWithUndefined();
return p.forget();
}
// Get the transferable
RefPtr<nsITransferable> transferable = aData.GetTransferable(0, context);
if (!transferable) {
p->MaybeRejectWithUndefined();
return p.forget();
}
// Create a runnable
RefPtr<nsIRunnable> r = NS_NewRunnableFunction(
[transferable, p, clipboard]() {
nsresult rv = clipboard->SetData(transferable,
/* owner of the transferable */ nullptr,
nsIClipboard::kGlobalClipboard);
if (NS_FAILED(rv)) {
p->MaybeRejectWithUndefined();
return;
}
p->MaybeResolveWithUndefined();
return;
});
// Dispatch the runnable
NS_DispatchToCurrentThread(r.forget());
return p.forget();
}
already_AddRefed<Promise>
Clipboard::WriteText(JSContext* aCx, const nsAString& aData,
nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
{
// We create a data transfer with text/plain format so that
// we can reuse Clipboard::Write(...) member function
RefPtr<DataTransfer> dataTransfer = new DataTransfer(this, eCopy,
/* is external */ true,
/* clipboard type */ -1);
dataTransfer->SetData(NS_LITERAL_STRING(kTextMime), aData, aSubjectPrincipal, aRv);
return Write(aCx, *dataTransfer, aSubjectPrincipal, aRv);
}
JSObject*
Clipboard::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return ClipboardBinding::Wrap(aCx, this, aGivenProto);
}
/* static */ LogModule*
Clipboard::GetClipboardLog()
{
return gClipboardLog;
}
NS_IMPL_CYCLE_COLLECTION_CLASS(Clipboard)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Clipboard,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Clipboard,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Clipboard)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(Clipboard, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(Clipboard, DOMEventTargetHelper)
} // namespace dom
} // namespace mozilla

60
dom/events/Clipboard.h Normal file
View file

@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_Clipboard_h_
#define mozilla_dom_Clipboard_h_
#include "nsString.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/Logging.h"
#include "mozilla/dom/DataTransfer.h"
namespace mozilla {
namespace dom {
enum ClipboardReadType {
eRead,
eReadText,
};
class Promise;
// https://www.w3.org/TR/clipboard-apis/#clipboard-interface
class Clipboard : public DOMEventTargetHelper
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Clipboard,
DOMEventTargetHelper)
IMPL_EVENT_HANDLER(message)
//IMPL_EVENT_HANDLER(messageerror)
explicit Clipboard(nsPIDOMWindowInner* aWindow);
already_AddRefed<Promise> Read(JSContext* aCx, nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv);
already_AddRefed<Promise> ReadText(JSContext* aCx, nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv);
already_AddRefed<Promise> Write(JSContext* aCx, DataTransfer& aData,
nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
already_AddRefed<Promise> WriteText(JSContext* aCx, const nsAString& aData,
nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv);
static LogModule* GetClipboardLog();
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
private:
already_AddRefed<Promise> ReadHelper(JSContext* aCx, nsIPrincipal& aSubjectPrincipal,
ClipboardReadType aClipboardReadType, ErrorResult& aRv);
~Clipboard();
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_Clipboard_h_

View file

@ -1204,16 +1204,12 @@ DataTransfer::ConvertFromVariant(nsIVariant* aVariant,
return true;
}
char16_t* chrs;
uint32_t len = 0;
nsresult rv = aVariant->GetAsWStringWithSize(&len, &chrs);
nsAutoString str;
nsresult rv = aVariant->GetAsAString(str);
if (NS_FAILED(rv)) {
return false;
}
nsAutoString str;
str.Adopt(chrs, len);
nsCOMPtr<nsISupportsString>
strSupports(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
if (!strSupports) {
@ -1225,7 +1221,7 @@ DataTransfer::ConvertFromVariant(nsIVariant* aVariant,
strSupports.forget(aSupports);
// each character is two bytes
*aLength = str.Length() << 1;
*aLength = str.Length() * 2;
return true;
}
@ -1410,9 +1406,7 @@ DataTransfer::CacheExternalClipboardFormats()
return;
}
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
nsCOMPtr<nsIPrincipal> sysPrincipal;
ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
// Check if the clipboard has any files
bool hasFileData = false;

View file

@ -315,6 +315,7 @@ protected:
uint32_t aIndex, nsIPrincipal* aSubjectPrincipal);
friend class ContentParent;
friend class Clipboard;
void FillAllExternalData();

View file

@ -152,7 +152,8 @@ DataTransferItemList::Add(const nsAString& aData,
return nullptr;
}
nsCOMPtr<nsIVariant> data(new storage::TextVariant(aData));
RefPtr<nsVariantCC> data(new nsVariantCC());
data->SetAsAString(aData);
nsAutoString format;
mDataTransfer->GetRealFormat(aType, format);

View file

@ -37,6 +37,7 @@ EXPORTS.mozilla.dom += [
'AnimationEvent.h',
'BeforeAfterKeyboardEvent.h',
'BeforeUnloadEvent.h',
'Clipboard.h',
'ClipboardEvent.h',
'CommandEvent.h',
'CompositionEvent.h',
@ -78,6 +79,7 @@ UNIFIED_SOURCES += [
'AsyncEventDispatcher.cpp',
'BeforeAfterKeyboardEvent.cpp',
'BeforeUnloadEvent.cpp',
'Clipboard.cpp',
'ClipboardEvent.cpp',
'CommandEvent.cpp',
'CompositionEvent.cpp',

View file

@ -0,0 +1,25 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* http://www.w3.org/TR/geolocation-API
*
* Copyright © 2018 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
[SecureContext, Exposed=Window, Pref="dom.events.asyncClipboard"]
interface Clipboard : EventTarget {
[Pref="dom.events.asyncClipboard.dataTransfer", Throws, NeedsSubjectPrincipal]
Promise<DataTransfer> read();
[Throws, NeedsSubjectPrincipal]
Promise<DOMString> readText();
[Pref="dom.events.asyncClipboard.dataTransfer", Throws, NeedsSubjectPrincipal]
Promise<void> write(DataTransfer data);
[Throws, NeedsSubjectPrincipal]
Promise<void> writeText(DOMString data);
};

View file

@ -324,3 +324,9 @@ partial interface Navigator {
interface NavigatorConcurrentHardware {
readonly attribute unsigned long long hardwareConcurrency;
};
// https://www.w3.org/TR/clipboard-apis/#navigator-interface
partial interface Navigator {
[Pref="dom.events.asyncClipboard", SecureContext, SameObject]
readonly attribute Clipboard clipboard;
};

View file

@ -74,6 +74,7 @@ WEBIDL_FILES = [
'ChromeUtils.webidl',
'Client.webidl',
'Clients.webidl',
'Clipboard.webidl',
'ClipboardEvent.webidl',
'CommandEvent.webidl',
'Comment.webidl',

View file

@ -5279,3 +5279,9 @@ pref("media.sourceErrorDetails.enabled", true);
#else
pref("media.sourceErrorDetails.enabled", false);
#endif
// Whether Navigator.Clipboard methods are a thing.
pref("dom.events.asyncClipboard", false);
// Whether arbitrary data transfer methods (not plaintext) are allowed.
pref("dom.events.asyncClipboard.dataTransfer", false);