Bug 268959 - event.target is incorrect when dragging selected text
Summary: event.target is incorrect when dragging selected text
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: UI Events (show other bugs)
Version: Safari 17
Hardware: Mac (Apple Silicon) macOS 14
: P2 Normal
Assignee: Nobody
URL:
Keywords: BrowserCompat, InRadar
Depends on:
Blocks:
 
Reported: 2024-02-07 19:42 PST by alexreardon
Modified: 2024-04-27 16:56 PDT (History)
4 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description alexreardon 2024-02-07 19:42:05 PST
When dragging a text selection, the `event.target` in the `"dragstart"` event should be the `Text` node that the drag started from.

> If it is a selection that is being dragged, then the source node is the `Text` node that the user started the drag on (typically the `Text` node that the user originally clicked). If the user did not specify a particular node, for example if the user just told the user agent to begin a drag of "the selection", then the source node is the first `Text` node containing a part of the selection.
> → [Drag and drop spec](https://html.spec.whatwg.org/multipage/dnd.html#drag-and-drop-processing-model)

`Chrome@121` and `Firefox@122` follow this behaviour. However, `Safari@17.2.1` does not. In Safari, the `event.target` when dragging a text selection, is being set to the `HTMLElement` that the drag started from.

**Reproduction**

```js
// reproduction case
window.addEventListener("dragstart", (event) => {
  console.log("dragstart", {
    target: event.target,
    // true in Chrome and Firefox
    isTextNode: event.target instanceof Text,
    // true in Safari
    isHTMLElement: event.target instanceof HTMLElement
  });
});
```

- [source code for example](https://codesandbox.io/s/text-selection-bug-for-safari-72spdd?file=/src/index.ts)
- [standalone example](https://72spdd.csb.app/) (see `console.log` for results)

**Workaround**

```js
function findTextNode(event: DragEvent): Text | null {
  // Standard: the `event.target` should be the closest `Text` node.
  if (!isSafari()) {
    return event.target instanceof Text ? event.target : null;
  }

  // `event.target` should be the `Text` node that the drag started from
  // when dragging a selection. However, in Safari the closest `HTMLElement` is returned.
  // So we need to figure out if text is dragging ourselves.
  if (!(event.target instanceof HTMLElement)) {
    return null;
  }

  // unlikely that this particular drag is a text selection drag
  if (event.target.draggable) {
    return null;
  }

  // if the drag contains no text data, then not doing a text drag
  if (event.dataTransfer?.getData('text/plain') === '') {
    return null;
  }

  // Grab the first text node and use that
  const text: Text | undefined = Array.from(event.target.childNodes).find(
    (node): node is Text => node.nodeType === Node.TEXT_NODE,
  );

  return text ?? null;
}
```
Comment 1 alexreardon 2024-02-07 19:56:26 PST
For the workaround:

```diff
- if (!(event.target instanceof HTMLElement)) {
-   return null;
- }
+ // return `null` if there is no dataTransfer, or if `getData()` returns ""
+ if (!event.dataTransfer?.getData('text/plain')) {
+   return null;
+ }
```
Comment 2 Ahmad Saleem 2024-02-08 07:11:34 PST
*** WebKit ToT (274283@main) ***

[Log] dragstart – {target: <p>, isTextNode: false, isHTMLElement: true}
{target: <p>, isTextNode: false, isHTMLElement: true}ObjectisHTMLElement: trueisTextNode: falsetarget: <p><p>Object Prototype

___

As mentioned in Comment 0, it is indeed `false` for isTextNode while `true` for isHTMLElement. Although it is opposite in both cases for Chrome Canary 123 and matches with Firefox Nightly 124.
Comment 3 Ahmad Saleem 2024-02-08 07:12:21 PST
(In reply to Ahmad Saleem from comment #2)
> *** WebKit ToT (274283@main) ***
> 
> [Log] dragstart – {target: <p>, isTextNode: false, isHTMLElement: true}
> {target: <p>, isTextNode: false, isHTMLElement: true}ObjectisHTMLElement:
> trueisTextNode: falsetarget: <p><p>Object Prototype
> 
> ___
> 
> As mentioned in Comment 0, it is indeed `false` for isTextNode while `true`
> for isHTMLElement. Although it is opposite in both cases for Chrome Canary
> 123 and matches with Firefox Nightly 124.

Chrome matches with Firefox (just to be clear) while Safari / WebKit is outlier. Hence, I added `BrowserCompat` tag as well.
Comment 4 Radar WebKit Bug Importer 2024-02-08 13:08:04 PST
<rdar://problem/122574058>
Comment 5 Ahmad Saleem 2024-04-27 16:56:52 PDT
Following library has workaround for this - https://github.com/atlassian/pragmatic-drag-and-drop