Home | History | Annotate | Download | only in include
      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