Repository: flutter
Issue: [iOS Web] EditableText's cursor position jumps to 0 after changing cursor position and entering some text when `readOnly=true`
Labels: a: text input,engine,platform-web,has reproducible steps,P2,browser: safari-ios,found in release: 2.10,found in release: 2.13,team-web,triaged-web

(This issue is split from #103024)

Update (May 8th): Edited to remove references to TextField which depends on EditableText and focusing this bug just on moving the cursor position before inserting text . See #103024 for the issue where the cursor loses its position after normal text input.

# Issue description (edited for clarity on May 8th)

I am building a custom keyboard for my Flutter web application and have found the EditableText widget resets the cursor position to zero after every key press on iOS web browsers (Chrome and Safari) when the readOnly property of the EditableText is set to TRUE. My application requires 'readOnly = true' to prevent unauthorized text from being entered in the input box.

Note: For simplicity, the sample code only includes 3 buttons ('A', 'B', and space). Changing the EditableText to TextField results in the same behavior (see #103075). 

Video of the issue comparing behavior on iOS Safari, OSX Safari, and OSX Chrome:
- Inserting characters into existing text results in the first character inserted at the cursor's location and all subsequent characters added at cursor position 0 ([link](https://youtu.be/7SCPEIGHL0o))

[Source on GitHub](https://github.com/the-ack/flutter_bug_editabletext_loses_cursor_position)

## Steps to Reproduce the error and Actual results
1. Run the code below on a web server which contains an EditableText with the readOnly = true 
a. For convenience, the code includes a toggle switch to change the EditableText's readOnly state so you can see how the behavior is inconsistent on iOS web devices

2. Access the web server using either:
a. An iOS device (iPhone or iPad) running mobile Safari or mobile Chrome 
b. The iOS simulator using the Safari browser built into the simulator

3. Click or tap in the EditableText to move the cursor to a different location. Tap a button to enter a character. The first character entered will be at the cursor's current location. Then the cursor will jump to position 0. Inserting additional characters will have them all entered at position zero. 

For example, if you want to add 'BBB' after the 'B' in the middle of the string 'A B A' to make 'A BBBB A', you will end up with 'BBA BB A'. Position the cursor at position 3 (after the 'B' in 'A B A'). Pressing the B key will create 'A BB A'. Then the cursor will reset to position 0 and the next 2 Bs will be inserted at position 0, creating 'BBA BB A'. 

In the video, I believe the first character in the initial text was highlighted and deleted upon new text entry (text selection highlight was not visible). The character will be entered at the cursor's current position. Then the cursor will jump back to position 0 and subsequent characters will always appear at position 0. For example, adding 'BBB' after the 'B' in 'A B A' results in 'BBBA B A' (video shows 'BBB B A' because I think the initial A was highlighted and replaced) instead of the expected 'A BBBB A'. ([video](https://youtu.be/7SCPEIGHL0o))

4. Change the switch to readOnly = false. Now the text entry behaves "normally" with each new character added after the cursor and the cursor does not jump to position 0 after each character is entered. Repositioning the cursor to somewhere in the middle of the inputted text keeps the cursor position at the expected location.

## Expected results
For both readOnly = true and readOnly = false, repositioning the cursor to somewhere in the middle of the inputted text keeps the cursor position at the expected location and text would be entered in the middle of the text as expected.  Adding 'BBB' after the 'B' in 'A B A' appears as expected 'A BBBB A'.

The expected behavior is what is observed on desktop browsers (Windows/OSX Chrome and Safari), Android Chrome web browser, iOS native app (simulated). Android native app was not tested.

As noted in #103024 and in the comments, the this bug also affected TextField which is an extension of EditableText.

<details>
<summary>Code sample</summary>



```dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: KeyboardDemo(),
    );
  }
}

class KeyboardDemo extends StatefulWidget {
  @override
  _KeyboardDemoState createState() => _KeyboardDemoState();
}

class _KeyboardDemoState extends State<KeyboardDemo> {
  TextEditingController _controller = TextEditingController();
  late FocusNode _focusNode;

  bool _readOnly = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: Container(
        padding: EdgeInsets.all(8),
        child: Column(
          children: [
            SizedBox(height: 50),
            Container(
              height: 50,
              decoration: BoxDecoration(
                border: Border.all(
                  width: 1,
                  color: Colors.grey,
                ),
                borderRadius: BorderRadius.circular(3),
              ),
              child: EditableText(
                controller: _controller,
                focusNode: _focusNode,
                style: TextStyle(
                  fontSize: 24,
                  color: Colors.black,
                ),
                cursorColor: Colors.black,
                backgroundCursorColor: Colors.grey,
                autocorrect: false,
                autofocus: true,
                showCursor: true,
                readOnly: _readOnly,
                toolbarOptions: const ToolbarOptions(
                  copy: true,
                  cut: false,
                  paste: false,
                  selectAll: false,
                ),
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('EditableText readOnly state: '),
                Switch(
                  value: _readOnly,
                  onChanged: (value) {
                    setState(() {
                      _readOnly = value;
                    });
                  },
                ),
              ],
            ),
            CustomKeyboard(
              onTextInput: (myText) {
                _insertText(myText);
              },
            ),
          ],
        ),
      ),
    );
  }

  void _insertText(String myText) {
    final text = _controller.text;
    final textSelection = _controller.selection;
    final newText = text.replaceRange(
      textSelection.start,
      textSelection.end,
      myText,
    );
    final myTextLength = myText.length;
    _controller.text = newText;
    _controller.selection = textSelection.copyWith(
      baseOffset: textSelection.start + myTextLength,
      extentOffset: textSelection.start + myTextLength,
    );
  }

  @override
  void initState() {
    super.initState();
    _focusNode = FocusNode();
  }

  @override
  void dispose() {
    _controller.dispose();
    _focusNode.dispose();
    super.dispose();
  }
}

class CustomKeyboard extends StatelessWidget {
  CustomKeyboard({
    Key? key,
    this.onTextInput,
  }) : super(key: key);

  final ValueSetter<String>? onTextInput;

  void _textInputHandler(String text) => onTextInput!.call(text);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 160,
      color: Colors.white,
      child: Column(
        children: [
          Expanded(
            child: Row(
              children: [
                TextKey(
                  text: 'A',
                  onTextInput: _textInputHandler,
                ),
                TextKey(
                  text: 'B',
                  onTextInput: _textInputHandler,
                ),
              ],
            ),
          ),
          Expanded(
            child: Row(
              children: [
                TextKey(
                  text: ' ',
                  onTextInput: _textInputHandler,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class TextKey extends StatelessWidget {
  const TextKey({
    Key? key,
    this.text,
    this.onTextInput,
    this.flex = 1,
  }) : super(key: key);

  final String? text;
  final ValueSetter<String>? onTextInput;
  final int flex;

  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: flex,
      child: Padding(
        padding: const EdgeInsets.all(1.0),
        child: Material(
          color: Colors.blue.shade100,
          child: InkWell(
            onTap: () {
              onTextInput?.call(text!);
            },
            child: Container(
              child: Center(child: Text(text!)),
            ),
          ),
        ),
      ),
    );
  }
}


```

</details>

<details>
  <summary>Logs</summary>
```
Because this only happens in mobile web browsers, I do not know how to capture the logs since the iOS simulator's web browser isn't connected to debugger.
```

<!--
     Run `flutter analyze` and attach any output of that command below.
     If there are any analysis errors, try resolving them before filing this issue.
-->

```
Analyzing bug_textfield_readonly...                                     

   info • Use key in widget constructors • lib/main.dart:5:7 • use_key_in_widget_constructors
   info • Use key in widget constructors • lib/main.dart:14:7 • use_key_in_widget_constructors
   info • Private field could be final • lib/main.dart:20:25 • prefer_final_fields
   info • Prefer const with constant constructors • lib/main.dart:29:11 • prefer_const_constructors
   info • Prefer const with constant constructors • lib/main.dart:37:20 • prefer_const_constructors
   info • Prefer const with constant constructors • lib/main.dart:45:15 • prefer_const_constructors
   info • Prefer declaring const constructors on `@immutable` classes • lib/main.dart:90:3 • prefer_const_constructors_in_immutables
   info • Avoid unnecessary containers • lib/main.dart:160:20 • avoid_unnecessary_containers
```

<!-- Finally, paste the output of running `flutter doctor -v` here. -->

```
[✓] Flutter (Channel stable, 2.10.5, on macOS 12.3.1 21E258 darwin-x64, locale en-US)
    • Flutter version 2.10.5 at <REDACTED>
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 5464c5bac7 (2 weeks ago), 2022-04-18 09:55:37 -0700
    • Engine revision 57d3bac3dd
    • Dart version 2.16.2
    • DevTools version 2.9.2

[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
    • Android SDK at <REDACTED>
    • Platform android-31, build-tools 31.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 13.3.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)

[✓] VS Code (version 1.66.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.40.0

[✓] Connected device (2 available)
    • iPhone 12 Pro (mobile) • <REDACTED> • ios            • com.apple.CoreSimulator.SimRuntime.iOS-15-4 (simulator)
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 100.0.4896.127

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!
```

</details>


<!--
      Consider also attaching screenshots and/or videos to better
      illustrate the issue.

      You can upload them directly on GitHub.
      Beware that video file size is limited to 10MB.
-->