PageSourceSearch

http://pravkhabarovsk.ru/admin/js/JsHttpRequest.js

js pravkhabarovsk.ru collected 2026-10-02 07:19:52 UTC 27,191 bytes, 803 lines download raw bytes

1/**
2* JsHttpRequest: JavaScript "AJAX" data loader
3*
4* @license LGPL
5* @author Dmitry Koterov, http://en.dklab.ru/lib/JsHttpRequest/
6* @version 5.x $Id$
7*/
8
9// {{{
10function JsHttpRequest() {
11	// Standard properties.
12	var t = this;
13	t.onreadystatechange = null;
14	t.readyState         = 0;
15	t.responseText       = null;
16	t.responseXML        = null;
17	t.status             = 200;
18	t.statusText         = "OK";
19	// JavaScript response array/hash
20	t.responseJS         = null;
21
22	// Additional properties.
23	t.caching            = false;        // need to use caching?
24	t.loader             = null;         // loader to use ('form', 'script', 'xml'; null - autodetect)
25	t.session_name       = "PHPSESSID";  // set to SID cookie or GET parameter name
26
27	// Internals.
28	t._ldObj              = null;  // used loader object
29	t._reqHeaders        = [];    // collected request headers
30	t._openArgs          = null;  // parameters from open()
31	t._errors = {
32		inv_form_el:        'Invalid FORM element detected: name=%, tag=%',
33		must_be_single_el:  'If used, <form> must be a single HTML element in the list.',
34		js_invalid:         'JavaScript code generated by backend is invalid!\n%',
35		url_too_long:       'Cannot use so long query with GET request (URL is larger than % bytes)',
36		unk_loader:         'Unknown loader: %',
37		no_loaders:         'No loaders registered at all, please check JsHttpRequest.LOADERS array',
38		no_loader_matched:  'Cannot find a loader which may process the request. Notices are:\n%'
39	}
40
41	/**
42	* Aborts the request. Behaviour of this function for onreadystatechange()
43	* is identical to IE (most universal and common case). E.g., readyState -> 4
44	* on abort() after send().
45	*/
46	t.abort = function() { with (this) {
47		if (_ldObj && _ldObj.abort) _ldObj.abort();
48		_cleanup();
49		if (readyState == 0) {
50			// start->abort: no change of readyState (IE behaviour)
51			return;
52		}
53		if (readyState == 1 && !_ldObj) {
54			// open->abort: no onreadystatechange call, but change readyState to 0 (IE).
55			// send->abort: change state to 4 (_ldObj is not null when send() is called)
56			readyState = 0;
57			return;
58		}
59		_changeReadyState(4, true); // 4 in IE & FF on abort() call; Opera does not change to 4.
60	}}
61
62	/**
63	* Prepares the object for data loading.
64	* You may also pass URLs like "GET url" or "script.GET url".
65	*/
66	t.open = function(method, url, asyncFlag, username, password) { with (this) {
67		
68		// Extract methor and loader from the URL (if present).
69		if (url.match(/^((\w+)\.)?(GET|POST)\s+(.*)/i)) {
70			this.loader = RegExp.$2? RegExp.$2 : null;
71			method = RegExp.$3;
72			url = RegExp.$4;
73		}
74		// Append SID to original URL. Use try...catch for security problems.
75		try {
76			if (
77			document.location.search.match(new RegExp('[&?]' + session_name + '=([^&?]*)'))
78			|| document.cookie.match(new RegExp('(?:;|^)\\s*' + session_name + '=([^;]*)'))
79			) {
80				url += (url.indexOf('?') >= 0? '&' : '?') + session_name + "=" + this.escape(RegExp.$1);
81			}
82		} catch (e) {}
83		// Store open arguments to hash.
84		_openArgs = {
85			method:     (method || '').toUpperCase(),
86			url:        url,
87			asyncFlag:  asyncFlag,
88			username:   username != null? username : '',
89			password:   password != null? password : ''
90		}
91		_ldObj = null;
92		_changeReadyState(1, true); // compatibility with XMLHttpRequest
93		return true;
94	}}
95
96	/**
97	* Sends a request to a server.
98	*/
99	t.send = function(content) {
100		if (!this.readyState) {
101			// send without open or after abort: no action (IE behaviour).
102			return;
103		}
104		this._changeReadyState(1, true); // compatibility with XMLHttpRequest
105		this._ldObj = null;
106
107		// Prepare to build QUERY_STRING from query hash.
108		var queryText = [];
109		var queryElem = [];
110
111
112		if (!this._hash2query(content, null, queryText, queryElem)) return;
113
114		// Solve the query hashcode & return on cache hit.
115		var hash = null;
116		if (this.caching && !queryElem.length) {
117			hash = this._openArgs.username + ':' + this._openArgs.password + '@' + this._openArgs.url + '|' + queryText + "#" + this._openArgs.method;
118			var cache = JsHttpRequest.CACHE[hash];
119			if (cache) {
120				this._dataReady(cache[0], cache[1]);
121				return false;
122			}
123		}
124
125		// Try all the loaders.
126		var loader = (this.loader || '').toLowerCase();
127		if (loader && !JsHttpRequest.LOADERS[loader]) return this._error('unk_loader', loader);
128		var errors = [];
129		var lds = JsHttpRequest.LOADERS;
130		for (var tryLoader in lds) {
131			var ldr = lds[tryLoader].loader;
132			if (!ldr) continue; // exclude possibly derived prototype properties from "for .. in".
133			if (loader && tryLoader != loader) continue;
134			// Create sending context.
135			var ldObj = new ldr(this);
136			JsHttpRequest.extend(ldObj, this._openArgs);
137			JsHttpRequest.extend(ldObj, {
138				queryText:  queryText.join('&'),
139				queryElem:  queryElem,
140				id:         (new Date().getTime()) + "" + JsHttpRequest.COUNT++,
141				hash:       hash,
142				span:       null
143			});
144			var error = ldObj.load();
145			if (!error) {
146				// Save loading script.
147				this._ldObj = ldObj;
148				JsHttpRequest.PENDING[ldObj.id] = this;
149				return true;
150			}
151			if (!loader) {
152				errors[errors.length] = '- ' + tryLoader.toUpperCase() + ': ' + this._l(error);
153			} else {
154				return this._error(error);
155			}
156		}
157
158		// If no loader matched, generate error message.
159		return tryLoader? this._error('no_loader_matched', errors.join('\n')) : this._error('no_loaders');
160	}
161
162	/**
163	* Returns all response headers (if supported).
164	*/
165	t.getAllResponseHeaders = function() { with (this) {
166		return _ldObj && _ldObj.getAllResponseHeaders? _ldObj.getAllResponseHeaders() : [];
167	}}
168
169	/**
170	* Returns one response header (if supported).
171	*/
172	t.getResponseHeader = function(label) { with (this) {
173		return _ldObj && _ldObj.getResponseHeader? _ldObj.getResponseHeader(label) : null;
174	}}
175
176	/**
177	* Adds a request header to a future query.
178	*/
179	t.setRequestHeader = function(label, value) { with (this) {
180		_reqHeaders[_reqHeaders.length] = [label, value];
181	}}
181
182
183	//
184	// Internal functions.
185	//
186
187	/**
188	* Do all the work when a data is ready.
189	*/
190	t._dataReady = function(text, js) { with (this) {
191		if (caching && _ldObj) JsHttpRequest.CACHE[_ldObj.hash] = [text, js];
192		responseText = responseXML = text;
193		responseJS = js;
194		if (js !== null) {
195			status = 200;
196			statusText = "OK";
197		} else {
198			status = 500;
199			statusText = "Internal Server Error";
200		}
201		_changeReadyState(2);
202		_changeReadyState(3);
203		_changeReadyState(4);
204		_cleanup();
205	}}
206
207	/**
208	* Analog of sprintf(), but translates the first parameter by _errors.
209	*/
210	t._l = function(args) {
211		var i = 0, p = 0, msg = this._errors[args[0]];
212		// Cannot use replace() with a callback, because it is incompatible with IE5.
213		while ((p = msg.indexOf('%', p)) >= 0) {
214			var a = args[++i] + "";
215			msg = msg.substring(0, p) + a + msg.substring(p + 1, msg.length);
216			p += 1 + a.length;
217		}
218		return msg;
219	}
220
221	/**
222	* Called on error.
223	*/
224	t._error = function(msg) {
225		msg = this._l(typeof(msg) == 'string'? arguments : msg)
226		msg = "JsHttpRequest: " + msg;
227		if (!window.Error) {
228			// Very old browser...
229			throw msg;
230		} else if ((new Error(1, 'test')).description == "test") {
231			// We MUST (!!!) pass 2 parameters to the Error() constructor for IE5.
232			throw new Error(1, msg);
233		} else {
234			// Mozilla does not support two-parameter call style.
235			throw new Error(msg);
236		}
237	}
238
239	/**
240	* Convert hash to QUERY_STRING.
241	* If next value is scalar or hash, push it to queryText.
242	* If next value is form element, push [name, element] to queryElem.
243	*/
244	t._hash2query = function(content, prefix, queryText, queryElem) {
245		if (prefix == null) prefix = "";
246		if((''+typeof(content)).toLowerCase() == 'object') {
247			var formAdded = false;
248			if (content && content.parentNode && content.parentNode.appendChild && content.tagName && content.tagName.toUpperCase() == 'FORM') {
249				content = { form: content };
250			}
251			for (var k in content) {
252				var v = content[k];
253				if (v instanceof Function) continue;
254				var curPrefix = prefix? prefix + '[' + this.escape(k) + ']' : this.escape(k);
255				var isFormElement = v && v.parentNode && v.parentNode.appendChild && v.tagName;
256
257				if (isFormElement) {
258					var tn = v.tagName.toUpperCase();
259
260					if (tn == 'FORM') {
261						// FORM itself is passed.
262						formAdded = true;
263					} else if (tn == 'INPUT' || tn == 'TEXTAREA' || tn == 'SELECT') {
264						// This is a single form elemenent.
265					} else {
266						return this._error('inv_form_el', (v.name||''), v.tagName);
267					}
268
269					queryElem[queryElem.length] = { name: curPrefix, e: v };
270				} else if (v instanceof Object) {
271					this._hash2query(v, curPrefix, queryText, queryElem);
272				} else {
273					// We MUST skip NULL values, because there is no method
274					// to pass NULL's via GET or POST request in PHP.
275					if (v === null) continue;
276					// Convert JS boolean true and false to corresponding PHP values.
277					if (v === true) v = 1;
278					if (v === false) v = '';
279					queryText[queryText.length] = curPrefix + "=" + this.escape('' + v);
280				}
281				if (formAdded && queryElem.length > 1) {
282					return this._error('must_be_single_el');
283				}
284			}
285		} else {
286			queryText[queryText.length] = content;
287		}
288		return true;
289	}
290
291	/**
292	* Remove last used script element (clean memory).
293	*/
294	t._cleanup = function() {
295		var ldObj = this._ldObj;
296		if (!ldObj) return;
297		// Mark this loading as aborted.
298		JsHttpRequest.PENDING[ldObj.id] = false;
299		var span = ldObj.span;
300		if (!span) return;
301		ldObj.span = null;
302		var closure = function() {
303			span.parentNode.removeChild(span);
304		}
305		// IE5 crashes on setTimeout(function() {...}, ...) construction! Use tmp variable.
306		JsHttpRequest.setTimeout(closure, 50);
307	}
308
309	/**
310	* Change current readyState and call trigger method.
311	*/
312	t._changeReadyState = function(s, reset) { with (this) {
313		if (reset) {
314			status = statusText = responseJS = null;
315			responseText = '';
316		}
317		readyState = s;
318		if (onreadystatechange) onreadystatechange();
319	}}
320
321	/**
322	* JS escape() does not quote '+'.
323	*/
324	t.escape = function(s) {
325		return escape(s).replace(new RegExp('\\+','g'), '%2B');
326	}
327}
328
329
330// Global library variables.
331JsHttpRequest.COUNT = 0;              // unique ID; used while loading IDs generation
332JsHttpRequest.MAX_URL_LEN = 2000;     // maximum URL length
333JsHttpRequest.CACHE = {};             // cached data
334JsHttpRequest.PENDING = {};           // pending loadings
335JsHttpRequest.LOADERS = {};           // list of supported data loaders (filled at the bottom of the file)
336JsHttpRequest._dummy = function() {}; // avoid memory leaks
337
338
339/**
340* These functions are dirty hacks for IE 5.0 which does not increment a
341* reference counter for an object passed via setTimeout(). So, if this
342* object (closure function) is out of scope at the moment of timeout
343* applying, IE 5.0 crashes.
344*/
345
346/**
347* Timeout wrappers storage. Used to avoid zeroing of referece counts in IE 5.0.
348* Please note that you MUST write "window.setTimeout", not "setTimeout", else
349* IE 5.0 crashes again. Strange, very strange...
350*/
351JsHttpRequest.TIMEOUTS = { s: window.setTimeout, c: window.clearTimeout };
352
353/**
354* Wrapper for IE5 buggy setTimeout.
355* Use this function instead of a usual setTimeout().
356*/
357JsHttpRequest.setTimeout = function(func, dt) {
358	// Always save inside the window object before a call (for FF)!
359	window.JsHttpRequest_tmp = JsHttpRequest.TIMEOUTS.s;
360	if (typeof(func) == "string") {
361		id = window.JsHttpRequest_tmp(func, dt);
362	} else {
363		var id = null;
364		var mediator = function() {
365			func();
366			delete JsHttpRequest.TIMEOUTS[id]; // remove circular reference
367		}
368		id = window.JsHttpRequest_tmp(mediator, dt);
369		// Store a reference to the mediator function to the global array
370		// (reference count >= 1); use timeout ID as an array key;
371		JsHttpRequest.TIMEOUTS[id] = mediator;
372	}
373	window.JsHttpRequest_tmp = null; // no delete() in IE5 for window
374	return id;
375}
376
377/**
378* Complimental wrapper for clearTimeout.
379* Use this function instead of usual clearTimeout().
380*/
381JsHttpRequest.clearTimeout = function(id) {
382	window.JsHttpRequest_tmp = JsHttpRequest.TIMEOUTS.c;
383	delete JsHttpRequest.TIMEOUTS[id]; // remove circular reference
384	var r = window.JsHttpRequest_tmp(id);
385	window.JsHttpRequest_tmp = null; // no delete() in IE5 for window
386	return r;
387}
388
389
390/**
391* Global static function.
392* Simple interface for most popular use-cases.
393* You may also pass URLs like "GET url" or "script.GET url".
394*/
395JsHttpRequest.query = function(url, content, onready, nocache) {
396	var req = new this();
397	req.caching = !nocache;
398	req.onreadystatechange = function() {
399		if (req.readyState == 4) {
400			onready(req.responseJS, req.responseText);
401		}
402	}
403	req.open(null, url, true);
404	req.send(content);
405}
406
407
408/**
409* Global static function.
410* Called by server backend script on data load.
411*/
412JsHttpRequest.dataReady = function(d) {
413	var th = this.PENDING[d.id];
414	delete this.PENDING[d.id];
415	if (th) {
416		th._dataReady(d.text, d.js);
417	} else if (th !== false) {
418		throw "dataReady(): unknown pending id: " + d.id;
419	}
420}
421
422
423// Adds all the properties of src to dest.
424JsHttpRequest.extend = function(dest, src) {
425	for (var k in src) dest[k] = src[k];
426}
427
428/**
429* Each loader has the following properties which must be initialized:
430* - method
431* - url
432* - asyncFlag (ignored)
433* - username
434* - password
435* - queryText (string)
436* - queryElem (array)
437* - id
438* - hash
439* - span
440*/
441
442// }}}
443
444// {{{ xml
445// Loader: XMLHttpRequest or ActiveX.
446// [+] GET and POST methods are supported.
447// [+] Most native and memory-cheap method.
448// [+] Backend data can be browser-cached.
449// [-] Cannot work in IE without ActiveX.
450// [-] No support for loading from different domains.
451// [-] No uploading support.
452//
453JsHttpRequest.LOADERS.xml = { loader: function(req) {
454	JsHttpRequest.extend(req._errors, {
455		xml_no:          'Cannot use XMLHttpRequest or ActiveX loader: not supported',
456		xml_no_diffdom:  'Cannot use XMLHttpRequest to load data from different domain %',
457		xml_no_headers:  'Cannot use XMLHttpRequest loader or ActiveX loader, POST method: headers setting is not supported, needed to work with encodings correctly',
458		xml_no_form_upl: 'Cannot use XMLHttpRequest loader: direct form elements using and uploading are not implemented'
459	});
460
461	this.load = function() {
462		if (this.queryElem.length) return ['xml_no_form_upl'];
463
464		// XMLHttpRequest (and MS ActiveX'es) cannot work with different domains.
465		if (this.url.match(new RegExp('^([a-z]+://[^\\/]+)(.*)', 'i'))) {
466			// We MUST also check if protocols matched: cannot send from HTTP
467			// to HTTPS and vice versa.
468			if (RegExp.$1.toLowerCase() != document.location.protocol + '//' + document.location.hostname.toLowerCase()) {
469				return ['xml_no_diffdom', RegExp.$1];
470			}
471		}
472
473		// Try to obtain a loader.
474		var xr = null;
475		if (window.XMLHttpRequest) {
476			try { xr = new XMLHttpRequest() } catch(e) {}
477		} else if (window.ActiveXObject) {
478			try { xr = new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
479			if (!xr) try { xr = new ActiveXObject("Msxml2.XMLHTTP") } catch (e) {}
480		}
481		if (!xr) return ['xml_no'];
482
483		// Loading method detection. We cannot POST if we cannot set "octet-stream"
484		// header, because we need to process the encoded data in the backend manually.
485		var canSetHeaders = window.ActiveXObject || xr.setRequestHeader;
486		if (!this.method) this.method = canSetHeaders && this.queryText.length? 'POST' : 'GET';
487
488		// Build & validate the full URL.
489		if (this.method == 'GET') {
490			if (this.queryText) this.url += (this.url.indexOf('?') >= 0? '&' : '?') + this.queryText;
491			this.queryText = '';
492			if (this.url.length > JsHttpRequest.MAX_URL_LEN) return ['url_too_long', JsHttpRequest.MAX_URL_LEN];
493		} else if (this.method == 'POST' && !canSetHeaders) {
494			return ['xml_no_headers'];
495		}
496
497		// Add ID to the url if we need to disable the cache.
498		this.url += (this.url.indexOf('?') >= 0? '&' : '?') + 'JsHttpRequest=' + (req.caching? '0' : this.id) + '-xml';
499
500		// Assign the result handler.
501		var id = this.id;
502		xr.onreadystatechange = function() {
503			if (xr.readyState != 4) return;
504			// Avoid memory leak by removing the closure.
505			xr.onreadystatechange = JsHttpRequest._dummy;
506			req.status = null;
507			try {
508				// In case of abort() call, xr.status is unavailable and generates exception.
509				// But xr.readyState equals to 4 in this case. Stupid behaviour. :-(
510				req.status = xr.status;
511				req.responseText = xr.responseText;
512			} catch (e) {}
513			if (!req.status) return;
514			try {
515				// Prepare generator function & catch syntax errors on this stage.
516				eval('JsHttpRequest._tmp = function(id) { var d = ' + req.responseText + '; d.id = id; JsHttpRequest.dataReady(d); }');
517			} catch (e) {
518				// Note that FF 2.0 does not throw any error from onreadystatechange handler.
519				return req._error('js_invalid', req.responseText)
520			}
521			// Call associated dataReady() outside the try-catch block
522			// to pass exceptions in onreadystatechange in usual manner.
523			JsHttpRequest._tmp(id);
524			JsHttpRequest._tmp = null;
525		};
526
527		// Open & send the request.
528		xr.open(this.method, this.url, true, this.username, this.password);
529		if (canSetHeaders) {
530			// Pass pending headers.
531			for (var i = 0; i < req._reqHeaders.length; i++) {
532				xr.setRequestHeader(req._reqHeaders[i][0], req._reqHeaders[i][1]);
533			}
534			// Set non-default Content-type. We cannot use
535			// "application/x-www-form-urlencoded" here, because
536			// in PHP variable HTTP_RAW_POST_DATA is accessible only when
537			// enctype is not default (e.g., "application/octet-stream"
538			// is a good start). We parse POST data manually in backend
539			// library code. Note that Safari sets by default "x-www-form-urlencoded"
540			// header, but FF sets "text/xml" by default.
541			xr.setRequestHeader('Content-Type', 'application/octet-stream');
542		}
543		xr.send(this.queryText);
544
545		// No SPAN is used for this loader.
546		this.span = null;
547		this.xr = xr; // save for later usage on abort()
548
549		// Success.
550		return null;
551	}
552
553	// Override req.getAllResponseHeaders method.
554	this.getAllResponseHeaders = function() {
555		return this.xr.getAllResponseHeaders();
556	}
557
558	// Override req.getResponseHeader method.
559	this.getResponseHeader = function(label) {
560		return this.xr.getResponseHeader(label);
561	}
562
563	this.abort = function() {
564		this.xr.abort();
565		this.xr = null;
566	}
567}}
568// }}}
569
570
571// {{{ script
572// Loader: SCRIPT tag.
573// [+] Most cross-browser.
574// [+] Supports loading from different domains.
575// [-] Only GET method is supported.
576// [-] No uploading support.
577// [-] Backend data cannot be browser-cached.
578//
579JsHttpRequest.LOADERS.script = { loader: function(req) {
580	JsHttpRequest.extend(req._errors, {
581		script_only_get:   'Cannot use SCRIPT loader: it supports only GET method',
582		script_no_form:    'Cannot use SCRIPT loader: direct form elements using and uploading are not implemented'
583	})
584
585	this.load = function() {
586		// Move GET parameters to the URL itself.
587		if (this.queryText) this.url += (this.url.indexOf('?') >= 0? '&' : '?') + this.queryText;
588		this.url += (this.url.indexOf('?') >= 0? '&' : '?') + 'JsHttpRequest=' + this.id + '-' + 'script';
589		this.queryText = '';
590
591		if (!this.method) this.method = 'GET';
592		if (this.method !== 'GET') return ['script_only_get'];
593		if (this.queryElem.length) return ['script_no_form'];
594		if (this.url.length > JsHttpRequest.MAX_URL_LEN) return ['url_too_long', JsHttpRequest.MAX_URL_LEN];
595
596		var th = this, d = document, s = null, b = d.body;
597		if (!window.opera) {
598			// Safari, IE, FF, Opera 7.20.
599			this.span = s = d.createElement('SCRIPT');
600			var closure = function() {
601				s.language = 'JavaScript';
602				if (s.setAttribute) s.setAttribute('src', th.url); else s.src = th.url;
603				b.insertBefore(s, b.lastChild);
604			}
605		} else {
606			// Oh shit! Damned stupid Opera 7.23 does not allow to create SCRIPT
607			// element over createElement (in HEAD or BODY section or in nested SPAN -
608			// no matter): it is created deadly, and does not response the href assignment.
609			// So - always create SPAN.
610			this.span = s = d.createElement('SPAN');
611			s.style.display = 'none';
612			b.insertBefore(s, b.lastChild);
613			s.innerHTML = 'Workaround for IE.<s'+'cript></' + 'script>';
614			var closure = function() {
615				s = s.getElementsByTagName('SCRIPT')[0]; // get with timeout!
616				s.language = 'JavaScript';
617				if (s.setAttribute) s.setAttribute('src', th.url); else s.src = th.url;
618			}
619		}
620		JsHttpRequest.setTimeout(closure, 10);
621
622		// Success.
623		return null;
624	}
625}}
626// }}}
627
628
629// {{{ form
630// Loader: FORM & IFRAME.
631// [+] Supports file uploading.
632// [+] GET and POST methods are supported.
633// [+] Supports loading from different domains.
634// [-] Uses a lot of system resources.
635// [-] Backend data cannot be browser-cached.
636// [-] Pollutes browser history on some old browsers.
637//
638JsHttpRequest.LOADERS.form = { loader: function(req) {
639	JsHttpRequest.extend(req._errors, {
640		form_el_not_belong:  'Element "%" does not belong to any form!',
641		form_el_belong_diff: 'Element "%" belongs to a different form. All elements must belong to the same form!',
642		form_el_inv_enctype: 'Attribute "enctype" of the form must be "%" (for IE), "%" given.'
643	})
644
645	this.load = function() {
646		var th = this;
647
648		if (!th.method) th.method = 'POST';
649		th.url += (th.url.indexOf('?') >= 0? '&' : '?') + 'JsHttpRequest=' + th.id + '-' + 'form';
650
651		// If GET, build full URL. Then copy QUERY_STRING to queryText.
652		if (th.method == 'GET') {
653			if (th.queryText) th.url += (th.url.indexOf('?') >= 0? '&' : '?') + th.queryText;
654			if (th.url.length > JsHttpRequest.MAX_URL_LEN) return ['url_too_long', JsHttpRequest.MAX_URL_LEN];
655			var p = th.url.split('?', 2);
656			th.url = p[0];
657			th.queryText = p[1] || '';
658		}
659
660		// Check if all form elements belong to same form.
661		var form = null;
662		var wholeFormSending = false;
663		if (th.queryElem.length) {
664			if (th.queryElem[0].e.tagName.toUpperCase() == 'FORM') {
665				// Whole FORM sending.
666				form = th.queryElem[0].e;
667				wholeFormSending = true;
668				th.queryElem = [];
669			} else {
670				// If we have at least one form element, we use its FORM as a POST container.
671				form = th.queryElem[0].e.form;
672				// Validate all the elements.
673				for (var i = 0; i < th.queryElem.length; i++) {
674					var e = th.queryElem[i].e;
675					if (!e.form) {
676						return ['form_el_not_belong', e.name];
677					}
678					if (e.form != form) {
679						return ['form_el_belong_diff', e.name];
680					}
681				}
682			}
683
684			// Check enctype of the form.
685			if (th.method == 'POST') {
686				var need = "multipart/form-data";
687				var given = (form.attributes.encType && form.attributes.encType.nodeValue) || (form.attributes.enctype && form.attributes.enctype.value) || form.enctype;
688				if (given != need) {
689					return ['form_el_inv_enctype', need, given];
690				}
691			}
692		}
693
694		// Create invisible IFRAME with temporary form (form is used on empty queryElem).
695		// We ALWAYS create th IFRAME in the document of the form - for Opera 7.20.
696		var d = form && (form.ownerDocument || form.document) || document;
697		var ifname = 'jshr_i_' + th.id;
698		var s = th.span = d.createElement('DIV');
699		s.style.position = 'absolute';
700		s.style.display = 'none';
701		s.style.visibility = 'hidden';
702		s.innerHTML =
703		(form? '' : '<form' + (th.method == 'POST'? ' enctype="multipart/form-data" method="post"' : '') + '></form>') + // stupid IE, MUST use innerHTML assignment :-(
704		'<iframe name="' + ifname + '" id="' + ifname + '" style="width:0px; height:0px; overflow:hidden; border:none"></iframe>'
705		if (!form) {
706			form = th.span.firstChild;
707		}
708
709		// Insert generated form inside the document.
710		// Be careful: don't forget to close FORM container in document body!
711		d.body.insertBefore(s, d.body.lastChild);
712
713		// Function to safely set the form attributes. Parameter attr is NOT a hash
714		// but an array, because "for ... in" may badly iterate over derived attributes.
715		var setAttributes = function(e, attr) {
716			var sv = [];
717			var form = e;
718			// This strange algorythm is needed, because form may  contain element
719			// with name like 'action'. In IE for such attribute will be returned
720			// form element node, not form action. Workaround: copy all attributes
721			// to new empty form and work with it, then copy them back. This is
722			// THE ONLY working algorythm since a lot of bugs in IE5.0 (e.g.
723			// with e.attributes property: causes IE crash).
724			if (e.mergeAttributes) {
725				var form = d.createElement('form');
726				form.mergeAttributes(e, false);
727			}
728			for (var i = 0; i < attr.length; i++) {
729				var k = attr[i][0], v = attr[i][1];
730				// TODO: http://forum.dklab.ru/viewtopic.php?p=129059#129059
731				sv[sv.length] = [k, form.getAttribute(k)];
732				form.setAttribute(k, v);
733			}
734			if (e.mergeAttributes) {
735				e.mergeAttributes(form, false);
736			}
737			return sv;
738		}
739
740		// Run submit with delay - for old Opera: it needs some time to create IFRAME.
741		var closure = function() {
742			// Save JsHttpRequest object to new IFRAME.
743			top.JsHttpRequestGlobal = JsHttpRequest;
744
745			// Disable ALL the form elements.
746			var savedNames = [];
747			if (!wholeFormSending) {
748				for (var i = 0, n = form.elements.length; i < n; i++) {
749					savedNames[i] = form.elements[i].name;
750					form.elements[i].name = '';
751				}
752			}
753
754			// Insert hidden fields to the form.
755			var qt = th.queryText.split('&');
756			for (var i = qt.length - 1; i >= 0; i--) {
757				var pair = qt[i].split('=', 2);
758				var e = d.createElement('INPUT');
759				e.type = 'hidden';
760				e.name = unescape(pair[0]);
761				e.value = pair[1] != null? unescape(pair[1]) : '';
762				form.appendChild(e);
763			}
764
765
766			// Change names of along user-passed form elements.
767			for (var i = 0; i < th.queryElem.length; i++) {
768				th.queryElem[i].e.name = th.queryElem[i].name;
769			}
770
771			// Temporary modify form attributes, submit form, restore attributes back.
772			var sv = setAttributes(
773			form,
774			[
775			['action',   th.url],
776			['method',   th.method],
777			['onsubmit', null],
778			['target',   ifname]
779			]
780			);
781			form.submit();
782			setAttributes(form, sv);
783
784			// Remove generated temporary hidden elements from the top of the form.
785			for (var i = 0; i < qt.length; i++) {
786				// Use "form.firstChild.parentNode", not "form", or IE5 crashes!
787				form.lastChild.parentNode.removeChild(form.lastChild);
788			}
789			// Enable all disabled elements back.
790			if (!wholeFormSending) {
791				for (var i = 0, n = form.elements.length; i < n; i++) {
792					form.elements[i].name = savedNames[i];
793				}
794			}
795		}
796		JsHttpRequest.setTimeout(closure, 100);
797
798		// Success.
799		return null;
800	}
801}}
802// }}}
803

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.