]>
git.proxmox.com Git - mirror_novnc.git/blob - core/util.js
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 * ------------------------------------------------------
17 * ------------------------------------------------------
21 * Logging/debug routines
24 Util
._log_level
= 'warn';
25 Util
.init_logging = function (level
) {
27 if (typeof level
=== 'undefined') {
28 level
= Util
._log_level
;
30 Util
._log_level
= level
;
33 Util
.Debug
= Util
.Info
= Util
.Warn
= Util
.Error = function (msg
) {};
34 if (typeof window
.console
!== "undefined") {
38 Util
.Debug
= console
.debug
.bind(window
.console
);
40 Util
.Info
= console
.info
.bind(window
.console
);
42 Util
.Warn
= console
.warn
.bind(window
.console
);
44 Util
.Error
= console
.error
.bind(window
.console
);
48 throw new Error("invalid logging type '" + level
+ "'");
53 Util
.get_logging = function () {
54 return Util
._log_level
;
56 // Initialize logging level
59 Util
.make_property = function (proto
, name
, mode
, type
) {
64 getter = function (idx
) {
65 if (typeof idx
!== 'undefined') {
66 return this['_' + name
][idx
];
68 return this['_' + name
];
72 getter = function () {
73 return this['_' + name
];
77 var make_setter = function (process_val
) {
79 return function (val
, idx
) {
80 if (typeof idx
!== 'undefined') {
81 this['_' + name
][idx
] = process_val(val
);
83 this['_' + name
] = process_val(val
);
87 return function (val
, idx
) {
88 if (typeof idx
!== 'undefined') {
89 this['_' + name
][idx
] = val
;
91 this['_' + name
] = val
;
98 if (type
=== 'bool') {
99 setter
= make_setter(function (val
) {
100 if (!val
|| (val
in {'0': 1, 'no': 1, 'false': 1})) {
106 } else if (type
=== 'int') {
107 setter
= make_setter(function (val
) { return parseInt(val
, 10); });
108 } else if (type
=== 'float') {
109 setter
= make_setter(parseFloat
);
110 } else if (type
=== 'str') {
111 setter
= make_setter(String
);
112 } else if (type
=== 'func') {
113 setter
= make_setter(function (val
) {
115 return function () {};
120 } else if (type
=== 'arr' || type
=== 'dom' || type
== 'raw') {
121 setter
= make_setter();
123 throw new Error('Unknown property type ' + type
); // some sanity checking
127 if (typeof proto
['get_' + name
] === 'undefined') {
128 proto
['get_' + name
] = getter
;
131 // set the setter if needed
132 if (typeof proto
['set_' + name
] === 'undefined') {
134 proto
['set_' + name
] = setter
;
135 } else if (mode
=== 'wo') {
136 proto
['set_' + name
] = function (val
, idx
) {
137 if (typeof this['_' + name
] !== 'undefined') {
138 throw new Error(name
+ " can only be set once");
140 setter
.call(this, val
, idx
);
145 // make a special setter that we can use in set defaults
146 proto
['_raw_set_' + name
] = function (val
, idx
) {
147 setter
.call(this, val
, idx
);
148 //delete this['_init_set_' + name]; // remove it after use
152 Util
.make_properties = function (constructor, arr
) {
154 for (var i
= 0; i
< arr
.length
; i
++) {
155 Util
.make_property(constructor.prototype, arr
[i
][0], arr
[i
][1], arr
[i
][2]);
159 Util
.set_defaults = function (obj
, conf
, defaults
) {
160 var defaults_keys
= Object
.keys(defaults
);
161 var conf_keys
= Object
.keys(conf
);
164 for (i
= 0; i
< defaults_keys
.length
; i
++) { keys_obj
[defaults_keys
[i
]] = 1; }
165 for (i
= 0; i
< conf_keys
.length
; i
++) { keys_obj
[conf_keys
[i
]] = 1; }
166 var keys
= Object
.keys(keys_obj
);
168 for (i
= 0; i
< keys
.length
; i
++) {
169 var setter
= obj
['_raw_set_' + keys
[i
]];
171 Util
.Warn('Invalid property ' + keys
[i
]);
175 if (keys
[i
] in conf
) {
176 setter
.call(obj
, conf
[keys
[i
]]);
178 setter
.call(obj
, defaults
[keys
[i
]]);
186 Util
.decodeUTF8 = function (utf8string
) {
188 return decodeURIComponent(escape(utf8string
));
194 * Cross-browser routines
197 Util
.getPointerEvent = function (e
) {
198 return e
.changedTouches
? e
.changedTouches
[0] : e
.touches
? e
.touches
[0] : e
;
201 Util
.stopEvent = function (e
) {
207 Util
.isTouchDevice
= ('ontouchstart' in document
.documentElement
) ||
208 // requried for Chrome debugger
209 (document
.ontouchstart
!== undefined) ||
210 // required for MS Surface
211 (navigator
.maxTouchPoints
> 0) ||
212 (navigator
.msMaxTouchPoints
> 0);
213 window
.addEventListener('touchstart', function onFirstTouch() {
214 Util
.isTouchDevice
= true;
215 window
.removeEventListener('touchstart', onFirstTouch
, false);
218 Util
._cursor_uris_supported
= null;
220 Util
.browserSupportsCursorURIs = function () {
221 if (Util
._cursor_uris_supported
=== null) {
223 var target
= document
.createElement('canvas');
224 target
.style
.cursor
= 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
226 if (target
.style
.cursor
) {
227 Util
.Info("Data URI scheme cursor supported");
228 Util
._cursor_uris_supported
= true;
230 Util
.Warn("Data URI scheme cursor not supported");
231 Util
._cursor_uris_supported
= false;
234 Util
.Error("Data URI scheme cursor test exception: " + exc
);
235 Util
._cursor_uris_supported
= false;
239 return Util
._cursor_uris_supported
;
242 // Set browser engine versions. Based on mootools.
243 Util
.Features
= {xpath
: !!(document
.evaluate
), air
: !!(window
.runtime
), query
: !!(document
.querySelector
)};
247 // 'presto': (function () { return (!window.opera) ? false : true; }()),
248 var detectPresto = function () {
249 return !!window
.opera
;
252 // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
253 var detectTrident = function () {
254 if (!window
.ActiveXObject
) {
257 if (window
.XMLHttpRequest
) {
258 return (document
.querySelectorAll
) ? 6 : 5;
265 // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
266 var detectInitialWebkit = function () {
268 if (navigator
.taintEnabled
) {
271 if (Util
.Features
.xpath
) {
272 return (Util
.Features
.query
) ? 525 : 420;
282 var detectActualWebkit = function (initial_ver
) {
283 var re
= /WebKit\/([0-9\.]*) /;
284 var str_ver
= (navigator
.userAgent
.match(re
) || ['', initial_ver
])[1];
285 return parseFloat(str_ver
, 10);
288 // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
289 var detectGecko = function () {
291 if (!document
.getBoxObjectFor
&& window
.mozInnerScreenX
== null) {
294 return (document
.getElementsByClassName
) ? 19 : 18;
300 // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
301 //'presto': (function() {
302 // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
303 'presto': detectPresto(),
304 'trident': detectTrident(),
305 'webkit': detectInitialWebkit(),
306 'gecko': detectGecko()
309 if (Util
.Engine
.webkit
) {
310 // Extract actual webkit version if available
311 Util
.Engine
.webkit
= detectActualWebkit(Util
.Engine
.webkit
);
315 Util
.Flash
= (function () {
319 v
= navigator
.plugins
['Shockwave Flash'].description
;
322 v
= new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
327 version
= v
.match(/\d+/g);
328 return {version
: parseInt(version
[0] || 0 + '.' + version
[1], 10) || 0, build
: parseInt(version
[2], 10) || 0};
332 Util
.Localisation
= {
333 // Currently configured language
336 // Configure suitable language based on user preferences
337 setup: function (supportedLanguages
) {
340 Util
.Localisation
.language
= 'en'; // Default: US English
343 * Navigator.languages only available in Chrome (32+) and FireFox (32+)
344 * Fall back to navigator.language for other browsers
346 if (typeof window
.navigator
.languages
== 'object') {
347 userLanguages
= window
.navigator
.languages
;
349 userLanguages
= [navigator
.language
|| navigator
.userLanguage
];
352 for (var i
= 0;i
< userLanguages
.length
;i
++) {
353 var userLang
= userLanguages
[i
];
354 userLang
= userLang
.toLowerCase();
355 userLang
= userLang
.replace("_", "-");
356 userLang
= userLang
.split("-");
359 if ((userLang
[0] === 'en') &&
360 ((userLang
[1] === undefined) || (userLang
[1] === 'us'))) {
364 // First pass: perfect match
365 for (var j
= 0;j
< supportedLanguages
.length
;j
++) {
366 var supLang
= supportedLanguages
[j
];
367 supLang
= supLang
.toLowerCase();
368 supLang
= supLang
.replace("_", "-");
369 supLang
= supLang
.split("-");
371 if (userLang
[0] !== supLang
[0])
373 if (userLang
[1] !== supLang
[1])
376 Util
.Localisation
.language
= supportedLanguages
[j
];
380 // Second pass: fallback
381 for (var j
= 0;j
< supportedLanguages
.length
;j
++) {
382 supLang
= supportedLanguages
[j
];
383 supLang
= supLang
.toLowerCase();
384 supLang
= supLang
.replace("_", "-");
385 supLang
= supLang
.split("-");
387 if (userLang
[0] !== supLang
[0])
389 if (supLang
[1] !== undefined)
392 Util
.Localisation
.language
= supportedLanguages
[j
];
398 // Retrieve localised text
400 if (typeof Language
!== 'undefined' && Language
[id
]) {
407 // Traverses the DOM and translates relevant fields
408 // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
409 translateDOM: function () {
410 function process(elem
, enabled
) {
411 function isAnyOf(searchElement
, items
) {
412 return items
.indexOf(searchElement
) !== -1;
415 function translateAttribute(elem
, attr
) {
416 var str
= elem
.getAttribute(attr
);
417 str
= Util
.Localisation
.get(str
);
418 elem
.setAttribute(attr
, str
);
421 function translateTextNode(node
) {
422 var str
= node
.data
.trim();
423 str
= Util
.Localisation
.get(str
);
427 if (elem
.hasAttribute("translate")) {
428 if (isAnyOf(elem
.getAttribute("translate"), ["", "yes"])) {
430 } else if (isAnyOf(elem
.getAttribute("translate"), ["no"])) {
436 if (elem
.hasAttribute("abbr") &&
437 elem
.tagName
=== "TH") {
438 translateAttribute(elem
, "abbr");
440 if (elem
.hasAttribute("alt") &&
441 isAnyOf(elem
.tagName
, ["AREA", "IMG", "INPUT"])) {
442 translateAttribute(elem
, "alt");
444 if (elem
.hasAttribute("download") &&
445 isAnyOf(elem
.tagName
, ["A", "AREA"])) {
446 translateAttribute(elem
, "download");
448 if (elem
.hasAttribute("label") &&
449 isAnyOf(elem
.tagName
, ["MENUITEM", "MENU", "OPTGROUP",
450 "OPTION", "TRACK"])) {
451 translateAttribute(elem
, "label");
453 // FIXME: Should update "lang"
454 if (elem
.hasAttribute("placeholder") &&
455 isAnyOf(elem
.tagName
in ["INPUT", "TEXTAREA"])) {
456 translateAttribute(elem
, "placeholder");
458 if (elem
.hasAttribute("title")) {
459 translateAttribute(elem
, "title");
461 if (elem
.hasAttribute("value") &&
462 elem
.tagName
=== "INPUT" &&
463 isAnyOf(elem
.getAttribute("type"), ["reset", "button"])) {
464 translateAttribute(elem
, "value");
468 for (var i
= 0;i
< elem
.childNodes
.length
;i
++) {
469 node
= elem
.childNodes
[i
];
470 if (node
.nodeType
=== node
.ELEMENT_NODE
) {
471 process(node
, enabled
);
472 } else if (node
.nodeType
=== node
.TEXT_NODE
&& enabled
) {
473 translateTextNode(node
);
478 process(document
.body
, true);
482 // Emulate Element.setCapture() when not supported
484 Util
._captureRecursion
= false;
485 Util
._captureProxy = function (e
) {
486 // Recursion protection as we'll see our own event
487 if (Util
._captureRecursion
) return;
489 // Clone the event as we cannot dispatch an already dispatched event
490 var newEv
= new e
.constructor(e
.type
, e
);
492 Util
._captureRecursion
= true;
493 Util
._captureElem
.dispatchEvent(newEv
);
494 Util
._captureRecursion
= false;
496 // Avoid double events
499 // Respect the wishes of the redirected event handlers
500 if (newEv
.defaultPrevented
) {
504 // Implicitly release the capture on button release
505 if ((e
.type
=== "mouseup") || (e
.type
=== "touchend")) {
506 Util
.releaseCapture();
510 // Follow cursor style of target element
511 Util
._captureElemChanged = function() {
512 var captureElem
= document
.getElementById("noVNC_mouse_capture_elem");
513 captureElem
.style
.cursor
= window
.getComputedStyle(Util
._captureElem
).cursor
;
515 Util
._captureObserver
= new MutationObserver(Util
._captureElemChanged
);
517 Util
.setCapture = function (elem
) {
518 if (elem
.setCapture
) {
522 // IE releases capture on 'click' events which might not trigger
523 elem
.addEventListener('mouseup', Util
.releaseCapture
);
524 elem
.addEventListener('touchend', Util
.releaseCapture
);
527 // Release any existing capture in case this method is
528 // called multiple times without coordination
529 Util
.releaseCapture();
531 // Safari on iOS 9 has a broken constructor for TouchEvent.
532 // We are fine in this case however, since Safari seems to
533 // have some sort of implicit setCapture magic anyway.
534 if (window
.TouchEvent
!== undefined) {
536 new TouchEvent("touchstart");
537 } catch (TypeError
) {
542 var captureElem
= document
.getElementById("noVNC_mouse_capture_elem");
544 if (captureElem
=== null) {
545 captureElem
= document
.createElement("div");
546 captureElem
.id
= "noVNC_mouse_capture_elem";
547 captureElem
.style
.position
= "fixed";
548 captureElem
.style
.top
= "0px";
549 captureElem
.style
.left
= "0px";
550 captureElem
.style
.width
= "100%";
551 captureElem
.style
.height
= "100%";
552 captureElem
.style
.zIndex
= 10000;
553 captureElem
.style
.display
= "none";
554 document
.body
.appendChild(captureElem
);
556 // This is to make sure callers don't get confused by having
557 // our blocking element as the target
558 captureElem
.addEventListener('contextmenu', Util
._captureProxy
);
560 captureElem
.addEventListener('mousemove', Util
._captureProxy
);
561 captureElem
.addEventListener('mouseup', Util
._captureProxy
);
563 captureElem
.addEventListener('touchmove', Util
._captureProxy
);
564 captureElem
.addEventListener('touchend', Util
._captureProxy
);
567 Util
._captureElem
= elem
;
569 // Track cursor and get initial cursor
570 Util
._captureObserver
.observe(elem
, {attributes
:true});
571 Util
._captureElemChanged();
573 captureElem
.style
.display
= null;
575 // We listen to events on window in order to keep tracking if it
576 // happens to leave the viewport
577 window
.addEventListener('mousemove', Util
._captureProxy
);
578 window
.addEventListener('mouseup', Util
._captureProxy
);
580 window
.addEventListener('touchmove', Util
._captureProxy
);
581 window
.addEventListener('touchend', Util
._captureProxy
);
585 Util
.releaseCapture = function () {
586 if (document
.releaseCapture
) {
588 document
.releaseCapture();
591 if (!Util
._captureElem
) {
595 // There might be events already queued, so we need to wait for
596 // them to flush. E.g. contextmenu in Microsoft Edge
598 // FIXME: What happens if setCapture is called before this fires?
599 window
.setTimeout(function() { Util
._captureElem
= null; });
601 Util
._captureObserver
.disconnect();
603 var captureElem
= document
.getElementById("noVNC_mouse_capture_elem");
604 captureElem
.style
.display
= "none";
606 window
.removeEventListener('mousemove', Util
._captureProxy
);
607 window
.removeEventListener('mouseup', Util
._captureProxy
);
609 window
.removeEventListener('touchmove', Util
._captureProxy
);
610 window
.removeEventListener('touchend', Util
._captureProxy
);
614 /* [module] export default Util; */