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

Contextmenu is not triggered on iOS13 #6817

Closed
ginger-777 opened this issue Sep 23, 2019 · 47 comments · Fixed by #6855 or #7010
Closed

Contextmenu is not triggered on iOS13 #6817

ginger-777 opened this issue Sep 23, 2019 · 47 comments · Fixed by #6855 or #7010

Comments

@ginger-777
Copy link

After iOS was updated to 13 version long press doesn't work and contextmenu event is not triggered.

@sveine
Copy link

sveine commented Sep 23, 2019

double-tap to fire doubleClickZoom won't work either in iOS 13. A fix for contextmenu is much more important to solve.

@ilblog
Copy link
Contributor

ilblog commented Sep 24, 2019

I do confirm the same issue. After initial examination is seems that iOS 13 Safari does not fire touch events properly.

Long tap in iOS Safari does not send any event to Leaflet library and is handled by iOS itself.

Double click emits following events: mouseover, mousemove, mousedown, mouseup, click, which results in leaflet's event click.

It seems to me like serious issue in mobile Safari iOS 13 and I guess it has something to do to make mobile Safari very similar to desktop Safari. Unless touch events are sent properly to Leaflet I am afraid there is not much to be fixed.

Is here anyone that will report this to Apple?

@ginger-777
Copy link
Author

Last week I send a report to Apple support. There is still no answer.

@ginger-777
Copy link
Author

ginger-777 commented Sep 24, 2019

I have a little bit more information. If add click handler on map div and zoom control is enabled, then the long-press started work as copy/paste context menu.
like that

<div id="mapid" onclick="javascript:void(0)"/>

 -webkit-user-select: none;
 -webkit-touch-callout: none;

also doesn't help, only disable copy/paste menu

Maybe it'd be useful for someone.

@ginger-777
Copy link
Author

ginger-777 commented Sep 25, 2019

As a temporary solution for long-press:
add this library
https://www.cssscript.com/handle-long-press-tap-event-in-javascript-long-press-js/
and in css
-webkit-user-select: none; -webkit-touch-callout: none;

than add listener for long-press event. And here in event handler convert coordinates to leaflet coordinates.

@ilblog
Copy link
Contributor

ilblog commented Sep 26, 2019

Does the library works? So far I have checked source code and the library listens to touch events that are not sent down to Safari anyway. Just curious.

@ginger-777
Copy link
Author

Yes, it works. For long-press at least.
I have checked it on iOS13 and iPadOS13

@ilblog
Copy link
Contributor

ilblog commented Sep 26, 2019

great tip thx

@ilblog
Copy link
Contributor

ilblog commented Sep 26, 2019

This is final solution as we use it. It detects long tap and emulate 'contextmenu' MenuEvent. Just place it in your JS codes and you should be good.

Note: It is good idea to limit execution just for iOS 13 users, not to have too much listeners

// Copied and modified from https://github.com/john-doherty/long-press-event/

import rs from 'rootScope'
import $ from '$'

if( rs.platform === 'ios' && / OS 13_/.test( navigator.userAgent ) ) {

	// This is our main map el
	const mapEl = $('#map-container')

	// This class adds 	-webkit-user-select: none; -webkit-touch-callout: none;
	// to the BODY el
	document.body.classList.add('ios13')

	let timer = null;

    const fireLongPressEvent = originalEvent => {

        clearLongPressTimer();

		const el = originalEvent.target
		, x = originalEvent.touches[0].clientX
		, y = originalEvent.touches[0].clientY

		// This will emulate contextmenu mouse event
		const event = new MouseEvent('contextmenu', {
									bubbles: true,
									cancelable: true,
									clientX: x,
									clientY: y
								})


        // fire the long-press event
        const suppressClickEvent = el.dispatchEvent.call(el,event);

        if (suppressClickEvent) {

            // temporarily intercept and clear the next click
            mapEl.addEventListener('touchend', function clearMouseUp(e) {
                mapEl.removeEventListener('touchend', clearMouseUp, true);
                cancelEvent(e);
            }, true);
        }
    }

    const startLongPressTimer = e => {
		clearLongPressTimer(e);
        timer = setTimeout( fireLongPressEvent.bind(null,e), 1000 );
    }

    const clearLongPressTimer = () => {
		if(timer) {
			clearTimeout(timer);
			timer = null;
		}
    }

    const cancelEvent = e => {
        e.stopImmediatePropagation();
        e.preventDefault();
        e.stopPropagation();
	}

    // hook events that clear a pending long press event
    mapEl.addEventListener('touchcancel', clearLongPressTimer, true);
    mapEl.addEventListener('touchend', clearLongPressTimer, true);
    mapEl.addEventListener('touchmove', clearLongPressTimer, true);

    // hook events that can trigger a long press event
	mapEl.addEventListener('touchstart', startLongPressTimer, true); // <- start

}

@felixhageloh
Copy link

came across this thread while looking into a similar issue. I found that calling preventDefault on touchstart will allow you to receive touch events as usual and thus check for double tap as before.

I believe this comes down to the fact that mobile Safari changed the way it is simulating hover events (to make websites work that solely rely on hover events to open menus and such)

@ilblog
Copy link
Contributor

ilblog commented Sep 28, 2019

came across this thread while looking into a similar issue. I found that calling preventDefault on touchstart will allow you to receive touch events as usual and thus check for double tap as before.

I believe this comes down to the fact that mobile Safari changed the way it is simulating hover events (to make websites work that solely rely on hover events to open menus and such)

So far works opposite for me, just suppress all the clicks except for long tap. Can you share piece of code that works for you?

@felixhageloh
Copy link

sorry I was assuming you were using raw touch events to detect double taps. If you are relying on click/mouse events then preventDefault on touchstart will prevent those.

Either way, it appears to have been a bug in iOS which was fixed in 13.1.

@majid701
Copy link

@felixhageloh I just tested on iOS 13.1 running this demo:
http://aratcliffe.github.io/Leaflet.contextmenu/examples/index.html

But it doesn't seem like the issue has been fixed! Is there something I'm missing out?
Really critical issue for us since we heavily rely on the long press event to work, any help will be appreciated.

@graouts
Copy link

graouts commented Sep 30, 2019

This is issue is referenced from WebKit bug #202143. The WebKit team (in this case, me) is investigating.

@ilblog
Copy link
Contributor

ilblog commented Sep 30, 2019

This is issue is referenced from WebKit bug #202143. The WebKit team (in this case, me) is investigating.

We are very happy you are diving into this issue. As a web developers we would be happy the Safari on iOS 13.x would behave the same way as on iOS 12.x or Chrome on Android in case of touch events.

Happy you are here.

@felixhageloh
Copy link

@majid701 sorry if I caused some confusion. The issue I was seeing in 13.0 was:
double tap (quickly) -> a single touchstart and touchend was fired.

Since 13.1 I am correctly receiving two touchstart and touchend events again. The app I am maintaining uses raw touch events to detect double taps, so this fixed double tap zoom for us. I was incorrectly assuming this would be the case for you as well.

@graouts
Copy link

graouts commented Sep 30, 2019

if( rs.platform === 'ios' && / OS 13_/.test( navigator.userAgent ) )

Note @ilblog that this isn't going to work on iPadOS where the user-agent string is the same as Safari on desktop and won't advertise iOS.

@graouts
Copy link

graouts commented Sep 30, 2019

(reposting from the WebKit bug so people Cc'd to this bug alone can see this.)

The contextmenu Leaflet event is not triggered because the Leaflet code uses pointerdown and touchstart events interchangeably and the availability of the former excludes the other. In Map.Tap.js the _onDown method has this check if (!e.touches) { return; } which will always lead to an early return since a PointerEvent event is not the same as a TouchEvent and does not have a touches property.

So the lack of a Leaflet contextmenu event being triggered is a Leaflet issue specifically.

@graouts
Copy link

graouts commented Sep 30, 2019

Actually, this code isn't even run with Pointer Events because of this code:

if (Browser.touch && !Browser.pointer) {
	Map.addInitHook('addHandler', 'tap', Tap);
}

On iOS 13 bothBrowser.touch and Browser.pointer are true so the whole package isn't initialized.

@graouts
Copy link

graouts commented Sep 30, 2019

Last week I send a report to Apple support. There is still no answer.

@ginger-777 Do you have a bug number / feedback ID? Generally though the best way to get the attention of WebKit developers is to file a bug directly at bugs.webkit.org.

@majid701
Copy link

majid701 commented Sep 30, 2019

@graouts Yes i noticed that as well after reading your last comment. But I tested out disabling Pointer Events from Settings -> General -> Experimental Features and it looked like that it works on this demo website:
http://aratcliffe.github.io/Leaflet.contextmenu/examples/index.html

But it doesn't work inside my app. I am loading leaflet inside a Webview.

@graouts
Copy link

graouts commented Sep 30, 2019

As for double-tap-to-zoom, that is also an issue with Leaflet. Consider this code in DomEvent.DoubleTap:

function onTouchStart(e) {
	var count;

	if (Browser.pointer) {
		if ((!Browser.edge) || e.pointerType === 'mouse') { return; }
		count = _pointersCount;
	} else {
		count = e.touches.length;
	}

	if (count > 1) { return; }

	var now = Date.now(),
	    delta = now - (last || now);

	touch = e.touches ? e.touches[0] : e;
	doubleTap = (delta > 0 && delta <= delay);
	last = now;
}

If a browser supports Pointer Events and it is not Edge then the function exits early. This is the case of Safari or any WebKit-based browser on iOS 13.

As far as I can tell, both issues reported here are Leaflet simply not being ready for a browser that supports both Touch Events and Pointer Events (long press and the contextmenu event) or supports Pointer Events but is not Microsoft Edge (double tap and the dblclick event).

I closed the WebKit bug as there is nothing for us to fix at this stage.

@graouts
Copy link

graouts commented Sep 30, 2019

@graouts Yes i noticed that as well after reading your last comment. But I tested out disabling Pointer Events from Settings -> General -> Experimental Features and it looked like that it works on this demo website:
http://aratcliffe.github.io/Leaflet.contextmenu/examples/index.html

But it doesn't work inside my app. I am loading leaflet inside a Webview.

Yes @majid701, I think this setting only applies to Safari, not WKWebView or any other way to embed WebKit on iOS. I'm not sure how you can disable Pointer Events outside of the Safari app.

@ilblog
Copy link
Contributor

ilblog commented Sep 30, 2019

I think the solution will be to fix Leaflet library which detects iOS 13 Safari as "pinterType" browser and thus does not handle Touch events not correctly.

Thanks a lot @graouts for your effort and a lot of help in this matter.

@ilblog
Copy link
Contributor

ilblog commented Sep 30, 2019

There is PR just in motion #6815 concerning double tap on mobile devices, patching same lines of code, so I have asked them to cover this issue also.

@ilblog
Copy link
Contributor

ilblog commented Oct 1, 2019

if( rs.platform === 'ios' && / OS 13_/.test( navigator.userAgent ) )

Note @ilblog that this isn't going to work on iPadOS where the user-agent string is the same as Safari on desktop and won't advertise iOS.

Well this is is interesting, since it can trigger another series of issues, when developers are unable to distinguish macOS vs iPad. This is not related of this Leaflet bug but I think it should be addressed also by Apple.

https://forums.developer.apple.com/thread/119186

@ilblog
Copy link
Contributor

ilblog commented Oct 1, 2019

SOLVED

Thanks to valuable input from Apple I have managed to fix iOS 13 issue at least on iPhone. Since iPad on iOS13 has the same user agent string as macOS, I was unable to fix the issue on iPad.

Just modify src/core/Browser.js like this:

// 'false' for all iOS devices, that (as of version iOS13 support both, touch and pointer events)
// Unfortunately as of iOS13 it is not possible to distinguish iPad from OS X by user agent string
export var pointer = !!(window.PointerEvent || msPointer) && !userAgentContains('iphone');

ilblog added a commit to ilblog/Leaflet that referenced this issue Oct 1, 2019
@rwillett
Copy link

rwillett commented Oct 29, 2019

Ah! We used the official release. We didn't realise that PR#6855 wasn't included. Our fault.

We downloaded the dev branch and we'll try that,

Thanks

Rob

@rwillett
Copy link

rwillett commented Oct 29, 2019

We downloaded 1.6-dev and it works fine. Both the double tap and the context-menu on ios 13.2.

Thanks for the help, appreciate it.

Rob

@cherniavskii
Copy link
Sponsor Collaborator

Released in 1.6.0

Schleuse pushed a commit to Schleuse/Leaflet that referenced this issue Dec 3, 2019
iOS 13 added Pointer Events to Safari, which triggers all sorts of
unexpected code paths, as Leaflet only expected Pointer Events on older
IE for mobile devices.

@mourner suggested a simple patch to have webkit not report having
pointer events, which works and avoids an iOS 13.1 bug on the iPad,
where pointer events might not be generated.

Fixes Leaflet#6817.
@johnd0e
Copy link
Collaborator

johnd0e commented Jan 28, 2020

maybe we should try disabling pointer events completely if we detected earlier that regular touch events work properly (so, something like export var pointer = !webkit && !!(window.PointerEvent || msPointer))?

@mourner
That was risky indeed, and caused issues like #6896.
Please join discussion at #6977 (comment)

johnd0e pushed a commit to johnd0e/Leaflet that referenced this issue Mar 3, 2020
johnd0e pushed a commit to johnd0e/Leaflet that referenced this issue Mar 4, 2020
johnd0e pushed a commit to johnd0e/Leaflet that referenced this issue Mar 11, 2020
johnd0e pushed a commit to johnd0e/Leaflet that referenced this issue Mar 11, 2020
johnd0e pushed a commit to johnd0e/Leaflet that referenced this issue Mar 11, 2020
mourner pushed a commit that referenced this issue Mar 20, 2020
* Revert "Fix pointer event hendling on iOS13 (#6855)"

This reverts commit 747b1f7.

Close #6977

* Fix iOS contextmenu issue in more proper way

Ref: #6817

* Fix iOS dblclick issue in more proper way

* dblclick emulation: simplify code paths a bit

* Make use of PointerEvent.isPrimary

https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary
https://caniuse.com/#search=isPrimary

IE 10 or later, but it is safe to use here, as for IE we anyway need return from function.
@johnd0e johnd0e added this to Issues (addressed) in Refactor [touch/pointer] events Apr 7, 2020
@johnd0e johnd0e moved this from Issues (addressed) to Unaddressed in Refactor [touch/pointer] events Feb 22, 2021
@johnd0e johnd0e moved this from Unaddressed to Issues (addressed) in Refactor [touch/pointer] events Mar 9, 2021
@Falke-Design Falke-Design moved this from Issues (addressed) to Fixed issues in Refactor [touch/pointer] events Apr 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment