1/*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2011 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.txt)
5 *
6 * See README.md for usage and integration instructions.
7 */
8
9"use strict";
10/*jslint bitwise: false, white: false */
11/*global window, console, document, navigator, ActiveXObject */
12
13// Globals defined here
14var Util = {};
15
16
17/*
18 * Make arrays quack
19 */
20
21Array.prototype.push8 = function (num) {
22    this.push(num & 0xFF);
23};
24
25Array.prototype.push16 = function (num) {
26    this.push((num >> 8) & 0xFF,
27              (num     ) & 0xFF  );
28};
29Array.prototype.push32 = function (num) {
30    this.push((num >> 24) & 0xFF,
31              (num >> 16) & 0xFF,
32              (num >>  8) & 0xFF,
33              (num      ) & 0xFF  );
34};
35
36// IE does not support map (even in IE9)
37//This prototype is provided by the Mozilla foundation and
38//is distributed under the MIT license.
39//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
40if (!Array.prototype.map)
41{
42  Array.prototype.map = function(fun /*, thisp*/)
43  {
44    var len = this.length;
45    if (typeof fun != "function")
46      throw new TypeError();
47
48    var res = new Array(len);
49    var thisp = arguments[1];
50    for (var i = 0; i < len; i++)
51    {
52      if (i in this)
53        res[i] = fun.call(thisp, this[i], i, this);
54    }
55
56    return res;
57  };
58}
59
60/*
61 * ------------------------------------------------------
62 * Namespaced in Util
63 * ------------------------------------------------------
64 */
65
66/*
67 * Logging/debug routines
68 */
69
70Util._log_level = 'warn';
71Util.init_logging = function (level) {
72    if (typeof level === 'undefined') {
73        level = Util._log_level;
74    } else {
75        Util._log_level = level;
76    }
77    if (typeof window.console === "undefined") {
78        if (typeof window.opera !== "undefined") {
79            window.console = {
80                'log'  : window.opera.postError,
81                'warn' : window.opera.postError,
82                'error': window.opera.postError };
83        } else {
84            window.console = {
85                'log'  : function(m) {},
86                'warn' : function(m) {},
87                'error': function(m) {}};
88        }
89    }
90
91    Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
92    switch (level) {
93        case 'debug': Util.Debug = function (msg) { console.log(msg); };
94        case 'info':  Util.Info  = function (msg) { console.log(msg); };
95        case 'warn':  Util.Warn  = function (msg) { console.warn(msg); };
96        case 'error': Util.Error = function (msg) { console.error(msg); };
97        case 'none':
98            break;
99        default:
100            throw("invalid logging type '" + level + "'");
101    }
102};
103Util.get_logging = function () {
104    return Util._log_level;
105};
106// Initialize logging level
107Util.init_logging();
108
109
110// Set configuration default for Crockford style function namespaces
111Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
112    var getter, setter;
113
114    // Default getter function
115    getter = function (idx) {
116        if ((type in {'arr':1, 'array':1}) &&
117            (typeof idx !== 'undefined')) {
118            return cfg[v][idx];
119        } else {
120            return cfg[v];
121        }
122    };
123
124    // Default setter function
125    setter = function (val, idx) {
126        if (type in {'boolean':1, 'bool':1}) {
127            if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
128                val = false;
129            } else {
130                val = true;
131            }
132        } else if (type in {'integer':1, 'int':1}) {
133            val = parseInt(val, 10);
134        } else if (type === 'func') {
135            if (!val) {
136                val = function () {};
137            }
138        }
139        if (typeof idx !== 'undefined') {
140            cfg[v][idx] = val;
141        } else {
142            cfg[v] = val;
143        }
144    };
145
146    // Set the description
147    api[v + '_description'] = desc;
148
149    // Set the getter function
150    if (typeof api['get_' + v] === 'undefined') {
151        api['get_' + v] = getter;
152    }
153
154    // Set the setter function with extra sanity checks
155    if (typeof api['set_' + v] === 'undefined') {
156        api['set_' + v] = function (val, idx) {
157            if (mode in {'RO':1, 'ro':1}) {
158                throw(v + " is read-only");
159            } else if ((mode in {'WO':1, 'wo':1}) &&
160                       (typeof cfg[v] !== 'undefined')) {
161                throw(v + " can only be set once");
162            }
163            setter(val, idx);
164        };
165    }
166
167    // Set the default value
168    if (typeof defaults[v] !== 'undefined') {
169        defval = defaults[v];
170    } else if ((type in {'arr':1, 'array':1}) &&
171            (! (defval instanceof Array))) {
172        defval = [];
173    }
174    // Coerce existing setting to the right type
175    //Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
176    setter(defval);
177};
178
179// Set group of configuration defaults
180Util.conf_defaults = function(cfg, api, defaults, arr) {
181    var i;
182    for (i = 0; i < arr.length; i++) {
183        Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
184                arr[i][2], arr[i][3], arr[i][4]);
185    }
186};
187
188
189/*
190 * Cross-browser routines
191 */
192
193// Get DOM element position on page
194Util.getPosition = function (obj) {
195    var x = 0, y = 0;
196    if (obj.offsetParent) {
197        do {
198            x += obj.offsetLeft;
199            y += obj.offsetTop;
200            obj = obj.offsetParent;
201        } while (obj);
202    }
203    return {'x': x, 'y': y};
204};
205
206// Get mouse event position in DOM element
207Util.getEventPosition = function (e, obj, scale) {
208    var evt, docX, docY, pos;
209    //if (!e) evt = window.event;
210    evt = (e ? e : window.event);
211    evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
212    if (evt.pageX || evt.pageY) {
213        docX = evt.pageX;
214        docY = evt.pageY;
215    } else if (evt.clientX || evt.clientY) {
216        docX = evt.clientX + document.body.scrollLeft +
217            document.documentElement.scrollLeft;
218        docY = evt.clientY + document.body.scrollTop +
219            document.documentElement.scrollTop;
220    }
221    pos = Util.getPosition(obj);
222    if (typeof scale === "undefined") {
223        scale = 1;
224    }
225    return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale};
226};
227
228
229// Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
230Util.addEvent = function (obj, evType, fn){
231    if (obj.attachEvent){
232        var r = obj.attachEvent("on"+evType, fn);
233        return r;
234    } else if (obj.addEventListener){
235        obj.addEventListener(evType, fn, false);
236        return true;
237    } else {
238        throw("Handler could not be attached");
239    }
240};
241
242Util.removeEvent = function(obj, evType, fn){
243    if (obj.detachEvent){
244        var r = obj.detachEvent("on"+evType, fn);
245        return r;
246    } else if (obj.removeEventListener){
247        obj.removeEventListener(evType, fn, false);
248        return true;
249    } else {
250        throw("Handler could not be removed");
251    }
252};
253
254Util.stopEvent = function(e) {
255    if (e.stopPropagation) { e.stopPropagation(); }
256    else                   { e.cancelBubble = true; }
257
258    if (e.preventDefault)  { e.preventDefault(); }
259    else                   { e.returnValue = false; }
260};
261
262
263// Set browser engine versions. Based on mootools.
264Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
265
266Util.Engine = {
267    // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
268    //'presto': (function() {
269    //         return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
270    'presto': (function() { return (!window.opera) ? false : true; }()),
271
272    'trident': (function() {
273            return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
274    'webkit': (function() {
275            try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
276    //'webkit': (function() {
277    //        return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
278    'gecko': (function() {
279            return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
280};
281if (Util.Engine.webkit) {
282    // Extract actual webkit version if available
283    Util.Engine.webkit = (function(v) {
284            var re = new RegExp('WebKit/([0-9\.]*) ');
285            v = (navigator.userAgent.match(re) || ['', v])[1];
286            return parseFloat(v, 10);
287        })(Util.Engine.webkit);
288}
289
290Util.Flash = (function(){
291    var v, version;
292    try {
293        v = navigator.plugins['Shockwave Flash'].description;
294    } catch(err1) {
295        try {
296            v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
297        } catch(err2) {
298            v = '0 r0';
299        }
300    }
301    version = v.match(/\d+/g);
302    return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
303}());
304