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;
}
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
nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
int32_t aClipboardType,
@ -716,29 +732,32 @@ nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
if (!piWindow)
return false;
// if a selection was not supplied, try to find it
nsCOMPtr<nsIContent> content;
// Event target of clipboard events should be an element node which
// contains selection start container.
RefPtr<Element> targetElement;
// If a selection was not supplied, try to find it.
RefPtr<Selection> sel = aSelection;
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) {
RefPtr<nsRange> range = sel->GetRangeAt(0);
nsRange* range = sel->GetRangeAt(0);
if (range) {
nsINode* startContainer = range->GetStartContainer();
if (startContainer) {
content = do_QueryInterface(startContainer);
}
targetElement =
GetElementOrNearestFlattenedTreeParentElement(
range->GetStartContainer());
}
}
// if no content node was set, just get the root
if (!content) {
content = doc->GetRootElement();
if (!content)
// If there is no selection ranges, use the <body> or <frameset> element.
if (!targetElement) {
targetElement = doc->GetBody();
if (!targetElement) {
return false;
}
}
// 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;
InternalClipboardEvent evt(true, originalEventMessage);
evt.mClipboardData = clipboardData;
EventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt,
EventDispatcher::Dispatch(targetElement, presShell->GetPresContext(), &evt,
nullptr, &status);
// If the event was cancelled, don't do the clipboard operation
doDefault = (status != nsEventStatus_eConsumeNoDefault);
@ -807,13 +826,13 @@ nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
uint32_t count = 0;
if (doDefault) {
// find the focused node
nsCOMPtr<nsIContent> srcNode = content;
if (content->IsInNativeAnonymousSubtree()) {
srcNode = content->FindFirstNonChromeOnlyAccessContent();
nsIContent* sourceContent = targetElement.get();
if (targetElement->IsInNativeAnonymousSubtree()) {
sourceContent = targetElement->FindFirstNonChromeOnlyAccessContent();
}
// 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->ControlType() == NS_FORM_INPUT_PASSWORD) {
return false;
@ -822,7 +841,7 @@ nsCopySupport::FireClipboardEvent(EventMessage aEventMessage,
// when cutting non-editable content, do nothing
// 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
if (sel->IsCollapsed()) {
if (aActionTaken) {

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

@ -18,10 +18,13 @@
oncopy="compareSynthetic(event, 'copy')"
onpaste="compareSynthetic(event, 'paste')">Spot</div>
<div id="contenteditableContainer"></div>
<pre id="test">
<script class="testbody" type="text/javascript">
var content = document.getElementById("content");
var contentInput = document.getElementById("content-input");
var contenteditableContainer = document.getElementById("contenteditableContainer");
var clipboardInitialValue = "empty";
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>
</pre>
</body>