/* * 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}; }());