mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-31 19:10:36 +00:00
Bug 1521396 - Make ClickHandlerChild prevent multiple action of middle click on link element for preventing middle click paste r=smaug
When user middle clicks a link, most users must not expect to expose clipboard content to the web application. Therefore, we should stop firing paste event when user click a link with middle button. This patch makes ClickHandlerChild.handleEvent() prevent multiple action when it posts middle click event on a link. Note that even if middle click event is consumed, default event handler will dispatch paste event. Unfortunately, this is compatible behavior with the other browsers. Therefore, we cannot change this behavior with calling preventDefault() and this is the reason why this patch adds Event.preventMultipleActions(). Out of scope of this bug though, if there is an element which looks like a link but implemented with JS, web apps can steal clipboard content if user enables middle click event and user just wants to open the link in new tab. It might be better to stop dispatching paste event in any browsers and request to change each web apps. Differential Revision: https://phabricator.services.mozilla.com/D17209 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
74a30370f2
commit
96bd720118
@ -88,6 +88,16 @@ class ClickHandlerChild extends ActorChild {
|
||||
json.originPrincipal = ownerDoc.nodePrincipal;
|
||||
json.triggeringPrincipal = ownerDoc.nodePrincipal;
|
||||
|
||||
// If a link element is clicked with middle button, user wants to open
|
||||
// the link somewhere rather than pasting clipboard content. Therefore,
|
||||
// when it's clicked with middle button, we should prevent multiple
|
||||
// actions here to avoid leaking clipboard content unexpectedly.
|
||||
// Note that whether the link will work actually or not does not matter
|
||||
// because in this case, user does not intent to paste clipboard content.
|
||||
if (event.button === 1) {
|
||||
event.preventMultipleActions();
|
||||
}
|
||||
|
||||
this.mm.sendAsyncMessage("Content:Click", json);
|
||||
return;
|
||||
}
|
||||
|
@ -60,6 +60,9 @@ skip-if = !e10s # Pref and test only relevant for e10s.
|
||||
[browser_open_newtab_start_observer_notification.js]
|
||||
[browser_opened_file_tab_navigated_to_web.js]
|
||||
[browser_overflowScroll.js]
|
||||
[browser_paste_event_at_middle_click_on_link.js]
|
||||
subsuite = clipboard
|
||||
support-files = file_anchor_elements.html
|
||||
[browser_pinnedTabs_clickOpen.js]
|
||||
[browser_pinnedTabs_closeByKeyboard.js]
|
||||
[browser_pinnedTabs.js]
|
||||
|
@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
|
||||
add_task(async function doCheckPasteEventAtMiddleClickOnAnchorElement() {
|
||||
await SpecialPowers.pushPrefEnv({set: [
|
||||
["browser.tabs.opentabfor.middleclick", true],
|
||||
["middlemouse.paste", true],
|
||||
["middlemouse.contentLoadURL", false],
|
||||
["general.autoScroll", false],
|
||||
]});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
SimpleTest.waitForClipboard("Text in the clipboard", () => {
|
||||
Cc["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(Ci.nsIClipboardHelper)
|
||||
.copyString("Text in the clipboard");
|
||||
}, resolve, () => {
|
||||
ok(false, "Clipboard copy failed");
|
||||
reject();
|
||||
});
|
||||
});
|
||||
|
||||
is(gBrowser.tabs.length, 1, "Number of tabs should be 1 at starting this test #1");
|
||||
|
||||
let pageURL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
|
||||
pageURL = `${pageURL}file_anchor_elements.html`;
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageURL);
|
||||
|
||||
let pasteEventCount = 0;
|
||||
BrowserTestUtils.addContentEventListener(gBrowser.selectedBrowser, "paste", () => { ++pasteEventCount; });
|
||||
|
||||
// Click the usual link.
|
||||
ok(true, "Clicking on usual link...");
|
||||
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/#a_with_href", true);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#a_with_href",
|
||||
{button: 1}, gBrowser.selectedBrowser);
|
||||
let openTabForUsualLink = await newTabPromise;
|
||||
is(openTabForUsualLink.linkedBrowser.currentURI.spec, "http://example.com/#a_with_href",
|
||||
"Middle click should open site to correct url at clicking on usual link");
|
||||
is(pasteEventCount, 0, "paste event should be suppressed when clicking on usual link");
|
||||
|
||||
// Click the link in editing host.
|
||||
is(gBrowser.tabs.length, 3, "Number of tabs should be 3 at starting this test #2");
|
||||
ok(true, "Clicking on editable link...");
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#editable_a_with_href",
|
||||
{button: 1}, gBrowser.selectedBrowser);
|
||||
await TestUtils.waitForCondition(() => pasteEventCount >= 1,
|
||||
"Waiting for paste event caused by clicking on editable link");
|
||||
is(pasteEventCount, 1, "paste event should be suppressed when clicking on editable link");
|
||||
is(gBrowser.tabs.length, 3, "Clicking on editable link shouldn't open new tab");
|
||||
|
||||
// Click the link in non-editable area in editing host.
|
||||
ok(true, "Clicking on non-editable link in an editing host...");
|
||||
newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/#non-editable_a_with_href", true);
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#non-editable_a_with_href",
|
||||
{button: 1}, gBrowser.selectedBrowser);
|
||||
let openTabForNonEditableLink = await newTabPromise;
|
||||
is(openTabForNonEditableLink.linkedBrowser.currentURI.spec, "http://example.com/#non-editable_a_with_href",
|
||||
"Middle click should open site to correct url at clicking on non-editable link in an editing host.");
|
||||
is(pasteEventCount, 1, "paste event should be suppressed when clicking on non-editable link in an editing host");
|
||||
|
||||
// Click the <a> element without href attribute.
|
||||
is(gBrowser.tabs.length, 4, "Number of tabs should be 4 at starting this test #3");
|
||||
ok(true, "Clicking on anchor element without href...");
|
||||
await BrowserTestUtils.synthesizeMouseAtCenter("#a_with_name",
|
||||
{button: 1}, gBrowser.selectedBrowser);
|
||||
await TestUtils.waitForCondition(() => pasteEventCount >= 2,
|
||||
"Waiting for paste event caused by clicking on anchor element without href");
|
||||
is(pasteEventCount, 2, "paste event should be suppressed when clicking on anchor element without href");
|
||||
is(gBrowser.tabs.length, 4, "Clicking on anchor element without href shouldn't open new tab");
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
BrowserTestUtils.removeTab(openTabForUsualLink);
|
||||
BrowserTestUtils.removeTab(openTabForNonEditableLink);
|
||||
});
|
12
browser/base/content/test/tabs/file_anchor_elements.html
Normal file
12
browser/base/content/test/tabs/file_anchor_elements.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Testing whether paste event is fired at middle click on anchor elements</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Here is an <a id="a_with_href" href="http://example.com/#a_with_href">anchor element</a></p>
|
||||
<p contenteditable>Here is an <a id="editable_a_with_href" href="http://example.com/#editable_a_with_href">editable anchor element</a></p>
|
||||
<p contenteditable>Here is <span contenteditable="false"><a id="non-editable_a_with_href" href="http://example.com/#non-editable_a_with_href">non-editable anchor element</a></span>
|
||||
<p>Here is an <a id="a_with_name" name="a_with_name">anchor element without href</a></p>
|
||||
</body>
|
||||
</html>
|
@ -221,6 +221,10 @@ class Event : public nsISupports, public nsWrapperCache {
|
||||
return mEvent->mFlags.mDefaultPreventedByContent;
|
||||
}
|
||||
|
||||
void PreventMultipleActions() {
|
||||
mEvent->mFlags.mMultipleActionsPrevented = true;
|
||||
}
|
||||
|
||||
bool MultipleActionsPrevented() const {
|
||||
return mEvent->mFlags.mMultipleActionsPrevented;
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ partial interface Event {
|
||||
*/
|
||||
readonly attribute EventTarget? explicitOriginalTarget;
|
||||
[ChromeOnly] readonly attribute EventTarget? composedTarget;
|
||||
[ChromeOnly] void preventMultipleActions();
|
||||
[ChromeOnly] readonly attribute boolean multipleActionsPrevented;
|
||||
[ChromeOnly] readonly attribute boolean isSynthesized;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user