util.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  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 (!setter) {
  308. Util.Warn('Invalid property ' + keys[i]);
  309. continue;
  310. }
  311. if (keys[i] in conf) {
  312. setter.call(obj, conf[keys[i]]);
  313. } else {
  314. setter.call(obj, defaults[keys[i]]);
  315. }
  316. }
  317. };
  318. /*
  319. * Decode from UTF-8
  320. */
  321. Util.decodeUTF8 = function (utf8string) {
  322. "use strict";
  323. return decodeURIComponent(escape(utf8string));
  324. };
  325. /*
  326. * Cross-browser routines
  327. */
  328. // Dynamically load scripts without using document.write()
  329. // Reference: http://unixpapa.com/js/dyna.html
  330. //
  331. // Handles the case where load_scripts is invoked from a script that
  332. // itself is loaded via load_scripts. Once all scripts are loaded the
  333. // window.onscriptsloaded handler is called (if set).
  334. Util.get_include_uri = function () {
  335. return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
  336. };
  337. Util._loading_scripts = [];
  338. Util._pending_scripts = [];
  339. Util.load_scripts = function (files) {
  340. "use strict";
  341. var head = document.getElementsByTagName('head')[0], script,
  342. ls = Util._loading_scripts, ps = Util._pending_scripts;
  343. var loadFunc = function (e) {
  344. while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
  345. ls[0].readyState === 'complete')) {
  346. // For IE, append the script to trigger execution
  347. var s = ls.shift();
  348. //console.log("loaded script: " + s.src);
  349. head.appendChild(s);
  350. }
  351. if (!this.readyState ||
  352. (Util.Engine.presto && this.readyState === 'loaded') ||
  353. this.readyState === 'complete') {
  354. if (ps.indexOf(this) >= 0) {
  355. this.onload = this.onreadystatechange = null;
  356. //console.log("completed script: " + this.src);
  357. ps.splice(ps.indexOf(this), 1);
  358. // Call window.onscriptsload after last script loads
  359. if (ps.length === 0 && window.onscriptsload) {
  360. window.onscriptsload();
  361. }
  362. }
  363. }
  364. };
  365. for (var f = 0; f < files.length; f++) {
  366. script = document.createElement('script');
  367. script.type = 'text/javascript';
  368. script.src = Util.get_include_uri() + files[f];
  369. //console.log("loading script: " + script.src);
  370. script.onload = script.onreadystatechange = loadFunc;
  371. // In-order script execution tricks
  372. if (Util.Engine.trident) {
  373. // For IE wait until readyState is 'loaded' before
  374. // appending it which will trigger execution
  375. // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
  376. ls.push(script);
  377. } else {
  378. // For webkit and firefox set async=false and append now
  379. // https://developer.mozilla.org/en-US/docs/HTML/Element/script
  380. script.async = false;
  381. head.appendChild(script);
  382. }
  383. ps.push(script);
  384. }
  385. };
  386. // Get DOM element position on page
  387. // This solution is based based on http://www.greywyvern.com/?post=331
  388. // Thanks to Brian Huisman AKA GreyWyvern!
  389. Util.getPosition = (function () {
  390. "use strict";
  391. function getStyle(obj, styleProp) {
  392. var y;
  393. if (obj.currentStyle) {
  394. y = obj.currentStyle[styleProp];
  395. } else if (window.getComputedStyle)
  396. y = window.getComputedStyle(obj, null)[styleProp];
  397. return y;
  398. }
  399. function scrollDist() {
  400. var myScrollTop = 0, myScrollLeft = 0;
  401. var html = document.getElementsByTagName('html')[0];
  402. // get the scrollTop part
  403. if (html.scrollTop && document.documentElement.scrollTop) {
  404. myScrollTop = html.scrollTop;
  405. } else if (html.scrollTop || document.documentElement.scrollTop) {
  406. myScrollTop = html.scrollTop + document.documentElement.scrollTop;
  407. } else if (document.body.scrollTop) {
  408. myScrollTop = document.body.scrollTop;
  409. } else {
  410. myScrollTop = 0;
  411. }
  412. // get the scrollLeft part
  413. if (html.scrollLeft && document.documentElement.scrollLeft) {
  414. myScrollLeft = html.scrollLeft;
  415. } else if (html.scrollLeft || document.documentElement.scrollLeft) {
  416. myScrollLeft = html.scrollLeft + document.documentElement.scrollLeft;
  417. } else if (document.body.scrollLeft) {
  418. myScrollLeft = document.body.scrollLeft;
  419. } else {
  420. myScrollLeft = 0;
  421. }
  422. return [myScrollLeft, myScrollTop];
  423. }
  424. return function (obj) {
  425. var curleft = 0, curtop = 0, scr = obj, fixed = false;
  426. while ((scr = scr.parentNode) && scr != document.body) {
  427. curleft -= scr.scrollLeft || 0;
  428. curtop -= scr.scrollTop || 0;
  429. if (getStyle(scr, "position") == "fixed") {
  430. fixed = true;
  431. }
  432. }
  433. if (fixed && !window.opera) {
  434. var scrDist = scrollDist();
  435. curleft += scrDist[0];
  436. curtop += scrDist[1];
  437. }
  438. do {
  439. curleft += obj.offsetLeft;
  440. curtop += obj.offsetTop;
  441. } while ((obj = obj.offsetParent));
  442. return {'x': curleft, 'y': curtop};
  443. };
  444. })();
  445. // Get mouse event position in DOM element
  446. Util.getEventPosition = function (e, obj, scale) {
  447. "use strict";
  448. var evt, docX, docY, pos;
  449. //if (!e) evt = window.event;
  450. evt = (e ? e : window.event);
  451. evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
  452. if (evt.pageX || evt.pageY) {
  453. docX = evt.pageX;
  454. docY = evt.pageY;
  455. } else if (evt.clientX || evt.clientY) {
  456. docX = evt.clientX + document.body.scrollLeft +
  457. document.documentElement.scrollLeft;
  458. docY = evt.clientY + document.body.scrollTop +
  459. document.documentElement.scrollTop;
  460. }
  461. pos = Util.getPosition(obj);
  462. if (typeof scale === "undefined") {
  463. scale = 1;
  464. }
  465. var realx = docX - pos.x;
  466. var realy = docY - pos.y;
  467. var x = Math.max(Math.min(realx, obj.width - 1), 0);
  468. var y = Math.max(Math.min(realy, obj.height - 1), 0);
  469. return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
  470. };
  471. // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
  472. Util.addEvent = function (obj, evType, fn) {
  473. "use strict";
  474. if (obj.attachEvent) {
  475. var r = obj.attachEvent("on" + evType, fn);
  476. return r;
  477. } else if (obj.addEventListener) {
  478. obj.addEventListener(evType, fn, false);
  479. return true;
  480. } else {
  481. throw new Error("Handler could not be attached");
  482. }
  483. };
  484. Util.removeEvent = function (obj, evType, fn) {
  485. "use strict";
  486. if (obj.detachEvent) {
  487. var r = obj.detachEvent("on" + evType, fn);
  488. return r;
  489. } else if (obj.removeEventListener) {
  490. obj.removeEventListener(evType, fn, false);
  491. return true;
  492. } else {
  493. throw new Error("Handler could not be removed");
  494. }
  495. };
  496. Util.stopEvent = function (e) {
  497. "use strict";
  498. if (e.stopPropagation) { e.stopPropagation(); }
  499. else { e.cancelBubble = true; }
  500. if (e.preventDefault) { e.preventDefault(); }
  501. else { e.returnValue = false; }
  502. };
  503. // Set browser engine versions. Based on mootools.
  504. Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
  505. (function () {
  506. "use strict";
  507. // 'presto': (function () { return (!window.opera) ? false : true; }()),
  508. var detectPresto = function () {
  509. return !!window.opera;
  510. };
  511. // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
  512. var detectTrident = function () {
  513. if (!window.ActiveXObject) {
  514. return false;
  515. } else {
  516. if (window.XMLHttpRequest) {
  517. return (document.querySelectorAll) ? 6 : 5;
  518. } else {
  519. return 4;
  520. }
  521. }
  522. };
  523. // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
  524. var detectInitialWebkit = function () {
  525. try {
  526. if (navigator.taintEnabled) {
  527. return false;
  528. } else {
  529. if (Util.Features.xpath) {
  530. return (Util.Features.query) ? 525 : 420;
  531. } else {
  532. return 419;
  533. }
  534. }
  535. } catch (e) {
  536. return false;
  537. }
  538. };
  539. var detectActualWebkit = function (initial_ver) {
  540. var re = /WebKit\/([0-9\.]*) /;
  541. var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
  542. return parseFloat(str_ver, 10);
  543. };
  544. // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
  545. var detectGecko = function () {
  546. /* jshint -W041 */
  547. if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
  548. return false;
  549. } else {
  550. return (document.getElementsByClassName) ? 19 : 18;
  551. }
  552. /* jshint +W041 */
  553. };
  554. Util.Engine = {
  555. // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
  556. //'presto': (function() {
  557. // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
  558. 'presto': detectPresto(),
  559. 'trident': detectTrident(),
  560. 'webkit': detectInitialWebkit(),
  561. 'gecko': detectGecko(),
  562. };
  563. if (Util.Engine.webkit) {
  564. // Extract actual webkit version if available
  565. Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit);
  566. }
  567. })();
  568. Util.Flash = (function () {
  569. "use strict";
  570. var v, version;
  571. try {
  572. v = navigator.plugins['Shockwave Flash'].description;
  573. } catch (err1) {
  574. try {
  575. v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
  576. } catch (err2) {
  577. v = '0 r0';
  578. }
  579. }
  580. version = v.match(/\d+/g);
  581. return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
  582. }());