Bug 1490884 - Make nsCopySupport set event target of clipboard events to conform to Clipboard API and events spec r=smaug

Clipboard API and events spec, event target of clipboard events should always
be an element node which contains the selection start and if there is no
Selection ranges, should use <body> or <frameset> of the document:
https://www.w3.org/TR/clipboard-apis/#fire-a-clipboard-event

This patch does not include the test for the latter because I have no idea how
to avoid adjusting selection adjustments immediately before pasting in editor's
middle click event handler or enable copy or paste commands without selection
ranges.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2018-09-14 07:22:24 +00:00
Родитель d9b9a8b93b
Коммит f4441de2f2
2 изменённых файлов: 79 добавлений и 19 удалений

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

@ -684,6 +684,22 @@ IsSelectionInsideRuby(Selection* aSelection)
return true; return true;
} }
static Element*
GetElementOrNearestFlattenedTreeParentElement(nsINode* aNode)
{
if (!aNode->IsContent()) {
return nullptr;
}
for (nsIContent* content = aNode->AsContent();
content;
content = content->GetFlattenedTreeParent()) {
if (content->IsElement()) {
return content->AsElement();
}
}
return nullptr;
}
bool bool
nsCopySupport::FireClipboardEvent(EventMessage aEventMessage, nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
int32_t aClipboardType, int32_t aClipboardType,
@ -716,29 +732,32 @@ nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
if (!piWindow) if (!piWindow)
return false; return false;
// if a selection was not supplied, try to find it // Event target of clipboard events should be an element node which
nsCOMPtr<nsIContent> content; // contains selection start container.
RefPtr<Element> targetElement;
// If a selection was not supplied, try to find it.
RefPtr<Selection> sel = aSelection; RefPtr<Selection> sel = aSelection;
if (!sel) { if (!sel) {
content = GetSelectionForCopy(doc, getter_AddRefs(sel)); GetSelectionForCopy(doc, getter_AddRefs(sel));
} }
// retrieve the event target node from the start of the selection // Retrieve the event target node from the start of the selection.
if (sel) { if (sel) {
RefPtr<nsRange> range = sel->GetRangeAt(0); nsRange* range = sel->GetRangeAt(0);
if (range) { if (range) {
nsINode* startContainer = range->GetStartContainer(); targetElement =
if (startContainer) { GetElementOrNearestFlattenedTreeParentElement(
content = do_QueryInterface(startContainer); range->GetStartContainer());
}
} }
} }
// if no content node was set, just get the root // If there is no selection ranges, use the <body> or <frameset> element.
if (!content) { if (!targetElement) {
content = doc->GetRootElement(); targetElement = doc->GetBody();
if (!content) if (!targetElement) {
return false; return false;
}
} }
// It seems to be unsafe to fire an event handler during reflow (bug 393696) // It seems to be unsafe to fire an event handler during reflow (bug 393696)
@ -762,7 +781,7 @@ nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
nsEventStatus status = nsEventStatus_eIgnore; nsEventStatus status = nsEventStatus_eIgnore;
InternalClipboardEvent evt(true, originalEventMessage); InternalClipboardEvent evt(true, originalEventMessage);
evt.mClipboardData = clipboardData; evt.mClipboardData = clipboardData;
EventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt, EventDispatcher::Dispatch(targetElement, presShell->GetPresContext(), &evt,
nullptr, &status); nullptr, &status);
// If the event was cancelled, don't do the clipboard operation // If the event was cancelled, don't do the clipboard operation
doDefault = (status != nsEventStatus_eConsumeNoDefault); doDefault = (status != nsEventStatus_eConsumeNoDefault);
@ -807,13 +826,13 @@ nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
uint32_t count = 0; uint32_t count = 0;
if (doDefault) { if (doDefault) {
// find the focused node // find the focused node
nsCOMPtr<nsIContent> srcNode = content; nsIContent* sourceContent = targetElement.get();
if (content->IsInNativeAnonymousSubtree()) { if (targetElement->IsInNativeAnonymousSubtree()) {
srcNode = content->FindFirstNonChromeOnlyAccessContent(); sourceContent = targetElement->FindFirstNonChromeOnlyAccessContent();
} }
// check if we are looking at a password input // check if we are looking at a password input
nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(srcNode); nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(sourceContent);
if (formControl) { if (formControl) {
if (formControl->ControlType() == NS_FORM_INPUT_PASSWORD) { if (formControl->ControlType() == NS_FORM_INPUT_PASSWORD) {
return false; return false;
@ -822,7 +841,7 @@ nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
// when cutting non-editable content, do nothing // when cutting non-editable content, do nothing
// XXX this is probably the wrong editable flag to check // XXX this is probably the wrong editable flag to check
if (originalEventMessage != eCut || content->IsEditable()) { if (originalEventMessage != eCut || targetElement->IsEditable()) {
// get the data from the selection if any // get the data from the selection if any
if (sel->IsCollapsed()) { if (sel->IsCollapsed()) {
if (aActionTaken) { if (aActionTaken) {

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

@ -18,10 +18,13 @@
oncopy="compareSynthetic(event, 'copy')" oncopy="compareSynthetic(event, 'copy')"
onpaste="compareSynthetic(event, 'paste')">Spot</div> onpaste="compareSynthetic(event, 'paste')">Spot</div>
<div id="contenteditableContainer"></div>
<pre id="test"> <pre id="test">
<script class="testbody" type="text/javascript"> <script class="testbody" type="text/javascript">
var content = document.getElementById("content"); var content = document.getElementById("content");
var contentInput = document.getElementById("content-input"); var contentInput = document.getElementById("content-input");
var contenteditableContainer = document.getElementById("contenteditableContainer");
var clipboardInitialValue = "empty"; var clipboardInitialValue = "empty";
var cachedCutData, cachedCopyData, cachedPasteData; var cachedCutData, cachedCopyData, cachedPasteData;
@ -768,6 +771,44 @@ add_task(async function test_image_dataTransfer() {
} }
}); });
add_task(async function test_event_target() {
await reset();
let copyTarget = null;
document.addEventListener("copy", (event) => { copyTarget = event.target; }, {once: true});
if (document.activeElement) {
document.activeElement.blur();
}
let selection = document.getSelection();
selection.setBaseAndExtent(content.firstChild, "CONTENT ".length,
content.firstChild, "CONTENT TEXT".length);
await putOnClipboard("TEXT", () => {
synthesizeKey("c", {accelKey: 1});
}, "copy text from non-editable element");
is(copyTarget.getAttribute("id"), "content", "Copy event's target should be always an element");
// Create a contenteditable element to check complicated event target.
contenteditableContainer.innerHTML = '<div contenteditable><p id="p1">foo</p><p id="p2">bar</p></div>';
contenteditableContainer.firstChild.focus();
let p1 = document.getElementById("p1");
let p2 = document.getElementById("p2");
selection.setBaseAndExtent(p1.firstChild, 1, p2.firstChild, 1);
let pasteTarget = null;
document.addEventListener("paste", (event) => { pasteTarget = event.target; }, {once: true});
synthesizeKey("v", {accelKey: 1});
is(pasteTarget.getAttribute("id"), "p1",
"'paste' event's target should be always an element which includes start container of the first Selection range");
contenteditableContainer.innerHTML = "";
});
</script> </script>
</pre> </pre>
</body> </body>