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

bug: Drag events not working in Android 4.4.2 #3695

Closed
MartinMa opened this issue May 11, 2015 · 25 comments
Closed

bug: Drag events not working in Android 4.4.2 #3695

MartinMa opened this issue May 11, 2015 · 25 comments
Assignees
Milestone

Comments

@MartinMa
Copy link

Type: bug

Platform: android 4.4 webview

This issue refers to a regression introduced with this commit from 19 Mar.

This is a new issue as desired by @perrygovier. Again (concerning above commit), what exactly do you mean by "Prevent gestures from breaking native scrolling"? Is there a use/test case? I could't find a related issue.

Here is a codepen that demonstrates the issue (try to drag the red box): http://codepen.io/MartinMa/pen/vOEmVR

Please confirm.

To get it up and running on your Android device, run the following commands using Ionic CLI.

ionic start dragtest http://codepen.io/MartinMa/pen/vOEmVR
cd dragtest
ionic platform add android
ionic run android

As already mentioned by @gregor-srdic, this issue also affects built-in events like on-hold. (see issue 1729)

There is a work around. You have to pass {prevent_default_directions = ['left', 'up', 'right', 'down']} as fourth argument to $ionicGesture.on.

I think preventDefault(); should be the default, if 'overflow-scroll' isn't present? With the current behavior it breaks drag gestures when using Ionic scroll - which is the default. So it's broken by default ;)

@perrygovier
Copy link
Contributor

Thanks for creating a new issue for this. I'd like to make it as a fast follower for the 1.0.

@perrygovier
Copy link
Contributor

Closed with #3769 on the 1.0.1 branch.

Thanks!

@MartinMa
Copy link
Author

Perfect, a one-liner. 👍 Thanks! 🍻

@benlozano
Copy link

@perrygovier I confirmed that this fix exists in my current ionic (1.1.0) and drag events are still broken on 4.4.4 (and have always been for me). Any insight into whether this is the same as your issue? I have seen this get tossed around and really haven't had any luck getting the ionic gestures to work cross-platform.

@MartinMa
Copy link
Author

I can confirm that this issue is present (again) on Android 4.4.2 running Ionic 1.2.1. This seems to be a regression (again) introduced with commit e10b5d2

This part of the code seems to change back and forth because of an iOS scrolling issue (see #4008).

If you have an Android device with a 4.4 webview you can reproduce the issue as mentioned above:

ionic start dragtest http://codepen.io/MartinMa/pen/vOEmVR
cd dragtest
ionic platform add android
ionic run android

@mlynch
Copy link
Contributor

mlynch commented Dec 24, 2015

Okay, I just reverted that old fix to enable dragging to work correctly. Looking more closely, I think the fix for the side menu issue in #4022 was not correct.

I'm still looking into how to get these both playing well together for 1.2.1.

@jskrzypek
Copy link

@mlynch I want to be sure that this doesn't break native scrolling again...

@mlynch
Copy link
Contributor

mlynch commented Dec 24, 2015

I'm not really sure what the original issue was, and scrolling works fine with this change on iOS and Android. Any more info or thoughts on the impact?

@jskrzypek
Copy link

You can see the original issue by following the steps to reproduce i listed in #4008 in the ios emulator or on a device.

It might no longer be an issue I'll try check it tomorrow.

@MartinMa
Copy link
Author

I created a new build of Ionic based on the current master branch (with the reverted old fix). The good news is, that the red-box-dragging-demo is working again.

Unfortunately native scrolling is broken for me now. It doesn't work at all, even though the webview is receiving all touchmove events. I used an example with a simple list as described in #4022 (first post). JS-scrolling on the other hand works fine.

All testing done on a Samsung Galaxy Tab 3 with Android 4.4.2.

This webview bug is also documented on the Android issue tracker: WebView touch events are not fired properly. See comment 41 and later.

This seems to be "normal behavior" on Android 4.4 "KitKat". The first touchmove event is fireing reliably, but immediately followed by touchcancel, which results in a release event by the ionic-ified hammer.js implementation.

If the browser is detecting a scroll gesture it cancels the touch event ("touchcancel") and native browser scrolling takes over.

Then, all subsequent touchmove events never reach the webview. But if you prevent default, you get all touch events, and native scrolling doesn't work. It's kind of a vicious cycle.

What do you think is the best solution? Fallback to js-scrolling on Android 4.4?

Which devices are you testing on?

@mlynch
Copy link
Contributor

mlynch commented Dec 28, 2015

I tested on 4.4.2 and it worked fine, so let's dig in: are you adding any gesture listeners to your app? Or is it breaking on a vanilla example?

@mlynch
Copy link
Contributor

mlynch commented Dec 29, 2015

Okay, can confirm now. Looking into this and will fix for 1.2.2

@mlynch mlynch reopened this Dec 29, 2015
mlynch added a commit that referenced this issue Dec 29, 2015
@mlynch
Copy link
Contributor

mlynch commented Dec 29, 2015

Fix for this will be out in 1.2.2

@mlynch mlynch closed this as completed Dec 29, 2015
mlynch added a commit that referenced this issue Dec 31, 2015
@mlynch mlynch reopened this Dec 31, 2015
@mlynch
Copy link
Contributor

mlynch commented Dec 31, 2015

Grrr, that had bad side effects. Need a different solution.

@mlynch mlynch modified the milestones: 1.2.3, 1.2.2 Dec 31, 2015
@MartinMa
Copy link
Author

MartinMa commented Jan 1, 2016

Hey @mlynch thank you very much for your effort! What kind of side effects? Have you really been able to reproduce? For me it is breaking in a vanilla example. I'll try to provide a new example utilizing native scrolling and custom drag gestures. From what you are experiencing it doesn't seem to affect "KitKat" in general.

@MartinMa
Copy link
Author

MartinMa commented Jan 5, 2016

I made a quick minimal vanilla example demonstrating the general issue at MartinMa/drag_vanilla with a possible solution. It is a simple Cordova project (build instructions included).

On Android 4.4.2 (at least on my device) the situation is as follows. I register event handlers for touchstart, touchmove, touchend and touchcancel. When doing a common swipe or drag gesture, the event handlers are fired three times. 1. touchstart, 2. touchmove and 3. touchcancel.

What's wrong here? Actually touchmove should be triggered multiple times and the gesture should end with touchend. What happens is, that after touchcancel "the browser takes over" and performs native scrolling. Also, all subsequent touchmove events don't make to to the webview.

But if you preventDefault on the first touchmove (or already at touchstart, that works as well), the touchmove events keep coming. This is demonstrated in the example (see line 36 in index.js). At the same time, native scrolling of the unordered list works fine, because preventDefault isn't called here. The touch events are only triggered in the "drag element" (red box).

For Ionic, I think, we need to figure out whether js-scrolling or native scrolling is active in a specific content view. If native scrolling is active: don't preventDefault, otherwise: always preventDefault on the first move to make the touch events keep coming in.

@MartinMa
Copy link
Author

MartinMa commented Jan 5, 2016

@mlynch I added a pull request with a fix for this issue. I also removed the length check because it is not needed.

The fix is inspired by @perrygovier (see commit 71e8971 fix(refresher): fix pull to refresh with native scrolling on kitkat).

(Sorry for the mess with the GitHub references above. I had to update my pull request because of a linter error.)

@jskrzypek
Copy link

Interesting @MartinMa. Now I understand the flow of the system a bit better, that makes sense and I agree, we probably need two separate behaviors for the native- and js-scrolling. This also seems like it should be a separate check from killing the drag event if its going in the wrong direction.

Your fix is a step in the right direction but I think a slightly more general rewrite might be called for.

tl;dr: In js-scrolling calling preventDefault on the first touchmove bypasses the browser and allows javascript handling of the event, so perhaps the directions in the prevent_default_directions array should be the ones for which we want to enable scrolling. This causes problems with native scrolling, so, more usefully, we might make it so that when not doing native scrolling, prevent_default_directions are the directions for which we want to preventDefault every touchmove, not just the first.

I'm also wondering about the comment Perry added for explanation, is it actually telling us what's going on? It seems to me now that this describes the opposite of the actual behavior at that point.

// Prevent gestures that are not intended for this event handler from firing subsequent times

It comes from commit 56ab0f2, but if we look at the code we see that if the current direction is in the prevent_default_directions array, then preventDefault is not called. This was the seemingly broken logic that I was trying to fix in e10b5d2.

I think possibly what's happened is that the logic needs to get inverted between the js-scrolling and native scrolling scenarios, as you suggest, but this wasn't always clear during development. Additionally I think the logic around the implementation of prevent_default_directions is buggy and should be moved.

It seems to me that the code Perry wrote in 56ab0f2 only worked to cancel wrong-direction drags in js-scrolling by abusing the fact that no overflow-scroll was enabled for those directions. When the drag handler received a drag event that started off in the wrong direction (i.e. a direction named in prevent_default_directions), preventDefault would not get called and the browser captured the event. When this happened, because there was no overflow for the browser to natively scroll over, the wrong-direction drag events were effectively canceled, because the native scrolling would swallow the subsequent touchmove events. I think because the logic was, the separate ideas of "enable js-scrolling by calling preventDefault so we receive touchmove" and "don't obey wrong-direction drags by killing touchmove events with bad directions" got coupled together. This coupling makes it hard to allow for both js-crolling and native scrolling scenarios. we need to decouple this code before moving forward. Additionally we should figure out if this way of cancelling wrong-direction drag gestures is actually the best way. We have separate overflow-x and overflow-y that should work on all browsers, so we shouldn't need to bother with disabling these in native, but what if we're doing js-scrolling and the element has inherited overflow-scroll for the wrong directions? I think the current regime will just hand off to the browser and the native scroll will happen even in the wrong directions. What we might want to do is call stopPropogating after preventDefault to prevent the browser from taking over, but that is possibly dangerous.

Here's a verbal description of what I think we should be doing in the drag handler, taken form the top:

  • First check to see if we are in a js-scrolling or native scrolling scenario. I'm not sure what the best way to do this is, but one way I've thought of is to test the computed styles of the element being dragged and see if it has overflow scrolling enabled. We can do that with a test like this one:

    var targetStyle = window.getComputedStyle(ev.target);
    if (targetStyle.overflow === 'auto' || targetStyle.overflow === 'scroll' || targetStyle.overflowX === 'auto' ||   targetStyle.overflowY === 'scroll') {
        // overflow scrolls in at least one direction, so we are in a native scrolling scenario.
    }
  • In the native scrolling scenario we should not call preventDefault in any direction. If people are using native scrolling and they want to limit scrolling in some direction, differentiating between overflow-x and overflow-y properties already handles that. Maybe there are other complexities, but as far as I can tell this is what overflow-scroll is for, and anything more complex should just be handled by its own logic passed to $ionicGesture.on.

  • In the js-scrolling scenario preventDefault should be called on either touchstart or the first touchmove, regardless of the direction. On subsequent touchmoves, if the direction of the move is good, we let things happen as normal, but if the direction is bad we should call preventDefault on it to stop these events from having further consequences. We could also return false to ensure that they are completely quashed. A benefit of this scenario is that even if the first touchmove goes in the wrong direction we still allow subsequent correct-direction touchmoves to be processed by the handler.

@MartinMa
Copy link
Author

MartinMa commented Jan 5, 2016

Hi @jskrzypek I agree that the coupling you mentioned is unfortunate. That's why I removed the length check altogether in the pull request. The code is equivalent without it. indexOf will return -1 when the array is empty. But I'm not sure if "cancelling wrong-direction drag gestures" works in all scenarios. I need to give this more thought.

I think the fix proposal doesn't break native scrolling. I tested it on my device, it works. This is because the touchstart covered in the new check stems from a custom attached gesture. It is only triggered on elements that get attached user defined gestures (i.e. $ionicGesture.on). Also, this codepath is only relevant on a specific version Android. So it doesn't affect the other platforms.

I have to think about your description...

@MartinMa
Copy link
Author

MartinMa commented Jan 6, 2016

I added an example to prove that this fix is functioning correctly: https://github.com/MartinMa/dragtest

I also added an explanation of how and why it works (see README.md). I'd be very happy see this fix land and be included in the next release 1.2.4. 😄

@jskrzypek I think we should move the prevent_default_directions topic to a new issue. From the built-in components only listView and sideMenuContent use this feature.

MartinMa added a commit to MartinMa/ionic that referenced this issue Jan 11, 2016
@MartinMa
Copy link
Author

Hold up. The fix provided, doesn't play well with sideMenuContent (and probably listView, too) — it prevents native scrolling there.

Update I updated the pull request and moved the check (slightly altered) to detect. Should be good now. I can add more examples if you like.

@harpritt
Copy link

Hi all, will @MartinMa 's fix make it to the next release?

Cheers in advance

@wbhob
Copy link

wbhob commented Jan 6, 2017

It probably made it into 1.3.2. I don't use v1 anymore, can anyone verify?

@jgw96
Copy link
Contributor

jgw96 commented Jan 13, 2017

This issue was moved to ionic-team/ionic-v1#11

@jgw96 jgw96 closed this as completed Jan 13, 2017
@ionitron-bot
Copy link

ionitron-bot bot commented Sep 5, 2018

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Sep 5, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants