diff options
Diffstat (limited to 'webclients/novnc/include/util.js')
-rw-r--r-- | webclients/novnc/include/util.js | 636 |
1 files changed, 486 insertions, 150 deletions
diff --git a/webclients/novnc/include/util.js b/webclients/novnc/include/util.js index 57ccb54..909d04b 100644 --- a/webclients/novnc/include/util.js +++ b/webclients/novnc/include/util.js @@ -1,14 +1,13 @@ /* * noVNC: HTML5 VNC client * Copyright (C) 2012 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ -"use strict"; -/*jslint bitwise: false, white: false */ -/*global window, console, document, navigator, ActiveXObject */ +/* jshint white: false, nonstandard: true */ +/*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */ // Globals defined here var Util = {}; @@ -18,61 +17,163 @@ var Util = {}; * Make arrays quack */ -Array.prototype.push8 = function (num) { - this.push(num & 0xFF); +var addFunc = function (cl, name, func) { + if (!cl.prototype[name]) { + Object.defineProperty(cl.prototype, name, { enumerable: false, value: func }); + } }; -Array.prototype.push16 = function (num) { +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 ); -}; -Array.prototype.push32 = function (num) { + num & 0xFF); +}); + +addFunc(Array, 'push32', function (num) { + "use strict"; this.push((num >> 24) & 0xFF, (num >> 16) & 0xFF, (num >> 8) & 0xFF, - (num ) & 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 -if (!Array.prototype.map) -{ - Array.prototype.map = function(fun /*, thisp*/) - { +addFunc(Array, 'map', function (fun /*, thisp*/) { + "use strict"; var len = this.length; - if (typeof fun != "function") - throw new TypeError(); + 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); + 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(){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback){ +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 * ------------------------------------------------------ @@ -84,6 +185,7 @@ window.requestAnimFrame = (function(){ Util._log_level = 'warn'; Util.init_logging = function (level) { + "use strict"; if (typeof level === 'undefined') { level = Util._log_level; } else { @@ -94,26 +196,34 @@ Util.init_logging = function (level) { window.console = { 'log' : window.opera.postError, 'warn' : window.opera.postError, - 'error': window.opera.postError }; + 'error': window.opera.postError + }; } else { window.console = { - 'log' : function(m) {}, - 'warn' : function(m) {}, - 'error': function(m) {}}; + '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 '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("invalid logging type '" + level + "'"); + throw new Error("invalid logging type '" + level + "'"); } + /* jshint +W086 */ }; Util.get_logging = function () { return Util._log_level; @@ -121,107 +231,279 @@ Util.get_logging = function () { // Initialize logging level Util.init_logging(); +Util.make_property = function (proto, name, mode, type) { + "use strict"; -// Set configuration default for Crockford style function namespaces -Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) { - var getter, setter; + 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]; + }; + } - // Default getter function - getter = function (idx) { - if ((type in {'arr':1, 'array':1}) && - (typeof idx !== 'undefined')) { - return cfg[v][idx]; + 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 cfg[v]; + return function (val, idx) { + if (typeof idx !== 'undefined') { + this['_' + name][idx] = val; + } else { + this['_' + name] = val; + } + }; } }; - // Default setter function - setter = function (val, idx) { - if (type in {'boolean':1, 'bool':1}) { - if ((!val) || (val in {'0':1, 'no':1, 'false':1})) { - val = false; + var setter; + if (type === 'bool') { + setter = make_setter(function (val) { + if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) { + return false; } else { - val = true; + return true; } - } else if (type in {'integer':1, 'int':1}) { - val = parseInt(val, 10); - } else if (type === 'str') { - val = String(val); - } else if (type === 'func') { + }); + } 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) { - val = function () {}; + return function () {}; + } else { + return val; } - } - if (typeof idx !== 'undefined') { - cfg[v][idx] = val; - } else { - cfg[v] = val; - } - }; - - // Set the description - api[v + '_description'] = desc; + }); + } 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 function - if (typeof api['get_' + v] === 'undefined') { - api['get_' + v] = getter; + // set the getter + if (typeof proto['get_' + name] === 'undefined') { + proto['get_' + name] = getter; } - // Set the setter function with extra sanity checks - if (typeof api['set_' + v] === 'undefined') { - api['set_' + v] = function (val, idx) { - if (mode in {'RO':1, 'ro':1}) { - throw(v + " is read-only"); - } else if ((mode in {'WO':1, 'wo':1}) && - (typeof cfg[v] !== 'undefined')) { - throw(v + " can only be set once"); - } - setter(val, idx); - }; + // 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); + }; + } } - // Set the default value - if (typeof defaults[v] !== 'undefined') { - defval = defaults[v]; - } else if ((type in {'arr':1, 'array':1}) && - (! (defval instanceof Array))) { - defval = []; + // 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]); } - // Coerce existing setting to the right type - //Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]); - setter(defval); }; -// Set group of configuration defaults -Util.conf_defaults = function(cfg, api, defaults, arr) { +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 < arr.length; i++) { - Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1], - arr[i][2], arr[i][3], arr[i][4]); + 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 */ -// Get DOM element position on page -Util.getPosition = function (obj) { - var x = 0, y = 0; - if (obj.offsetParent) { - do { - x += obj.offsetLeft; - y += obj.offsetTop; - obj = obj.offsetParent; - } while (obj); + +// 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); } - return {'x': x, 'y': y}; }; + +// 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); @@ -239,36 +521,43 @@ Util.getEventPosition = function (e, obj, scale) { if (typeof scale === "undefined") { scale = 1; } - return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale}; + 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){ - if (obj.attachEvent){ - var r = obj.attachEvent("on"+evType, fn); +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); + } else if (obj.addEventListener) { + obj.addEventListener(evType, fn, false); return true; } else { - throw("Handler could not be attached"); + throw new Error("Handler could not be attached"); } }; -Util.removeEvent = function(obj, evType, fn){ - if (obj.detachEvent){ - var r = obj.detachEvent("on"+evType, fn); +Util.removeEvent = function (obj, evType, fn) { + "use strict"; + if (obj.detachEvent) { + var r = obj.detachEvent("on" + evType, fn); return r; - } else if (obj.removeEventListener){ + } else if (obj.removeEventListener) { obj.removeEventListener(evType, fn, false); return true; } else { - throw("Handler could not be removed"); + throw new Error("Handler could not be removed"); } }; -Util.stopEvent = function(e) { +Util.stopEvent = function (e) { + "use strict"; if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } @@ -280,41 +569,88 @@ Util.stopEvent = function(e) { // Set browser engine versions. Based on mootools. Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}; -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': (function() { return (!window.opera) ? false : true; }()), - - 'trident': (function() { - return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()), - 'webkit': (function() { - try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()), - //'webkit': (function() { - // return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()), - 'gecko': (function() { - return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }()) -}; -if (Util.Engine.webkit) { - // Extract actual webkit version if available - Util.Engine.webkit = (function(v) { - var re = new RegExp('WebKit/([0-9\.]*) '); - v = (navigator.userAgent.match(re) || ['', v])[1]; - return parseFloat(v, 10); - })(Util.Engine.webkit); -} +(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(){ +Util.Flash = (function () { + "use strict"; var v, version; try { v = navigator.plugins['Shockwave Flash'].description; - } catch(err1) { + } catch (err1) { try { v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); - } catch(err2) { + } 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}; -}()); +}()); |