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 var addFunc = function (cl
, name
, func
) {
21 if (!cl
.prototype[name
]) {
22 Object
.defineProperty(cl
.prototype, name
, { enumerable
: false, value
: func
});
26 addFunc(Array
, 'push8', function (num
) {
28 this.push(num
& 0xFF);
31 addFunc(Array
, 'push16', function (num
) {
33 this.push((num
>> 8) & 0xFF,
37 addFunc(Array
, 'push32', function (num
) {
39 this.push((num
>> 24) & 0xFF,
45 // IE does not support map (even in IE9)
46 //This prototype is provided by the Mozilla foundation and
47 //is distributed under the MIT license.
48 //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
49 addFunc(Array
, 'map', function (fun
/*, thisp*/) {
51 var len
= this.length
;
52 if (typeof fun
!= "function") {
53 throw new TypeError();
56 var res
= new Array(len
);
57 var thisp
= arguments
[1];
58 for (var i
= 0; i
< len
; i
++) {
60 res
[i
] = fun
.call(thisp
, this[i
], i
, this);
67 // IE <9 does not support indexOf
68 //This prototype is provided by the Mozilla foundation and
69 //is distributed under the MIT license.
70 //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
71 addFunc(Array
, 'indexOf', function (elt
/*, from*/) {
73 var len
= this.length
>>> 0;
75 var from = Number(arguments
[1]) || 0;
76 from = (from < 0) ? Math
.ceil(from) : Math
.floor(from);
81 for (; from < len
; from++) {
90 // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
92 Object
.keys
= (function () {
94 var hasOwnProperty
= Object
.prototype.hasOwnProperty
,
95 hasDontEnumBug
= !({toString
: null}).propertyIsEnumerable('toString'),
102 'propertyIsEnumerable',
105 dontEnumsLength
= dontEnums
.length
;
107 return function (obj
) {
108 if (typeof obj
!== 'object' && (typeof obj
!== 'function' || obj
=== null)) {
109 throw new TypeError('Object.keys called on non-object');
112 var result
= [], prop
, i
;
115 if (hasOwnProperty
.call(obj
, prop
)) {
120 if (hasDontEnumBug
) {
121 for (i
= 0; i
< dontEnumsLength
; i
++) {
122 if (hasOwnProperty
.call(obj
, dontEnums
[i
])) {
123 result
.push(dontEnums
[i
]);
132 // PhantomJS 1.x doesn't support bind,
133 // so leave this in until PhantomJS 2.0 is released
134 //This prototype is provided by the Mozilla foundation and
135 //is distributed under the MIT license.
136 //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
137 addFunc(Function
, 'bind', function (oThis
) {
138 if (typeof this !== "function") {
139 // closest thing possible to the ECMAScript 5
140 // internal IsCallable function
141 throw new TypeError("Function.prototype.bind - " +
142 "what is trying to be bound is not callable");
145 var aArgs
= Array
.prototype.slice
.call(arguments
, 1),
147 fNOP = function () {},
148 fBound = function () {
149 return fToBind
.apply(this instanceof fNOP
&& oThis
? this
151 aArgs
.concat(Array
.prototype.slice
.call(arguments
)));
154 fNOP
.prototype = this.prototype;
155 fBound
.prototype = new fNOP();
161 // requestAnimationFrame shim with setTimeout fallback
164 window
.requestAnimFrame
= (function () {
166 return window
.requestAnimationFrame
||
167 window
.webkitRequestAnimationFrame
||
168 window
.mozRequestAnimationFrame
||
169 window
.oRequestAnimationFrame
||
170 window
.msRequestAnimationFrame
||
171 function (callback
) {
172 window
.setTimeout(callback
, 1000 / 60);
177 * ------------------------------------------------------
179 * ------------------------------------------------------
183 * Logging/debug routines
186 Util
._log_level
= 'warn';
187 Util
.init_logging = function (level
) {
189 if (typeof level
=== 'undefined') {
190 level
= Util
._log_level
;
192 Util
._log_level
= level
;
194 if (typeof window
.console
=== "undefined") {
195 if (typeof window
.opera
!== "undefined") {
197 'log' : window
.opera
.postError
,
198 'warn' : window
.opera
.postError
,
199 'error': window
.opera
.postError
203 'log' : function (m
) {},
204 'warn' : function (m
) {},
205 'error': function (m
) {}
210 Util
.Debug
= Util
.Info
= Util
.Warn
= Util
.Error = function (msg
) {};
214 Util
.Debug = function (msg
) { console
.log(msg
); };
216 Util
.Info = function (msg
) { console
.log(msg
); };
218 Util
.Warn = function (msg
) { console
.warn(msg
); };
220 Util
.Error = function (msg
) { console
.error(msg
); };
224 throw new Error("invalid logging type '" + level
+ "'");
228 Util
.get_logging = function () {
229 return Util
._log_level
;
231 // Initialize logging level
234 Util
.make_property = function (proto
, name
, mode
, type
) {
238 if (type
=== 'arr') {
239 getter = function (idx
) {
240 if (typeof idx
!== 'undefined') {
241 return this['_' + name
][idx
];
243 return this['_' + name
];
247 getter = function () {
248 return this['_' + name
];
252 var make_setter = function (process_val
) {
254 return function (val
, idx
) {
255 if (typeof idx
!== 'undefined') {
256 this['_' + name
][idx
] = process_val(val
);
258 this['_' + name
] = process_val(val
);
262 return function (val
, idx
) {
263 if (typeof idx
!== 'undefined') {
264 this['_' + name
][idx
] = val
;
266 this['_' + name
] = val
;
273 if (type
=== 'bool') {
274 setter
= make_setter(function (val
) {
275 if (!val
|| (val
in {'0': 1, 'no': 1, 'false': 1})) {
281 } else if (type
=== 'int') {
282 setter
= make_setter(function (val
) { return parseInt(val
, 10); });
283 } else if (type
=== 'float') {
284 setter
= make_setter(parseFloat
);
285 } else if (type
=== 'str') {
286 setter
= make_setter(String
);
287 } else if (type
=== 'func') {
288 setter
= make_setter(function (val
) {
290 return function () {};
295 } else if (type
=== 'arr' || type
=== 'dom' || type
== 'raw') {
296 setter
= make_setter();
298 throw new Error('Unknown property type ' + type
); // some sanity checking
302 if (typeof proto
['get_' + name
] === 'undefined') {
303 proto
['get_' + name
] = getter
;
306 // set the setter if needed
307 if (typeof proto
['set_' + name
] === 'undefined') {
309 proto
['set_' + name
] = setter
;
310 } else if (mode
=== 'wo') {
311 proto
['set_' + name
] = function (val
, idx
) {
312 if (typeof this['_' + name
] !== 'undefined') {
313 throw new Error(name
+ " can only be set once");
315 setter
.call(this, val
, idx
);
320 // make a special setter that we can use in set defaults
321 proto
['_raw_set_' + name
] = function (val
, idx
) {
322 setter
.call(this, val
, idx
);
323 //delete this['_init_set_' + name]; // remove it after use
327 Util
.make_properties = function (constructor, arr
) {
329 for (var i
= 0; i
< arr
.length
; i
++) {
330 Util
.make_property(constructor.prototype, arr
[i
][0], arr
[i
][1], arr
[i
][2]);
334 Util
.set_defaults = function (obj
, conf
, defaults
) {
335 var defaults_keys
= Object
.keys(defaults
);
336 var conf_keys
= Object
.keys(conf
);
339 for (i
= 0; i
< defaults_keys
.length
; i
++) { keys_obj
[defaults_keys
[i
]] = 1; }
340 for (i
= 0; i
< conf_keys
.length
; i
++) { keys_obj
[conf_keys
[i
]] = 1; }
341 var keys
= Object
.keys(keys_obj
);
343 for (i
= 0; i
< keys
.length
; i
++) {
344 var setter
= obj
['_raw_set_' + keys
[i
]];
346 Util
.Warn('Invalid property ' + keys
[i
]);
350 if (keys
[i
] in conf
) {
351 setter
.call(obj
, conf
[keys
[i
]]);
353 setter
.call(obj
, defaults
[keys
[i
]]);
361 Util
.decodeUTF8 = function (utf8string
) {
363 return decodeURIComponent(escape(utf8string
));
369 * Cross-browser routines
373 // Dynamically load scripts without using document.write()
374 // Reference: http://unixpapa.com/js/dyna.html
376 // Handles the case where load_scripts is invoked from a script that
377 // itself is loaded via load_scripts. Once all scripts are loaded the
378 // window.onscriptsloaded handler is called (if set).
379 Util
.get_include_uri = function () {
380 return (typeof INCLUDE_URI
!== "undefined") ? INCLUDE_URI
: "include/";
382 Util
._loading_scripts
= [];
383 Util
._pending_scripts
= [];
384 Util
.load_scripts = function (files
) {
386 var head
= document
.getElementsByTagName('head')[0], script
,
387 ls
= Util
._loading_scripts
, ps
= Util
._pending_scripts
;
389 var loadFunc = function (e
) {
390 while (ls
.length
> 0 && (ls
[0].readyState
=== 'loaded' ||
391 ls
[0].readyState
=== 'complete')) {
392 // For IE, append the script to trigger execution
394 //console.log("loaded script: " + s.src);
397 if (!this.readyState
||
398 (Util
.Engine
.presto
&& this.readyState
=== 'loaded') ||
399 this.readyState
=== 'complete') {
400 if (ps
.indexOf(this) >= 0) {
401 this.onload
= this.onreadystatechange
= null;
402 //console.log("completed script: " + this.src);
403 ps
.splice(ps
.indexOf(this), 1);
405 // Call window.onscriptsload after last script loads
406 if (ps
.length
=== 0 && window
.onscriptsload
) {
407 window
.onscriptsload();
413 for (var f
= 0; f
< files
.length
; f
++) {
414 script
= document
.createElement('script');
415 script
.type
= 'text/javascript';
416 script
.src
= Util
.get_include_uri() + files
[f
];
417 //console.log("loading script: " + script.src);
418 script
.onload
= script
.onreadystatechange
= loadFunc
;
419 // In-order script execution tricks
420 if (Util
.Engine
.trident
) {
421 // For IE wait until readyState is 'loaded' before
422 // appending it which will trigger execution
423 // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
426 // For webkit and firefox set async=false and append now
427 // https://developer.mozilla.org/en-US/docs/HTML/Element/script
428 script
.async
= false;
429 head
.appendChild(script
);
436 Util
.getPosition = function(obj
) {
438 var objPosition
= obj
.getBoundingClientRect();
439 return {'x': objPosition
.left
, 'y': objPosition
.top
};
443 // Get mouse event position in DOM element
444 Util
.getEventPosition = function (e
, obj
, scale
) {
446 var evt
, docX
, docY
, pos
;
447 //if (!e) evt = window.event;
448 evt
= (e
? e
: window
.event
);
449 evt
= (evt
.changedTouches
? evt
.changedTouches
[0] : evt
.touches
? evt
.touches
[0] : evt
);
450 if (evt
.pageX
|| evt
.pageY
) {
453 } else if (evt
.clientX
|| evt
.clientY
) {
454 docX
= evt
.clientX
+ document
.body
.scrollLeft
+
455 document
.documentElement
.scrollLeft
;
456 docY
= evt
.clientY
+ document
.body
.scrollTop
+
457 document
.documentElement
.scrollTop
;
459 pos
= Util
.getPosition(obj
);
460 if (typeof scale
=== "undefined") {
463 var realx
= docX
- pos
.x
;
464 var realy
= docY
- pos
.y
;
465 var x
= Math
.max(Math
.min(realx
, obj
.width
- 1), 0);
466 var y
= Math
.max(Math
.min(realy
, obj
.height
- 1), 0);
467 return {'x': x
/ scale
, 'y': y
/ scale
, 'realx': realx
/ scale
, 'realy': realy
/ scale
};
471 // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
472 Util
.addEvent = function (obj
, evType
, fn
) {
474 if (obj
.attachEvent
) {
475 var r
= obj
.attachEvent("on" + evType
, fn
);
477 } else if (obj
.addEventListener
) {
478 obj
.addEventListener(evType
, fn
, false);
481 throw new Error("Handler could not be attached");
485 Util
.removeEvent = function (obj
, evType
, fn
) {
487 if (obj
.detachEvent
) {
488 var r
= obj
.detachEvent("on" + evType
, fn
);
490 } else if (obj
.removeEventListener
) {
491 obj
.removeEventListener(evType
, fn
, false);
494 throw new Error("Handler could not be removed");
498 Util
.stopEvent = function (e
) {
500 if (e
.stopPropagation
) { e
.stopPropagation(); }
501 else { e
.cancelBubble
= true; }
503 if (e
.preventDefault
) { e
.preventDefault(); }
504 else { e
.returnValue
= false; }
508 // Set browser engine versions. Based on mootools.
509 Util
.Features
= {xpath
: !!(document
.evaluate
), air
: !!(window
.runtime
), query
: !!(document
.querySelector
)};
513 // 'presto': (function () { return (!window.opera) ? false : true; }()),
514 var detectPresto = function () {
515 return !!window
.opera
;
518 // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
519 var detectTrident = function () {
520 if (!window
.ActiveXObject
) {
523 if (window
.XMLHttpRequest
) {
524 return (document
.querySelectorAll
) ? 6 : 5;
531 // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
532 var detectInitialWebkit = function () {
534 if (navigator
.taintEnabled
) {
537 if (Util
.Features
.xpath
) {
538 return (Util
.Features
.query
) ? 525 : 420;
548 var detectActualWebkit = function (initial_ver
) {
549 var re
= /WebKit\/([0-9\.]*) /;
550 var str_ver
= (navigator
.userAgent
.match(re
) || ['', initial_ver
])[1];
551 return parseFloat(str_ver
, 10);
554 // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
555 var detectGecko = function () {
557 if (!document
.getBoxObjectFor
&& window
.mozInnerScreenX
== null) {
560 return (document
.getElementsByClassName
) ? 19 : 18;
566 // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
567 //'presto': (function() {
568 // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
569 'presto': detectPresto(),
570 'trident': detectTrident(),
571 'webkit': detectInitialWebkit(),
572 'gecko': detectGecko(),
575 if (Util
.Engine
.webkit
) {
576 // Extract actual webkit version if available
577 Util
.Engine
.webkit
= detectActualWebkit(Util
.Engine
.webkit
);
581 Util
.Flash
= (function () {
585 v
= navigator
.plugins
['Shockwave Flash'].description
;
588 v
= new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
593 version
= v
.match(/\d+/g);
594 return {version
: parseInt(version
[0] || 0 + '.' + version
[1], 10) || 0, build
: parseInt(version
[2], 10) || 0};