On an existing web page, via a TamperMonkey script, I want to automate hovering over an image, waiting for a "pop-up" (actually a < DIV >) to appear then clicking buttons on that pop-up.

Now from the console this code triggers a mouseover event as I'd expect and a 'pop-up' appears.

$('div.photo.mpHover img').eq(0).mouseover()

From my TM script, this code doesn't trigger the event unless I reference the unsafeWindow..

unsafeWindow.$('div.photo.mpHover img').eq(0).mouseover()

Why is this the case? I'm confused because simulating a 'click' event for example from the TM script works as expected without accessing unsafeWindow..

$('div.photo.mpHover img').eq(0).click()
有帮助吗?

解决方案

jQuery's .click() and .mouseover() are just shortcuts for .trigger().
From the docs:

Any event handlers attached with .on() or one of its shortcut methods are triggered when the corresponding event occurs. They can be fired manually, however, with the .trigger() method.

And:

Note: For both plain objects and DOM objects other than window, if a triggered event name matches the name of a property on the object, jQuery will attempt to invoke the property as a method if no event handler calls event.preventDefault().


What this means in practice is that:

  1. .trigger(), or one of its shortcut methods, only reliably works on event handlers set by jQuery, not by other javascript.

  2. .trigger() will normally only work when called from the same jQuery instance, in the same scope.
    A userscript operates in a different scope unless you inject its code or you jump scopes with unsafeWindow.

  3. However, if the target element has a matching native method, eg click, jQuery will attempt to invoke this by default.
    Many elements have a native click method, but few (¿none?) have a native mouseover method.

    This is why jQuery .click() will sometimes work from a userscript, even if injection is not used. But, this is not reliable and will often fail due to the security restrictions of trying to access JS code cross-sandbox.


The most robust solution is send actual mouse events (Updated code):

triggerMouseEvent ( $('div.photo.mpHover img').eq(0), "mouseover");

function triggerMouseEvent (jNode, eventType) {
    if (jNode  &&  jNode.length) {
        var clickEvent  = new MouseEvent (
            eventType, {canBubble: true, cancelable: true}
        );
        jNode[0].dispatchEvent (clickEvent);
    }
}


Old method: Still works but has been deprecated.

triggerMouseEvent ( $('div.photo.mpHover img').eq(0), "mouseover");

function triggerMouseEvent (jNode, eventType) {
    if (jNode  &&  jNode.length) {
        var clickEvent = document.createEvent('MouseEvents');
        clickEvent.initEvent (eventType, true, true);
        jNode[0].dispatchEvent (clickEvent);
    }
}

This works in almost all cases and usually avoids the need for injection or unsafeWindow.

See "Choosing and activating the right controls on an AJAX-driven site" for more complicated scenarios.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top