How MooTools jQuery took over, or a Colombo-style detective
For 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:
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):

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
On 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.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: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 does
evalall 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.