Search code examples
javascripthtmldom-eventsdom-manipulationevent-bubbling

Removing an ancestor during the event bubbling phase


In the following code (JSFiddle here):

<form>
   <button>ok</button>
</form>
$(function(){
    $('form').submit(false) ;
    $('button').click(function(){ $('form').remove() }) ;
}) ;

When you click the button in Google Chrome 48, it triggers a form submission.
If you do it in Firefox 43, however, there is no form submission.

It seems to me that the Firefox behavior should be the correct one, but since I don't have such a deep knowledge of the standard, I don't really know.

Is either behavior wrong or buggy?


Follow up:
I just found out that the same test case but without using jQuery doesn't trigger a form submission in neither browser.

<form onsubmit="return false">
    <button onclick="form.remove()">ok</button>
</form>

This could not be a timing issue because there's no thread concurrency in Javascript. Event threads will always run in sequence, so the button event handler must finish before the form event handler starts.
I'm blind here. jQuery must be doing some weird cumbersome stuff for this to happen in Chrome.


Follow up 2:
It's not a jQuery problem. At the jQuery bug tracker I was told that inline event handlers don't follow the same specification as those attached with addEventListener, so a true functionally equivalent code should be like this:

<form>
    <button>ok</button>
</form>
<script>
document.querySelector('form').addEventListener('submit',function(){ return false }) ;
document.querySelector('button').addEventListener('click',function(evt){ evt.target.form.remove() }) ;
</script>

And this does behave like the jQuery version.


Solution

  • Your first code adds a returnFalse jQuery event listener:

    $('form').submit(false);
    

    In a jQuery event listener, return false is equivalent to

    event.stopPropagation();
    event.preventDefault();
    

    The later should prevent the submission of the form.

    However, before the submit event is fired, you use $.fn.remove. This not only removes the form from the document, it also cleans its jQuery data, including the event listener.

    Therefore, when the browser fires the submit event to the form, it is not canceled.

    Then browsers behave differently (demo):

    • Firefox doesn't submit removed forms
    • Chrome doesn't care whether the form has been removed and submits it anyway

    If you don't want to remove jQuery data, you should remove the form using vanilla-js methods instead of $.fn.remove.


    In your second code, you cancel the event in a vanilla-js event handler.

    Since it's not jQuery data, $.fn.remove does not remove it, so the submit event is canceled and the form is not submitted.


    In your third code, you you remove the form using vanilla-js methods, so its jQuery data is not cleaned.

    This does not matter because the submit event listener is added with vanilla-js too.

    However, the event is not canceled. That's because, unlike vanilla-js event handlers and jQuery event listeners, the value returned in a vanilla-js event listener is completely ignored.

    So at the end the result is the same than in the first code, but they are not equivalent.

    If you want to cancel an event using a vanilla-js event listener, you should use

    event.preventDefault();
    

    This would make it behave like the second code.