Bug 1533989 - Make InputEvent.data and InputEvent.dataTransfer not expose clipboard data if user disables clipboard events r=smaug

If user disables clipboard events, it means that they don't want to expose
clipboard data to web apps even if web apps cannot handle "paste" operation.
Therefore, they must not want to leak clipboard data with `InputEvent.data`
and `InputEvent.dataTransfer`.

This patch makes `InputEvent::GetData()` and `InputEvent::GetDataTransfer()`
returns empty string or new `DataTransfer` object which has only empty string
if:
- They are called by content JS.
- The event is a trusted event.
- `inputType` value is `insertFromPaste` or `insertFromPasteAsQuotation`.

The reason why we don't return null for both is, Input Events spec declares
`data` or `dataTransfer` shouldn't be null in the `inputType` values.  And
the reason why we don't return empty `DataTransfer` is, web apps may expect
at least one data is stored in non-null `dataTransfer` value.

Differential Revision: https://phabricator.services.mozilla.com/D25350

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-03-29 16:08:11 +00:00
Родитель 2dc9aa5a90
Коммит e28c807e5a
9 изменённых файлов: 82 добавлений и 13 удалений

Просмотреть файл

@ -718,8 +718,7 @@ bool nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
// next, fire the cut, copy or paste event
bool doDefault = true;
RefPtr<DataTransfer> clipboardData;
if (chromeShell ||
Preferences::GetBool("dom.event.clipboardevents.enabled", true)) {
if (chromeShell || StaticPrefs::dom_event_clipboardevents_enabled()) {
clipboardData =
new DataTransfer(doc->GetScopeObject(), aEventMessage,
originalEventMessage == ePaste, aClipboardType);

Просмотреть файл

@ -27,16 +27,40 @@ InputEvent::InputEvent(EventTarget* aOwner, nsPresContext* aPresContext,
}
}
void InputEvent::GetData(nsAString& aData) {
void InputEvent::GetData(nsAString& aData, CallerType aCallerType) {
InternalEditorInputEvent* editorInputEvent = mEvent->AsEditorInputEvent();
MOZ_ASSERT(editorInputEvent);
// If clipboard event is disabled, user may not want to leak clipboard
// information via DOM events. If so, we should return empty string instead.
if (mEvent->IsTrusted() && aCallerType != CallerType::System &&
!StaticPrefs::dom_event_clipboardevents_enabled() &&
ExposesClipboardDataOrDataTransfer(editorInputEvent->mInputType)) {
aData = editorInputEvent->mData.IsVoid() ? VoidString() : EmptyString();
return;
}
aData = editorInputEvent->mData;
}
DataTransfer* InputEvent::GetDataTransfer() {
already_AddRefed<DataTransfer> InputEvent::GetDataTransfer(
CallerType aCallerType) {
InternalEditorInputEvent* editorInputEvent = mEvent->AsEditorInputEvent();
MOZ_ASSERT(editorInputEvent);
return editorInputEvent->mDataTransfer;
// If clipboard event is disabled, user may not want to leak clipboard
// information via DOM events. If so, we should return DataTransfer which
// has empty string instead. The reason why we make it have empty string is,
// web apps may not expect that InputEvent.dataTransfer returns empty and
// non-null DataTransfer instance.
if (mEvent->IsTrusted() && aCallerType != CallerType::System &&
!StaticPrefs::dom_event_clipboardevents_enabled() &&
ExposesClipboardDataOrDataTransfer(editorInputEvent->mInputType)) {
if (!editorInputEvent->mDataTransfer) {
return nullptr;
}
return do_AddRef(
new DataTransfer(editorInputEvent->mDataTransfer->GetParentObject(),
editorInputEvent->mMessage, EmptyString()));
}
return do_AddRef(editorInputEvent->mDataTransfer);
}
void InputEvent::GetInputType(nsAString& aInputType) {

Просмотреть файл

@ -34,8 +34,9 @@ class InputEvent : public UIEvent {
}
void GetInputType(nsAString& aInputType);
void GetData(nsAString& aData);
DataTransfer* GetDataTransfer();
void GetData(nsAString& aData, CallerType aCallerType = CallerType::System);
already_AddRefed<DataTransfer> GetDataTransfer(
CallerType aCallerType = CallerType::System);
bool IsComposing();
protected:

Просмотреть файл

@ -602,9 +602,11 @@ add_task(async function test_eventspref_disabled() {
});
var event_fired = false;
var input_data = undefined;
contentInput.oncut = function() { event_fired = true; };
contentInput.oncopy = function() { event_fired = true; };
contentInput.onpaste = function() { event_fired = true; };
contentInput.oninput = function(event) { input_data = event.data; };
try {
selectContentInput();
contentInput.setSelectionRange(1, 4);
@ -613,24 +615,30 @@ add_task(async function test_eventspref_disabled() {
synthesizeKey("x", {accelKey: 1});
}, "cut changed clipboard when preference is disabled");
is(contentInput.value, "IT TEXT", "cut changed text when preference is disabled");
ok(!event_fired, "cut event did not fire when preference is disabled")
ok(!event_fired, "cut event did not fire when preference is disabled");
is(input_data, null, "cut should cause input event whose data value is null");
event_fired = false;
input_data = undefined;
contentInput.setSelectionRange(3, 6);
await putOnClipboard("TEX", () => {
synthesizeKey("c", {accelKey: 1});
}, "copy changed clipboard when preference is disabled");
ok(!event_fired, "copy event did not fire when preference is disabled")
is(input_data, undefined, "copy shouldn't cause input event");
event_fired = false;
contentInput.setSelectionRange(0, 2);
synthesizeKey("v", {accelKey: 1});
is(contentInput.value, "TEX TEXT", "paste changed text when preference is disabled");
ok(!event_fired, "paste event did not fire when preference is disabled")
ok(!event_fired, "paste event did not fire when preference is disabled");
is(input_data, "",
"paste should cause input event but whose data value should be empty string if clipboard event is disabled");
} finally {
contentInput.oncut = null;
contentInput.oncopy = null;
contentInput.onpaste = null;
contentInput.oninput = null;
}
await new Promise(resolve => {

Просмотреть файл

@ -12,7 +12,7 @@ interface InputEvent : UIEvent
[Pref="dom.inputevent.inputtype.enabled"]
readonly attribute DOMString inputType;
[Pref="dom.inputevent.data.enabled"]
[NeedsCallerType, Pref="dom.inputevent.data.enabled"]
readonly attribute DOMString? data;
};
@ -32,7 +32,7 @@ dictionary InputEventInit : UIEventInit
// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent
partial interface InputEvent
{
[Pref="dom.inputevent.datatransfer.enabled"]
[NeedsCallerType, Pref="dom.inputevent.datatransfer.enabled"]
readonly attribute DataTransfer? dataTransfer;
};

Просмотреть файл

@ -305,6 +305,24 @@ async function doContenteditableTests(aEditableDiv) {
'No "input" event should be fired if "auxclick" event is canceled (contenteditable)');
aEditableDiv.innerHTML = "";
// If clipboard event is disabled, InputEvent.dataTransfer should have only empty string.
await SpecialPowers.pushPrefEnv({"set": [["dom.event.clipboardevents.enabled", false]]});
await copyPlaintext("abc");
aEditableDiv.focus();
pasteEventCount = 0;
inputEvents = [];
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
is(aEditableDiv.innerHTML, "abc",
"Even if clipboard event is disabled, paste should be done");
is(pasteEventCount, 0,
"If clipboard event is disabled, 'paste' event shouldn't be fired once");
is(inputEvents.length, 1,
'One "input" event should be fired even if clipboard event is disabled (contenteditable)');
checkInputEvent(inputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: ""}],
"when clipboard event is disabled (contenteditable)");
await SpecialPowers.pushPrefEnv({"set": [["dom.event.clipboardevents.enabled", true]]});
aEditableDiv.innerHTML = "";
aEditableDiv.removeEventListener("paste", pasteEventLogger);
// Oddly, copyHTMLContent fails randomly only on Linux. Let's skip this.
@ -418,7 +436,8 @@ async function doAfterRemoveOfClickedElementTest(aEditableDiv) {
async function doTests() {
await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", true],
["middlemouse.contentLoadURL", false]]});
["middlemouse.contentLoadURL", false],
["dom.event.clipboardevents.enabled", true]]});
let container = document.getElementById("container");
container.innerHTML = "<textarea id=\"editor\"></textarea>";
await doTextareaTests(document.getElementById("editor"));

Просмотреть файл

@ -209,6 +209,15 @@ VARCACHE_PREF(
)
#undef PREF_VALUE
// If this is true, it's allowed to fire "cut", "copy" and "paste" events.
// Additionally, "input" events may expose clipboard content when inputType
// is "insertFromPaste" or something.
VARCACHE_PREF(
"dom.event.clipboardevents.enabled",
dom_event_clipboardevents_enabled,
bool, true
)
// If this is true, "keypress" event's keyCode value and charCode value always
// become same if the event is not created/initialized by JS.
VARCACHE_PREF(

Просмотреть файл

@ -1441,7 +1441,6 @@ pref("privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 1000);
pref("privacy.resistFingerprinting.reduceTimerPrecision.jitter", true);
pref("dom.event.contextmenu.enabled", true);
pref("dom.event.clipboardevents.enabled", true);
pref("dom.event.coalesce_mouse_move", true);
pref("javascript.enabled", true);

Просмотреть файл

@ -130,6 +130,16 @@ enum class EditorInputType : EditorInputTypeType {
#undef NS_DEFINE_INPUTTYPE
inline bool ExposesClipboardDataOrDataTransfer(EditorInputType aInputType) {
switch (aInputType) {
case EditorInputType::eInsertFromPaste:
case EditorInputType::eInsertFromPasteAsQuotation:
return true;
default:
return false;
}
}
/**
* IsDataAvailableOnTextEditor() returns true if aInputType on TextEditor
* should have non-null InputEvent.data value.