зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1461465 - Implement async Clipboard APIs, r=nika,enndeakin
MozReview-Commit-ID: 3vCxbaGZtiv
This commit is contained in:
Родитель
da1ac338dc
Коммит
05520e664b
|
@ -33,6 +33,7 @@
|
|||
#include "mozilla/Telemetry.h"
|
||||
#include "BatteryManager.h"
|
||||
#include "mozilla/dom/CredentialsContainer.h"
|
||||
#include "mozilla/dom/Clipboard.h"
|
||||
#include "mozilla/dom/GamepadServiceTest.h"
|
||||
#include "mozilla/dom/MediaCapabilities.h"
|
||||
#include "mozilla/dom/WakeLock.h"
|
||||
|
@ -1806,6 +1807,15 @@ Navigator::MediaCapabilities()
|
|||
return mMediaCapabilities;
|
||||
}
|
||||
|
||||
Clipboard*
|
||||
Navigator::Clipboard()
|
||||
{
|
||||
if (!mClipboard) {
|
||||
mClipboard = new dom::Clipboard(GetWindow());
|
||||
}
|
||||
return mClipboard;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
Navigator::Webdriver()
|
||||
|
|
|
@ -40,6 +40,7 @@ class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
|
|||
class ServiceWorkerContainer;
|
||||
class DOMRequest;
|
||||
class CredentialsContainer;
|
||||
class Clipboard;
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -207,6 +208,7 @@ public:
|
|||
already_AddRefed<ServiceWorkerContainer> ServiceWorker();
|
||||
|
||||
mozilla::dom::CredentialsContainer* Credentials();
|
||||
dom::Clipboard* Clipboard();
|
||||
|
||||
static bool Webdriver();
|
||||
|
||||
|
@ -268,6 +270,7 @@ private:
|
|||
RefPtr<Promise> mBatteryPromise;
|
||||
RefPtr<network::Connection> mConnection;
|
||||
RefPtr<CredentialsContainer> mCredentials;
|
||||
RefPtr<dom::Clipboard> mClipboard;
|
||||
RefPtr<MediaDevices> mMediaDevices;
|
||||
RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
|
||||
nsCOMPtr<nsPIDOMWindowInner> mWindow;
|
||||
|
|
|
@ -143,6 +143,10 @@ DOMInterfaces = {
|
|||
'concrete': False
|
||||
},
|
||||
|
||||
'Clipboard' : {
|
||||
'implicitJSContext' : ['write', 'writeText', 'read', 'readText'],
|
||||
},
|
||||
|
||||
'console': {
|
||||
'nativeType': 'mozilla::dom::Console',
|
||||
},
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 want to disable security check for automated tests that have the pref
|
||||
// dom.events.testing.asyncClipboard set to true
|
||||
if (!IsTestingPrefEnabled() && !nsContentUtils::PrincipalHasPermission(&aSubjectPrincipal,
|
||||
nsGkAtoms::clipboardRead)) {
|
||||
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(
|
||||
"Clipboard::Read",
|
||||
[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
|
||||
GetParentObject()->Dispatch(TaskCategory::Other, 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 security check for automated tests that have the pref
|
||||
// dom.events.testing.asyncClipboard set to true
|
||||
if (!IsTestingPrefEnabled() && !nsContentUtils::IsCutCopyAllowed(&aSubjectPrincipal)) {
|
||||
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(
|
||||
"Clipboard::Write",
|
||||
[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
|
||||
GetParentObject()->Dispatch(TaskCategory::Other, 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 Clipboard_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
/* static */ LogModule*
|
||||
Clipboard::GetClipboardLog()
|
||||
{
|
||||
return gClipboardLog;
|
||||
}
|
||||
|
||||
bool
|
||||
Clipboard::IsTestingPrefEnabled()
|
||||
{
|
||||
static bool sPrefCached = false;
|
||||
static bool sPrefCacheValue = false;
|
||||
|
||||
if (!sPrefCached) {
|
||||
sPrefCached = true;
|
||||
Preferences::AddBoolVarCache(&sPrefCacheValue, "dom.events.testing.asyncClipboard");
|
||||
}
|
||||
MOZ_LOG(GetClipboardLog(), LogLevel::Debug,
|
||||
("Clipboard, Is testing enabled? %d\n", sPrefCacheValue));
|
||||
return sPrefCacheValue;
|
||||
}
|
||||
|
||||
|
||||
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
|
|
@ -0,0 +1,69 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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:
|
||||
// Checks if dom.events.testing.asyncClipboard pref is enabled.
|
||||
// The aforementioned pref allows automated tests to bypass the security checks when writing to
|
||||
// or reading from the clipboard.
|
||||
bool IsTestingPrefEnabled();
|
||||
|
||||
already_AddRefed<Promise> ReadHelper(JSContext* aCx, nsIPrincipal& aSubjectPrincipal,
|
||||
ClipboardReadType aClipboardReadType, ErrorResult& aRv);
|
||||
|
||||
~Clipboard();
|
||||
|
||||
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
#endif // mozilla_dom_Clipboard_h_
|
|
@ -1177,16 +1177,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) {
|
||||
|
@ -1198,7 +1194,7 @@ DataTransfer::ConvertFromVariant(nsIVariant* aVariant,
|
|||
strSupports.forget(aSupports);
|
||||
|
||||
// each character is two bytes
|
||||
*aLength = str.Length() << 1;
|
||||
*aLength = str.Length() * 2;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1386,9 +1382,7 @@ DataTransfer::CacheExternalClipboardFormats(bool aPlainTextOnly)
|
|||
NS_ASSERTION(mEventMessage == ePaste,
|
||||
"caching clipboard data for invalid event");
|
||||
|
||||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||||
nsCOMPtr<nsIPrincipal> sysPrincipal;
|
||||
ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
|
||||
nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
|
||||
|
||||
nsTArray<nsCString> typesArray;
|
||||
|
||||
|
|
|
@ -451,6 +451,7 @@ protected:
|
|||
uint32_t aIndex, nsIPrincipal* aSubjectPrincipal);
|
||||
|
||||
friend class ContentParent;
|
||||
friend class Clipboard;
|
||||
|
||||
void FillAllExternalData();
|
||||
|
||||
|
|
|
@ -103,6 +103,7 @@ public:
|
|||
}
|
||||
|
||||
already_AddRefed<nsIVariant> DataNoSecurityCheck();
|
||||
// Data may return null if the clipboard state has changed since the type was detected.
|
||||
already_AddRefed<nsIVariant> Data(nsIPrincipal* aPrincipal, ErrorResult& aRv);
|
||||
|
||||
// Note: This can modify the mKind. Callers of this method must let the
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "ipc/IPCMessageUtils.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/ShadowRoot.h"
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/ContentEvents.h"
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
|
|
|
@ -47,6 +47,7 @@ EXPORTS.mozilla += [
|
|||
EXPORTS.mozilla.dom += [
|
||||
'AnimationEvent.h',
|
||||
'BeforeUnloadEvent.h',
|
||||
'Clipboard.h',
|
||||
'ClipboardEvent.h',
|
||||
'CommandEvent.h',
|
||||
'CompositionEvent.h',
|
||||
|
@ -90,6 +91,7 @@ UNIFIED_SOURCES += [
|
|||
'AnimationEvent.cpp',
|
||||
'AsyncEventDispatcher.cpp',
|
||||
'BeforeUnloadEvent.cpp',
|
||||
'Clipboard.cpp',
|
||||
'ClipboardEvent.cpp',
|
||||
'CommandEvent.cpp',
|
||||
'CompositionEvent.cpp',
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/* -*- 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);
|
||||
};
|
|
@ -331,3 +331,9 @@ interface NavigatorAutomationInformation {
|
|||
[Pref="dom.webdriver.enabled"]
|
||||
readonly attribute boolean webdriver;
|
||||
};
|
||||
|
||||
// https://www.w3.org/TR/clipboard-apis/#navigator-interface
|
||||
partial interface Navigator {
|
||||
[Pref="dom.events.asyncClipboard", SecureContext, SameObject]
|
||||
readonly attribute Clipboard clipboard;
|
||||
};
|
||||
|
|
|
@ -58,6 +58,9 @@ with Files("Channel*"):
|
|||
with Files("Client*"):
|
||||
BUG_COMPONENT = ("Core", "DOM: Service Workers")
|
||||
|
||||
with Files("Clipboard.webidl"):
|
||||
BUG_COMPONENT = ("Core", "DOM: Events")
|
||||
|
||||
with Files("ClipboardEvent.webidl"):
|
||||
BUG_COMPONENT = ("Core", "DOM: Events")
|
||||
|
||||
|
@ -419,6 +422,7 @@ WEBIDL_FILES = [
|
|||
'ChromeNodeList.webidl',
|
||||
'Client.webidl',
|
||||
'Clients.webidl',
|
||||
'Clipboard.webidl',
|
||||
'ClipboardEvent.webidl',
|
||||
'CommandEvent.webidl',
|
||||
'Comment.webidl',
|
||||
|
|
|
@ -5841,3 +5841,9 @@ pref("general.document_open_conversion_depth_limit", 20);
|
|||
// If true, touchstart and touchmove listeners on window, document,
|
||||
// documentElement and document.body are passive by default.
|
||||
pref("dom.event.default_to_passive_touch_listeners", true);
|
||||
|
||||
// Disables clipboard reads and writes by default.
|
||||
pref("dom.events.asyncClipboard", false);
|
||||
pref("dom.events.asyncClipboard.dataTransfer", false);
|
||||
// Should only be enabled in tests
|
||||
pref("dom.events.testing.asyncClipboard", false);
|
|
@ -439008,7 +439008,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"content-security-policy/securitypolicyviolation/img-src-redirect-upgrade-reporting.https.html": [
|
||||
"e338e94ea726419db64ed5b98c95b862c394409e",
|
||||
"f6623c80b2b4be3fd9dd0f5dc0a6417652f1b797",
|
||||
"testharness"
|
||||
],
|
||||
"content-security-policy/securitypolicyviolation/img-src-redirect-upgrade-reporting.https.html.headers": [
|
||||
|
@ -439060,7 +439060,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"content-security-policy/securitypolicyviolation/support/inside-worker.sub.js": [
|
||||
"f425a7ae6c167bfe9857f08f460897e16bf6ca95",
|
||||
"d94662579190653a3b3e9d076b79d7b0f01f2dc7",
|
||||
"support"
|
||||
],
|
||||
"content-security-policy/securitypolicyviolation/support/inside-worker.sub.js.headers": [
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
prefs: [dom.events.asyncClipboard:true, dom.events.asyncClipboard.dataTransfer:true, dom.events.testing.asyncClipboard:true]
|
|
@ -1,79 +1,4 @@
|
|||
[async-interfaces.https.html]
|
||||
[Navigator interface: attribute clipboard]
|
||||
expected: FAIL
|
||||
|
||||
[Navigator interface: navigator must inherit property "clipboard" with the proper type (0)]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: operation read()]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: operation readText()]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: operation write(DataTransfer)]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: operation writeText(DOMString)]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard must be primary interface of navigator.clipboard]
|
||||
expected: FAIL
|
||||
|
||||
[Stringification of navigator.clipboard]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: navigator.clipboard must inherit property "read" with the proper type (0)]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: navigator.clipboard must inherit property "readText" with the proper type (1)]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: navigator.clipboard must inherit property "write" with the proper type (2)]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: calling write(DataTransfer) on navigator.clipboard with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: navigator.clipboard must inherit property "writeText" with the proper type (3)]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: calling writeText(DOMString) on navigator.clipboard with too few arguments must throw TypeError]
|
||||
expected: FAIL
|
||||
|
||||
[Navigator interface: navigator must inherit property "clipboard" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: navigator.clipboard must inherit property "read()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: navigator.clipboard must inherit property "readText()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: navigator.clipboard must inherit property "write(DataTransfer)" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[Clipboard interface: navigator.clipboard must inherit property "writeText(DOMString)" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[ClipboardEvent interface: new ClipboardEvent("x") must inherit property "clipboardData" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
[async-navigator-clipboard-basics.https.html]
|
||||
[navigator.clipboard exists]
|
||||
expected: FAIL
|
||||
|
||||
[navigator.clipboard.write(DataTransfer) succeeds]
|
||||
expected: FAIL
|
||||
|
||||
[navigator.clipboard.write() fails (expect DataTransfer)]
|
||||
expected: FAIL
|
||||
|
||||
[navigator.clipboard.write(null) fails (expect DataTransfer)]
|
||||
expected: FAIL
|
||||
|
||||
[navigator.clipboard.write(DOMString) fails (expect DataTransfer)]
|
||||
expected: FAIL
|
||||
|
||||
[navigator.clipboard.writeText(DOMString) succeeds]
|
||||
expected: FAIL
|
||||
|
||||
[navigator.clipboard.writeText() fails (expect DOMString)]
|
||||
expected: FAIL
|
||||
|
||||
[navigator.clipboard.read() succeeds]
|
||||
expected: FAIL
|
||||
|
||||
[navigator.clipboard.readText() succeeds]
|
||||
expected: FAIL
|
||||
|
|
@ -55,6 +55,7 @@ prefs =
|
|||
security.mixed_content.upgrade_display_content=false
|
||||
browser.chrome.guess_favicon=true
|
||||
|
||||
[test_ext_async_clipboard.html]
|
||||
[test_ext_background_canvas.html]
|
||||
[test_ext_background_page.html]
|
||||
skip-if = (toolkit == 'android') # android doesn't have devtools
|
||||
|
|
|
@ -0,0 +1,364 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Async Clipboard permissions tests</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/AddTask.js"></script>
|
||||
<script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script src="head.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
/* globals clipboardWriteText, clipboardWrite, clipboardReadText, clipboardRead */
|
||||
function shared() {
|
||||
this.clipboardWriteText = function(txt) {
|
||||
return navigator.clipboard.writeText(txt);
|
||||
};
|
||||
|
||||
this.clipboardWrite = function(dt) {
|
||||
return navigator.clipboard.write(dt);
|
||||
};
|
||||
|
||||
this.clipboardReadText = function() {
|
||||
return navigator.clipboard.readText();
|
||||
};
|
||||
|
||||
this.clipboardRead = function() {
|
||||
return navigator.clipboard.read();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the clipboard.
|
||||
*
|
||||
* This is needed because Services.clipboard.emptyClipboard() does not clear the actual system clipboard.
|
||||
*/
|
||||
function clearClipboard() {
|
||||
let transf = SpecialPowers.Cc["@mozilla.org/widget/transferable;1"]
|
||||
.createInstance(SpecialPowers.Ci.nsITransferable);
|
||||
transf.init(null);
|
||||
// Empty transferables may cause crashes, so just add an unknown type.
|
||||
const TYPE = "text/x-moz-place-empty";
|
||||
transf.addDataFlavor(TYPE);
|
||||
transf.setTransferData(TYPE, {}, 0);
|
||||
SpecialPowers.Services.clipboard.setData(transf, null, SpecialPowers.Services.clipboard.kGlobalClipboard);
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.events.asyncClipboard", true],
|
||||
["dom.events.asyncClipboard.dataTransfer", true],
|
||||
]});
|
||||
});
|
||||
|
||||
// Test that without enough permissions, we are NOT allowed to use writeText, write, read or readText in background script
|
||||
add_task(async function test_background_async_clipboard_no_permissions() {
|
||||
function backgroundScript() {
|
||||
let dt = new DataTransfer();
|
||||
dt.items.add("Howdy", "text/plain");
|
||||
browser.test.assertRejects(clipboardRead(), undefined, "Read should be denied without permission");
|
||||
browser.test.assertRejects(clipboardWrite(dt), undefined, "Write should be denied without permission");
|
||||
browser.test.assertRejects(clipboardWriteText("blabla"), undefined, "WriteText should be denied without permission");
|
||||
browser.test.assertRejects(clipboardReadText(), undefined, "ReadText should be denied without permission");
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
let extensionData = {
|
||||
background: [shared, backgroundScript],
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
await extension.awaitMessage("ready");
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
// Test that without enough permissions, we are NOT allowed to use writeText, write, read or readText in content script
|
||||
add_task(async function test_contentscript_async_clipboard_no_permission() {
|
||||
function contentScript() {
|
||||
let dt = new DataTransfer();
|
||||
dt.items.add("Howdy", "text/plain");
|
||||
browser.test.assertRejects(clipboardRead(), undefined, "Read should be denied without permission");
|
||||
browser.test.assertRejects(clipboardWrite(dt), undefined, "Write should be denied without permission");
|
||||
browser.test.assertRejects(clipboardWriteText("blabla"), undefined, "WriteText should be denied without permission");
|
||||
browser.test.assertRejects(clipboardReadText(), undefined, "ReadText should be denied without permission");
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [{
|
||||
js: ["shared.js", "contentscript.js"],
|
||||
matches: ["https://example.com/*/file_sample.html"],
|
||||
}],
|
||||
},
|
||||
files: {
|
||||
"shared.js": shared,
|
||||
"contentscript.js": contentScript,
|
||||
},
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
let win = window.open("https://example.com/tests/toolkit/components/extensions/test/mochitest/test-oop-extensions/file_sample.html");
|
||||
await extension.awaitMessage("ready");
|
||||
await extension.unload();
|
||||
win.close();
|
||||
});
|
||||
|
||||
// Test that with enough permissions, we are allowed to use writeText in content script
|
||||
add_task(async function test_contentscript_clipboard_permission_writetext() {
|
||||
function contentScript() {
|
||||
let str = "HI";
|
||||
clipboardWriteText(str).then(function() {
|
||||
// nothing here
|
||||
browser.test.sendMessage("ready");
|
||||
}, function(err) {
|
||||
browser.test.fail("WriteText promise rejected");
|
||||
browser.test.sendMessage("ready");
|
||||
}); // clipboardWriteText
|
||||
}
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [{
|
||||
js: ["shared.js", "contentscript.js"],
|
||||
matches: ["https://example.com/*/file_sample.html"],
|
||||
}],
|
||||
permissions: [
|
||||
"clipboardWrite",
|
||||
"clipboardRead",
|
||||
],
|
||||
},
|
||||
files: {
|
||||
"shared.js": shared,
|
||||
"contentscript.js": contentScript,
|
||||
},
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
let win = window.open("https://example.com/tests/toolkit/components/extensions/test/mochitest/test-oop-extensions/file_sample.html");
|
||||
await extension.awaitMessage("ready");
|
||||
const actual = SpecialPowers.getClipboardData("text/unicode");
|
||||
is(actual, "HI", "right string copied by write");
|
||||
await extension.unload();
|
||||
win.close();
|
||||
});
|
||||
|
||||
// Test that with enough permissions, we are allowed to use readText in content script
|
||||
add_task(async function test_contentscript_clipboard_permission_readtext() {
|
||||
function contentScript() {
|
||||
let str = "HI";
|
||||
clipboardReadText().then(function(strData) {
|
||||
if (strData == str) {
|
||||
browser.test.succeed("Successfully read from clipboard");
|
||||
} else {
|
||||
browser.test.fail("ReadText read the wrong thing from clipboard:" + strData);
|
||||
}
|
||||
browser.test.sendMessage("ready");
|
||||
}, function(err) {
|
||||
browser.test.fail("ReadText promise rejected");
|
||||
browser.test.sendMessage("ready");
|
||||
}); // clipboardReadText
|
||||
}
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [{
|
||||
js: ["shared.js", "contentscript.js"],
|
||||
matches: ["https://example.com/*/file_sample.html"],
|
||||
}],
|
||||
permissions: [
|
||||
"clipboardWrite",
|
||||
"clipboardRead",
|
||||
],
|
||||
},
|
||||
files: {
|
||||
"shared.js": shared,
|
||||
"contentscript.js": contentScript,
|
||||
},
|
||||
};
|
||||
SpecialPowers.clipboardCopyString("HI");
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
let win = window.open("https://example.com/tests/toolkit/components/extensions/test/mochitest/test-oop-extensions/file_sample.html");
|
||||
await extension.awaitMessage("ready");
|
||||
await extension.unload();
|
||||
win.close();
|
||||
});
|
||||
|
||||
// Test that with enough permissions, we are allowed to use write in content script
|
||||
add_task(async function test_contentscript_clipboard_permission_write() {
|
||||
function contentScript() {
|
||||
let str = "HI";
|
||||
let dt = new DataTransfer();
|
||||
dt.items.add(str, "text/plain");
|
||||
clipboardWrite(dt).then(function() {
|
||||
// nothing here
|
||||
browser.test.sendMessage("ready");
|
||||
}, function(err) { // clipboardWrite promise error function
|
||||
browser.test.fail("Write promise rejected");
|
||||
browser.test.sendMessage("ready");
|
||||
}); // clipboard write
|
||||
}
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [{
|
||||
js: ["shared.js", "contentscript.js"],
|
||||
matches: ["https://example.com/*/file_sample.html"],
|
||||
}],
|
||||
permissions: [
|
||||
"clipboardWrite",
|
||||
"clipboardRead",
|
||||
],
|
||||
},
|
||||
files: {
|
||||
"shared.js": shared,
|
||||
"contentscript.js": contentScript,
|
||||
},
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
let win = window.open("https://example.com/tests/toolkit/components/extensions/test/mochitest/test-oop-extensions/file_sample.html");
|
||||
await extension.awaitMessage("ready");
|
||||
const actual = SpecialPowers.getClipboardData("text/unicode");
|
||||
is(actual, "HI", "right string copied by write");
|
||||
await extension.unload();
|
||||
win.close();
|
||||
});
|
||||
|
||||
// Test that with enough permissions, we are allowed to use read in content script
|
||||
add_task(async function test_contentscript_clipboard_permission_read() {
|
||||
function contentScript() {
|
||||
clipboardRead().then(function(dt) {
|
||||
let s = dt.getData("text/plain");
|
||||
if (s == "HELLO") {
|
||||
browser.test.succeed("Read promise successfully read the right thing");
|
||||
} else {
|
||||
browser.test.fail("Read read the wrong string from clipboard:" + s);
|
||||
}
|
||||
browser.test.sendMessage("ready");
|
||||
}, function(err) { // clipboardRead promise error function
|
||||
browser.test.fail("Read promise rejected");
|
||||
browser.test.sendMessage("ready");
|
||||
}); // clipboard read
|
||||
}
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [{
|
||||
js: ["shared.js", "contentscript.js"],
|
||||
matches: ["https://example.com/*/file_sample.html"],
|
||||
}],
|
||||
permissions: [
|
||||
"clipboardWrite",
|
||||
"clipboardRead",
|
||||
],
|
||||
},
|
||||
files: {
|
||||
"shared.js": shared,
|
||||
"contentscript.js": contentScript,
|
||||
},
|
||||
};
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
SpecialPowers.clipboardCopyString("HELLO");
|
||||
await extension.startup();
|
||||
let win = window.open("https://example.com/tests/toolkit/components/extensions/test/mochitest/test-oop-extensions/file_sample.html");
|
||||
await extension.awaitMessage("ready");
|
||||
await extension.unload();
|
||||
win.close();
|
||||
});
|
||||
|
||||
// Test that performing readText(...) when the clipboard is empty returns an empty string
|
||||
add_task(async function test_contentscript_clipboard_nocontents_readtext() {
|
||||
function contentScript() {
|
||||
clipboardReadText().then(function(strData) {
|
||||
if (strData == "") {
|
||||
browser.test.succeed("ReadText successfully read correct thing from an empty clipboard");
|
||||
} else {
|
||||
browser.test.fail("ReadText should have read an empty string, but read:" + strData);
|
||||
}
|
||||
browser.test.sendMessage("ready");
|
||||
}, function(err) {
|
||||
browser.test.fail("ReadText promise rejected: " + err);
|
||||
browser.test.sendMessage("ready");
|
||||
});
|
||||
}
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [{
|
||||
js: ["shared.js", "contentscript.js"],
|
||||
matches: ["https://example.com/*/file_sample.html"],
|
||||
}],
|
||||
permissions: [
|
||||
"clipboardRead",
|
||||
],
|
||||
},
|
||||
files: {
|
||||
"shared.js": shared,
|
||||
"contentscript.js": contentScript,
|
||||
},
|
||||
};
|
||||
|
||||
await SimpleTest.promiseClipboardChange("", () => {
|
||||
clearClipboard();
|
||||
}, "text/x-moz-place-empty");
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
let win = window.open("https://example.com/tests/toolkit/components/extensions/test/mochitest/test-oop-extensions/file_sample.html");
|
||||
await extension.awaitMessage("ready");
|
||||
await extension.unload();
|
||||
win.close();
|
||||
});
|
||||
|
||||
// Test that performing read(...) when the clipboard is empty returns an empty data transfer
|
||||
add_task(async function test_contentscript_clipboard_nocontents_read() {
|
||||
function contentScript() {
|
||||
clipboardRead().then(function(dataT) {
|
||||
// On macOS if we clear the clipboard and read from it, there will be
|
||||
// no items in the data transfer object.
|
||||
// On linux with e10s enabled clearing of the clipboard does not happen in
|
||||
// the same way as it does on other platforms. So when we clear the clipboard
|
||||
// and read from it, the data transfer object contains an item of type
|
||||
// text/plain and kind string, but we can't call getAsString on it to verify
|
||||
// that at least it is an empty string because the callback never gets invoked.
|
||||
if (dataT.items.length == 0 ||
|
||||
(dataT.items.length == 1 && dataT.items[0].type == "text/plain" &&
|
||||
dataT.items[0].kind == "string")) {
|
||||
browser.test.succeed("Read promise successfully resolved");
|
||||
} else {
|
||||
browser.test.fail("Read read the wrong thing from clipboard, " +
|
||||
"data transfer has this many items:" + dataT.items.length);
|
||||
}
|
||||
browser.test.sendMessage("ready");
|
||||
}, function(err) {
|
||||
browser.test.fail("Read promise rejected: " + err);
|
||||
browser.test.sendMessage("ready");
|
||||
});
|
||||
}
|
||||
let extensionData = {
|
||||
manifest: {
|
||||
content_scripts: [{
|
||||
js: ["shared.js", "contentscript.js"],
|
||||
matches: ["https://example.com/*/file_sample.html"],
|
||||
}],
|
||||
permissions: [
|
||||
"clipboardRead",
|
||||
],
|
||||
},
|
||||
files: {
|
||||
"shared.js": shared,
|
||||
"contentscript.js": contentScript,
|
||||
},
|
||||
};
|
||||
|
||||
await SimpleTest.promiseClipboardChange("", () => {
|
||||
clearClipboard();
|
||||
}, "text/x-moz-place-empty");
|
||||
let extension = ExtensionTestUtils.loadExtension(extensionData);
|
||||
await extension.startup();
|
||||
let win = window.open("https://example.com/tests/toolkit/components/extensions/test/mochitest/test-oop-extensions/file_sample.html");
|
||||
await extension.awaitMessage("ready");
|
||||
await extension.unload();
|
||||
win.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче