Back to Home

How MooTools jQuery took over, or a Colombo-style detective

airy · website acceleration · javascript · recursion · mootools · jquery · joomla · developer tools · call stack

How MooTools jQuery took over, or a Colombo-style detective

    JQuery / MooTools call stackFor the duration of my work at Airy, I sometimes analyze the errors of the site functioning at the network / browser interaction level. This usually comes down to a simple analysis of the request-response headers and reproducing trivial conditions. But sometimes there are interesting cases.

    It all began on a cold February evening. The client wrote about a strange problem when accelerating the site: the slide show multiplied and blocked the behavior of the site, the pages were inaccessible. Two days after finding out all the details, I found out why Mootools and jQuery are categorically not allowed to be used together. And it was confirmed in the thought that both “alcohol is evil” and “eval is evil”.

    But first things first.

    Find out the root of trouble


    At the moment, browsers have a sufficient number of profiling tools (even, you can say, this number is somewhat redundant) that allow you to fix the problem place, reproduce the error and fix it. This is, first of all:
    • Network panel in the developer’s tools: it displays all requests legitimately transmitted by the browser, with all the headers and answers. If we are not talking about some kind of traffic encryption and we need standard information about requests (headers, status, size), then it is more than enough.
    • Console (bugs) in developer tools. If a problem occurs, you can see both the cause of the error and the callback stack. And also repeat the behavior of the browser when executing the code.
    • DOM tree and site source code. It is not always clear which properties were set and which ones were dynamically applied to HTML elements. But you can always see first-hand information - from the browser.

    If we were talking about a simple error tracing, then this article could have ended. But the error was not simple, but recursive. And the browser tab “fell” a few seconds after the page was loaded, leaving the following trace in the error console:
    Call stack
    c.Request.Class.send @ mootools-core.js: 182
    i.extend. $ owner @ mootools-core.js: 50
    Element.implement.load @ mootools-core.js: 187
    st.event.trigger @ jquery.js: 2989
    (anonymous function) @ jquery.js: 3639
    st.extend.each @ jquery.js: 642
    st.fn.st.each @ jquery.js: 263
    st.fn.extend.trigger @ jquery.js: 3638
    st.fn. (anonymous function) @ jquery.js: 3662
    st.fn.load @ jquery.js: 7498
    (anonymous function) @ jquery.bxslider.js: 11
    st.extend.each @ jquery.js: 642
    st.fn.st.each @ jquery.js: 263
    (anonymous function) @ jquery.bxslider.js: 11
    st.extend.each @ jquery.js: 642
    st.fn.st.each @ jquery.js: 263
    loadElements @ jquery.bxslider.js: 11
    setup @ jquery.bxslider.js: 11
    init @ jquery.bxslider.js: 6
    $ .fn.bxSlider @ jquery.bxslider.js: 52
    (anonymous function) @ VM375: 2
    f @ jquery.js: 1026
    p.fireWith @ jquery.js: 1138
    st.extend.ready @ jquery.js: 427
    xt @ jquery.js: 97

    On the site, this was displayed in a repeating slider (multiple arrows on the right and left are several copies of the slider superimposed on each other due to an error):

    Error in bxslider slider

    Lyrical digression


    It is worth saying that both MooTools and jQuery came to the site in a compressed form. However, a source map was used for jQuery , which greatly facilitated the search for the extreme one (= guilty).

    The source map is an extremely useful thing in debugging compressed code; the third specification has already been released, which reduced the size of the map several times. If you hear about the source map for the first time, then I advise you to pay attention to it. But move on.

    Digging deeper


    In any problem, it is important to identify the minimum conditions that still lead to the problem (but without additional data), and analyze these conditions in sequence. As it turned out, the minimum condition for stable reproduction of the problem was the presence of inline JavaScript code in the HTML page. Caching mechanisms in the browser and Internet providers can prevent you from drawing simple conclusions, so when working with public unencrypted HTML pages, you must clearly state and recheck the conditions several times to be sure of them.

    But why the inclusion of JavaScript code in HTML led to its recursive execution in the browser? Yandex and Google do not know anything about such situations. I need to help him.

    Hypotheses: there are few


    HypothesesOn the one hand, everything is clear: JavaScript inline-code is executed recursively, you can not use it on this site. On the other hand: what exactly leads to the recursive execution of inline code?

    The call stack helps you figure it out (listing above). Bxslider ( Written while drinking Belgian ales and listening to jazz- Belgian ale just prevented the author from predicting some non-standard scenarios) on each object (in our case, the picture) called the load property, which was processed through jQuery in the following way:

    JQuery event handling
    jQuery.event.triggered = type;
    try {
    	elem [type] ();
    } catch (e) {
    	// IE <9 dies on focus / blur to hidden element (# 1486, # 12518)
    	// only reproducible on winXP IE8 native, not IE9 in IE8 mode
    }

    Everything seems to be clear: jQuery calls the native method on the element as soon as the rest of the wrapper is finished. In this case, this img["load"](). What should lead to the completion of the load event of the image, it must rely on the browser cache, and everyone should be happy. But the MooTools library does not agree with this situation:

    Handling load MooTools
    Element.Properties.load = {
    	set: function (a) {
    		var b = this.get ("load"). cancel ();
    		b.setOptions (a);
    		return this;
    	},
    	get: function () {
    		var a = this.retrieve ("load");
    		if (! a) {
    			a = new Request.HTML ({data: this, link: "cancel", update: this, method: "get"});
    			this.store ("load", a);
    		}
    		return a
    	}
    };

    The MooTools method loadis understood in its own way. And in the absence of information about the object, the object is loaded through new Request.HTML. It seems okay too: let's upload the image once again if MooTools has no information about it (after all, the image has already been loaded into the browser cache, it's just an operation in the memory of the local user’s computer). But jQuery, when it calls this method on the image, for some reason forgets to pass parameters, in particular, the URL. Probably jQuery does not know that after it MooTools will still work, which will need these parameters as needed. And MooTools without parameters loads the "empty" URL (current page).

    Also, it seems like a valid layout: when the page loads, the browser will download it another 5 times from the server (just an HTML document), if 5 pictures are loaded in the slider (this happened on the site). If the page is in the server cache, then this (almost) does not affect performance at all (and it’s also difficult to find these “extra” calls mixed with pictures and counters in the resources on the network toolbar of the developer).

    But the problem is that by default MooTools doesevalall scripts in the uploaded HTML document. And this is worse: we can survive the execution of the code of the counters several times on the site. And if the DOMReady handler starts, which loads the slider, which causes the image to load, which causes the HTML page to load, which executes all the inline code that runs the DOMReady ... Well, you get the idea.

    Summary


    Do not share multiple JavaScript frameworks. Never (for Joomla! There is even a plugin that cuts MooTools from the system). If suddenly there is a desire - re-read this article. The described problem was "on the surface" and was quickly identified. But there may be a situation where the joint behavior of the frameworks will depend on the network (delays and the order of passing requests) and the browser used. And then it does not seem at all possible to find the cause of the problem and fix it.

    Use evalonly in cases where you control the executable code. If there is no control, there is no eval.

    Alcohol is evil, ale is evil. A sober lifestyle is our everything.

    Developer tools in browsers can really give you all the information you need. You must be able to use them.

    Read Next