I'm working on a Google Chrome extension that monitors mouse events. For some reason the following javascript code in the extension's content script is causing embedded Flash content to break:
$(window).mouseup(function() {
// do benign stuff
});
If you mousedown inside a Flash element, it never registers the mouseup and it appears as though you're still holding your mouse button down even though you've let go. At first I thought it was some kind of event bubbling issue, that this method was swallowing the event, so I tried returning true (and false for that matter) but it didn't seem to have any effect. Any ideas?
Well, no response from the peanut gallery after a few days, but I figured it out on my lonesome:
// Bad
$(window).mouseup(function() { ... });
// Good
window.addEventListener("mouseup", function(event) { ... });
Related
I need to safely detect when the mouse leaves the window. I have jQuery included, so normally this would be fine:
$(document).on("mouseleave", function(event) {
doSomething();
});
However, there is a major bug in Chrome currently where this mouseleave function fires randomly when clicking in the element.
Normally, there's an easy work around for this:
$("#some-id").on("mouseleave", function(event) {
var e = event.originalEvent;
if (!e.relatedTarget || !e.toElement) {
// BUG in Chrome
return;
}
doSomething();
}
However, this doesn't work for document, or for any element when the mouse leaves the window, since in this case, e.relatedTarget and e.toElement are null exactly like when the bug occurs. So I'm trying to come up with something that will be able to safely determine when Chrome is acting up, and when the mouseleave event actually should fire.
Update: Just tried an approach with mouseout instead. The same chrome bug affects this event too, so no good. :/
UPDATE: this was apparently fixed, the Chrome issue was marked "fixed", and my tests show that the current version of Chrome no longer has the issue. :D
For now, this is the solution I came up with. Hopefully someday this major bug in Chrome gets enough attention to get a fix.
In the event that mouseleave occurs as a bug, it is usually almost immediately by the mouseenter event. So what I did here was just wait 1/10 of a second after the mouseleave event to make sure it's not a Chrome fluke.
var documentMouseLeaveTimeout = null;
$(document).on("mouseleave", function() {
documentMouseLeaveTimeout = setTimeout(function() {
doSomething();
}, 100);
});
$(document).on("mouseenter", function() {
clearTimeout(documentMouseLeaveTimeout);
});
Still hoping there's a better answer.
UPDATE: this was apparently fixed, the Chrome issue was marked "fixed", and my tests show that the current version of Chrome no longer has the issue. :D
I'm using jQuery 1.7.1
My keyup handler works fine, even though the setup is a little complicated. This is happening in an iframe overlay. The main page does
our.Catalog.makeViewer = function(id) {
var iframe = document.createElement('iframe');
iframe.setAttribute()... // src, id, name, etc. Exact same host and method
document.body.appendChild(iframe);
iframe.contentWindow.focus();
$('html, body').css('overflow', 'hidden');
}
our.Catalog.makeViewer('foo') // actually bound to a click hander
In the iframe <head> we have something like
our.Viewer = function(){
$(document).keyup(function(e){ doSomething() })
// and lots more, of course
}
$(function() {
new our.Viewer();
});
The browser displays the URL of the iframe. I get all the real keyups for the whole browser window which is fine, but for testing I want to be able to simulate keyups from JavaScript. However, whatever I try doesn't work. I've tried
$('body').trigger($.Event('keyup', {keycode: 40}))
$(document).trigger($.Event('keyup', {keycode: 40}))
$(document).keyup()
and none of them triggered my handler. Then I thought maybe the problem was that I'm using iframes, so I tried
$('#frameId').contents().find('body').trigger($.Event('keyup', {keycode: 40}))
Nope, that doesn't work either. I don't think it's any kind of permissions/security issue because $('#frameId').contents().find('body').addClass('test') works fine.
I hope I'm doing something stupid someone can easily correct. How can I simulate keyups from my JavaScript driver?
OK, the code recommended by the jQuery docs works from Selenium. Or at least the one-liner version:
$('body').trigger($.Event('keyup', {keycode: 40, which: 40}))
In my particular case I wasn't seeing it from Selenium because our handler checks e.which not e.keycode and e.which remained undefined until we set it explicitly. Maybe a bug in jQuery 1.7.1? Maybe due to an interaction between jQuery and Selenium? Anyway, setting e.which got the test passing.
On the other hand, getting stuff to work from a browser console was more challenging. From Firebug on Firefox I had to cd(frames[2]) to set the context to the iframe in question. No other way of accessing the iframe from another frame would let me trigger events, probably due to some kind of sandboxing. (I can access the iframe's DOM but not the iframe's jQuery.) In Chrome I have to select the iframe from the popup menu at the bottom of the console. In Safari I have to use
window.frames[2].$('body').trigger($.Event('keyup', {keycode: 40, which: 40}))
So as I expected, something relatively stupid, not setting the iframe context in the console and actually failing the test in Selenium for other reasons.
Thank you #Ian for trying.
try tihis
$(document).on("keyUp",function(){
//your logic
});
.trigger() only works with events binded with .on().
Try binding with:
$(document).on('keyup', function (e) {
doSomething(e.keyCode);
});
And testing with:
$(document).trigger($.Event("keyup", { keyCode: 40 }));
Consider the following Web page:
<html>
<body onscroll="alert('body scroll event')">
<div style='width:200px;height:200px;overflow:auto' onscroll="alert('div scroll event')">
<div style='height:400px'>
</div>
</div>
</body>
</html>
This html creates a div with a scrollbar. If you move the scrollbar, the onscroll event on the div element is triggered. However, the onscroll event on the body is NOT fired. This is expected, since the W3C states that element onscroll events do not "bubble".
However, I'm developing a client-side web framework that needs to know any time a scroll bar on ANY element of the page is scrolled. This would be easy to do if onscroll bubbled, but unfortunately it does not. Is there any other way to detect onscroll events across an entire page? (Right now I'm focusing mainly on Webkit, so a Webkit-specific solution would be fine...)
Here are some things I've tried:
Capturing DOMAttrModified (doesn't seem to fire for moving scrollbars.)
Using DOM Observers (also don't seem to fire for scrollbars)
Changing the onscroll event type to bubble (seems to not be possible)
It seems the ONLY way to capture onscroll events globally is to attach an onscroll event to EVERY element that may scroll, which is very ugly and is going to hurt the performance of my framework.
Anyone know a better way?
The simplest way to detect all scroll events in modern browser would be using 'capturing' rather than 'bubbling' when attaching the event:
window.addEventListener('scroll', function(){ code goes here }, true)
Unfortunately as I am aware there is no equivalent in older browser such as <= IE8
I had this same issue.
The easiest way of course is to use jQuery. Be aware that this method could potentially slow down your page significantly. Also it will not account for any new elements that are added after the event is bound.
$("*").scroll(function(e) {
// Handle scroll event
});
In vanilla JavaScript, you can set the useCapture boolean to true on your addEventListener call, and it will fire on all elements, including those added dynamically.
document.addEventListener('scroll', function(e) {
// Handle scroll event
}, true);
Note though that this will fire before the scroll event actually happens. As I understand it, there's two phases events go through. The capture phase happens first, and starts from the page root (ownerDocument?) and traverses down to the element where the event happened. After this comes the bubbling phase, which traverses from the element back up to the root.
Some quick testing too showed that this may be how jQuery handles it (for tracking scrolls on all page elements at least), but I'm not 100% sure.
Here's a JSFiddle showing the vanilla JavaScript method http://jsfiddle.net/0qpq8pcf/
*...crickets chirping... *
OK, I guess this question isn't going to get any stackoverflow love, so I might as well answer my own question as to the best solution I've found so far, in case another user stumbles across this question:
The best solution I've come up with is to capture "onmousedown" and "onkeydown" for the BODY element: These events bubble, and so if a user tries to move a scrollbar on the page these global functions will fire as a by-product. Then, in these functions, simply look up event.target and attach a temporary "onscroll" event to those objects until the mouse/key is "up" again. Using that method, you can avoid "handler bloat" and still globally capture all "onscroll" events. (I think this will work for "mouse wheel" scrolling as well, but my research on that final wrinkle is still pending.)
The following works fine when you want to i.e. close a dialog after anything in the background is scrolled:
var scrollListener = function(e) {
// TODO: hide dialog
document.removeEventListener('scroll', scrollListener, true);
};
document.addEventListener('scroll', scrollListener, true);
I needed to handle scrolling in any context in a complex of custom elements with scroll events in shadowRoots. This covers at least part of the problem in my scenario and generally borrows from the answers and comments here in the context of these newer web component APIs. Attaching and detaching the listener in the appropriate lifecycle callbacks works well so-far (once might not fit your use-case).
self.addEventListener('mousewheel', handler, {capture: true, once: true});
self.addEventListener('keydown', handler, {capture: true, once: true});
Note too the event.composedPath() provides the entire event path through all the shadowRoots with all the nodes if specifics about the scrolling context are needed. If this is the case it might make sense to use this approach and attach a new handler for that specific scenario--to the node of interest.
Like drcode said, capture on body tag is the trick. I just add touchmove to work on mobile.
document
.querySelector("body")
.addEventListener("mousewheel", e => {
console.log("scroll");
});
document
.querySelector("body")
.addEventListener("touchmove", e => {
console.log("scroll");
});
Regards,
This question relates closely to the stack overflow question "window.resize event firing in Internet Explorer".
The Issue:
I am attempting to fix a resizing issue in Internet Explorer 8. Currently, the resize function gets called repeatedly causing IE to essentially lock up - the user can no longer use buttons that call Javascript actions.
Previous Attempt(s):
var resizeTimeout;
var resizeHandler = function() {
clearTimeout(resizeTimeout);
//$(window).unbind('resize', resizeHandler);
//window.removeEventListener('resize');
window.removeEventListener('resize', resizeHandler, false);
scrollHandler();
setTimeout("$(window).resize(resizeHandler);", 100);
return true;
}
//$(window).resize(resizeHandler);
window.addEventListener('resize', resizeHandler, false);
Problems: It appears that window cannot implement addEventListener or removeEventListener and unbinding jQuery doesn't stop IE from continuing to freak out. It works fine in all other browsers.
Desired Behavior: The goal here is really to get IE to stop repetitively executing code so other functions like onclick events work.
Does anyone know how I can remove the resize event after it's been added or simply make IE stop being retarded. (<-- Extra points if you can make IE not be retarded.)
Resolution: Inside of the scrollHandler function a variable was not declared using the var prefix. Adding var made all the evil fairies go away.
I think you're going about this the wrong way. What you should be doing is using that timeout to block the invocation of "scrollHandler()" until the window resizing activity has paused for a little while (like the 100ms delay you're using).
var resizeTimeout;
function resizeHandler() {
cancelTimeout(resizeTimeout);
resizeTimeout = setTimeout(scrollHandler, 100);
}
$(window).resize(resizeHandler);
Trying to do DOM updates (which I assume to be what goes on inside "scrollHandler") in a "resize" handler is really not a good idea in any browser. By doing that, you won't need to get rid of the "resize" handler at all.
edit — OK now I see that that's effectively what you were trying to do. I still think it's a lot simpler this way.
Looks like Apple has disabled the window.onbeforeunload event for iOS devices (iPhone, iPad, iPod Touch). Unfortunately I can't find any documentation as to why this event doesn't work in Mobile Safari.
Does anyone know if there's a reliable alternative to this function? Android's browser appears to support it just fine, and the Safari desktop application also supports the onbeforeunload event without issue.
I see that it's an old question, but i faced this problem recently.
I'm using window.unload and it works fine in ios browsers (although if you look at Apple documentation it seems to be deprecated and they recommend to use document.pagehide)
If you really need it, you cant just get all links, forms and DOM objects that have a handler changing the url and make those wait until you've done what you want.
For the links, you get them by getElementsByTagName, check if the href starts with anything but a # and just add your onbeforeunload function add onclick (which will be invoked before the href is looked at).
Same for the forms but with onsubmit.
And finaly, for the elements changing the href with JavaScript, you should make sure when you add the lsitener that you call your onbeforeunlaod function (or, if you use DOM0 or DOM1 listeners, you can just add some class and then use a global script that checks all elements with the class and adds it to the event listener with a closure.
But you should normaly be able to avoid the use of this event (probably using cookies to store the thing you wanted to send every x seconds and allowing to, in the worst case, have a look at it next time the user loads a page and, in the best case, be able to send an Ajax request at onbeforeunload or onunload which, even if it sends only the http headers, woudl allow you to get what you want).
Based on Xavier's answer, I devised a solution along these lines:
function doStuff() {
// here goes your logic
}
function isSafariMobile() {
return navigator && /Safari/.test(navigator.userAgent) && /iPhone|iPad/.test(navigator.userAgent)
}
function addWatcherToLinks(baseNode) {
if (!baseNode || !baseNode.querySelectorAll) { return; } // ignore comments, text, etc.
for (const link of baseNode.querySelectorAll("a")) {
link.addEventListener('click', doStuff);
}
for (const form of baseNode.querySelectorAll("form")) {
form.addEventListener('submit', doStuff);
}
}
// ...when the page loads...
// we watch the page for beforeunload to call doStuff
// Since Safari mobile does not support this, we attach a listener (watcher) to each link and form and then call doStuff.
// Also, we add such a watcher to all new incoming nodes (DOMNodeInserted).
if (isSafariMobile()) {
addWatcherToLinks(document);
window.addEventListener("DOMNodeInserted", (event) => { addWatcherToLinks(event.target); }, false);
} else {
window.addEventListener('beforeunload', doStuff);
}
This solution has some limitations. The biggest one is that it attaches itself to all forms and all links. Sometimes this might not be desired. If you need it you can skip some nodes (e.g. mark them with a particular data- attribute).
I was having the same problem. it seems safari browser in iphone triggers only focus and blur events and almost every other event is not triggered, e.g.(pagehide, pageshow, visibility change) but the good news is focus and blur event are supported and triggered on iphone, ipad & android mobiles as well.
window.addEventListener('focus', function(){
// do stuff
});
window.addEventListener('blur', function(){
// do stuff
});
hope this helps anyone.