]> git.proxmox.com Git - mirror_novnc.git/blob - include/util.js
Prevent noVNC loading error when invalid property is set on object
[mirror_novnc.git] / include / util.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Licensed under MPL 2.0 (see LICENSE.txt)
5 *
6 * See README.md for usage and integration instructions.
7 */
8
9 /* jshint white: false, nonstandard: true */
10 /*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */
11
12 // Globals defined here
13 var Util = {};
14
15
16 /*
17 * Make arrays quack
18 */
19
20 Array.prototype.push8 = function (num) {
21 "use strict";
22 this.push(num & 0xFF);
23 };
24
25 Array.prototype.push16 = function (num) {
26 "use strict";
27 this.push((num >> 8) & 0xFF,
28 num & 0xFF);
29 };
30 Array.prototype.push32 = function (num) {
31 "use strict";
32 this.push((num >> 24) & 0xFF,
33 (num >> 16) & 0xFF,
34 (num >> 8) & 0xFF,
35 num & 0xFF);
36 };
37
38 // IE does not support map (even in IE9)
39 //This prototype is provided by the Mozilla foundation and
40 //is distributed under the MIT license.
41 //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
42 if (!Array.prototype.map) {
43 Array.prototype.map = function (fun /*, thisp*/) {
44 "use strict";
45 var len = this.length;
46 if (typeof fun != "function") {
47 throw new TypeError();
48 }
49
50 var res = new Array(len);
51 var thisp = arguments[1];
52 for (var i = 0; i < len; i++) {
53 if (i in this) {
54 res[i] = fun.call(thisp, this[i], i, this);
55 }
56 }
57
58 return res;
59 };
60 }
61
62 // IE <9 does not support indexOf
63 //This prototype is provided by the Mozilla foundation and
64 //is distributed under the MIT license.
65 //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
66 if (!Array.prototype.indexOf) {
67 Array.prototype.indexOf = function (elt /*, from*/) {
68 "use strict";
69 var len = this.length >>> 0;
70
71 var from = Number(arguments[1]) || 0;
72 from = (from < 0) ? Math.ceil(from) : Math.floor(from);
73 if (from < 0) {
74 from += len;
75 }
76
77 for (; from < len; from++) {
78 if (from in this &&
79 this[from] === elt) {
80 return from;
81 }
82 }
83 return -1;
84 };
85 }
86
87 // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
88 if (!Object.keys) {
89 Object.keys = (function () {
90 'use strict';
91 var hasOwnProperty = Object.prototype.hasOwnProperty,
92 hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
93 dontEnums = [
94 'toString',
95 'toLocaleString',
96 'valueOf',
97 'hasOwnProperty',
98 'isPrototypeOf',
99 'propertyIsEnumerable',
100 'constructor'
101 ],
102 dontEnumsLength = dontEnums.length;
103
104 return function (obj) {
105 if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
106 throw new TypeError('Object.keys called on non-object');
107 }
108
109 var result = [], prop, i;
110
111 for (prop in obj) {
112 if (hasOwnProperty.call(obj, prop)) {
113 result.push(prop);
114 }
115 }
116
117 if (hasDontEnumBug) {
118 for (i = 0; i < dontEnumsLength; i++) {
119 if (hasOwnProperty.call(obj, dontEnums[i])) {
120 result.push(dontEnums[i]);
121 }
122 }
123 }
124 return result;
125 };
126 })();
127 }
128
129 // PhantomJS 1.x doesn't support bind,
130 // so leave this in until PhantomJS 2.0 is released
131 //This prototype is provided by the Mozilla foundation and
132 //is distributed under the MIT license.
133 //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
134 if (!Function.prototype.bind) {
135 Function.prototype.bind = function (oThis) {
136 if (typeof this !== "function") {
137 // closest thing possible to the ECMAScript 5
138 // internal IsCallable function
139 throw new TypeError("Function.prototype.bind - " +
140 "what is trying to be bound is not callable");
141 }
142
143 var aArgs = Array.prototype.slice.call(arguments, 1),
144 fToBind = this,
145 fNOP = function () {},
146 fBound = function () {
147 return fToBind.apply(this instanceof fNOP && oThis ? this
148 : oThis,
149 aArgs.concat(Array.prototype.slice.call(arguments)));
150 };
151
152 fNOP.prototype = this.prototype;
153 fBound.prototype = new fNOP();
154
155 return fBound;
156 };
157 }
158
159 //
160 // requestAnimationFrame shim with setTimeout fallback
161 //
162
163 window.requestAnimFrame = (function () {
164 "use strict";
165 return window.requestAnimationFrame ||
166 window.webkitRequestAnimationFrame ||
167 window.mozRequestAnimationFrame ||
168 window.oRequestAnimationFrame ||
169 window.msRequestAnimationFrame ||
170 function (callback) {
171 window.setTimeout(callback, 1000 / 60);
172 };
173 })();
174
175 /*
176 * ------------------------------------------------------
177 * Namespaced in Util
178 * ------------------------------------------------------
179 */
180
181 /*
182 * Logging/debug routines
183 */
184
185 Util._log_level = 'warn';
186 Util.init_logging = function (level) {
187 "use strict";
188 if (typeof level === 'undefined') {
189 level = Util._log_level;
190 } else {
191 Util._log_level = level;
192 }
193 if (typeof window.console === "undefined") {
194 if (typeof window.opera !== "undefined") {
195 window.console = {
196 'log' : window.opera.postError,
197 'warn' : window.opera.postError,
198 'error': window.opera.postError
199 };
200 } else {
201 window.console = {
202 'log' : function (m) {},
203 'warn' : function (m) {},
204 'error': function (m) {}
205 };
206 }
207 }
208
209 Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
210 /* jshint -W086 */
211 switch (level) {
212 case 'debug':
213 Util.Debug = function (msg) { console.log(msg); };
214 case 'info':
215 Util.Info = function (msg) { console.log(msg); };
216 case 'warn':
217 Util.Warn = function (msg) { console.warn(msg); };
218 case 'error':
219 Util.Error = function (msg) { console.error(msg); };
220 case 'none':
221 break;
222 default:
223 throw new Error("invalid logging type '" + level + "'");
224 }
225 /* jshint +W086 */
226 };
227 Util.get_logging = function () {
228 return Util._log_level;
229 };
230 // Initialize logging level
231 Util.init_logging();
232
233 Util.make_property = function (proto, name, mode, type) {
234 "use strict";
235
236 var getter;
237 if (type === 'arr') {
238 getter = function (idx) {
239 if (typeof idx !== 'undefined') {
240 return this['_' + name][idx];
241 } else {
242 return this['_' + name];
243 }
244 };
245 } else {
246 getter = function () {
247 return this['_' + name];
248 };
249 }
250
251 var make_setter = function (process_val) {
252 if (process_val) {
253 return function (val, idx) {
254 if (typeof idx !== 'undefined') {
255 this['_' + name][idx] = process_val(val);
256 } else {
257 this['_' + name] = process_val(val);
258 }
259 };
260 } else {
261 return function (val, idx) {
262 if (typeof idx !== 'undefined') {
263 this['_' + name][idx] = val;
264 } else {
265 this['_' + name] = val;
266 }
267 };
268 }
269 };
270
271 var setter;
272 if (type === 'bool') {
273 setter = make_setter(function (val) {
274 if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
275 return false;
276 } else {
277 return true;
278 }
279 });
280 } else if (type === 'int') {
281 setter = make_setter(function (val) { return parseInt(val, 10); });
282 } else if (type === 'float') {
283 setter = make_setter(parseFloat);
284 } else if (type === 'str') {
285 setter = make_setter(String);
286 } else if (type === 'func') {
287 setter = make_setter(function (val) {
288 if (!val) {
289 return function () {};
290 } else {
291 return val;
292 }
293 });
294 } else if (type === 'arr' || type === 'dom' || type == 'raw') {
295 setter = make_setter();
296 } else {
297 throw new Error('Unknown property type ' + type); // some sanity checking
298 }
299
300 // set the getter
301 if (typeof proto['get_' + name] === 'undefined') {
302 proto['get_' + name] = getter;
303 }
304
305 // set the setter if needed
306 if (typeof proto['set_' + name] === 'undefined') {
307 if (mode === 'rw') {
308 proto['set_' + name] = setter;
309 } else if (mode === 'wo') {
310 proto['set_' + name] = function (val, idx) {
311 if (typeof this['_' + name] !== 'undefined') {
312 throw new Error(name + " can only be set once");
313 }
314 setter.call(this, val, idx);
315 };
316 }
317 }
318
319 // make a special setter that we can use in set defaults
320 proto['_raw_set_' + name] = function (val, idx) {
321 setter.call(this, val, idx);
322 //delete this['_init_set_' + name]; // remove it after use
323 };
324 };
325
326 Util.make_properties = function (constructor, arr) {
327 "use strict";
328 for (var i = 0; i < arr.length; i++) {
329 Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
330 }
331 };
332
333 Util.set_defaults = function (obj, conf, defaults) {
334 var defaults_keys = Object.keys(defaults);
335 var conf_keys = Object.keys(conf);
336 var keys_obj = {};
337 var i;
338 for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
339 for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
340 var keys = Object.keys(keys_obj);
341
342 for (i = 0; i < keys.length; i++) {
343 var setter = obj['_raw_set_' + keys[i]];
344 if (!setter) {
345 Util.Warn('Invalid property ' + keys[i]);
346 continue;
347 }
348
349 if (conf[keys[i]]) {
350 setter.call(obj, conf[keys[i]]);
351 } else {
352 setter.call(obj, defaults[keys[i]]);
353 }
354 }
355 };
356
357 /*
358 * Decode from UTF-8
359 */
360 Util.decodeUTF8 = function (utf8string) {
361 "use strict";
362 return decodeURIComponent(escape(utf8string));
363 };
364
365
366
367 /*
368 * Cross-browser routines
369 */
370
371
372 // Dynamically load scripts without using document.write()
373 // Reference: http://unixpapa.com/js/dyna.html
374 //
375 // Handles the case where load_scripts is invoked from a script that
376 // itself is loaded via load_scripts. Once all scripts are loaded the
377 // window.onscriptsloaded handler is called (if set).
378 Util.get_include_uri = function () {
379 return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
380 };
381 Util._loading_scripts = [];
382 Util._pending_scripts = [];
383 Util.load_scripts = function (files) {
384 "use strict";
385 var head = document.getElementsByTagName('head')[0], script,
386 ls = Util._loading_scripts, ps = Util._pending_scripts;
387
388 var loadFunc = function (e) {
389 while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
390 ls[0].readyState === 'complete')) {
391 // For IE, append the script to trigger execution
392 var s = ls.shift();
393 //console.log("loaded script: " + s.src);
394 head.appendChild(s);
395 }
396 if (!this.readyState ||
397 (Util.Engine.presto && this.readyState === 'loaded') ||
398 this.readyState === 'complete') {
399 if (ps.indexOf(this) >= 0) {
400 this.onload = this.onreadystatechange = null;
401 //console.log("completed script: " + this.src);
402 ps.splice(ps.indexOf(this), 1);
403
404 // Call window.onscriptsload after last script loads
405 if (ps.length === 0 && window.onscriptsload) {
406 window.onscriptsload();
407 }
408 }
409 }
410 };
411
412 for (var f = 0; f < files.length; f++) {
413 script = document.createElement('script');
414 script.type = 'text/javascript';
415 script.src = Util.get_include_uri() + files[f];
416 //console.log("loading script: " + script.src);
417 script.onload = script.onreadystatechange = loadFunc;
418 // In-order script execution tricks
419 if (Util.Engine.trident) {
420 // For IE wait until readyState is 'loaded' before
421 // appending it which will trigger execution
422 // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
423 ls.push(script);
424 } else {
425 // For webkit and firefox set async=false and append now
426 // https://developer.mozilla.org/en-US/docs/HTML/Element/script
427 script.async = false;
428 head.appendChild(script);
429 }
430 ps.push(script);
431 }
432 };
433
434
435 // Get DOM element position on page
436 // This solution is based based on http://www.greywyvern.com/?post=331
437 // Thanks to Brian Huisman AKA GreyWyvern!
438 Util.getPosition = (function () {
439 "use strict";
440 function getStyle(obj, styleProp) {
441 var y;
442 if (obj.currentStyle) {
443 y = obj.currentStyle[styleProp];
444 } else if (window.getComputedStyle)
445 y = window.getComputedStyle(obj, null)[styleProp];
446 return y;
447 }
448
449 function scrollDist() {
450 var myScrollTop = 0, myScrollLeft = 0;
451 var html = document.getElementsByTagName('html')[0];
452
453 // get the scrollTop part
454 if (html.scrollTop && document.documentElement.scrollTop) {
455 myScrollTop = html.scrollTop;
456 } else if (html.scrollTop || document.documentElement.scrollTop) {
457 myScrollTop = html.scrollTop + document.documentElement.scrollTop;
458 } else if (document.body.scrollTop) {
459 myScrollTop = document.body.scrollTop;
460 } else {
461 myScrollTop = 0;
462 }
463
464 // get the scrollLeft part
465 if (html.scrollLeft && document.documentElement.scrollLeft) {
466 myScrollLeft = html.scrollLeft;
467 } else if (html.scrollLeft || document.documentElement.scrollLeft) {
468 myScrollLeft = html.scrollLeft + document.documentElement.scrollLeft;
469 } else if (document.body.scrollLeft) {
470 myScrollLeft = document.body.scrollLeft;
471 } else {
472 myScrollLeft = 0;
473 }
474
475 return [myScrollLeft, myScrollTop];
476 }
477
478 return function (obj) {
479 var curleft = 0, curtop = 0, scr = obj, fixed = false;
480 while ((scr = scr.parentNode) && scr != document.body) {
481 curleft -= scr.scrollLeft || 0;
482 curtop -= scr.scrollTop || 0;
483 if (getStyle(scr, "position") == "fixed") {
484 fixed = true;
485 }
486 }
487 if (fixed && !window.opera) {
488 var scrDist = scrollDist();
489 curleft += scrDist[0];
490 curtop += scrDist[1];
491 }
492
493 do {
494 curleft += obj.offsetLeft;
495 curtop += obj.offsetTop;
496 } while ((obj = obj.offsetParent));
497
498 return {'x': curleft, 'y': curtop};
499 };
500 })();
501
502
503 // Get mouse event position in DOM element
504 Util.getEventPosition = function (e, obj, scale) {
505 "use strict";
506 var evt, docX, docY, pos;
507 //if (!e) evt = window.event;
508 evt = (e ? e : window.event);
509 evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
510 if (evt.pageX || evt.pageY) {
511 docX = evt.pageX;
512 docY = evt.pageY;
513 } else if (evt.clientX || evt.clientY) {
514 docX = evt.clientX + document.body.scrollLeft +
515 document.documentElement.scrollLeft;
516 docY = evt.clientY + document.body.scrollTop +
517 document.documentElement.scrollTop;
518 }
519 pos = Util.getPosition(obj);
520 if (typeof scale === "undefined") {
521 scale = 1;
522 }
523 var realx = docX - pos.x;
524 var realy = docY - pos.y;
525 var x = Math.max(Math.min(realx, obj.width - 1), 0);
526 var y = Math.max(Math.min(realy, obj.height - 1), 0);
527 return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
528 };
529
530
531 // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
532 Util.addEvent = function (obj, evType, fn) {
533 "use strict";
534 if (obj.attachEvent) {
535 var r = obj.attachEvent("on" + evType, fn);
536 return r;
537 } else if (obj.addEventListener) {
538 obj.addEventListener(evType, fn, false);
539 return true;
540 } else {
541 throw new Error("Handler could not be attached");
542 }
543 };
544
545 Util.removeEvent = function (obj, evType, fn) {
546 "use strict";
547 if (obj.detachEvent) {
548 var r = obj.detachEvent("on" + evType, fn);
549 return r;
550 } else if (obj.removeEventListener) {
551 obj.removeEventListener(evType, fn, false);
552 return true;
553 } else {
554 throw new Error("Handler could not be removed");
555 }
556 };
557
558 Util.stopEvent = function (e) {
559 "use strict";
560 if (e.stopPropagation) { e.stopPropagation(); }
561 else { e.cancelBubble = true; }
562
563 if (e.preventDefault) { e.preventDefault(); }
564 else { e.returnValue = false; }
565 };
566
567
568 // Set browser engine versions. Based on mootools.
569 Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
570
571 (function () {
572 "use strict";
573 // 'presto': (function () { return (!window.opera) ? false : true; }()),
574 var detectPresto = function () {
575 return !!window.opera;
576 };
577
578 // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
579 var detectTrident = function () {
580 if (!window.ActiveXObject) {
581 return false;
582 } else {
583 if (window.XMLHttpRequest) {
584 return (document.querySelectorAll) ? 6 : 5;
585 } else {
586 return 4;
587 }
588 }
589 };
590
591 // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
592 var detectInitialWebkit = function () {
593 try {
594 if (navigator.taintEnabled) {
595 return false;
596 } else {
597 if (Util.Features.xpath) {
598 return (Util.Features.query) ? 525 : 420;
599 } else {
600 return 419;
601 }
602 }
603 } catch (e) {
604 return false;
605 }
606 };
607
608 var detectActualWebkit = function (initial_ver) {
609 var re = /WebKit\/([0-9\.]*) /;
610 var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
611 return parseFloat(str_ver, 10);
612 };
613
614 // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
615 var detectGecko = function () {
616 /* jshint -W041 */
617 if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
618 return false;
619 } else {
620 return (document.getElementsByClassName) ? 19 : 18;
621 }
622 /* jshint +W041 */
623 };
624
625 Util.Engine = {
626 // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
627 //'presto': (function() {
628 // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
629 'presto': detectPresto(),
630 'trident': detectTrident(),
631 'webkit': detectInitialWebkit(),
632 'gecko': detectGecko(),
633 };
634
635 if (Util.Engine.webkit) {
636 // Extract actual webkit version if available
637 Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit);
638 }
639 })();
640
641 Util.Flash = (function () {
642 "use strict";
643 var v, version;
644 try {
645 v = navigator.plugins['Shockwave Flash'].description;
646 } catch (err1) {
647 try {
648 v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
649 } catch (err2) {
650 v = '0 r0';
651 }
652 }
653 version = v.match(/\d+/g);
654 return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
655 }());