util.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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. "use strict";
  9. /*jslint bitwise: false, white: false */
  10. /*global window, console, document, navigator, ActiveXObject */
  11. // Globals defined here
  12. var Util = {};
  13. /*
  14. * Make arrays quack
  15. */
  16. Array.prototype.push8 = function (num) {
  17. this.push(num & 0xFF);
  18. };
  19. Array.prototype.push16 = function (num) {
  20. this.push((num >> 8) & 0xFF,
  21. (num ) & 0xFF );
  22. };
  23. Array.prototype.push32 = function (num) {
  24. this.push((num >> 24) & 0xFF,
  25. (num >> 16) & 0xFF,
  26. (num >> 8) & 0xFF,
  27. (num ) & 0xFF );
  28. };
  29. // IE does not support map (even in IE9)
  30. //This prototype is provided by the Mozilla foundation and
  31. //is distributed under the MIT license.
  32. //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
  33. if (!Array.prototype.map)
  34. {
  35. Array.prototype.map = function(fun /*, thisp*/)
  36. {
  37. var len = this.length;
  38. if (typeof fun != "function")
  39. throw new TypeError();
  40. var res = new Array(len);
  41. var thisp = arguments[1];
  42. for (var i = 0; i < len; i++)
  43. {
  44. if (i in this)
  45. res[i] = fun.call(thisp, this[i], i, this);
  46. }
  47. return res;
  48. };
  49. }
  50. //
  51. // requestAnimationFrame shim with setTimeout fallback
  52. //
  53. window.requestAnimFrame = (function(){
  54. return window.requestAnimationFrame ||
  55. window.webkitRequestAnimationFrame ||
  56. window.mozRequestAnimationFrame ||
  57. window.oRequestAnimationFrame ||
  58. window.msRequestAnimationFrame ||
  59. function(callback){
  60. window.setTimeout(callback, 1000 / 60);
  61. };
  62. })();
  63. /*
  64. * ------------------------------------------------------
  65. * Namespaced in Util
  66. * ------------------------------------------------------
  67. */
  68. /*
  69. * Logging/debug routines
  70. */
  71. Util._log_level = 'warn';
  72. Util.init_logging = function (level) {
  73. if (typeof level === 'undefined') {
  74. level = Util._log_level;
  75. } else {
  76. Util._log_level = level;
  77. }
  78. if (typeof window.console === "undefined") {
  79. if (typeof window.opera !== "undefined") {
  80. window.console = {
  81. 'log' : window.opera.postError,
  82. 'warn' : window.opera.postError,
  83. 'error': window.opera.postError };
  84. } else {
  85. window.console = {
  86. 'log' : function(m) {},
  87. 'warn' : function(m) {},
  88. 'error': function(m) {}};
  89. }
  90. }
  91. Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
  92. switch (level) {
  93. case 'debug': Util.Debug = function (msg) { console.log(msg); };
  94. case 'info': Util.Info = function (msg) { console.log(msg); };
  95. case 'warn': Util.Warn = function (msg) { console.warn(msg); };
  96. case 'error': Util.Error = function (msg) { console.error(msg); };
  97. case 'none':
  98. break;
  99. default:
  100. throw("invalid logging type '" + level + "'");
  101. }
  102. };
  103. Util.get_logging = function () {
  104. return Util._log_level;
  105. };
  106. // Initialize logging level
  107. Util.init_logging();
  108. // Set configuration default for Crockford style function namespaces
  109. Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
  110. var getter, setter;
  111. // Default getter function
  112. getter = function (idx) {
  113. if ((type in {'arr':1, 'array':1}) &&
  114. (typeof idx !== 'undefined')) {
  115. return cfg[v][idx];
  116. } else {
  117. return cfg[v];
  118. }
  119. };
  120. // Default setter function
  121. setter = function (val, idx) {
  122. if (type in {'boolean':1, 'bool':1}) {
  123. if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
  124. val = false;
  125. } else {
  126. val = true;
  127. }
  128. } else if (type in {'integer':1, 'int':1}) {
  129. val = parseInt(val, 10);
  130. } else if (type === 'str') {
  131. val = String(val);
  132. } else if (type === 'func') {
  133. if (!val) {
  134. val = function () {};
  135. }
  136. }
  137. if (typeof idx !== 'undefined') {
  138. cfg[v][idx] = val;
  139. } else {
  140. cfg[v] = val;
  141. }
  142. };
  143. // Set the description
  144. api[v + '_description'] = desc;
  145. // Set the getter function
  146. if (typeof api['get_' + v] === 'undefined') {
  147. api['get_' + v] = getter;
  148. }
  149. // Set the setter function with extra sanity checks
  150. if (typeof api['set_' + v] === 'undefined') {
  151. api['set_' + v] = function (val, idx) {
  152. if (mode in {'RO':1, 'ro':1}) {
  153. throw(v + " is read-only");
  154. } else if ((mode in {'WO':1, 'wo':1}) &&
  155. (typeof cfg[v] !== 'undefined')) {
  156. throw(v + " can only be set once");
  157. }
  158. setter(val, idx);
  159. };
  160. }
  161. // Set the default value
  162. if (typeof defaults[v] !== 'undefined') {
  163. defval = defaults[v];
  164. } else if ((type in {'arr':1, 'array':1}) &&
  165. (! (defval instanceof Array))) {
  166. defval = [];
  167. }
  168. // Coerce existing setting to the right type
  169. //Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
  170. setter(defval);
  171. };
  172. // Set group of configuration defaults
  173. Util.conf_defaults = function(cfg, api, defaults, arr) {
  174. var i;
  175. for (i = 0; i < arr.length; i++) {
  176. Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
  177. arr[i][2], arr[i][3], arr[i][4]);
  178. }
  179. };
  180. /*
  181. * Cross-browser routines
  182. */
  183. // Dynamically load scripts without using document.write()
  184. // Reference: http://unixpapa.com/js/dyna.html
  185. //
  186. // Handles the case where load_scripts is invoked from a script that
  187. // itself is loaded via load_scripts. Once all scripts are loaded the
  188. // window.onscriptsloaded handler is called (if set).
  189. Util.get_include_uri = function() {
  190. return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
  191. }
  192. Util._loading_scripts = [];
  193. Util._pending_scripts = [];
  194. Util.load_scripts = function(files) {
  195. var head = document.getElementsByTagName('head')[0], script,
  196. ls = Util._loading_scripts, ps = Util._pending_scripts;
  197. for (var f=0; f<files.length; f++) {
  198. script = document.createElement('script');
  199. script.type = 'text/javascript';
  200. script.src = Util.get_include_uri() + files[f];
  201. //console.log("loading script: " + script.src);
  202. script.onload = script.onreadystatechange = function (e) {
  203. while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
  204. ls[0].readyState === 'complete')) {
  205. // For IE, append the script to trigger execution
  206. var s = ls.shift();
  207. //console.log("loaded script: " + s.src);
  208. head.appendChild(s);
  209. }
  210. if (!this.readyState ||
  211. (Util.Engine.presto && this.readyState === 'loaded') ||
  212. this.readyState === 'complete') {
  213. if (ps.indexOf(this) >= 0) {
  214. this.onload = this.onreadystatechange = null;
  215. //console.log("completed script: " + this.src);
  216. ps.splice(ps.indexOf(this), 1);
  217. // Call window.onscriptsload after last script loads
  218. if (ps.length === 0 && window.onscriptsload) {
  219. window.onscriptsload();
  220. }
  221. }
  222. }
  223. };
  224. // In-order script execution tricks
  225. if (Util.Engine.trident) {
  226. // For IE wait until readyState is 'loaded' before
  227. // appending it which will trigger execution
  228. // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
  229. ls.push(script);
  230. } else {
  231. // For webkit and firefox set async=false and append now
  232. // https://developer.mozilla.org/en-US/docs/HTML/Element/script
  233. script.async = false;
  234. head.appendChild(script);
  235. }
  236. ps.push(script);
  237. }
  238. }
  239. // Get DOM element position on page
  240. Util.getPosition = function (obj) {
  241. var x = 0, y = 0;
  242. if (obj.offsetParent) {
  243. do {
  244. x += obj.offsetLeft;
  245. y += obj.offsetTop;
  246. obj = obj.offsetParent;
  247. } while (obj);
  248. }
  249. return {'x': x, 'y': y};
  250. };
  251. // Get mouse event position in DOM element
  252. Util.getEventPosition = function (e, obj, scale) {
  253. var evt, docX, docY, pos;
  254. //if (!e) evt = window.event;
  255. evt = (e ? e : window.event);
  256. evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
  257. if (evt.pageX || evt.pageY) {
  258. docX = evt.pageX;
  259. docY = evt.pageY;
  260. } else if (evt.clientX || evt.clientY) {
  261. docX = evt.clientX + document.body.scrollLeft +
  262. document.documentElement.scrollLeft;
  263. docY = evt.clientY + document.body.scrollTop +
  264. document.documentElement.scrollTop;
  265. }
  266. pos = Util.getPosition(obj);
  267. if (typeof scale === "undefined") {
  268. scale = 1;
  269. }
  270. var x = Math.max(Math.min(docX - pos.x, obj.width-1), 0);
  271. var y = Math.max(Math.min(docY - pos.y, obj.height-1), 0);
  272. return {'x': x / scale, 'y': y / scale};
  273. };
  274. // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
  275. Util.addEvent = function (obj, evType, fn){
  276. if (obj.attachEvent){
  277. var r = obj.attachEvent("on"+evType, fn);
  278. return r;
  279. } else if (obj.addEventListener){
  280. obj.addEventListener(evType, fn, false);
  281. return true;
  282. } else {
  283. throw("Handler could not be attached");
  284. }
  285. };
  286. Util.removeEvent = function(obj, evType, fn){
  287. if (obj.detachEvent){
  288. var r = obj.detachEvent("on"+evType, fn);
  289. return r;
  290. } else if (obj.removeEventListener){
  291. obj.removeEventListener(evType, fn, false);
  292. return true;
  293. } else {
  294. throw("Handler could not be removed");
  295. }
  296. };
  297. Util.stopEvent = function(e) {
  298. if (e.stopPropagation) { e.stopPropagation(); }
  299. else { e.cancelBubble = true; }
  300. if (e.preventDefault) { e.preventDefault(); }
  301. else { e.returnValue = false; }
  302. };
  303. // Set browser engine versions. Based on mootools.
  304. Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
  305. Util.Engine = {
  306. // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
  307. //'presto': (function() {
  308. // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
  309. 'presto': (function() { return (!window.opera) ? false : true; }()),
  310. 'trident': (function() {
  311. return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
  312. 'webkit': (function() {
  313. try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
  314. //'webkit': (function() {
  315. // return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
  316. 'gecko': (function() {
  317. return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
  318. };
  319. if (Util.Engine.webkit) {
  320. // Extract actual webkit version if available
  321. Util.Engine.webkit = (function(v) {
  322. var re = new RegExp('WebKit/([0-9\.]*) ');
  323. v = (navigator.userAgent.match(re) || ['', v])[1];
  324. return parseFloat(v, 10);
  325. })(Util.Engine.webkit);
  326. }
  327. Util.Flash = (function(){
  328. var v, version;
  329. try {
  330. v = navigator.plugins['Shockwave Flash'].description;
  331. } catch(err1) {
  332. try {
  333. v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
  334. } catch(err2) {
  335. v = '0 r0';
  336. }
  337. }
  338. version = v.match(/\d+/g);
  339. return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
  340. }());