Skip to content
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

Android native app using WebView, backspace doesn't work properly #1196

Closed
glinden opened this issue Jan 27, 2013 · 27 comments
Closed

Android native app using WebView, backspace doesn't work properly #1196

glinden opened this issue Jan 27, 2013 · 27 comments

Comments

@glinden
Copy link

glinden commented Jan 27, 2013

This may be no surprise as mobile is only loosely supported, but I don't see an existing report for this, so let me report it.

CodeMirror does work in Chrome on Android (4.2) tablets and phones, but does not work with WebView in a native Android App. Specifically, if you load an html file that uses CodeMirror using WebView in a native Android App, when the user tries to use the CodeMirror editor to edit text, the Android keyboard will not let you delete existing text.

This may sound like an obscure problem, but it is fairly common to want to bundle up an HTML5 website as an Android app, including the website files in the app and then using WebView to load them locally, all without using the network. But you can't if your html uses CodeMirror, which is somewhat unfortunate.

I have a simple repro case, but it does require setting up an Android app development environment (http://developer.android.com/sdk/index.html). Then, with the latest CodeMirror, the simplest repro is to create a Blank Activity, then change MainActivity.java to contain

public class MainActivity extends Activity {

    @SuppressLint("SetJavaScriptEnabled") @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        WebView myWebView = (WebView) findViewById(R.id.webview);
        myWebView.setWebViewClient(new MyWebViewClient());
        WebSettings webSettings = myWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setUseWideViewPort(true);
        myWebView.loadUrl("file:///android_asset/test.html");
    }
}

Then you need to set up assets to contain your local html and js files. Simplest html is a file test.html:

<html>
<head>
<script src="codemirror.js"></script>
<link rel="stylesheet" href="codemirror.css">
<script src="javascript.js"></script>
</head>
<body>
<script>
var myCodeMirror = CodeMirror(document.body, {
  value: "function myScript(){return 100;}\n",
  mode:  "javascript"
});
</script>
</body>
</html>

which you will need to create in assets/ or create elsewhere and import into assets/. Then you need to import into assets at the top-level codemirror.css, codemirror.css, and javascript.js from the latest CodeMirror library. Run it to bring up a virtual device, when the app comes up, click in the code in the CodeMirror editor, and you'll see you can't use backspace to delete the existing code in the virtual keyboard.

The problem may be related to how Android handles editing with the virtual keyboard. In particular, the way CodeMirror sets up a div (instead of a textarea) and handles all the selection in the div might be conflicting with how Android determines whether there is any text left to backspace over (the discussion at mathquill/mathquill#78 might be related). Android does let you edit textareas, so the issue appears to be somewhere between CodeMirror being clever about selection in its div and some optimization Android's virtual keyboard is doing on selection that only applies to a native app using WebView, not to the Chrome browser.

Anyway, totally realize that CodeMirror isn't designed for mobile, that the problem is related to Android's implementation of WebView, and that this is extremely likely to just be marked as "closed will not fix" immediately. But I thought you and others might want to know about the issue, so I thought I'd write about it here.

Thanks for the amazing work on this library, by the way, absolutely love it.

@marijnh
Copy link
Member

marijnh commented Jan 28, 2013

Thanks for the detailed bug report. Are you able to reproduce something like this directly in the Android browser, or only when you wrap the whole thing in an App?

@glinden
Copy link
Author

glinden commented Jan 28, 2013

Only when you wrap the whole thing in an app. There apparently are subtle differences between Chrome on Android and the WebView render used by Android app, and it's only a bug with the WebView renderer (or, possibly, some weird optimization related to text selection and backspace in the Android virtual keyboard that shows up only when the WebView renderer is used in an app).

Honestly, my expectation is that you will close this as "will not fix". I know mobile is only loosely supported and this is a bit of an obscure case (though it is true a lot of people want to package up HTML5 sites as WebView apps so that the code never needs to contact the network from the mobile device). I'm more just reporting something that wasn't reported yet, something that you might want to know about, not really expecting a fix, just giving information in case others hit it.

@metatheos
Copy link
Contributor

I tested the stock browser 'Browser' on my Phone (Android 4.2.2) as well (autoresize-demo on the homepage, should be CodeMirror 3.02), and it has the issue. This App might only be present on devices upgraded from lower versions of Android (as far as I know the only preinstalled browser on Nexus 7 is Chrome, no idea about the other recent official devices).

Mostly this seems to be an issue with how the soft keyboard works on android (or more specifically how it works with a havily customized textarea in a WebView). If you try running your android-emulator with the hw.keyboard flag enabled, the backspace-key works as it is supposed to.

Try following page in a WebView or the stock browser:

<html>
<head></head>
<body>
<textarea id="textin">Hello world</textarea>
<div id="eventout"></div>
<script>
  document.getElementById("textin").onkeydown=function(e){
    e.preventDefault();
    var it=document.createElement("li");
    it.innerHTML="Down: "+e.which;
    document.getElementById("eventout").appendChild(it);
  };
</script>
</body>
</html>

What you will notice is that the default IME will allow you to change its own representation of the text (the one where it makes suggestions) and will send events down to the page, expecting them to have the same effect on the textarea. Once you have deleted 'Hello', it will stop sending backspace-events, although the the actual content of the textarea won't have changed.

While looking into the issue I ran into this workaround specifically for CodeMirror. I didn't try it myself yet, but the idea seems to be "dumbing the IME down" by overriding the InputConnection the IME uses to retrieve text for smart things and to send changes back (details on that here). As a caveat it is stated that IMEs tend to call deleteSurrondingText() on the InputConnection instead of sending the key-event for backspace (KeyEvent.KEYCODE_DEL), which is handled by overriding the function and making it send the key-events.

@Nisk
Copy link

Nisk commented May 10, 2013

Hi, I'm also running into this issue, I've only started playing with codemirror(on Android 4.2.2), so it's far too early to give up yet. If the link to the solution above works, I'll be happy, though I'm using PhoneGap, so editing Java might be difficult (it's compiled into .class jar library), I'll have to see. Just giving this issue a bump and wondering if anyone has any other solutions?

@sthomp
Copy link

sthomp commented Sep 12, 2013

Noticed this issue in the stock browser (not chrome) on Android 4.1.2. Just going to http://www.codemirror.net and trying the simple demo seems to be enough to trigger this issue of backspace not working.

@mrjoelkemp
Copy link

This version of CodeMirror works fine on a Nexus 4. https://code.google.com/p/codemirror-android/source/browse/assets/codemirror2/lib/codemirror.js. Not sure what the version number is.

@fhoffa
Copy link

fhoffa commented Jan 17, 2014

Seems this is more of an accessibility issue, instead of Android issue.

Many virtual keyboards don't send a backspace key event when the text is empty. (This comes from a conversation I had with people that understand keyboards way better than me - I'm being the messenger).

To reproduce:

  1. Install the "Chrome virtual keyboard": https://chrome.google.com/webstore/detail/chrome-virtual-keyboard/pflmllfnnabikmfkkaddkoolinlfninn/details. (I did it on Linux). Turn it on.
  2. Go to http://codemirror.net/demo/complete.html.
  3. Try to delete the existing text.

Expected result:

I should be able to delete the pre-existent text.

Actual result:

I can't.
(but I can add text and then delete it without problem)

Picture:
http://i.imgur.com/6j2HtyL.png

Actual request:
Is it possible to make CodeMirror compatible with all accessibility and virtual keyboards?

@marijnh
Copy link
Member

marijnh commented Jan 23, 2014

Could someone please test whether attached patch (on the v4 branch) helps with the backspace problem?

@AntoineFouilleul
Copy link

I don't know if this patch helps with this issue but now CodeMirror crash on FireFox 26 :
line 172 : d.input.setSelectionRange(1, 1);

Error : "NS_ERROR_FAILURE"

Know Bug : Firefox explode when calling 'setSelectionRange' on hidden element !

@marijnh
Copy link
Member

marijnh commented Jan 28, 2014

@WorthlessSHU See patch 3c3003c , which seems to fix that problem.

@AntoineFouilleul
Copy link

Fixed. Thanks !

@marijnh
Copy link
Member

marijnh commented Jan 28, 2014

Great. Only the Firefox error, or also the Android backspace part?

@AntoineFouilleul
Copy link

Only the Firefox error for now. I will test with a nexus 7 in a few hour and i will tell you if it's fixed.

@AntoineFouilleul
Copy link

Can't tell you if backspace problem is fixed on Android because i can't install and dev custom application on it.

@glinden
Copy link
Author

glinden commented Jan 28, 2014

Hi, Marijn. Thanks for remembering it and looking at it again.

Short answer is that it doesn't appear to be fixed with this patch.

Longer answer is that I went back and re-coded up the simple test case described in the first post in this thread, then tried it with the latest v4 codemirror code. The behavior is slightly different; you can backspace one character, but not over the rest, when selecting into existing text (so, if you click into the CodeMirror text area on some existing code that was in there when it was initialized, then hit backspace, you oddly can go back one character, but no more). I also tried requiring the latest Android SDK (4.4), which apparently has a lot of changes to WebView to make it closer to Chrome, so I had hoped that might yield a solution (since this works fine in the Chrome browser on Android devices, just not in a WebView Android app that has the HTML embedded in the app). But that didn't make any difference when I tested it.

In the end, I think this is a bug in Android's WebView client. As described in the first post on this thread, WebView appears to make an assumption about what text is editable as an optimization, but CodeMirror's use of a div seems to break that assumption. But since CodeMirror only loosely supports mobile and using WebView to package up a website as an app is a bit of a hack, I really had no expectation that this would be fixed when I reported it. Very much appreciate you looking at it again, though, and thanks again for your hard work on this CodeMirror library.

marijnh added a commit that referenced this issue Jan 29, 2014
Issue #1196

Makes sure placeholder text is restored after deleting it

Fixes problem with editor that is initialized when hidden, and
thus won't allow selectionStart/End to be set.
@marijnh
Copy link
Member

marijnh commented Jan 29, 2014

@glinden Ah, I can explain the fact that it only worked a single time, and I think patch 39e7c78 should take care of that problem.

@glinden
Copy link
Author

glinden commented Jan 29, 2014

Hi, Marijn. You are persistent, aren't you! Unfortunately, that patch didn't work, no change in the behavior. But, given the time you spent on this, I thought it only right that I fire up a debugger and try to get to the bottom of this, so I spent a few hours doing that.

Short story is that readInput() in codemirror.js isn't even getting key events when the backspace key isn't working. So, I don't know, but I'm not sure there are solutions you can do involving changes to readInput() that are likely to matter.

Longer story is that I attached debuggers to a small running app implementing the test case at the beginning of this thread using the latest v4 codemirror.js. I debugged both codemirror.js and made an attempt to step through the Android code in the debugger to find out why key events aren't getting sent in this case.

What is going on is that the virtual keyboard (aka "soft input") on Android has some amount of text it considers editable. When I stepped through in the debugger, I noticed that codemirror.js: readinput() isn't even getting called when you hit backspace. Why not? It appears to be because WebKit (which is what Android WebView uses) doesn't consider the div with the source code in it to be editable, so it uses the default behavior, which is to say the virtual keyboard is editing an empty string. When you try to backspace over an empty selection, the Android code gobbles up the backspace event and doesn't pass it on.

What I'd say this means is that you aren't likely to find a fix for this in readInput() in codemirror.js. You might find a workaround, possibly a quite ugly one, if you were sufficiently hacky about what you do with the div CodeMirror uses to hold the source code (though I'd guess this is complicated by the fact that that div contains HTML to do the syntax highlighting of the code). You might also be able to trick the WebKit browser into thinking it's editing one thing but actually have it edit another by swapping things out right as an attempt to edit the code starts, but, again, that's a lot of work and ugly just for this.

Unfortunately, what we appear to have here is an obscure and nasty interaction between Android's rather odd virtual keyboard, Android's WebView implementation and the underlying WebKit renderer, and the way CodeMirror tries to use a div to contain the editable source code. Fortunately, the damage here is pretty limited, as this only comes up if you try to build an Android app using WebView, not if you try to view a web page using Chrome on Android.

So, I'd say this is a case where you probably want to say, "known problem, will not fix". But, if you want to try something else, let me know and I can see if I can help.

@glinden
Copy link
Author

glinden commented Jan 30, 2014

By the way, my coding skills are sad and tired at this point (some might even say decrepit). So, if anyone else wants to give this a go, it's not too bad to do. Basically, follow the instructions at

http://developer.android.com/sdk/installing/bundle.html
http://developer.android.com/guide/webapps/webview.html
https://developers.google.com/chrome-developer-tools/docs/remote-debugging

Then code up a very simple WebView app (like the Java code at the top of this thread) with a very simple index.html file that includes codemirror.js (like the one at the top of this thread). You can then hook up your Android device to your computer using a USB cable, enable debugging on the Android device, run your app in Eclipse, then have Chrome/Eclipse debuggers set all kinds of breakpoints in your Javascript (like codemirror.js) and your Java libraries (like Android) to investigate. It's kind of a fun way to learn more about all this stuff too. If anyone else wants to try it, please do.

@marijnh
Copy link
Member

marijnh commented Jan 30, 2014

@glinden The focused element in CodeMirror is simply a normal textarea, so all your theories about this having something to do with non-editable DOM nodes can be safely put aside. readInput does not do anything with key events, rather it looks at the content of this textarea to determine whether it changed, and applied changes to CodeMirror's representation of the document based on those changes. The idea with the underscore patches was that if the textarea contains a character before the cursor, the backspace key will remove this, allowing CodeMirror to see that something was deleted.

But yes, if it doesn't work then I will revert it again.

Any other people with an interest in this bug who want to work on this?

@glinden
Copy link
Author

glinden commented Jan 30, 2014

@marijnh I've failed to be clear, sorry, I probably should have kept it shorter

I get that you're doing a hack with the textarea to try to toss in an underscore char into it that can then be deleted. I think the problem is that the hack isn't working with WebView under Android because the virtual keyboard isn't looking again at the textarea to see if it has changed. So, you try to give it another underscore character to delete, but the virtual keyboard is already up and ignores that. And, because it ignores the change to the textarea, it stops sending backspace events when it has deleted the first underscore character.

@glinden
Copy link
Author

glinden commented Jan 30, 2014

So, I'm a big fan of trying to make simple test cases that illustrate the problem. Here's a small HTML file that attempts to do something like the hack you are doing with the textarea.

<html>
<head>
</head>
<body>
<textarea id=mytext></textarea>
<script>
function resetText() {
  var mytext = document.getElementById("mytext");
  mytext.focus();
  mytext.value = "_";
  window.setTimeout(resetText, 100);
}
window.setTimeout(resetText, 100);
</script>
</body>
</html>

You presumably expect that to constantly keep an underscore character in that textarea no matter how much you try to delete it, right? And that's how it works under most web browsers and most keyboards.

I loaded a simple Android WebView app on to a Nexus 7 where the app just loads that HTML into a WebView. Then, in that WebView app, if you try to edit the textarea using the virtual keyboard, you can only delete the underscore once, then it no longer registers that you are clicking backspace anymore. That's a problem. That's not the behavior you are expecting. You are expecting that you can keep deleting as long as you keep changing the textarea, but that's not the behavior this test case produces under Android WebView.

I should add that it's a little inconsistent about it. Sometimes you can delete the underscore more than once. It appears to be some kind of timing issue where the virtual keyboard can enter some state where it thinks there is no longer anything to delete. What is consistent is that, if you keep trying to backspace, it always eventually enters some state where it stops registering the backspace (and stops sending keydown backspace events to the web page).

In any case, you're depending on something like this working consistently. You're assuming that, when you update the textarea, the virtual keyboard will start working with that textarea's updated value. But there appears to be a bug in the interaction between WebView and the virtual keyboard on Android that breaks that. Tracking down exactly where that is in the Android code might be helpful and was what I was unsuccessfully trying to bungle through yesterday.

Long story short, if you can't depend on this simple test case working properly on Android under WebView, I think you have to either do something significantly different than your current attempt at a workaround, find the problem in the Android code and submit a patch, or give up on supporting CodeMirror under WebView on Android.

I hope that helps!

marijnh added a commit that referenced this issue Jan 30, 2014
The Android virtual keyboard is apparently not looking
at that (at least not in a sane way).
@marijnh
Copy link
Member

marijnh commented Jan 30, 2014

Hm, so either the logic for the virtual keyboard is a mess, or it is doing something that we haven't isolated yet. Anyway, I reverted the kludge, since there's no use having it if it doesn't solve the problem properly.

@glinden
Copy link
Author

glinden commented Jan 30, 2014

If it helps, I looked around a bit more, and it looks like several others have struggled with this and got much further than I did. If you're at all interested in the details of the mess, here are some good sources:

https://code.google.com/p/android/issues/detail?id=42904
http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection/14561345#14561345
http://stackoverflow.com/questions/18581636/android-cannot-capture-backspace-delete-press-in-soft-keyboard/19980975#19980975

Long story short, Google claims this is by design, that you should never count on the virtual keyboard actually sending key events to the application. So, not only can you not fix this with changes to CodeMirror.js, an Android patch wouldn't be accepted.

It seems the only way to fix this is to, as some people have done, override the default behavior of the virtual keyboard in the Android app (by defining your own InputConnection, it appears). But I'd say that means you should definitely punt on supporting CodeMirror under WebView on Android, maybe just refer people to the workarounds people have done (second and third links above) if they ask about this.

@marijnh
Copy link
Member

marijnh commented Jan 30, 2014

Thanks for digging into this and coming up with a more or less definite perspective on the problem.

I'm going to close this as WONTFIX then.

@jaredrummler
Copy link

I just want a confirmation: @glinden were you able to get this working with one of the answers from StackOverflow?

@glinden
Copy link
Author

glinden commented Mar 8, 2015

@jaredrummler I didn't try the InputConnection override of the default behavior of the virtual keyboard. That seems like a fragile solution to me, too fragile to be worthwhile for my particular application. I put effort into this more because I was interested in the root cause ("Google claims this is by design, that you should never count on the virtual keyboard actually sending key events to the application").

@merbin2012
Copy link

This is the "Android system webview" problem. Thats why after update this onle i faced many problem in my application, the application name is HTML Code Play.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants