Juanjo Tugores cf40f73057 Fix unexpected ViewFocus events when Text Editing utilities change focus in the middle of a blur call. (flutter/engine#54965)
In [some cases][1], text editing utilities re-focus the `<input />` element during a blur event. This causes an unusual sequence of `focusin` and `focusout` events, leading to the engine sending unintended events.

Consider the following HTML code:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <div id="container">
    <input type="" value="1" id="input1">
    <input type="" value="2" id="input2">
    <input type="" value="3" id="input3">
  </div>

  <script>
    container.addEventListener('focusin', (ev) => {
      console.log('focusin: focus was gained by', ev.target);
    });
    container.addEventListener('focusout', (ev) => {
      console.log('focusout: focus is leaving', ev.target, 'and it will go to', ev.relatedTarget);
    });
  </script>
</body>
</html>
```

Clicking input1, then input2, then input3 produces the following console logs:

```
// Input1 is clicked
focusin: focus was gained by <input type value=​"1" id=​"input1">​

// Input2 is clicked
focusout: focus is leaving <input type value=​"1" id=​"input1">​ and it will go to <input type value=​"2" id=​"input2">​
focusin: focus was gained by <input type value=​"2" id=​"input2">​

// Input3 is clicked
focusout: focus is leaving <input type value=​"2" id=​"input2">​ and it will go to <input type value=​"3" id=​"input3">​
focusin: focus was gained by <input type value=​"3" id=​"input3">​
```

Now, let's add a blur handler that changes focus:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <div id="container">
    <input type="" value="1" id="input1">
    <input type="" value="2" id="input2">
    <input type="" value="3" id="input3">
  </div>

  <script>
    container.addEventListener('focusin', (ev) => {
      console.log('focusin: focus was gained by', ev.target);
    });
    container.addEventListener('focusout', (ev) => {
      console.log('focusout: focus is leaving', ev.target, 'and it will go to', ev.relatedTarget);
    });
    input2.addEventListener('blur', (ev) => {
      input2.focus();
    });
  </script>
</body>
</html>
```

The log sequence changes and gives the wrong impression that no dom element has focus:

```
// Input1 is clicked
focusin: focus was gained by <input type value=​"1" id=​"input1">​

// Input2 is clicked
focusout: focus is leaving <input type value=​"1" id=​"input1">​ and it will go to <input type value=​"2" id=​"input2">​
focusin: focus was gained by <input type value=​"2" id=​"input2">​

// Input3 is clicked, but the handler kicks in and instead of the following line being a focusout, it results in a focusin call first.
focusin: focus was gained by <input type value=​"2" id=​"input2">​
focusout: focus is leaving <input type value=​"2" id=​"input2">​ and it will go to null
```

In addition to that, during `focusout` processing, `activeElement` typically points to `<body />`. However, if an element is focused during a `blur` event, `activeElement` points to that focused element.  Although, undocumented it can be verified with:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <div id="container">
    <input type="" value="1" id="input1">
    <input type="" value="2" id="input2">
    <input type="" value="3" id="input3">
  </div>

  <script>
    container.addEventListener('focusin', (ev) => {
      console.log('focusin: was gained by', ev.target);
    });
    container.addEventListener('focusout', (ev) => {
      console.log('document.hasFocus()', document.hasFocus());     
      console.log('document.activeElement', document.activeElement);
      console.log('focusout: focus is leaving', ev.target, 'and it will go to', ev.relatedTarget);
    });
    input2.addEventListener('blur', (ev) => {
      input2.focus();
    });
  </script>
</body>
</html>
```

We leverage these behaviors to ignore `focusout` events when the document has focus but `activeElement` is not `<body />`.

https://github.com/flutter/flutter/issues/153022

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
2024-09-05 18:23:55 +00:00
Languages
Dart 75%
C++ 16.5%
Objective-C++ 2.9%
Java 2.8%
Objective-C 0.7%
Other 1.9%