Search code examples
javascriptevent-handlingdom-eventsgreasemonkeyecmascript-5

Detect default event handling


Is it possible to detect whether a particular DOM event has any event handler bound to it (including browser default event handling) - within Firefox's Greasemonkey code (EcmaScript 5.1 strict mode)?

So far I managed to cancel my click event handling when a text is selected, but I would like to cancel my event handling in all following situations where click event triggers a default action:

  • selecting text on page
  • context menu is displayed (i.e. right click)
  • a link is followed (left click on <a>)
  • form elements are manipulated (text box gains focus and cursor starts blinking, radio button is selected, ...)
  • some other JavaScript code that handles the event (e.g. pop up help dialog is displayed)

Is there a catch-all mechanism for this or do I need to spell all situations in my code explicitly?

Following code is how I detect that selection happened:

var toggleHighlight = function(){
    var oldTargt;
    return function(e){
        targt = e.target;
        sel = window.getSelection();
        if (!sel.isCollapsed && sel.containsNode(targt, true)) {
            return;
        }
        ...
        oldTargt = targt;
}()
document.addEventListener('click', toggleHighlight);

Notes:
- I don't want to STOP propagation with my script, I want to stop MY SCRIPT if the event will be propagated to other handler.
- I'm not interested in JQuery-event-handlers-only solutions if they don't catch default browser event handlers.
- If this feature does not indeed exist, please cite some articles or blogs, not just your own opinion.
- Particular suggestions are welcome, but I'm mainly interested in the existence of a general solution.

Update: acceptable solutions include:

  • exhaustive(ish) list of if conditions that i need to check for
  • non-javascript way accessible from a Firefox plugin
  • explanation of how Firefox knows that a click on a valid <a> element should trigger reloading of new URL + how this logic is not accessible from javascript/plugins

Solution

  • This question is almost a duplicate of "How to do an action only when there is no default in Javascript / jQuery?", except that as a Greasemonkey scripter there is one more (complicated) option available to you.

    From your question, it looks like you want to set a default action for nodes that don't have one, and then abort that default action if any other event handler fires for a given event. There is no "catch-all mechanism" for this, and pretty much any technique you try could potentially bust some page out there. Here's why:

    1. Neither javascript nor the Greasemonkey API provide any mechanism to list events for a given node. There was an old proposal, but it has never been implemented.

      However, Firefox add-ons can list events via nsIEventListenerService, so there is a possibility that you could write a helper add-on for that part, that interfaces with Greasemonkey. The details are (way) beyond the scope of this question.

    2. Likewise, there is no mechanism to detect prior events in an event chain from the Event object, unless event.defaultPrevented is set. (It usually won't be.)

    3. Even if you could list event listeners for node X, that won't help with fall-back listeners on parent nodes. Modern JS libraries can, and sometimes do, make any old node clickable. And/or they may set a listener on, say, document that gets all click events, but doesn't actually do anything except when the original target was some specific node.


    A good compromise strategy is to follow the lead of that other question and not do anything for nodes with a default action.
    To handle right-clicks, or center clicks, check event.which.

    So you might add the following snippets to your checks:

    return function(e){
        targt   = e.target;
        sel     = window.getSelection();
    
        if (e.which != 1) {
            return; //-- Abort on anything that is not a left-click.
        }
        if (e.defaultPrevented) {
            return; //-- The one time we know that some other handler fired.
        }
        if (isInteractiveElement (targt) ) {
            // Do nothing for elements that trigger browser actions
            return; 
        }
        ...
    

    where:

    function isInteractiveElement (node) {
        var intElems = [
            //-- Must be uppercase
            "A", "BUTTON", "INPUT", "TEXTAREA", "VIDEO", "MAP", "OBJECT"
        ];
        if (intElems.indexOf (node.nodeName) >= 0) {
            return true;
        }
        else if (node.nodeName === "BODY") {
            return false;
        }
    
        return isInteractiveElement (node.parentNode);
    }