TL;DR question:
How can some javascript tell the window that the mouse button has been released? The mouseup event is being lost because of a cross-domain iframe. I can detect that the problem has happened but I don't know what to do to cure it. If I could force the mouse pointer position, the problem would go away; but no javascript is allowed to change the mouse pointer position. If I could "fire a mouseup" then the problem would go away, because it would replace the lost mouseup event; but using dispatchEvent on a new mouseup event does nothing.
Situation:
- Any recent version of Chrome, any operating system.
- An outer document containing an iframe positioned inside. No javascript required at all.
- Outer document and inner iframe document hosted on different domains, both https. Not differing subdomains of the same base domain, but actually different domains. My examples use www.pressero.com and client-prototype.dev2.edocbuilder.com. Problem still happens whether or not certificates are valid (mine are).
- Textbox input in the iframe, positioned at or close to the left edge. Standard LTR text direction.
- Nearly minimal example: Demo1. This has one tiny bit of javascript in the outer doc, for convenience in altering the iframe src. Removing this javascript has no effect at all.
Important restrictions:
- Happens only with Chrome and Chromium-based browsers; not Firefox, Safari, Edge or IE.
- There is some ambiguity about whether it happens identically for all Chrome clients. I have personally reproduced identical behavior on about five different Windows PCs running Chrome 68 or 69. One colleague has gotten slightly different results on a Mac running Chrome 69, and on Chrome 69 in the Windows VM running on that same Mac.
Triggering the problem:
- User attempting to select all text in the textbox input, using the mouse, moving mouse from end of textbox towards beginning.
- Inadvertently, user moves the mouse pointer past the left edge, and therefore out of the iframe into the outer document.
- User releases the left mouse button after the mouse pointer has left the iframe.
- Happens only when the mouse is used to select text and only when the pointer ends up outside the iframe at the time of left button release. Other methods of selecting text do not cause the symptom:
- ctrl-A to select all
- tabbing into the field
- using shift-arrows to select text
- using the mouse to select text but from left to right
- using the mouse to select text but being careful that the pointer is inside the iframe when the mouse button is released
Another way of making reverse inserted text happen:
- Position your mouse pointer at the upper left of a textbox.
- Hold the left mouse button down.
- Type text. It is reverse inserted, because after each keystroke the insertion point is repositioned to before the character just typed.
- This is meant only as an illustration of the undesired behavior. It is obviously not the bug since it is a contrived way of showing it.
Perceived symptom:
- Text typed after letting go of the left button is inserted in reverse order: i.e., typing "abcde" will appear as "edcba", with the insertion point left to the left of the first character.
Related symptoms:
- If javascript is in use, for example a feature for resizing an object, or dragging it around the canvas, then dragging will continue even after the left button has been released while outside the iframe. Even after moving the pointer back inside the iframe, dragging continues.
- The fact that it is indeed the
mouseup
event being lost is proven by attaching analert()
to mouseup on thebody
in the iframe document. The alert happens if the mouse button is released while the pointer is inside the iframe; it does not happen if the mouse button is released while the pointer is outside the iframe.
Again, I emphasize that ONLY CHROME has this behavior. Doing the exact same thing in Firefox, Edge, or IE, the mouse button release is detected immediately no matter where the mouse pointer is.
Workarounds tried:
- Advise users to be careful of their mouse position. Not a popular solution.
- Javascript applied to the textbox to select all when the textbox is focused. This makes it unnecessary to use the mouse for selecting, and therefore prevents users from running into the problem. However, it makes it impossible to select small parts of the text instead of the whole thing.
- Moving the textbox so it is not up against the left edge. This makes it less likely the user will let go while the pointer is outside the iframe.
Catching the lost mouseup event:
By attaching an event handler to the <body>
in the outer document, I can catch the mouseup. I can then use standard postMessage technique to tell the inner iframe window that the mouseup happened. The inner iframe knows what element was active during the drag, so that's good. However, I'm having no luck whatsoever at actually simulating the mouseup event. I've tried the triggerMouseEvent
technique here. It runs without error, but it does not apparently do anything.
Demonstration here has the minimal example from above, plus the plumbing to capture the mouseup in the outer document, post a message to the inner one, and call triggerMouseEvent
: Demo2
Another weird possibility
While trying to create the minimal examples, I discovered that maybe the box model is involved somehow. Demo1a is identical to demo1 above, except that the iframe element has a height of 710px instead of 700px. On my test machines this eliminates the bug. On my colleague's test machine, the bug remains.
Possible related tracked Chromium issue:
- 269917 preventDefault on mousedown prevents proper handling of mouse events in iframes
- opened Aug 8 2013; last comment Jun 18 2018
- I mention it because it involves mouseup outside an iframe. As stated above, our issue happens even when no javascript at all is in use.
EDIT 2018-09-25
I have submitted Chromium issue #882491. It has not yet seen any real activity.
This bug is already fixed in Chrome 70, which has been announced to release October 16, 2018. I have already tested with Chrome 70 in the beta channel and I confirm that it was indeed fixed.