util.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  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. /* jshint white: false, nonstandard: true */
  9. /*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */
  10. // Globals defined here
  11. var Util = {};
  12. /*
  13. * Make arrays quack
  14. */
  15. Array.prototype.push8 = function (num) {
  16. "use strict";
  17. this.push(num & 0xFF);
  18. };
  19. Array.prototype.push16 = function (num) {
  20. "use strict";
  21. this.push((num >> 8) & 0xFF,
  22. num & 0xFF);
  23. };
  24. Array.prototype.push32 = function (num) {
  25. "use strict";
  26. this.push((num >> 24) & 0xFF,
  27. (num >> 16) & 0xFF,
  28. (num >> 8) & 0xFF,
  29. num & 0xFF);
  30. };
  31. // IE does not support map (even in IE9)
  32. //This prototype is provided by the Mozilla foundation and
  33. //is distributed under the MIT license.
  34. //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
  35. if (!Array.prototype.map) {
  36. Array.prototype.map = function (fun /*, thisp*/) {
  37. "use strict";
  38. var len = this.length;
  39. if (typeof fun != "function") {
  40. throw new TypeError();
  41. }
  42. var res = new Array(len);
  43. var thisp = arguments[1];
  44. for (var i = 0; i < len; i++) {
  45. if (i in this) {
  46. res[i] = fun.call(thisp, this[i], i, this);
  47. }
  48. }
  49. return res;
  50. };
  51. }
  52. // IE <9 does not support indexOf
  53. //This prototype is provided by the Mozilla foundation and
  54. //is distributed under the MIT license.
  55. //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
  56. if (!Array.prototype.indexOf) {
  57. Array.prototype.indexOf = function (elt /*, from*/) {
  58. "use strict";
  59. var len = this.length >>> 0;
  60. var from = Number(arguments[1]) || 0;
  61. from = (from < 0) ? Math.ceil(from) : Math.floor(from);
  62. if (from < 0) {
  63. from += len;
  64. }
  65. for (; from < len; from++) {
  66. if (from in this &&
  67. this[from] === elt) {
  68. return from;
  69. }
  70. }
  71. return -1;
  72. };
  73. }
  74. // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
  75. if (!Object.keys) {
  76. Object.keys = (function () {
  77. 'use strict';
  78. var hasOwnProperty = Object.prototype.hasOwnProperty,
  79. hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
  80. dontEnums = [
  81. 'toString',
  82. 'toLocaleString',
  83. 'valueOf',
  84. 'hasOwnProperty',
  85. 'isPrototypeOf',
  86. 'propertyIsEnumerable',
  87. 'constructor'
  88. ],
  89. dontEnumsLength = dontEnums.length;
  90. return function (obj) {
  91. if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
  92. throw new TypeError('Object.keys called on non-object');
  93. }
  94. var result = [], prop, i;
  95. for (prop in obj) {
  96. if (hasOwnProperty.call(obj, prop)) {
  97. result.push(prop);
  98. }
  99. }
  100. if (hasDontEnumBug) {
  101. for (i = 0; i < dontEnumsLength; i++) {
  102. if (hasOwnProperty.call(obj, dontEnums[i])) {
  103. result.push(dontEnums[i]);
  104. }
  105. }
  106. }
  107. return result;
  108. };
  109. })();
  110. }
  111. // PhantomJS 1.x doesn't support bind,
  112. // so leave this in until PhantomJS 2.0 is released
  113. //This prototype is provided by the Mozilla foundation and
  114. //is distributed under the MIT license.
  115. //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
  116. if (!Function.prototype.bind) {
  117. Function.prototype.bind = function (oThis) {
  118. if (typeof this !== "function") {
  119. // closest thing possible to the ECMAScript 5
  120. // internal IsCallable function
  121. throw new TypeError("Function.prototype.bind - " +
  122. "what is trying to be bound is not callable");
  123. }
  124. var aArgs = Array.prototype.slice.call(arguments, 1),
  125. fToBind = this,
  126. fNOP = function () {},
  127. fBound = function () {
  128. return fToBind.apply(this instanceof fNOP && oThis ? this
  129. : oThis,
  130. aArgs.concat(Array.prototype.slice.call(arguments)));
  131. };
  132. fNOP.prototype = this.prototype;
  133. fBound.prototype = new fNOP();
  134. return fBound;
  135. };
  136. }
  137. //
  138. // requestAnimationFrame shim with setTimeout fallback
  139. //
  140. window.requestAnimFrame = (function () {
  141. "use strict";
  142. return window.requestAnimationFrame ||
  143. window.webkitRequestAnimationFrame ||
  144. window.mozRequestAnimationFrame ||
  145. window.oRequestAnimationFrame ||
  146. window.msRequestAnimationFrame ||
  147. function (callback) {
  148. window.setTimeout(callback, 1000 / 60);
  149. };
  150. })();
  151. /*
  152. * ------------------------------------------------------
  153. * Namespaced in Util
  154. * ------------------------------------------------------
  155. */
  156. /*
  157. * Logging/debug routines
  158. */
  159. Util._log_level = 'warn';
  160. Util.init_logging = function (level) {
  161. "use strict";
  162. if (typeof level === 'undefined') {
  163. level = Util._log_level;
  164. } else {
  165. Util._log_level = level;
  166. }
  167. if (typeof window.console === "undefined") {
  168. if (typeof window.opera !== "undefined") {
  169. window.console = {
  170. 'log' : window.opera.postError,
  171. 'warn' : window.opera.postError,
  172. 'error': window.opera.postError
  173. };
  174. } else {
  175. window.console = {
  176. 'log' : function (m) {},
  177. 'warn' : function (m) {},
  178. 'error': function (m) {}
  179. };
  180. }
  181. }
  182. Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
  183. /* jshint -W086 */
  184. switch (level) {
  185. case 'debug':
  186. Util.Debug = function (msg) { console.log(msg); };
  187. case 'info':
  188. Util.Info = function (msg) { console.log(msg); };
  189. case 'warn':
  190. Util.Warn = function (msg) { console.warn(msg); };
  191. case 'error':
  192. Util.Error = function (msg) { console.error(msg); };
  193. case 'none':
  194. break;
  195. default:
  196. throw new Error("invalid logging type '" + level + "'");
  197. }
  198. /* jshint +W086 */
  199. };
  200. Util.get_logging = function () {
  201. return Util._log_level;
  202. };
  203. // Initialize logging level
  204. Util.init_logging();
  205. Util.make_property = function (proto, name, mode, type) {
  206. "use strict";
  207. var getter;
  208. if (type === 'arr') {
  209. getter = function (idx) {
  210. if (typeof idx !== 'undefined') {
  211. return this['_' + name][idx];
  212. } else {
  213. return this['_' + name];
  214. }
  215. };
  216. } else {
  217. getter = function () {
  218. return this['_' + name];
  219. };
  220. }
  221. var make_setter = function (process_val) {
  222. if (process_val) {
  223. return function (val, idx) {
  224. if (typeof idx !== 'undefined') {
  225. this['_' + name][idx] = process_val(val);
  226. } else {
  227. this['_' + name] = process_val(val);
  228. }
  229. };
  230. } else {
  231. return function (val, idx) {
  232. if (typeof idx !== 'undefined') {
  233. this['_' + name][idx] = val;
  234. } else {
  235. this['_' + name] = val;
  236. }
  237. };
  238. }
  239. };
  240. var setter;
  241. if (type === 'bool') {
  242. setter = make_setter(function (val) {
  243. if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
  244. return false;
  245. } else {
  246. return true;
  247. }
  248. });
  249. } else if (type === 'int') {
  250. setter = make_setter(function (val) { return parseInt(val, 10); });
  251. } else if (type === 'float') {
  252. setter = make_setter(parseFloat);
  253. } else if (type === 'str') {
  254. setter = make_setter(String);
  255. } else if (type === 'func') {
  256. setter = make_setter(function (val) {
  257. if (!val) {
  258. return function () {};
  259. } else {
  260. return val;
  261. }
  262. });
  263. } else if (type === 'arr' || type === 'dom' || type == 'raw') {
  264. setter = make_setter();
  265. } else {
  266. throw new Error('Unknown property type ' + type); // some sanity checking
  267. }
  268. // set the getter
  269. if (typeof proto['get_' + name] === 'undefined') {
  270. proto['get_' + name] = getter;
  271. }
  272. // set the setter if needed
  273. if (typeof proto['set_' + name] === 'undefined') {
  274. if (mode === 'rw') {
  275. proto['set_' + name] = setter;
  276. } else if (mode === 'wo') {
  277. proto['set_' + name] = function (val, idx) {
  278. if (typeof this['_' + name] !== 'undefined') {
  279. throw new Error(name + " can only be set once");
  280. }
  281. setter.call(this, val, idx);
  282. };
  283. }
  284. }
  285. // make a special setter that we can use in set defaults
  286. proto['_raw_set_' + name] = function (val, idx) {
  287. setter.call(this, val, idx);
  288. //delete this['_init_set_' + name]; // remove it after use
  289. };
  290. };
  291. Util.make_properties = function (constructor, arr) {
  292. "use strict";
  293. for (var i = 0; i < arr.length; i++) {
  294. Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
  295. }
  296. };
  297. Util.set_defaults = function (obj, conf, defaults) {
  298. var defaults_keys = Object.keys(defaults);
  299. var conf_keys = Object.keys(conf);
  300. var keys_obj = {};
  301. var i;
  302. for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
  303. for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
  304. var keys = Object.keys(keys_obj);
  305. for (i = 0; i < keys.length; i++) {
  306. var setter = obj['_raw_set_' + keys[i]];
  307. if (conf[keys[i]]) {
  308. setter.call(obj, conf[keys[i]]);
  309. } else {
  310. setter.call(obj, defaults[keys[i]]);
  311. }
  312. }
  313. };
  314. /*
  315. * Decode from UTF-8
  316. */
  317. Util.decodeUTF8 = function (utf8string) {
  318. "use strict";
  319. return decodeURIComponent(escape(utf8string));
  320. };
  321. /*
  322. * Cross-browser routines
  323. */
  324. // Dynamically load scripts without using document.write()
  325. // Reference: http://unixpapa.com/js/dyna.html
  326. //
  327. // Handles the case where load_scripts is invoked from a script that
  328. // itself is loaded via load_scripts. Once all scripts are loaded the
  329. // window.onscriptsloaded handler is called (if set).
  330. Util.get_include_uri = function () {
  331. return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
  332. };
  333. Util._loading_scripts = [];
  334. Util._pending_scripts = [];
  335. Util.load_scripts = function (files) {
  336. "use strict";
  337. var head = document.getElementsByTagName('head')[0], script,
  338. ls = Util._loading_scripts, ps = Util._pending_scripts;
  339. var loadFunc = function (e) {
  340. while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
  341. ls[0].readyState === 'complete')) {
  342. // For IE, append the script to trigger execution
  343. var s = ls.shift();
  344. //console.log("loaded script: " + s.src);
  345. head.appendChild(s);
  346. }
  347. if (!this.readyState ||
  348. (Util.Engine.presto && this.readyState === 'loaded') ||
  349. this.readyState === 'complete') {
  350. if (ps.indexOf(this) >= 0) {
  351. this.onload = this.onreadystatechange = null;
  352. //console.log("completed script: " + this.src);
  353. ps.splice(ps.indexOf(this), 1);
  354. // Call window.onscriptsload after last script loads
  355. if (ps.length === 0 && window.onscriptsload) {
  356. window.onscriptsload();
  357. }
  358. }
  359. }
  360. };
  361. for (var f = 0; f < files.length; f++) {
  362. script = document.createElement('script');
  363. script.type = 'text/javascript';
  364. script.src = Util.get_include_uri() + files[f];
  365. //console.log("loading script: " + script.src);
  366. script.onload = script.onreadystatechange = loadFunc;
  367. // In-order script execution tricks
  368. if (Util.Engine.trident) {
  369. // For IE wait until readyState is 'loaded' before
  370. // appending it which will trigger execution
  371. // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
  372. ls.push(script);
  373. } else {
  374. // For webkit and firefox set async=false and append now
  375. // https://developer.mozilla.org/en-US/docs/HTML/Element/script
  376. script.async = false;
  377. head.appendChild(script);
  378. }
  379. ps.push(script);
  380. }
  381. };
  382. // Get DOM element position on page
  383. // This solution is based based on http://www.greywyvern.com/?post=331
  384. // Thanks to Brian Huisman AKA GreyWyvern!
  385. Util.getPosition = (function () {
  386. "use strict";
  387. function getStyle(obj, styleProp) {
  388. var y;
  389. if (obj.currentStyle) {
  390. y = obj.currentStyle[styleProp];
  391. } else if (window.getComputedStyle)
  392. y = window.getComputedStyle(obj, null)[styleProp];
  393. return y;
  394. }
  395. function scrollDist() {
  396. var myScrollTop = 0, myScrollLeft = 0;
  397. var html = document.getElementsByTagName('html')[0];
  398. // get the scrollTop part
  399. if (html.scrollTop && document.documentElement.scrollTop) {
  400. myScrollTop = html.scrollTop;
  401. } else if (html.scrollTop || document.documentElement.scrollTop) {
  402. myScrollTop = html.scrollTop + document.documentElement.scrollTop;
  403. } else if (document.body.scrollTop) {
  404. myScrollTop = document.body.scrollTop;
  405. } else {
  406. myScrollTop = 0;
  407. }
  408. // get the scrollLeft part
  409. if (html.scrollLeft && document.documentElement.scrollLeft) {
  410. myScrollLeft = html.scrollLeft;
  411. } else if (html.scrollLeft || document.documentElement.scrollLeft) {
  412. myScrollLeft = html.scrollLeft + document.documentElement.scrollLeft;
  413. } else if (document.body.scrollLeft) {
  414. myScrollLeft = document.body.scrollLeft;
  415. } else {
  416. myScrollLeft = 0;
  417. }
  418. return [myScrollLeft, myScrollTop];
  419. }
  420. return function (obj) {
  421. var curleft = 0, curtop = 0, scr = obj, fixed = false;
  422. while ((scr = scr.parentNode) && scr != document.body) {
  423. curleft -= scr.scrollLeft || 0;
  424. curtop -= scr.scrollTop || 0;
  425. if (getStyle(scr, "position") == "fixed") {
  426. fixed = true;
  427. }
  428. }
  429. if (fixed && !window.opera) {
  430. var scrDist = scrollDist();
  431. curleft += scrDist[0];
  432. curtop += scrDist[1];
  433. }
  434. do {
  435. curleft += obj.offsetLeft;
  436. curtop += obj.offsetTop;
  437. } while ((obj = obj.offsetParent));
  438. return {'x': curleft, 'y': curtop};
  439. };
  440. })();
  441. // Get mouse event position in DOM element
  442. Util.getEventPosition = function (e, obj, scale) {
  443. "use strict";
  444. var evt, docX, docY, pos;
  445. //if (!e) evt = window.event;
  446. evt = (e ? e : window.event);
  447. evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
  448. if (evt.pageX || evt.pageY) {
  449. docX = evt.pageX;
  450. docY = evt.pageY;
  451. } else if (evt.clientX || evt.clientY) {
  452. docX = evt.clientX + document.body.scrollLeft +
  453. document.documentElement.scrollLeft;
  454. docY = evt.clientY + document.body.scrollTop +
  455. document.documentElement.scrollTop;
  456. }
  457. pos = Util.getPosition(obj);
  458. if (typeof scale === "undefined") {
  459. scale = 1;
  460. }
  461. var realx = docX - pos.x;
  462. var realy = docY - pos.y;
  463. var x = Math.max(Math.min(realx, obj.width - 1), 0);
  464. var y = Math.max(Math.min(realy, obj.height - 1), 0);
  465. return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
  466. };
  467. // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
  468. Util.addEvent = function (obj, evType, fn) {
  469. "use strict";
  470. if (obj.attachEvent) {
  471. var r = obj.attachEvent("on" + evType, fn);
  472. return r;
  473. } else if (obj.addEventListener) {
  474. obj.addEventListener(evType, fn, false);
  475. return true;
  476. } else {
  477. throw new Error("Handler could not be attached");
  478. }
  479. };
  480. Util.removeEvent = function (obj, evType, fn) {
  481. "use strict";
  482. if (obj.detachEvent) {
  483. var r = obj.detachEvent("on" + evType, fn);
  484. return r;
  485. } else if (obj.removeEventListener) {
  486. obj.removeEventListener(evType, fn, false);
  487. return true;
  488. } else {
  489. throw new Error("Handler could not be removed");
  490. }
  491. };
  492. Util.stopEvent = function (e) {
  493. "use strict";
  494. if (e.stopPropagation) { e.stopPropagation(); }
  495. else { e.cancelBubble = true; }
  496. if (e.preventDefault) { e.preventDefault(); }
  497. else { e.returnValue = false; }
  498. };
  499. // Set browser engine versions. Based on mootools.
  500. Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
  501. (function () {
  502. "use strict";
  503. // 'presto': (function () { return (!window.opera) ? false : true; }()),
  504. var detectPresto = function () {
  505. return !!window.opera;
  506. };
  507. // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
  508. var detectTrident = function () {
  509. if (!window.ActiveXObject) {
  510. return false;
  511. } else {
  512. if (window.XMLHttpRequest) {
  513. return (document.querySelectorAll) ? 6 : 5;
  514. } else {
  515. return 4;
  516. }
  517. }
  518. };
  519. // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
  520. var detectInitialWebkit = function () {
  521. try {
  522. if (navigator.taintEnabled) {
  523. return false;
  524. } else {
  525. if (Util.Features.xpath) {
  526. return (Util.Features.query) ? 525 : 420;
  527. } else {
  528. return 419;
  529. }
  530. }
  531. } catch (e) {
  532. return false;
  533. }
  534. };
  535. var detectActualWebkit = function (initial_ver) {
  536. var re = /WebKit\/([0-9\.]*) /;
  537. var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
  538. return parseFloat(str_ver, 10);
  539. };
  540. // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
  541. var detectGecko = function () {
  542. /* jshint -W041 */
  543. if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
  544. return false;
  545. } else {
  546. return (document.getElementsByClassName) ? 19 : 18;
  547. }
  548. /* jshint +W041 */
  549. };
  550. Util.Engine = {
  551. // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
  552. //'presto': (function() {
  553. // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
  554. 'presto': detectPresto(),
  555. 'trident': detectTrident(),
  556. 'webkit': detectInitialWebkit(),
  557. 'gecko': detectGecko(),
  558. };
  559. if (Util.Engine.webkit) {
  560. // Extract actual webkit version if available
  561. Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit);
  562. }
  563. })();
  564. Util.Flash = (function () {
  565. "use strict";
  566. var v, version;
  567. try {
  568. v = navigator.plugins['Shockwave Flash'].description;
  569. } catch (err1) {
  570. try {
  571. v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
  572. } catch (err2) {
  573. v = '0 r0';
  574. }
  575. }
  576. version = v.match(/\d+/g);
  577. return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
  578. }());