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