Skip to content

[pointer_interceptor_web] Fix unresponsive input above PointerInterceptor on Safari and Firefox. #9362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

ksokolovskyi
Copy link
Contributor

@ksokolovskyi ksokolovskyi commented Jun 3, 2025

Fixes flutter/flutter#157920

Description

When the TextField is placed above the HtmlElementView, it becomes unresponsive on Safari and Firefox. After the investigation, I found that this happens because the underlying input/textarea loses focus, leading to not listening to the keyboard input.

After some investigation, I found out that calling preventDefault on mousedown events on PointerInterceptor div element prevents the input/textarea from losing focus.

The same was already done for SelectionArea in flutter/flutter#167275

Before After
https://input-above-interceptor-bug.web.app https://input-above-interceptor-fix.web.app
bug.mov
fix.mov
Application Source Code
import 'package:flutter/material.dart';
import 'package:web/web.dart' as web;
import 'package:pointer_interceptor/pointer_interceptor.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Stack(
          children: [
            Positioned.fill(
              child: HtmlElementView.fromTagName(
                tagName: 'iframe',
                onElementCreated: (element) {
                  (element as web.HTMLIFrameElement);
                  element.src = 'https://flutter.dev';
                  element.style
                    ..border = 'none'
                    ..height = '100%'
                    ..width = '100%';
                },
              ),
            ),
            Center(
              child: PointerInterceptor(
                debug: true,
                child: Container(
                  width: 400,
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(20),
                    color: Colors.grey.shade300,
                  ),
                  padding: const EdgeInsets.all(20),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      OneLineTextField(),
                      OneLineTextField(),
                      OneLineTextField(),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class OneLineTextField extends StatelessWidget {
  const OneLineTextField({super.key});

  @override
  Widget build(BuildContext context) {
    return TextField(
      decoration: InputDecoration(
        labelText: 'One-line',
        floatingLabelBehavior: FloatingLabelBehavior.always,
      ),
    );
  }
}

Pre-Review Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. 2 3

@stuartmorgan-g
Copy link
Contributor

This should be reviewed by web experts. /cc @mdebbar to find an appropriate reviewer.

Copy link
Contributor

@mdebbar mdebbar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 46 to 59
onElementCreated: (Object element) {
if (debug) {
_debugOnElementCreated(element);
}

// Prevent the default action of `mousedown` events to avoid
// input focus loss.
(element as web.HTMLElement).addEventListener(
'mousedown',
(web.Event event) {
event.preventDefault();
}.toJS,
);
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's do the type casting only once:

Suggested change
onElementCreated: (Object element) {
if (debug) {
_debugOnElementCreated(element);
}
// Prevent the default action of `mousedown` events to avoid
// input focus loss.
(element as web.HTMLElement).addEventListener(
'mousedown',
(web.Event event) {
event.preventDefault();
}.toJS,
);
},
onElementCreated: (Object element) {
element as web.HTMLElement;
if (debug) {
_debugOnElementCreated(element);
}
// Prevent the default action of `mousedown` events to avoid
// input focus loss.
element.addEventListener(
'mousedown',
(web.Event event) {
event.preventDefault();
}.toJS,
);
},

And _debugOnElementCreated can be changed to:

  void _debugOnElementCreated(Object element) {
    element.style
      ..width = '100%'
      ..height = '100%'
      ..backgroundColor = 'rgba(255, 0, 0, .5)';
  }

@ksokolovskyi
Copy link
Contributor Author

@mdebbar thanks a lot for the review!

@stuartmorgan-g, I haven't updated the version in this PR and added the change into the ## NEXT section right after non-released changes related to the Flutter/Dart version constraints update. Could you please suggest how to proceed properly with the version update?

@stuartmorgan-g
Copy link
Contributor

Could you please suggest how to proceed properly with the version update?

I'm not sure what you mean by "how to proceed". Are you asking what kind of version bump this should be?

@ksokolovskyi
Copy link
Contributor Author

I'm not sure what you mean by "how to proceed". Are you asking what kind of version bump this should be?

@stuartmorgan-g, there are some unreleased changes already, so I added mine to the ## NEXT section of the changelog next to them. I am unsure whether I need to bump the version in this PR, or if it will be updated and released later.

@stuartmorgan-g
Copy link
Contributor

I am unsure whether I need to bump the version in this PR, or if it will be updated and released later.

Could you let me know what part of our policy (linked from the PR checklist) isn't clear as applied to this PR, so I can clarify it?

@ksokolovskyi
Copy link
Contributor Author

@stuartmorgan-g Sorry, I reread the docs and figured everything out.

@ksokolovskyi ksokolovskyi added the autosubmit Merge PR when tree becomes green via auto submit App label Jun 3, 2025
@auto-submit auto-submit bot merged commit fc4a91e into flutter:main Jun 3, 2025
80 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 4, 2025
github-merge-queue bot pushed a commit to flutter/flutter that referenced this pull request Jun 4, 2025
flutter/packages@1765c95...2bac766

2025-06-03 [email protected] [in_app_purchase_android]
GooglePlayPurchaseParam add possibility set selected offerToken
(flutter/packages#8452)
2025-06-03 [email protected] [camera] 🐛 Fix toggles overflow in the
camera example (flutter/packages#9274)
2025-06-03 [email protected] [shared_preferences] Remove duplicate
integration test (flutter/packages#9368)
2025-06-03 [email protected] [pointer_interceptor_web]
Fix unresponsive input above PointerInterceptor on Safari and Firefox.
(flutter/packages#9362)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected] on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Ortes pushed a commit to Ortes/packages that referenced this pull request Jun 25, 2025
…ptor on Safari and Firefox. (flutter#9362)

Fixes flutter/flutter#157920

### Description

When the `TextField` is placed above the `HtmlElementView`, it becomes unresponsive on Safari and Firefox. After the investigation, I found that this happens because the underlying `input`/`textarea` loses focus, leading to not listening to the keyboard input.

After some investigation, I found out that calling `preventDefault` on `mousedown` events on `PointerInterceptor` `div` element prevents the `input/textarea` from losing focus.

The same was already done for `SelectionArea` in flutter/flutter#167275

| Before | After |
| :---: | :---: |
| https://input-above-interceptor-bug.web.app | https://input-above-interceptor-fix.web.app |
| <video src="https://github.com/user-attachments/assets/3537d34d-9eb2-4a36-bbcf-4cb0de01133d" /> | <video src="https://github.com/user-attachments/assets/14458b51-bcf0-4761-9b57-7735a214125b" /> |

<details>
<summary>Application Source Code</summary>

```dart
import 'package:flutter/material.dart';
import 'package:web/web.dart' as web;
import 'package:pointer_interceptor/pointer_interceptor.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @OverRide
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Stack(
          children: [
            Positioned.fill(
              child: HtmlElementView.fromTagName(
                tagName: 'iframe',
                onElementCreated: (element) {
                  (element as web.HTMLIFrameElement);
                  element.src = 'https://flutter.dev';
                  element.style
                    ..border = 'none'
                    ..height = '100%'
                    ..width = '100%';
                },
              ),
            ),
            Center(
              child: PointerInterceptor(
                debug: true,
                child: Container(
                  width: 400,
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(20),
                    color: Colors.grey.shade300,
                  ),
                  padding: const EdgeInsets.all(20),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      OneLineTextField(),
                      OneLineTextField(),
                      OneLineTextField(),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class OneLineTextField extends StatelessWidget {
  const OneLineTextField({super.key});

  @OverRide
  Widget build(BuildContext context) {
    return TextField(
      decoration: InputDecoration(
        labelText: 'One-line',
        floatingLabelBehavior: FloatingLabelBehavior.always,
      ),
    );
  }
}
```

</details>

## Pre-Review Checklist

[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: pointer_interceptor platform-web
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Firefox/Safari: Wrapping TextField widget with PointerInterceptor makes it unresponsive
3 participants