diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index a636a31ecb..a507dea841 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -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) diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index 3a0c9cc45f..69626d1a7c 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -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 ServiceWorker(); + + dom::Clipboard* Clipboard(); void GetLanguages(nsTArray& aLanguages); @@ -277,6 +280,7 @@ private: RefPtr mNotification; RefPtr mPowerManager; RefPtr mConnection; + RefPtr mClipboard; #ifdef MOZ_AUDIO_CHANNEL_MANAGER RefPtr mAudioChannelManager; #endif diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index fcd84e28b6..26751ac8c6 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -169,6 +169,10 @@ DOMInterfaces = { 'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClients.h', }, +'Clipboard' : { + 'implicitJSContext' : ['write', 'writeText', 'read', 'readText'], +}, + 'console': { 'nativeType': 'mozilla::dom::Console', }, diff --git a/dom/events/Clipboard.cpp b/dom/events/Clipboard.cpp new file mode 100644 index 0000000000..11b7388b8d --- /dev/null +++ b/dom/events/Clipboard.cpp @@ -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 +Clipboard::ReadHelper(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, + ClipboardReadType aClipboardReadType, ErrorResult& aRv) +{ + // Create a new promise + RefPtr 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 = new DataTransfer(this, ePaste, /* is external */ true, + nsIClipboard::kGlobalClipboard); + + // Create a new runnable + RefPtr 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 +Clipboard::Read(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) +{ + return ReadHelper(aCx, aSubjectPrincipal, eRead, aRv); +} + +already_AddRefed +Clipboard::ReadText(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) +{ + return ReadHelper(aCx, aSubjectPrincipal, eReadText, aRv); +} + +already_AddRefed +Clipboard::Write(JSContext* aCx, DataTransfer& aData, nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv) +{ + // Create a promise + RefPtr 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 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 transferable = aData.GetTransferable(0, context); + if (!transferable) { + p->MaybeRejectWithUndefined(); + return p.forget(); + } + + // Create a runnable + RefPtr 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 +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 = 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 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 diff --git a/dom/events/Clipboard.h b/dom/events/Clipboard.h new file mode 100644 index 0000000000..9960de652b --- /dev/null +++ b/dom/events/Clipboard.h @@ -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 Read(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + already_AddRefed ReadText(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv); + already_AddRefed Write(JSContext* aCx, DataTransfer& aData, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv); + already_AddRefed WriteText(JSContext* aCx, const nsAString& aData, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv); + + static LogModule* GetClipboardLog(); + + + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + +private: + already_AddRefed ReadHelper(JSContext* aCx, nsIPrincipal& aSubjectPrincipal, + ClipboardReadType aClipboardReadType, ErrorResult& aRv); + + ~Clipboard(); +}; + +} // namespace dom +} // namespace mozilla +#endif // mozilla_dom_Clipboard_h_ diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp index 3a3f5464d2..7ac154ea4b 100644 --- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -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 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 sysPrincipal; - ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal)); + nsCOMPtr sysPrincipal = nsContentUtils::GetSystemPrincipal(); // Check if the clipboard has any files bool hasFileData = false; diff --git a/dom/events/DataTransfer.h b/dom/events/DataTransfer.h index dbb4ec9bf8..f35020e111 100644 --- a/dom/events/DataTransfer.h +++ b/dom/events/DataTransfer.h @@ -315,6 +315,7 @@ protected: uint32_t aIndex, nsIPrincipal* aSubjectPrincipal); friend class ContentParent; + friend class Clipboard; void FillAllExternalData(); diff --git a/dom/events/DataTransferItemList.cpp b/dom/events/DataTransferItemList.cpp index ecea4968ce..630ae8a957 100644 --- a/dom/events/DataTransferItemList.cpp +++ b/dom/events/DataTransferItemList.cpp @@ -152,7 +152,8 @@ DataTransferItemList::Add(const nsAString& aData, return nullptr; } - nsCOMPtr data(new storage::TextVariant(aData)); + RefPtr data(new nsVariantCC()); + data->SetAsAString(aData); nsAutoString format; mDataTransfer->GetRealFormat(aType, format); diff --git a/dom/events/moz.build b/dom/events/moz.build index fcb59a2975..ae57e91fe3 100644 --- a/dom/events/moz.build +++ b/dom/events/moz.build @@ -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', diff --git a/dom/webidl/Clipboard.webidl b/dom/webidl/Clipboard.webidl new file mode 100644 index 0000000000..65fa976af6 --- /dev/null +++ b/dom/webidl/Clipboard.webidl @@ -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 read(); + [Throws, NeedsSubjectPrincipal] + Promise readText(); + [Pref="dom.events.asyncClipboard.dataTransfer", Throws, NeedsSubjectPrincipal] + Promise write(DataTransfer data); + [Throws, NeedsSubjectPrincipal] + Promise writeText(DOMString data); +}; + diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index eacb6c9443..60ed84d535 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -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; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index e69d4b77d0..0f47aabae4 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -74,6 +74,7 @@ WEBIDL_FILES = [ 'ChromeUtils.webidl', 'Client.webidl', 'Clients.webidl', + 'Clipboard.webidl', 'ClipboardEvent.webidl', 'CommandEvent.webidl', 'Comment.webidl', diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 5978669ce3..f8014c2dfc 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -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); +