2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Licensed under MPL 2.0 (see LICENSE.txt)
6 * See README.md for usage and integration instructions.
9 /* jshint white: false, nonstandard: true */
10 /*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */
12 // Globals defined here
20 Array
.prototype.push8 = function (num
) {
22 this.push(num
& 0xFF);
25 Array
.prototype.push16 = function (num
) {
27 this.push((num
>> 8) & 0xFF,
30 Array
.prototype.push32 = function (num
) {
32 this.push((num
>> 24) & 0xFF,
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*/) {
45 var len
= this.length
;
46 if (typeof fun
!= "function") {
47 throw new TypeError();
50 var res
= new Array(len
);
51 var thisp
= arguments
[1];
52 for (var i
= 0; i
< len
; i
++) {
54 res
[i
] = fun
.call(thisp
, this[i
], i
, this);
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*/) {
69 var len
= this.length
>>> 0;
71 var from = Number(arguments
[1]) || 0;
72 from = (from < 0) ? Math
.ceil(from) : Math
.floor(from);
77 for (; from < len
; from++) {
87 // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
89 Object
.keys
= (function () {
91 var hasOwnProperty
= Object
.prototype.hasOwnProperty
,
92 hasDontEnumBug
= !({toString
: null}).propertyIsEnumerable('toString'),
99 'propertyIsEnumerable',
102 dontEnumsLength
= dontEnums
.length
;
104 return function (obj
) {
105 if (typeof obj
!== 'object' && (typeof obj
!== 'function' || obj
=== null)) {
106 throw new TypeError('Object.keys called on non-object');
109 var result
= [], prop
, i
;
112 if (hasOwnProperty
.call(obj
, prop
)) {
117 if (hasDontEnumBug
) {
118 for (i
= 0; i
< dontEnumsLength
; i
++) {
119 if (hasOwnProperty
.call(obj
, dontEnums
[i
])) {
120 result
.push(dontEnums
[i
]);
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");
143 var aArgs
= Array
.prototype.slice
.call(arguments
, 1),
145 fNOP = function () {},
146 fBound = function () {
147 return fToBind
.apply(this instanceof fNOP
&& oThis
? this
149 aArgs
.concat(Array
.prototype.slice
.call(arguments
)));
152 fNOP
.prototype = this.prototype;
153 fBound
.prototype = new fNOP();
160 // requestAnimationFrame shim with setTimeout fallback
163 window
.requestAnimFrame
= (function () {
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);
176 * ------------------------------------------------------
178 * ------------------------------------------------------
182 * Logging/debug routines
185 Util
._log_level
= 'warn';
186 Util
.init_logging = function (level
) {
188 if (typeof level
=== 'undefined') {
189 level
= Util
._log_level
;
191 Util
._log_level
= level
;
193 if (typeof window
.console
=== "undefined") {
194 if (typeof window
.opera
!== "undefined") {
196 'log' : window
.opera
.postError
,
197 'warn' : window
.opera
.postError
,
198 'error': window
.opera
.postError
202 'log' : function (m
) {},
203 'warn' : function (m
) {},
204 'error': function (m
) {}
209 Util
.Debug
= Util
.Info
= Util
.Warn
= Util
.Error = function (msg
) {};
213 Util
.Debug = function (msg
) { console
.log(msg
); };
215 Util
.Info = function (msg
) { console
.log(msg
); };
217 Util
.Warn = function (msg
) { console
.warn(msg
); };
219 Util
.Error = function (msg
) { console
.error(msg
); };
223 throw new Error("invalid logging type '" + level
+ "'");
227 Util
.get_logging = function () {
228 return Util
._log_level
;
230 // Initialize logging level
233 Util
.make_property = function (proto
, name
, mode
, type
) {
237 if (type
=== 'arr') {
238 getter = function (idx
) {
239 if (typeof idx
!== 'undefined') {
240 return this['_' + name
][idx
];
242 return this['_' + name
];
246 getter = function () {
247 return this['_' + name
];
251 var make_setter = function (process_val
) {
253 return function (val
, idx
) {
254 if (typeof idx
!== 'undefined') {
255 this['_' + name
][idx
] = process_val(val
);
257 this['_' + name
] = process_val(val
);
261 return function (val
, idx
) {
262 if (typeof idx
!== 'undefined') {
263 this['_' + name
][idx
] = val
;
265 this['_' + name
] = val
;
272 if (type
=== 'bool') {
273 setter
= make_setter(function (val
) {
274 if (!val
|| (val
in {'0': 1, 'no': 1, 'false': 1})) {
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
) {
289 return function () {};
294 } else if (type
=== 'arr' || type
=== 'dom' || type
== 'raw') {
295 setter
= make_setter();
297 throw new Error('Unknown property type ' + type
); // some sanity checking
301 if (typeof proto
['get_' + name
] === 'undefined') {
302 proto
['get_' + name
] = getter
;
305 // set the setter if needed
306 if (typeof proto
['set_' + name
] === 'undefined') {
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");
314 setter
.call(this, val
, idx
);
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
326 Util
.make_properties = function (constructor, arr
) {
328 for (var i
= 0; i
< arr
.length
; i
++) {
329 Util
.make_property(constructor.prototype, arr
[i
][0], arr
[i
][1], arr
[i
][2]);
333 Util
.set_defaults = function (obj
, conf
, defaults
) {
334 var defaults_keys
= Object
.keys(defaults
);
335 var conf_keys
= Object
.keys(conf
);
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
);
342 for (i
= 0; i
< keys
.length
; i
++) {
343 var setter
= obj
['_raw_set_' + keys
[i
]];
345 Util
.Warn('Invalid property ' + keys
[i
]);
350 setter
.call(obj
, conf
[keys
[i
]]);
352 setter
.call(obj
, defaults
[keys
[i
]]);
360 Util
.decodeUTF8 = function (utf8string
) {
362 return decodeURIComponent(escape(utf8string
));
368 * Cross-browser routines
372 // Dynamically load scripts without using document.write()
373 // Reference: http://unixpapa.com/js/dyna.html
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/";
381 Util
._loading_scripts
= [];
382 Util
._pending_scripts
= [];
383 Util
.load_scripts = function (files
) {
385 var head
= document
.getElementsByTagName('head')[0], script
,
386 ls
= Util
._loading_scripts
, ps
= Util
._pending_scripts
;
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
393 //console.log("loaded script: " + s.src);
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);
404 // Call window.onscriptsload after last script loads
405 if (ps
.length
=== 0 && window
.onscriptsload
) {
406 window
.onscriptsload();
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
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
);
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 () {
440 function getStyle(obj
, styleProp
) {
442 if (obj
.currentStyle
) {
443 y
= obj
.currentStyle
[styleProp
];
444 } else if (window
.getComputedStyle
)
445 y
= window
.getComputedStyle(obj
, null)[styleProp
];
449 function scrollDist() {
450 var myScrollTop
= 0, myScrollLeft
= 0;
451 var html
= document
.getElementsByTagName('html')[0];
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
;
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
;
475 return [myScrollLeft
, myScrollTop
];
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") {
487 if (fixed
&& !window
.opera
) {
488 var scrDist
= scrollDist();
489 curleft
+= scrDist
[0];
490 curtop
+= scrDist
[1];
494 curleft
+= obj
.offsetLeft
;
495 curtop
+= obj
.offsetTop
;
496 } while ((obj
= obj
.offsetParent
));
498 return {'x': curleft
, 'y': curtop
};
503 // Get mouse event position in DOM element
504 Util
.getEventPosition = function (e
, obj
, scale
) {
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
) {
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
;
519 pos
= Util
.getPosition(obj
);
520 if (typeof scale
=== "undefined") {
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
};
531 // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
532 Util
.addEvent = function (obj
, evType
, fn
) {
534 if (obj
.attachEvent
) {
535 var r
= obj
.attachEvent("on" + evType
, fn
);
537 } else if (obj
.addEventListener
) {
538 obj
.addEventListener(evType
, fn
, false);
541 throw new Error("Handler could not be attached");
545 Util
.removeEvent = function (obj
, evType
, fn
) {
547 if (obj
.detachEvent
) {
548 var r
= obj
.detachEvent("on" + evType
, fn
);
550 } else if (obj
.removeEventListener
) {
551 obj
.removeEventListener(evType
, fn
, false);
554 throw new Error("Handler could not be removed");
558 Util
.stopEvent = function (e
) {
560 if (e
.stopPropagation
) { e
.stopPropagation(); }
561 else { e
.cancelBubble
= true; }
563 if (e
.preventDefault
) { e
.preventDefault(); }
564 else { e
.returnValue
= false; }
568 // Set browser engine versions. Based on mootools.
569 Util
.Features
= {xpath
: !!(document
.evaluate
), air
: !!(window
.runtime
), query
: !!(document
.querySelector
)};
573 // 'presto': (function () { return (!window.opera) ? false : true; }()),
574 var detectPresto = function () {
575 return !!window
.opera
;
578 // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
579 var detectTrident = function () {
580 if (!window
.ActiveXObject
) {
583 if (window
.XMLHttpRequest
) {
584 return (document
.querySelectorAll
) ? 6 : 5;
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 () {
594 if (navigator
.taintEnabled
) {
597 if (Util
.Features
.xpath
) {
598 return (Util
.Features
.query
) ? 525 : 420;
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);
614 // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
615 var detectGecko = function () {
617 if (!document
.getBoxObjectFor
&& window
.mozInnerScreenX
== null) {
620 return (document
.getElementsByClassName
) ? 19 : 18;
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(),
635 if (Util
.Engine
.webkit
) {
636 // Extract actual webkit version if available
637 Util
.Engine
.webkit
= detectActualWebkit(Util
.Engine
.webkit
);
641 Util
.Flash
= (function () {
645 v
= navigator
.plugins
['Shockwave Flash'].description
;
648 v
= new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
653 version
= v
.match(/\d+/g);
654 return {version
: parseInt(version
[0] || 0 + '.' + version
[1], 10) || 0, build
: parseInt(version
[2], 10) || 0};