| /* |
| * noVNC: HTML5 VNC client |
| * Copyright (C) 2012 Joel Martin |
| * Licensed under MPL 2.0 (see LICENSE.txt) |
| * |
| * See README.md for usage and integration instructions. |
| */ |
| |
| /* jshint white: false, nonstandard: true */ |
| /*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */ |
| |
| // Globals defined here |
| var Util = {}; |
| |
| |
| /* |
| * Make arrays quack |
| */ |
| |
| var addFunc = function (cl, name, func) { |
| if (!cl.prototype[name]) { |
| Object.defineProperty(cl.prototype, name, { enumerable: false, value: func }); |
| } |
| }; |
| |
| addFunc(Array, 'push8', function (num) { |
| "use strict"; |
| this.push(num & 0xFF); |
| }); |
| |
| addFunc(Array, 'push16', function (num) { |
| "use strict"; |
| this.push((num >> 8) & 0xFF, |
| num & 0xFF); |
| }); |
| |
| addFunc(Array, 'push32', function (num) { |
| "use strict"; |
| this.push((num >> 24) & 0xFF, |
| (num >> 16) & 0xFF, |
| (num >> 8) & 0xFF, |
| num & 0xFF); |
| }); |
| |
| // IE does not support map (even in IE9) |
| //This prototype is provided by the Mozilla foundation and |
| //is distributed under the MIT license. |
| //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license |
| addFunc(Array, 'map', function (fun /*, thisp*/) { |
| "use strict"; |
| var len = this.length; |
| if (typeof fun != "function") { |
| throw new TypeError(); |
| } |
| |
| var res = new Array(len); |
| var thisp = arguments[1]; |
| for (var i = 0; i < len; i++) { |
| if (i in this) { |
| res[i] = fun.call(thisp, this[i], i, this); |
| } |
| } |
| |
| return res; |
| }); |
| |
| // IE <9 does not support indexOf |
| //This prototype is provided by the Mozilla foundation and |
| //is distributed under the MIT license. |
| //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license |
| addFunc(Array, 'indexOf', function (elt /*, from*/) { |
| "use strict"; |
| var len = this.length >>> 0; |
| |
| var from = Number(arguments[1]) || 0; |
| from = (from < 0) ? Math.ceil(from) : Math.floor(from); |
| if (from < 0) { |
| from += len; |
| } |
| |
| for (; from < len; from++) { |
| if (from in this && |
| this[from] === elt) { |
| return from; |
| } |
| } |
| return -1; |
| }); |
| |
| // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys |
| if (!Object.keys) { |
| Object.keys = (function () { |
| 'use strict'; |
| var hasOwnProperty = Object.prototype.hasOwnProperty, |
| hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'), |
| dontEnums = [ |
| 'toString', |
| 'toLocaleString', |
| 'valueOf', |
| 'hasOwnProperty', |
| 'isPrototypeOf', |
| 'propertyIsEnumerable', |
| 'constructor' |
| ], |
| dontEnumsLength = dontEnums.length; |
| |
| return function (obj) { |
| if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { |
| throw new TypeError('Object.keys called on non-object'); |
| } |
| |
| var result = [], prop, i; |
| |
| for (prop in obj) { |
| if (hasOwnProperty.call(obj, prop)) { |
| result.push(prop); |
| } |
| } |
| |
| if (hasDontEnumBug) { |
| for (i = 0; i < dontEnumsLength; i++) { |
| if (hasOwnProperty.call(obj, dontEnums[i])) { |
| result.push(dontEnums[i]); |
| } |
| } |
| } |
| return result; |
| }; |
| })(); |
| } |
| |
| // PhantomJS 1.x doesn't support bind, |
| // so leave this in until PhantomJS 2.0 is released |
| //This prototype is provided by the Mozilla foundation and |
| //is distributed under the MIT license. |
| //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license |
| addFunc(Function, 'bind', function (oThis) { |
| if (typeof this !== "function") { |
| // closest thing possible to the ECMAScript 5 |
| // internal IsCallable function |
| throw new TypeError("Function.prototype.bind - " + |
| "what is trying to be bound is not callable"); |
| } |
| |
| var aArgs = Array.prototype.slice.call(arguments, 1), |
| fToBind = this, |
| fNOP = function () {}, |
| fBound = function () { |
| return fToBind.apply(this instanceof fNOP && oThis ? this |
| : oThis, |
| aArgs.concat(Array.prototype.slice.call(arguments))); |
| }; |
| |
| fNOP.prototype = this.prototype; |
| fBound.prototype = new fNOP(); |
| |
| return fBound; |
| }); |
| |
| // |
| // requestAnimationFrame shim with setTimeout fallback |
| // |
| |
| window.requestAnimFrame = (function () { |
| "use strict"; |
| return window.requestAnimationFrame || |
| window.webkitRequestAnimationFrame || |
| window.mozRequestAnimationFrame || |
| window.oRequestAnimationFrame || |
| window.msRequestAnimationFrame || |
| function (callback) { |
| window.setTimeout(callback, 1000 / 60); |
| }; |
| })(); |
| |
| /* |
| * ------------------------------------------------------ |
| * Namespaced in Util |
| * ------------------------------------------------------ |
| */ |
| |
| /* |
| * Logging/debug routines |
| */ |
| |
| Util._log_level = 'warn'; |
| Util.init_logging = function (level) { |
| "use strict"; |
| if (typeof level === 'undefined') { |
| level = Util._log_level; |
| } else { |
| Util._log_level = level; |
| } |
| if (typeof window.console === "undefined") { |
| if (typeof window.opera !== "undefined") { |
| window.console = { |
| 'log' : window.opera.postError, |
| 'warn' : window.opera.postError, |
| 'error': window.opera.postError |
| }; |
| } else { |
| window.console = { |
| 'log' : function (m) {}, |
| 'warn' : function (m) {}, |
| 'error': function (m) {} |
| }; |
| } |
| } |
| |
| Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {}; |
| /* jshint -W086 */ |
| switch (level) { |
| case 'debug': |
| Util.Debug = function (msg) { console.log(msg); }; |
| case 'info': |
| Util.Info = function (msg) { console.log(msg); }; |
| case 'warn': |
| Util.Warn = function (msg) { console.warn(msg); }; |
| case 'error': |
| Util.Error = function (msg) { console.error(msg); }; |
| case 'none': |
| break; |
| default: |
| throw new Error("invalid logging type '" + level + "'"); |
| } |
| /* jshint +W086 */ |
| }; |
| Util.get_logging = function () { |
| return Util._log_level; |
| }; |
| // Initialize logging level |
| Util.init_logging(); |
| |
| Util.make_property = function (proto, name, mode, type) { |
| "use strict"; |
| |
| var getter; |
| if (type === 'arr') { |
| getter = function (idx) { |
| if (typeof idx !== 'undefined') { |
| return this['_' + name][idx]; |
| } else { |
| return this['_' + name]; |
| } |
| }; |
| } else { |
| getter = function () { |
| return this['_' + name]; |
| }; |
| } |
| |
| var make_setter = function (process_val) { |
| if (process_val) { |
| return function (val, idx) { |
| if (typeof idx !== 'undefined') { |
| this['_' + name][idx] = process_val(val); |
| } else { |
| this['_' + name] = process_val(val); |
| } |
| }; |
| } else { |
| return function (val, idx) { |
| if (typeof idx !== 'undefined') { |
| this['_' + name][idx] = val; |
| } else { |
| this['_' + name] = val; |
| } |
| }; |
| } |
| }; |
| |
| var setter; |
| if (type === 'bool') { |
| setter = make_setter(function (val) { |
| if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) { |
| return false; |
| } else { |
| return true; |
| } |
| }); |
| } else if (type === 'int') { |
| setter = make_setter(function (val) { return parseInt(val, 10); }); |
| } else if (type === 'float') { |
| setter = make_setter(parseFloat); |
| } else if (type === 'str') { |
| setter = make_setter(String); |
| } else if (type === 'func') { |
| setter = make_setter(function (val) { |
| if (!val) { |
| return function () {}; |
| } else { |
| return val; |
| } |
| }); |
| } else if (type === 'arr' || type === 'dom' || type == 'raw') { |
| setter = make_setter(); |
| } else { |
| throw new Error('Unknown property type ' + type); // some sanity checking |
| } |
| |
| // set the getter |
| if (typeof proto['get_' + name] === 'undefined') { |
| proto['get_' + name] = getter; |
| } |
| |
| // set the setter if needed |
| if (typeof proto['set_' + name] === 'undefined') { |
| if (mode === 'rw') { |
| proto['set_' + name] = setter; |
| } else if (mode === 'wo') { |
| proto['set_' + name] = function (val, idx) { |
| if (typeof this['_' + name] !== 'undefined') { |
| throw new Error(name + " can only be set once"); |
| } |
| setter.call(this, val, idx); |
| }; |
| } |
| } |
| |
| // make a special setter that we can use in set defaults |
| proto['_raw_set_' + name] = function (val, idx) { |
| setter.call(this, val, idx); |
| //delete this['_init_set_' + name]; // remove it after use |
| }; |
| }; |
| |
| Util.make_properties = function (constructor, arr) { |
| "use strict"; |
| for (var i = 0; i < arr.length; i++) { |
| Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]); |
| } |
| }; |
| |
| Util.set_defaults = function (obj, conf, defaults) { |
| var defaults_keys = Object.keys(defaults); |
| var conf_keys = Object.keys(conf); |
| var keys_obj = {}; |
| var i; |
| for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; } |
| for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; } |
| var keys = Object.keys(keys_obj); |
| |
| for (i = 0; i < keys.length; i++) { |
| var setter = obj['_raw_set_' + keys[i]]; |
| if (!setter) { |
| Util.Warn('Invalid property ' + keys[i]); |
| continue; |
| } |
| |
| if (keys[i] in conf) { |
| setter.call(obj, conf[keys[i]]); |
| } else { |
| setter.call(obj, defaults[keys[i]]); |
| } |
| } |
| }; |
| |
| /* |
| * Decode from UTF-8 |
| */ |
| Util.decodeUTF8 = function (utf8string) { |
| "use strict"; |
| return decodeURIComponent(escape(utf8string)); |
| }; |
| |
| |
| |
| /* |
| * Cross-browser routines |
| */ |
| |
| |
| // Dynamically load scripts without using document.write() |
| // Reference: http://unixpapa.com/js/dyna.html |
| // |
| // Handles the case where load_scripts is invoked from a script that |
| // itself is loaded via load_scripts. Once all scripts are loaded the |
| // window.onscriptsloaded handler is called (if set). |
| Util.get_include_uri = function () { |
| return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/"; |
| }; |
| Util._loading_scripts = []; |
| Util._pending_scripts = []; |
| Util.load_scripts = function (files) { |
| "use strict"; |
| var head = document.getElementsByTagName('head')[0], script, |
| ls = Util._loading_scripts, ps = Util._pending_scripts; |
| |
| var loadFunc = function (e) { |
| while (ls.length > 0 && (ls[0].readyState === 'loaded' || |
| ls[0].readyState === 'complete')) { |
| // For IE, append the script to trigger execution |
| var s = ls.shift(); |
| //console.log("loaded script: " + s.src); |
| head.appendChild(s); |
| } |
| if (!this.readyState || |
| (Util.Engine.presto && this.readyState === 'loaded') || |
| this.readyState === 'complete') { |
| if (ps.indexOf(this) >= 0) { |
| this.onload = this.onreadystatechange = null; |
| //console.log("completed script: " + this.src); |
| ps.splice(ps.indexOf(this), 1); |
| |
| // Call window.onscriptsload after last script loads |
| if (ps.length === 0 && window.onscriptsload) { |
| window.onscriptsload(); |
| } |
| } |
| } |
| }; |
| |
| for (var f = 0; f < files.length; f++) { |
| script = document.createElement('script'); |
| script.type = 'text/javascript'; |
| script.src = Util.get_include_uri() + files[f]; |
| //console.log("loading script: " + script.src); |
| script.onload = script.onreadystatechange = loadFunc; |
| // In-order script execution tricks |
| if (Util.Engine.trident) { |
| // For IE wait until readyState is 'loaded' before |
| // appending it which will trigger execution |
| // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order |
| ls.push(script); |
| } else { |
| // For webkit and firefox set async=false and append now |
| // https://developer.mozilla.org/en-US/docs/HTML/Element/script |
| script.async = false; |
| head.appendChild(script); |
| } |
| ps.push(script); |
| } |
| }; |
| |
| |
| // Get DOM element position on page |
| // This solution is based based on http://www.greywyvern.com/?post=331 |
| // Thanks to Brian Huisman AKA GreyWyvern! |
| Util.getPosition = (function () { |
| "use strict"; |
| function getStyle(obj, styleProp) { |
| var y; |
| if (obj.currentStyle) { |
| y = obj.currentStyle[styleProp]; |
| } else if (window.getComputedStyle) |
| y = window.getComputedStyle(obj, null)[styleProp]; |
| return y; |
| } |
| |
| function scrollDist() { |
| var myScrollTop = 0, myScrollLeft = 0; |
| var html = document.getElementsByTagName('html')[0]; |
| |
| // get the scrollTop part |
| if (html.scrollTop && document.documentElement.scrollTop) { |
| myScrollTop = html.scrollTop; |
| } else if (html.scrollTop || document.documentElement.scrollTop) { |
| myScrollTop = html.scrollTop + document.documentElement.scrollTop; |
| } else if (document.body.scrollTop) { |
| myScrollTop = document.body.scrollTop; |
| } else { |
| myScrollTop = 0; |
| } |
| |
| // get the scrollLeft part |
| if (html.scrollLeft && document.documentElement.scrollLeft) { |
| myScrollLeft = html.scrollLeft; |
| } else if (html.scrollLeft || document.documentElement.scrollLeft) { |
| myScrollLeft = html.scrollLeft + document.documentElement.scrollLeft; |
| } else if (document.body.scrollLeft) { |
| myScrollLeft = document.body.scrollLeft; |
| } else { |
| myScrollLeft = 0; |
| } |
| |
| return [myScrollLeft, myScrollTop]; |
| } |
| |
| return function (obj) { |
| var curleft = 0, curtop = 0, scr = obj, fixed = false; |
| while ((scr = scr.parentNode) && scr != document.body) { |
| curleft -= scr.scrollLeft || 0; |
| curtop -= scr.scrollTop || 0; |
| if (getStyle(scr, "position") == "fixed") { |
| fixed = true; |
| } |
| } |
| if (fixed && !window.opera) { |
| var scrDist = scrollDist(); |
| curleft += scrDist[0]; |
| curtop += scrDist[1]; |
| } |
| |
| do { |
| curleft += obj.offsetLeft; |
| curtop += obj.offsetTop; |
| } while ((obj = obj.offsetParent)); |
| |
| return {'x': curleft, 'y': curtop}; |
| }; |
| })(); |
| |
| |
| // Get mouse event position in DOM element |
| Util.getEventPosition = function (e, obj, scale) { |
| "use strict"; |
| var evt, docX, docY, pos; |
| //if (!e) evt = window.event; |
| evt = (e ? e : window.event); |
| evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt); |
| if (evt.pageX || evt.pageY) { |
| docX = evt.pageX; |
| docY = evt.pageY; |
| } else if (evt.clientX || evt.clientY) { |
| docX = evt.clientX + document.body.scrollLeft + |
| document.documentElement.scrollLeft; |
| docY = evt.clientY + document.body.scrollTop + |
| document.documentElement.scrollTop; |
| } |
| pos = Util.getPosition(obj); |
| if (typeof scale === "undefined") { |
| scale = 1; |
| } |
| var realx = docX - pos.x; |
| var realy = docY - pos.y; |
| var x = Math.max(Math.min(realx, obj.width - 1), 0); |
| var y = Math.max(Math.min(realy, obj.height - 1), 0); |
| return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale}; |
| }; |
| |
| |
| // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events |
| Util.addEvent = function (obj, evType, fn) { |
| "use strict"; |
| if (obj.attachEvent) { |
| var r = obj.attachEvent("on" + evType, fn); |
| return r; |
| } else if (obj.addEventListener) { |
| obj.addEventListener(evType, fn, false); |
| return true; |
| } else { |
| throw new Error("Handler could not be attached"); |
| } |
| }; |
| |
| Util.removeEvent = function (obj, evType, fn) { |
| "use strict"; |
| if (obj.detachEvent) { |
| var r = obj.detachEvent("on" + evType, fn); |
| return r; |
| } else if (obj.removeEventListener) { |
| obj.removeEventListener(evType, fn, false); |
| return true; |
| } else { |
| throw new Error("Handler could not be removed"); |
| } |
| }; |
| |
| Util.stopEvent = function (e) { |
| "use strict"; |
| if (e.stopPropagation) { e.stopPropagation(); } |
| else { e.cancelBubble = true; } |
| |
| if (e.preventDefault) { e.preventDefault(); } |
| else { e.returnValue = false; } |
| }; |
| |
| |
| // Set browser engine versions. Based on mootools. |
| Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}; |
| |
| (function () { |
| "use strict"; |
| // 'presto': (function () { return (!window.opera) ? false : true; }()), |
| var detectPresto = function () { |
| return !!window.opera; |
| }; |
| |
| // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); |
| var detectTrident = function () { |
| if (!window.ActiveXObject) { |
| return false; |
| } else { |
| if (window.XMLHttpRequest) { |
| return (document.querySelectorAll) ? 6 : 5; |
| } else { |
| return 4; |
| } |
| } |
| }; |
| |
| // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()), |
| var detectInitialWebkit = function () { |
| try { |
| if (navigator.taintEnabled) { |
| return false; |
| } else { |
| if (Util.Features.xpath) { |
| return (Util.Features.query) ? 525 : 420; |
| } else { |
| return 419; |
| } |
| } |
| } catch (e) { |
| return false; |
| } |
| }; |
| |
| var detectActualWebkit = function (initial_ver) { |
| var re = /WebKit\/([0-9\.]*) /; |
| var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1]; |
| return parseFloat(str_ver, 10); |
| }; |
| |
| // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }()) |
| var detectGecko = function () { |
| /* jshint -W041 */ |
| if (!document.getBoxObjectFor && window.mozInnerScreenX == null) { |
| return false; |
| } else { |
| return (document.getElementsByClassName) ? 19 : 18; |
| } |
| /* jshint +W041 */ |
| }; |
| |
| Util.Engine = { |
| // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference) |
| //'presto': (function() { |
| // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()), |
| 'presto': detectPresto(), |
| 'trident': detectTrident(), |
| 'webkit': detectInitialWebkit(), |
| 'gecko': detectGecko(), |
| }; |
| |
| if (Util.Engine.webkit) { |
| // Extract actual webkit version if available |
| Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit); |
| } |
| })(); |
| |
| Util.Flash = (function () { |
| "use strict"; |
| var v, version; |
| try { |
| v = navigator.plugins['Shockwave Flash'].description; |
| } catch (err1) { |
| try { |
| v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); |
| } catch (err2) { |
| v = '0 r0'; |
| } |
| } |
| version = v.match(/\d+/g); |
| return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0}; |
| }()); |