]> git.proxmox.com Git - mirror_novnc.git/blob - include/util.js
Refactor dynamic script loading. Add util.js:load_scripts()
[mirror_novnc.git] / include / util.js
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 "use strict";
10 /*jslint bitwise: false, white: false */
11 /*global window, console, document, navigator, ActiveXObject */
12
13 // Globals defined here
14 var Util = {};
15
16
17 /*
18 * Make arrays quack
19 */
20
21 Array.prototype.push8 = function (num) {
22 this.push(num & 0xFF);
23 };
24
25 Array.prototype.push16 = function (num) {
26 this.push((num >> 8) & 0xFF,
27 (num ) & 0xFF );
28 };
29 Array.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
40 if (!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 // requestAnimationFrame shim with setTimeout fallback
62 //
63
64 window.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
75 /*
76 * ------------------------------------------------------
77 * Namespaced in Util
78 * ------------------------------------------------------
79 */
80
81 /*
82 * Logging/debug routines
83 */
84
85 Util._log_level = 'warn';
86 Util.init_logging = function (level) {
87 if (typeof level === 'undefined') {
88 level = Util._log_level;
89 } else {
90 Util._log_level = level;
91 }
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 };
118 Util.get_logging = function () {
119 return Util._log_level;
120 };
121 // Initialize logging level
122 Util.init_logging();
123
124
125 // Set configuration default for Crockford style function namespaces
126 Util.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;
144 } else {
145 val = true;
146 }
147 } else if (type in {'integer':1, 'int':1}) {
148 val = parseInt(val, 10);
149 } else if (type === 'str') {
150 val = String(val);
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;
169 }
170
171 // Set the setter function with extra sanity checks
172 if (typeof api['set_' + v] === 'undefined') {
173 api['set_' + v] = function (val, idx) {
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");
179 }
180 setter(val, idx);
181 };
182 }
183
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 = [];
190 }
191 // Coerce existing setting to the right type
192 //Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
193 setter(defval);
194 };
195
196 // Set group of configuration defaults
197 Util.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 }
203 };
204
205
206 /*
207 * Cross-browser routines
208 */
209
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).
217 Util.get_include_uri = function() {
218 return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
219 }
220 Util._pending_scripts = [];
221 Util.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
249 // Get DOM element position on page
250 Util.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
263 Util.getEventPosition = function (e, obj, scale) {
264 var evt, docX, docY, pos;
265 //if (!e) evt = window.event;
266 evt = (e ? e : window.event);
267 evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
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);
278 if (typeof scale === "undefined") {
279 scale = 1;
280 }
281 return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale};
282 };
283
284
285 // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
286 Util.addEvent = function (obj, evType, fn){
287 if (obj.attachEvent){
288 var r = obj.attachEvent("on"+evType, fn);
289 return r;
290 } else if (obj.addEventListener){
291 obj.addEventListener(evType, fn, false);
292 return true;
293 } else {
294 throw("Handler could not be attached");
295 }
296 };
297
298 Util.removeEvent = function(obj, evType, fn){
299 if (obj.detachEvent){
300 var r = obj.detachEvent("on"+evType, fn);
301 return r;
302 } else if (obj.removeEventListener){
303 obj.removeEventListener(evType, fn, false);
304 return true;
305 } else {
306 throw("Handler could not be removed");
307 }
308 };
309
310 Util.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.
320 Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
321
322 Util.Engine = {
323 // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
324 //'presto': (function() {
325 // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
326 'presto': (function() { return (!window.opera) ? false : true; }()),
327
328 'trident': (function() {
329 return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
330 'webkit': (function() {
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); }()),
334 'gecko': (function() {
335 return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
336 };
337 if (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 }
345
346 Util.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};
359 }());