📜 ⬆️ ⬇️

Like MooTools jQuery fence, or detective in the style of Colombo

JQuery / MooTools call stack As an obligation to work in Airi, I sometimes sort out website functioning errors at the network / browser level. This usually comes down to a simple analysis of request-response headers and the reproduction of trivial conditions. But sometimes there are interesting cases.

It all started on a cold February evening. The client wrote about a strange problem while speeding up the site: the slide show multiplied and blocked the site’s behavior, the pages were not available. Two days after finding out all the details, I found out why Mootools and jQuery absolutely cannot be used together. And it was confirmed in the thought that “alcohol is evil” and “eval is evil”.

But first things first.

We find out the root of troubles


At the moment, browsers now have a sufficient number of profiling tools (even, one might say, this number is somewhat redundant), allowing to fix the problem area, reproduce the error and eliminate it. This is primarily:

If we were talking about a simple error tracing, then this article could have been completed. But the error was not easy, but recursive. And the browser tab “fell” a few seconds after the page was loaded, leaving the following error 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 website, 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):
')
Bxslider slider error

Lyrical digression


It should be said that both MooTools and jQuery came to the site in a compressed form. However, to jQuery was a source map (source map) , which greatly facilitated the search for the last (= guilty).

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

Digging deep


In any problem, it is important to highlight the minimum conditions that still lead to the occurrence of the problem (but without additional data), and analyze these conditions sequentially. 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 with internet providers can prevent simple conclusions, so when working with public unencrypted HTML pages, you need to clearly formulate and re-check conditions several times to be sure of them.

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

Hypotheses: not enough


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

Understand helps call stack (listing above). Bxslider ( Written while drinking Belgian ales and listening to jazz - the Belgian ale definitely prevented the author from predicting some non-standard scenarios) on each object (in our case, the picture) caused the load property, which was processed via jQuery approximately as follows:

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
 } 

It seems everything is clear: jQuery calls the native method of the element, as soon as we’ve finished with the rest of the wrapper. In this case, it is img["load"]() . What should lead to the completion of the load event on 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
	 }
 }; 

MooTools load method understands its own way. And in the absence of information about the object, the object is loaded via new Request.HTML . It seems to be normal too: let's upload the image again, if MooTools has no information about it (after all, the picture has already been loaded into the browser cache, these are just operations in the memory of the local user’s computer). But jQuery, when it calls this method on an image, for some reason forgets to pass parameters, in particular, a URL. Probably, jQuery does not know that after it MooTools will still work, to which these parameters will be needed. And MooTools without parameters loads an “empty” URL (current page).

It also seems to be a valid layout: when the page is loaded, the browser will load it 5 more times from the server (just an HTML document), if 5 pictures are loaded in the slider (this is what happened on the site). If the page is in the server cache, then performance (almost) is not reflected in any way (and it’s also not easy to find resources in the developer’s network toolbar to find these “extra” calls mixed with pictures and counters).

But the problem is that by default MooTools executes the eval all scripts in the loaded HTML document. And this is worse: we can survive the execution of the counter code several times on the site. And if the DOMReady handler starts running, which loads the slider, which causes images to load, which cause loading the HTML page, which executes all the inline code that starts the DOMReady handler ... Well, you understand.

Summary


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

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

Alcohol is evil, el is evil. Sober lifestyle is our everything.

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

Source: https://habr.com/ru/post/249733/


All Articles