зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
d9b9a8b93b
Коммит
f4441de2f2
|
@ -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>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче