]> git.proxmox.com Git - mirror_novnc.git/blame - include/util.js
Refactor dynamic script loading. Add util.js:load_scripts()
[mirror_novnc.git] / include / util.js
CommitLineData
61dd52c9 1/*
15046f00 2 * noVNC: HTML5 VNC client
d58f8b51 3 * Copyright (C) 2012 Joel Martin
1d728ace 4 * Licensed under MPL 2.0 (see LICENSE.txt)
15046f00
JM
5 *
6 * See README.md for usage and integration instructions.
61dd52c9
JM
7 */
8
15046f00
JM
9"use strict";
10/*jslint bitwise: false, white: false */
a59f1cd2 11/*global window, console, document, navigator, ActiveXObject */
61dd52c9 12
15046f00 13// Globals defined here
a59f1cd2 14var Util = {};
15046f00 15
81e5adaf 16
56ec48be
JM
17/*
18 * Make arrays quack
19 */
20
56ec48be
JM
21Array.prototype.push8 = function (num) {
22 this.push(num & 0xFF);
23};
24
56ec48be
JM
25Array.prototype.push16 = function (num) {
26 this.push((num >> 8) & 0xFF,
27 (num ) & 0xFF );
28};
56ec48be
JM
29Array.prototype.push32 = function (num) {
30 this.push((num >> 24) & 0xFF,
31 (num >> 16) & 0xFF,
32 (num >> 8) & 0xFF,
33 (num ) & 0xFF );
34};
56ec48be 35
32f135d7
JM
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
34d8b844
JM
60//
61// requestAnimationFrame shim with setTimeout fallback
62//
63
64window.requestAnimFrame = (function(){
65 return window.requestAnimationFrame ||
66 window.webkitRequestAnimationFrame ||
67 window.mozRequestAnimationFrame ||
68 window.oRequestAnimationFrame ||
69 window.msRequestAnimationFrame ||
70 function(callback){
71 window.setTimeout(callback, 1000 / 60);
72 };
73})();
74
15046f00
JM
75/*
76 * ------------------------------------------------------
77 * Namespaced in Util
78 * ------------------------------------------------------
79 */
80
8db09746
JM
81/*
82 * Logging/debug routines
83 */
84
c1eba48f 85Util._log_level = 'warn';
8db09746 86Util.init_logging = function (level) {
c1eba48f 87 if (typeof level === 'undefined') {
c1eba48f
JM
88 level = Util._log_level;
89 } else {
90 Util._log_level = level;
91 }
8db09746
JM
92 if (typeof window.console === "undefined") {
93 if (typeof window.opera !== "undefined") {
94 window.console = {
95 'log' : window.opera.postError,
96 'warn' : window.opera.postError,
97 'error': window.opera.postError };
98 } else {
99 window.console = {
100 'log' : function(m) {},
101 'warn' : function(m) {},
102 'error': function(m) {}};
103 }
104 }
105
106 Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
107 switch (level) {
108 case 'debug': Util.Debug = function (msg) { console.log(msg); };
109 case 'info': Util.Info = function (msg) { console.log(msg); };
110 case 'warn': Util.Warn = function (msg) { console.warn(msg); };
111 case 'error': Util.Error = function (msg) { console.error(msg); };
112 case 'none':
113 break;
114 default:
115 throw("invalid logging type '" + level + "'");
116 }
117};
c1eba48f 118Util.get_logging = function () {
8d5d2c82 119 return Util._log_level;
d3796c14 120};
8db09746 121// Initialize logging level
c1eba48f 122Util.init_logging();
8db09746 123
a8edf9d8 124
5210330a
JM
125// Set configuration default for Crockford style function namespaces
126Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
127 var getter, setter;
128
129 // Default getter function
130 getter = function (idx) {
131 if ((type in {'arr':1, 'array':1}) &&
132 (typeof idx !== 'undefined')) {
133 return cfg[v][idx];
134 } else {
135 return cfg[v];
136 }
137 };
138
139 // Default setter function
140 setter = function (val, idx) {
141 if (type in {'boolean':1, 'bool':1}) {
142 if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
143 val = false;
d890e864 144 } else {
5210330a 145 val = true;
d890e864 146 }
5210330a
JM
147 } else if (type in {'integer':1, 'int':1}) {
148 val = parseInt(val, 10);
b50f3406 149 } else if (type === 'str') {
12acb663 150 val = String(val);
5210330a
JM
151 } else if (type === 'func') {
152 if (!val) {
153 val = function () {};
154 }
155 }
156 if (typeof idx !== 'undefined') {
157 cfg[v][idx] = val;
158 } else {
159 cfg[v] = val;
160 }
161 };
162
163 // Set the description
164 api[v + '_description'] = desc;
165
166 // Set the getter function
167 if (typeof api['get_' + v] === 'undefined') {
168 api['get_' + v] = getter;
125d8bbb 169 }
d890e864 170
5210330a 171 // Set the setter function with extra sanity checks
125d8bbb 172 if (typeof api['set_' + v] === 'undefined') {
d890e864 173 api['set_' + v] = function (val, idx) {
5210330a
JM
174 if (mode in {'RO':1, 'ro':1}) {
175 throw(v + " is read-only");
176 } else if ((mode in {'WO':1, 'wo':1}) &&
177 (typeof cfg[v] !== 'undefined')) {
178 throw(v + " can only be set once");
d890e864 179 }
5210330a 180 setter(val, idx);
d890e864 181 };
125d8bbb 182 }
ff36b127 183
5210330a
JM
184 // Set the default value
185 if (typeof defaults[v] !== 'undefined') {
186 defval = defaults[v];
187 } else if ((type in {'arr':1, 'array':1}) &&
188 (! (defval instanceof Array))) {
189 defval = [];
ff36b127 190 }
5210330a
JM
191 // Coerce existing setting to the right type
192 //Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
193 setter(defval);
125d8bbb
JM
194};
195
5210330a
JM
196// Set group of configuration defaults
197Util.conf_defaults = function(cfg, api, defaults, arr) {
198 var i;
199 for (i = 0; i < arr.length; i++) {
200 Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
201 arr[i][2], arr[i][3], arr[i][4]);
202 }
ff4bfcb7 203};
125d8bbb 204
a8edf9d8 205
15046f00
JM
206/*
207 * Cross-browser routines
208 */
209
6f4b1e40
JM
210
211// Dynamically load scripts without using document.write()
212// Reference: http://unixpapa.com/js/dyna.html
213//
214// Handles the case where load_scripts is invoked from a script that
215// itself is loaded via load_scripts. Once all scripts are loaded the
216// window.onscriptsloaded handler is called (if set).
217Util.get_include_uri = function() {
218 return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
219}
220Util._pending_scripts = [];
221Util.load_scripts = function(files) {
222 var head = document.getElementsByTagName('head')[0],
223 ps = Util._pending_scripts;
224 for (var f=0; f<files.length; f++) {
225 var script = document.createElement('script');
226 script.type = 'text/javascript';
227 script.src = Util.get_include_uri() + files[f];
228 //console.log("loading script: " + Util.get_include_uri() + files[f]);
229 head.appendChild(script);
230 ps.push(script);
231 script.onload = script.onreadystatechange = function (e) {
232 if (!this.readyState ||
233 this.readyState == 'complete' ||
234 this.readyState == 'loaded') {
235 this.onload = this.onreadystatechange = null;
236 if (ps.indexOf(this) >= 0) {
237 //console.log("loaded script: " + this.src);
238 ps.splice(ps.indexOf(this), 1);
239 }
240 // Call window.onscriptsload after last script loads
241 if (ps.length === 0 && window.onscriptsload) {
242 window.onscriptsload();
243 }
244 }
245 }
246 }
247}
248
15046f00
JM
249// Get DOM element position on page
250Util.getPosition = function (obj) {
251 var x = 0, y = 0;
252 if (obj.offsetParent) {
253 do {
254 x += obj.offsetLeft;
255 y += obj.offsetTop;
256 obj = obj.offsetParent;
257 } while (obj);
258 }
259 return {'x': x, 'y': y};
260};
261
262// Get mouse event position in DOM element
125d8bbb 263Util.getEventPosition = function (e, obj, scale) {
15046f00
JM
264 var evt, docX, docY, pos;
265 //if (!e) evt = window.event;
266 evt = (e ? e : window.event);
ad3f7624 267 evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
15046f00
JM
268 if (evt.pageX || evt.pageY) {
269 docX = evt.pageX;
270 docY = evt.pageY;
271 } else if (evt.clientX || evt.clientY) {
272 docX = evt.clientX + document.body.scrollLeft +
273 document.documentElement.scrollLeft;
274 docY = evt.clientY + document.body.scrollTop +
275 document.documentElement.scrollTop;
276 }
277 pos = Util.getPosition(obj);
125d8bbb
JM
278 if (typeof scale === "undefined") {
279 scale = 1;
280 }
281 return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale};
15046f00
JM
282};
283
284
285// Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
286Util.addEvent = function (obj, evType, fn){
d93d3e09 287 if (obj.attachEvent){
15046f00
JM
288 var r = obj.attachEvent("on"+evType, fn);
289 return r;
d93d3e09
JM
290 } else if (obj.addEventListener){
291 obj.addEventListener(evType, fn, false);
292 return true;
15046f00
JM
293 } else {
294 throw("Handler could not be attached");
295 }
296};
297
298Util.removeEvent = function(obj, evType, fn){
d93d3e09 299 if (obj.detachEvent){
15046f00
JM
300 var r = obj.detachEvent("on"+evType, fn);
301 return r;
d93d3e09
JM
302 } else if (obj.removeEventListener){
303 obj.removeEventListener(evType, fn, false);
304 return true;
15046f00
JM
305 } else {
306 throw("Handler could not be removed");
307 }
308};
309
310Util.stopEvent = function(e) {
311 if (e.stopPropagation) { e.stopPropagation(); }
312 else { e.cancelBubble = true; }
313
314 if (e.preventDefault) { e.preventDefault(); }
315 else { e.returnValue = false; }
316};
317
318
319// Set browser engine versions. Based on mootools.
320Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
321
322Util.Engine = {
aa670567
JM
323 // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
324 //'presto': (function() {
c3a172b9 325 // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
aa670567
JM
326 'presto': (function() { return (!window.opera) ? false : true; }()),
327
15046f00
JM
328 'trident': (function() {
329 return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
330 'webkit': (function() {
11bb7a4a
JM
331 try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
332 //'webkit': (function() {
333 // return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
15046f00 334 'gecko': (function() {
8787e49b 335 return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
15046f00 336};
cdb55d26
JM
337if (Util.Engine.webkit) {
338 // Extract actual webkit version if available
339 Util.Engine.webkit = (function(v) {
340 var re = new RegExp('WebKit/([0-9\.]*) ');
341 v = (navigator.userAgent.match(re) || ['', v])[1];
342 return parseFloat(v, 10);
343 })(Util.Engine.webkit);
344}
15046f00
JM
345
346Util.Flash = (function(){
347 var v, version;
348 try {
349 v = navigator.plugins['Shockwave Flash'].description;
350 } catch(err1) {
351 try {
352 v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
353 } catch(err2) {
354 v = '0 r0';
355 }
356 }
357 version = v.match(/\d+/g);
358 return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
a59f1cd2 359}());