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 */
15 // requestAnimationFrame shim with setTimeout fallback
18 window
.requestAnimFrame
= (function () {
20 return window
.requestAnimationFrame
||
21 window
.webkitRequestAnimationFrame
||
22 window
.mozRequestAnimationFrame
||
23 window
.oRequestAnimationFrame
||
24 window
.msRequestAnimationFrame
||
26 window
.setTimeout(callback
, 1000 / 60);
31 * ------------------------------------------------------
33 * ------------------------------------------------------
37 * Logging/debug routines
40 Util
._log_level
= 'warn';
41 Util
.init_logging = function (level
) {
43 if (typeof level
=== 'undefined') {
44 level
= Util
._log_level
;
46 Util
._log_level
= level
;
48 if (typeof window
.console
=== "undefined") {
49 if (typeof window
.opera
!== "undefined") {
51 'log' : window
.opera
.postError
,
52 'warn' : window
.opera
.postError
,
53 'error': window
.opera
.postError
57 'log' : function (m
) {},
58 'warn' : function (m
) {},
59 'error': function (m
) {}
64 Util
.Debug
= Util
.Info
= Util
.Warn
= Util
.Error = function (msg
) {};
68 Util
.Debug = function (msg
) { console
.log(msg
); };
70 Util
.Info = function (msg
) { console
.log(msg
); };
72 Util
.Warn = function (msg
) { console
.warn(msg
); };
74 Util
.Error = function (msg
) { console
.error(msg
); };
78 throw new Error("invalid logging type '" + level
+ "'");
82 Util
.get_logging = function () {
83 return Util
._log_level
;
85 // Initialize logging level
88 Util
.make_property = function (proto
, name
, mode
, type
) {
93 getter = function (idx
) {
94 if (typeof idx
!== 'undefined') {
95 return this['_' + name
][idx
];
97 return this['_' + name
];
101 getter = function () {
102 return this['_' + name
];
106 var make_setter = function (process_val
) {
108 return function (val
, idx
) {
109 if (typeof idx
!== 'undefined') {
110 this['_' + name
][idx
] = process_val(val
);
112 this['_' + name
] = process_val(val
);
116 return function (val
, idx
) {
117 if (typeof idx
!== 'undefined') {
118 this['_' + name
][idx
] = val
;
120 this['_' + name
] = val
;
127 if (type
=== 'bool') {
128 setter
= make_setter(function (val
) {
129 if (!val
|| (val
in {'0': 1, 'no': 1, 'false': 1})) {
135 } else if (type
=== 'int') {
136 setter
= make_setter(function (val
) { return parseInt(val
, 10); });
137 } else if (type
=== 'float') {
138 setter
= make_setter(parseFloat
);
139 } else if (type
=== 'str') {
140 setter
= make_setter(String
);
141 } else if (type
=== 'func') {
142 setter
= make_setter(function (val
) {
144 return function () {};
149 } else if (type
=== 'arr' || type
=== 'dom' || type
== 'raw') {
150 setter
= make_setter();
152 throw new Error('Unknown property type ' + type
); // some sanity checking
156 if (typeof proto
['get_' + name
] === 'undefined') {
157 proto
['get_' + name
] = getter
;
160 // set the setter if needed
161 if (typeof proto
['set_' + name
] === 'undefined') {
163 proto
['set_' + name
] = setter
;
164 } else if (mode
=== 'wo') {
165 proto
['set_' + name
] = function (val
, idx
) {
166 if (typeof this['_' + name
] !== 'undefined') {
167 throw new Error(name
+ " can only be set once");
169 setter
.call(this, val
, idx
);
174 // make a special setter that we can use in set defaults
175 proto
['_raw_set_' + name
] = function (val
, idx
) {
176 setter
.call(this, val
, idx
);
177 //delete this['_init_set_' + name]; // remove it after use
181 Util
.make_properties = function (constructor, arr
) {
183 for (var i
= 0; i
< arr
.length
; i
++) {
184 Util
.make_property(constructor.prototype, arr
[i
][0], arr
[i
][1], arr
[i
][2]);
188 Util
.set_defaults = function (obj
, conf
, defaults
) {
189 var defaults_keys
= Object
.keys(defaults
);
190 var conf_keys
= Object
.keys(conf
);
193 for (i
= 0; i
< defaults_keys
.length
; i
++) { keys_obj
[defaults_keys
[i
]] = 1; }
194 for (i
= 0; i
< conf_keys
.length
; i
++) { keys_obj
[conf_keys
[i
]] = 1; }
195 var keys
= Object
.keys(keys_obj
);
197 for (i
= 0; i
< keys
.length
; i
++) {
198 var setter
= obj
['_raw_set_' + keys
[i
]];
200 Util
.Warn('Invalid property ' + keys
[i
]);
204 if (keys
[i
] in conf
) {
205 setter
.call(obj
, conf
[keys
[i
]]);
207 setter
.call(obj
, defaults
[keys
[i
]]);
215 Util
.decodeUTF8 = function (utf8string
) {
217 return decodeURIComponent(escape(utf8string
));
223 * Cross-browser routines
226 Util
.getPosition = function(obj
) {
228 // NB(sross): the Mozilla developer reference seems to indicate that
229 // getBoundingClientRect includes border and padding, so the canvas
230 // style should NOT include either.
231 var objPosition
= obj
.getBoundingClientRect();
232 return {'x': objPosition
.left
+ window
.pageXOffset
, 'y': objPosition
.top
+ window
.pageYOffset
,
233 'width': objPosition
.width
, 'height': objPosition
.height
};
237 // Get mouse event position in DOM element
238 Util
.getEventPosition = function (e
, obj
, scale
) {
240 var evt
, docX
, docY
, pos
;
241 //if (!e) evt = window.event;
242 evt
= (e
? e
: window
.event
);
243 evt
= (evt
.changedTouches
? evt
.changedTouches
[0] : evt
.touches
? evt
.touches
[0] : evt
);
244 if (evt
.pageX
|| evt
.pageY
) {
247 } else if (evt
.clientX
|| evt
.clientY
) {
248 docX
= evt
.clientX
+ document
.body
.scrollLeft
+
249 document
.documentElement
.scrollLeft
;
250 docY
= evt
.clientY
+ document
.body
.scrollTop
+
251 document
.documentElement
.scrollTop
;
253 pos
= Util
.getPosition(obj
);
254 if (typeof scale
=== "undefined") {
257 var realx
= docX
- pos
.x
;
258 var realy
= docY
- pos
.y
;
259 var x
= Math
.max(Math
.min(realx
, pos
.width
- 1), 0);
260 var y
= Math
.max(Math
.min(realy
, pos
.height
- 1), 0);
261 return {'x': x
/ scale
, 'y': y
/ scale
, 'realx': realx
/ scale
, 'realy': realy
/ scale
};
265 // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
266 Util
.addEvent = function (obj
, evType
, fn
) {
268 if (obj
.attachEvent
) {
269 var r
= obj
.attachEvent("on" + evType
, fn
);
271 } else if (obj
.addEventListener
) {
272 obj
.addEventListener(evType
, fn
, false);
275 throw new Error("Handler could not be attached");
279 Util
.removeEvent = function (obj
, evType
, fn
) {
281 if (obj
.detachEvent
) {
282 var r
= obj
.detachEvent("on" + evType
, fn
);
284 } else if (obj
.removeEventListener
) {
285 obj
.removeEventListener(evType
, fn
, false);
288 throw new Error("Handler could not be removed");
292 Util
.stopEvent = function (e
) {
294 if (e
.stopPropagation
) { e
.stopPropagation(); }
295 else { e
.cancelBubble
= true; }
297 if (e
.preventDefault
) { e
.preventDefault(); }
298 else { e
.returnValue
= false; }
301 Util
._cursor_uris_supported
= null;
303 Util
.browserSupportsCursorURIs = function () {
304 if (Util
._cursor_uris_supported
=== null) {
306 var target
= document
.createElement('canvas');
307 target
.style
.cursor
= 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
309 if (target
.style
.cursor
) {
310 Util
.Info("Data URI scheme cursor supported");
311 Util
._cursor_uris_supported
= true;
313 Util
.Warn("Data URI scheme cursor not supported");
314 Util
._cursor_uris_supported
= false;
317 Util
.Error("Data URI scheme cursor test exception: " + exc
);
318 Util
._cursor_uris_supported
= false;
322 return Util
._cursor_uris_supported
;
325 // Set browser engine versions. Based on mootools.
326 Util
.Features
= {xpath
: !!(document
.evaluate
), air
: !!(window
.runtime
), query
: !!(document
.querySelector
)};
330 // 'presto': (function () { return (!window.opera) ? false : true; }()),
331 var detectPresto = function () {
332 return !!window
.opera
;
335 // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
336 var detectTrident = function () {
337 if (!window
.ActiveXObject
) {
340 if (window
.XMLHttpRequest
) {
341 return (document
.querySelectorAll
) ? 6 : 5;
348 // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
349 var detectInitialWebkit = function () {
351 if (navigator
.taintEnabled
) {
354 if (Util
.Features
.xpath
) {
355 return (Util
.Features
.query
) ? 525 : 420;
365 var detectActualWebkit = function (initial_ver
) {
366 var re
= /WebKit\/([0-9\.]*) /;
367 var str_ver
= (navigator
.userAgent
.match(re
) || ['', initial_ver
])[1];
368 return parseFloat(str_ver
, 10);
371 // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
372 var detectGecko = function () {
374 if (!document
.getBoxObjectFor
&& window
.mozInnerScreenX
== null) {
377 return (document
.getElementsByClassName
) ? 19 : 18;
383 // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
384 //'presto': (function() {
385 // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
386 'presto': detectPresto(),
387 'trident': detectTrident(),
388 'webkit': detectInitialWebkit(),
389 'gecko': detectGecko(),
392 if (Util
.Engine
.webkit
) {
393 // Extract actual webkit version if available
394 Util
.Engine
.webkit
= detectActualWebkit(Util
.Engine
.webkit
);
398 Util
.Flash
= (function () {
402 v
= navigator
.plugins
['Shockwave Flash'].description
;
405 v
= new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
410 version
= v
.match(/\d+/g);
411 return {version
: parseInt(version
[0] || 0 + '.' + version
[1], 10) || 0, build
: parseInt(version
[2], 10) || 0};
414 /* [module] export default Util; */