瀏覽代碼

Various cross-browser fixes.

Now working under Arora 0.5.

But not Konqueror 4.2.2 (WebSockets never connects).

IE support with excanvas still pending.
Joel Martin 15 年之前
父節點
當前提交
d93d3e09ab
共有 9 個文件被更改,包括 238 次插入99 次删除
  1. 8 3
      README.md
  2. 4 4
      include/base64.js
  3. 156 40
      include/canvas.js
  4. 8 8
      include/util.js
  5. 8 5
      include/vnc.js
  6. 46 33
      tests/canvas.html
  7. 0 0
      tests/face.png.js
  8. 4 6
      tests/input.html
  9. 4 0
      vnc.html

+ 8 - 3
README.md

@@ -84,11 +84,16 @@ Browser Support
 
 I only currently test under Linux. Here are the current results:
 
-* Chrome 5.0.* beta: Works great. Native WebSockets support. Very fast.
+* Chrome 5.0.375.29 beta: Works great. Native WebSockets support. Very
+  fast.
 * firefox 3.5, 3.7: Works. Uses flash WebSockets emulator. Large
-  desktops with full-color image backgrounds are slow.
+  full-color images are slow.
+* Arora 0.50: Works. Broken putImageData so large full-color images
+  are slow.
+
 * Opera 10.10: Unusable: drops web-socket-js events.
-* Opera 10.60: Unusable: throws "WRONG_ARGUMENTS_ERR" on connect.
+* Opera 10.60: Broken: throws "WRONG_ARGUMENTS_ERR" on connect.
+* Konqueror 4.2.2: Broken: flash WebSockets emulator never connects.
 
 
 Integration

+ 4 - 4
include/base64.js

@@ -49,7 +49,7 @@ base64Pad     : '=',
 
 encode: function (data) {
     var result = '';
-    var chrTable = Base64.toBase64Table;
+    var chrTable = Base64.toBase64Table.split('');
     var pad = Base64.base64Pad;
     var length = data.length;
     var i;
@@ -107,11 +107,11 @@ decode: function (data, offset) {
     // Convert one by one.
     var idx = 0;
     for (var i = offset; i < data.length; i++) {
-        var c = binTable[data[i].charCodeAt(0) & 0x7f];
-        var padding = (data[i] == pad);
+        var c = binTable[data.charCodeAt(i) & 0x7f];
+        var padding = (data.charAt(i) == pad);
         // Skip illegal characters and whitespace
         if (c == -1) {
-            console.log("Illegal character '" + data[i].charCodeAt(0) + "'");
+            console.log("Illegal character '" + data.charCodeAt(i) + "'");
             continue;
         }
         

+ 156 - 40
include/canvas.js

@@ -10,10 +10,22 @@
 /*jslint white: false, bitwise: false */
 /*global window, console, $, Util */
 
+var Canvas, Canvas_native;
+
+(function () {
+    var pre, extra = "", start, end;
+    if (document.createElement('canvas').getContext) {
+        Canvas_native = true;
+    } else {
+        Canvas_native = false;
+        document.write("<script src='excanvas'><\/script>");
+    }
+}());
+
 // Everything namespaced inside Canvas
-var Canvas = {
+Canvas = {
 
-prefer_js : false,
+prefer_js  : false,
 
 true_color : false,
 colourMap  : [],
@@ -93,7 +105,7 @@ onKeyDown: function (e) {
 },
 
 onKeyUp : function (e) {
-    //console.log("keyup: " + e.key + "(" + e.code + ")");
+    //console.log("keyup: " + Canvas.getKeysym(e));
     if (! Canvas.focused) {
         return true;
     }
@@ -122,17 +134,86 @@ onMouseDisable: function (e) {
 },
 
 
-init: function (id, width, height, true_color, keyPress,
-                mouseButton, mouseMove) {
+init: function (id) {
+    var c, imgTest, arora;
     console.log(">> Canvas.init");
 
     Canvas.id = id;
+    c = $(Canvas.id);
+
+    if (Canvas_native) {
+        console.log("Using native canvas");
+        // Use default Canvas functions
+    } else {
+        console.warn("Using excanvas canvas emulation");
+        G_vmlCanvasManager.initElement(c);
+    }
+
+    if (! c.getContext) { throw("No getContext method"); }
+    Canvas.ctx = c.getContext('2d'); 
+
+    Canvas.clear();
+
+
+    /*
+     * Determine browser feature support and most optimal rendering
+     * methods
+     */
+    tval = 0;
+    try {
+        imgTest = Canvas.ctx.getImageData(0, 0, 1,1);
+        imgTest.data[0] = 123;
+        imgTest.data[3] = 255;
+        Canvas.ctx.putImageData(imgTest, 0, 0);
+        tval = Canvas.ctx.getImageData(0, 0, 1, 1).data[0];
+    } catch (exc) {
+    }
+    if (tval === 123) {
+        Canvas._rgbxImage = Canvas._rgbxImageData;
+        Canvas._cmapImage = Canvas._cmapImageData;
+        if (Canvas.ctx.createImageData) {
+            // If it's there, it's faster
+            console.log("Using Canvas createImageData");
+            Canvas._imageData = Canvas._imageDataCreate;
+        } else if (Canvas.ctx.getImageData) {
+            console.log("Using Canvas getImageData");
+            Canvas._imageData = Canvas._imageDataGet;
+        } else {
+            console.log("No imageData support");
+            return false;
+        }
+        if (Util.Engine.webkit) {
+            console.log("Prefering javascript operations");
+            Canvas.prefer_js = true;
+        } else {
+            console.log("Prefering Canvas operations");
+            Canvas.prefer_js = false;
+        }
+    } else {
+        console.log("No imageData, using fillRect (slow)");
+        Canvas._rgbxImage = Canvas._rgbxImageFill;
+        Canvas._cmapImage = Canvas._cmapImageFill;
+        Canvas.prefer_js = false;
+    }
 
+    Canvas.colourMap = [];
+    Canvas.prevStyle = "";
+    Canvas.focused = true;
+
+    //console.log("<< Canvas.init");
+    return true;
+},
+    
+
+start: function (keyPress, mouseButton, mouseMove) {
+    var c;
+    console.log(">> Canvas.start");
+
+    c = $(Canvas.id);
     Canvas.keyPress = keyPress || null;
     Canvas.mouseButton = mouseButton || null;
     Canvas.mouseMove = mouseMove || null;
 
-    var c = $(Canvas.id);
     Util.addEvent(document, 'keydown', Canvas.onKeyDown);
     Util.addEvent(document, 'keyup', Canvas.onKeyUp);
     Util.addEvent(c, 'mousedown', Canvas.onMouseDown);
@@ -145,34 +226,26 @@ init: function (id, width, height, true_color, keyPress,
     Util.addEvent(document, 'click', Canvas.onMouseDisable);
     Util.addEvent(document.body, 'contextmenu', Canvas.onMouseDisable);
 
-    Canvas.resize(width, height);
-    Canvas.c_wx = c.offsetWidth;
-    Canvas.c_wy = c.offsetHeight;
-    Canvas.true_color = true_color;
-    Canvas.colourMap = [];
-
-    if (! c.getContext) { return; }
-    Canvas.ctx = c.getContext('2d'); 
-    
-    Canvas.prevStyle = "";
-    Canvas.focused = true;
-
-    if (Util.Engine.webkit) {
-        Canvas.prefer_js = true;
-    }
-
-    //console.log("<< Canvas.init");
+    //console.log("<< Canvas.start");
 },
 
 clear: function () {
-    Canvas.ctx.clearRect(0, 0, Canvas.c_wx, Canvas.c_wy);
     Canvas.resize(640, 20);
+    Canvas.ctx.clearRect(0, 0, Canvas.c_wx, Canvas.c_wy);
 },
 
-resize: function (width, height) {
+resize: function (width, height, true_color) {
     var c = $(Canvas.id);
+
+    if (typeof true_color !== "undefined") {
+        Canvas.true_color = true_color;
+    }
+
     c.width = width;
     c.height = height;
+
+    Canvas.c_wx = c.offsetWidth;
+    Canvas.c_wy = c.offsetHeight;
 },
 
 stop: function () {
@@ -226,7 +299,7 @@ getTile: function(x, y, width, height, color) {
     return img;
 },
 
-setTile: function(img, x, y, w, h, color) {
+setSubTile: function(img, x, y, w, h, color) {
     var data, p, rgb, red, green, blue, width, j, i;
     if (Canvas.prefer_js) {
         data = img.data;
@@ -255,19 +328,25 @@ setTile: function(img, x, y, w, h, color) {
 
 putTile: function(img) {
     if (Canvas.prefer_js) {
-        Canvas.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
-        //Canvas.ctx.putImageData(img, img.x, img.y);
+        Canvas._rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
     } else {
-        // No-op, under gecko already done by setTile
+        // No-op, under gecko already done by setSubTile
     }
 },
 
+_imageDataGet: function(width, height) {
+    return Canvas.ctx.getImageData(0, 0, width, height);
+},
+_imageDataCreate: function(width, height) {
+    return Canvas.ctx.createImageData(width, height);
+},
+_imageDataRaw: function(width, height) {
+    return {'data': [], 'width': width, 'height': height};
+},
 
-rgbxImage: function(x, y, width, height, arr, offset) {
+_rgbxImageData: function(x, y, width, height, arr, offset) {
     var img, i, j, data;
-    //console.log("rfbxImage: img: " + img + " x: " + x + " y: " + y + " width: " + width + " height: " + height);
-    /* Old firefox and Opera don't support createImageData */
-    img = Canvas.ctx.getImageData(0, 0, width, height);
+    img = Canvas._imageData(width, height);
     data = img.data;
     for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
         data[i + 0] = arr[j + 0];
@@ -278,13 +357,25 @@ rgbxImage: function(x, y, width, height, arr, offset) {
     Canvas.ctx.putImageData(img, x, y);
 },
 
-cmapImage: function(x, y, width, height, arr, offset) {
+// really slow fallback if we don't have imageData
+_rgbxImageFill: function(x, y, width, height, arr, offset) {
+    var sx = 0, sy = 0;
+    for (i=0, j=offset; i < (width * height); i+=1, j+=4) {
+        Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]);
+        sx += 1;
+        if ((sx % width) === 0) {
+            sx = 0;
+            sy += 1;
+        }
+    }
+},
+
+_cmapImageData: function(x, y, width, height, arr, offset) {
     var img, i, j, data, rgb, cmap;
-    img = Canvas.ctx.getImageData(0, 0, width, height);
+    img = Canvas._imageData(width, height);
     data = img.data;
     cmap = Canvas.colourMap;
-    //console.log("cmapImage x: " + x + ", y: " + y + "arr.slice(0,20): " + arr.slice(0,20));
-    for (i=0, j=offset; i < (width * height * 4); i=i+4, j += 1) {
+    for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
         rgb = cmap[arr[j]];
         data[i + 0] = rgb[0];
         data[i + 1] = rgb[1];
@@ -294,15 +385,36 @@ cmapImage: function(x, y, width, height, arr, offset) {
     Canvas.ctx.putImageData(img, x, y);
 },
 
+_cmapImageFill: function(x, y, width, height, arr, offset) {
+    var sx = 0, sy = 0;
+    cmap = Canvas.colourMap;
+    console.log("here1: arr[2]: " + arr[2] + ", cmap[arr[2]]: " + cmap[arr[2]]);
+    for (i=0, j=offset; i < (width * height); i+=1, j+=1) {
+        Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j]]);
+        sx += 1;
+        if ((sx % width) === 0) {
+            sx = 0;
+            sy += 1;
+        }
+    }
+},
+
+
 blitImage: function(x, y, width, height, arr, offset) {
     if (Canvas.true_color) {
-        Canvas.rgbxImage(x, y, width, height, arr, offset);
+        Canvas._rgbxImage(x, y, width, height, arr, offset);
     } else {
-        Canvas.cmapImage(x, y, width, height, arr, offset);
+        Canvas._cmapImage(x, y, width, height, arr, offset);
     }
 },
 
-fillRect: function(x, y, width, height, color) {
+blitStringImage: function(str, x, y) {
+    var img = new Image();
+    img.onload = function () { Canvas.ctx.drawImage(img, x, y); };
+    img.src = str;
+},
+
+setFillColor: function(color) {
     var rgb, newStyle;
     if (Canvas.true_color) {
         rgb = color;
@@ -314,6 +426,10 @@ fillRect: function(x, y, width, height, color) {
         Canvas.ctx.fillStyle = newStyle;
         Canvas.prevStyle = newStyle;
     }
+},
+
+fillRect: function(x, y, width, height, color) {
+    Canvas.setFillColor(color);
     Canvas.ctx.fillRect(x, y, width, height);
 },
 

+ 8 - 8
include/util.js

@@ -171,24 +171,24 @@ Util.getEventPosition = function (e, obj) {
 
 // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
 Util.addEvent = function (obj, evType, fn){
-    if (obj.addEventListener){
-        obj.addEventListener(evType, fn, false); 
-        return true;
-    } else if (obj.attachEvent){
+    if (obj.attachEvent){
         var r = obj.attachEvent("on"+evType, fn);
         return r;
+    } else if (obj.addEventListener){
+        obj.addEventListener(evType, fn, false); 
+        return true;
     } else {
         throw("Handler could not be attached");
     }
 };
 
 Util.removeEvent = function(obj, evType, fn){
-    if (obj.removeEventListener){
-        obj.removeEventListener(evType, fn, false);
-        return true;
-    } else if (obj.detachEvent){
+    if (obj.detachEvent){
         var r = obj.detachEvent("on"+evType, fn);
         return r;
+    } else if (obj.removeEventListener){
+        obj.removeEventListener(evType, fn, false);
+        return true;
     } else {
         throw("Handler could not be removed");
     }

+ 8 - 5
include/vnc.js

@@ -64,7 +64,7 @@ true_color     : false,
 
 b64encode      : true,  // false means UTF-8 on the wire
 //b64encode      : false,  // false means UTF-8 on the wire
-connectTimeout : 1000,  // time to wait for connection
+connectTimeout : 3000,  // time to wait for connection
 
 
 // In preference order
@@ -140,6 +140,9 @@ load: function () {
         }
     }
 
+    // Initialize canvas/fxcanvas
+    Canvas.init(RFB.canvasID);
+
     // Populate encoding lookup tables
     RFB.encHandlers = {};
     RFB.encNames = {};
@@ -466,8 +469,8 @@ init_msg: function () {
         name_length   = RQ.shift32();
         RFB.fb_name = RQ.shiftStr(name_length);
 
-        Canvas.init(RFB.canvasID, RFB.fb_width, RFB.fb_height, RFB.true_color,
-                RFB.keyPress, RFB.mouseButton, RFB.mouseMove);
+        Canvas.resize(RFB.fb_width, RFB.fb_height, RFB.true_color);
+        Canvas.start(RFB.keyPress, RFB.mouseButton, RFB.mouseMove);
 
         if (RFB.true_color) {
             RFB.fb_Bpp           = 4;
@@ -873,7 +876,7 @@ display_hextile: function() {
                     sw = (wh >> 4)   + 1;
                     sh = (wh & 0x0f) + 1;
 
-                    Canvas.setTile(tile, sx, sy, sw, sh, color);
+                    Canvas.setSubTile(tile, sx, sy, sw, sh, color);
                 }
             }
             Canvas.putTile(tile);
@@ -1191,7 +1194,7 @@ recv_message_reorder: function(e) {
     } else {
         console.warn("sequence number mismatch: expected " +
                      RFB.RQ_seq_num + ", got " + seq_num);
-        if (RFB.RQ_reorder.length > 20) {
+        if (RFB.RQ_reorder.length > 40) {
             RFB.updateState('failed', "Re-order queue too long");
         } else {
             RFB.RQ_reorder = RFB.RQ_reorder.concat(e.data.substr(0));

+ 46 - 33
tests/canvas.html

@@ -3,13 +3,16 @@
     <body>
         Iterations: <input id='iterations' style='width:50' value="100">&nbsp;
 
-        <input id='startButton' type='button' value='Start' style='width:100px'
-            onclick="start();">&nbsp;
+        Width: <input id='width' style='width:50' value="640">&nbsp;
+        Height: <input id='height' style='width:50' value="480">&nbsp;
+
+        <input id='startButton' type='button' value='Do Performance Test'
+            style='width:150px' onclick="begin();">&nbsp;
 
         <br><br>
 
-        Canvas:<br>
-        <canvas id="canvas" width="640" height="20"
+        <b>Canvas</b> (should see three squares and two happy faces):<br>
+        <canvas id="canvas" width="200" height="100"
                 style="border-style: dotted; border-width: 1px;">
             Canvas not supported.
         </canvas>
@@ -23,14 +26,12 @@
     <script type='text/javascript' 
         src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
     -->
-
-    <script src="include/mootools.js"></script>
     <script src="include/util.js"></script>
     <script src="include/canvas.js"></script>
-
+    <script src="face.png.js"></script>
     <script>
         var msg_cnt = 0;
-        var width = 800, height = 600;
+        var start_width = 300, start_height = 100;
         var iterations;
 
         function message(str) {
@@ -40,35 +41,39 @@
             cell.scrollTop = cell.scrollHeight;
         }
 
-        function draw () {
+        function test_functions () {
             var img, x, y;
-            /* Border */
-            Canvas.ctx.stroke();  
-            Canvas.ctx.rect(0, 0, Canvas.c_wx, Canvas.c_wy);
-            Canvas.ctx.stroke();  
+            Canvas.fillRect(0, 0, Canvas.c_wx, Canvas.c_wy, [240,240,240]);
+
+            Canvas.blitStringImage("data:image/png;base64," + face64, 150, 40);
 
-            /*
-            // Does not work in firefox
             var himg = new Image();
-            himg.src = "head_ani2.gif"
-            Canvas.ctx.drawImage(himg, 10, 10);
-            */
+            himg.onload = function () {
+                Canvas.ctx.drawImage(himg, 200, 10); };
+            himg.src = "face.png";
 
             /* Test array image data */
-            //img = Canvas.ctx.createImageData(50, 50);
-            img = Canvas.ctx.getImageData(0, 0, 50, 50);
+            data = [];
             for (y=0; y< 50; y++) {
                 for (x=0; x< 50; x++) {
-                    img.data[(y*50 + x)*4 + 0] = 255 - parseInt((255 / 50) * y, 10);
-                    img.data[(y*50 + x)*4 + 1] = parseInt((255 / 50) * y, 10);
-                    img.data[(y*50 + x)*4 + 2] = parseInt((255 / 50) * x, 10);
-                    img.data[(y*50 + x)*4 + 3] = 255;
+                    data[(y*50 + x)*4 + 0] = 255 - parseInt((255 / 50) * y, 10);
+                    data[(y*50 + x)*4 + 1] = parseInt((255 / 50) * y, 10);
+                    data[(y*50 + x)*4 + 2] = parseInt((255 / 50) * x, 10);
+                    data[(y*50 + x)*4 + 3] = 255;
                 }
             }
-            Canvas.ctx.putImageData(img, 100, 100);
+            Canvas.blitImage(30, 10, 50, 50, data, 0);
+
+            //Canvas.prefer_js = false;
+            img = Canvas.getTile(5,5,16,16,[0,128,128]);
+            Canvas.putTile(img);
+
+            img = Canvas.getTile(90,15,16,16,[0,0,0]);
+            Canvas.setSubTile(img, 0,0,16,16,[128,128,0]);
+            Canvas.putTile(img);
         }
 
-        function start () {
+        function begin () {
             $('startButton').value = "Running";
             $('startButton').disabled = true;
             setTimeout(start_delayed, 250);
@@ -89,18 +94,26 @@
             message("prefer Canvas ops: " + time2 + "ms total, " +
                     (time2 / iterations) + "ms per frame");
 
+            Canvas.resize(start_width, start_height, true);
+            test_functions();
             $('startButton').disabled = false;
             $('startButton').value = "Start";
         }
 
         function run_test () {
-            var color, start_time = (new Date()).getTime();
+            var width, height;
+            width = $('width').value;
+            height = $('height').value;
+            Canvas.resize(width, height);
+            var color, start_time = (new Date()).getTime(), w, h;
             for (var i=0; i < iterations; i++) {
                 color = [128, 128, (255 / iterations) * i, 0];
                 for (var x=0; x < width; x = x + 16) {
-                    for (var y=0; y < width; y = y + 16) {
-                        var tile = Canvas.getTile(x, y, 16, 16, color);
-                        Canvas.setTile(tile, 0, 0, 16, 16, color);
+                    for (var y=0; y < height; y = y + 16) {
+                        w = Math.min(16, width - x);
+                        h = Math.min(16, height - y);
+                        var tile = Canvas.getTile(x, y, w, h, color);
+                        Canvas.setSubTile(tile, 0, 0, w, h, color);
                         Canvas.putTile(tile);
                     }
                 }
@@ -110,11 +123,11 @@
         }
 
         window.onload = function() {
-            Canvas.init('canvas', width, height);
-            Canvas.stop();   // Shut-off event interception
             $('iterations').value = 10;
-            draw();
+            Canvas.init('canvas');
+            Canvas.resize(start_width, start_height, true);
             message("Canvas initialized");
+            test_functions();
         }
     </script>
 </html>

File diff suppressed because it is too large
+ 0 - 0
tests/face.png.js


+ 4 - 6
tests/input.html

@@ -18,14 +18,11 @@
     <script type='text/javascript' 
         src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
     -->
-
-    <script src="include/mootools.js"></script>
     <script src="include/util.js"></script>
     <script src="include/canvas.js"></script>
-
     <script>
         var msg_cnt = 0;
-        var width = 1280, height = 600;
+        var width = 400, height = 200;
         var iterations;
 
         function message(str) {
@@ -54,8 +51,9 @@
         }
 
         window.onload = function() {
-            Canvas.init('canvas', width, height, true, keyPress,
-                        mouseButton, mouseMove);
+            Canvas.init('canvas');
+            Canvas.resize(width, height);
+            Canvas.start(keyPress, mouseButton, mouseMove);
             message("Canvas initialized");
         }
     </script>

+ 4 - 0
vnc.html

@@ -11,6 +11,10 @@ noVNC example: simple example using default controls
         <div id='vnc'>Loading</div>
     </body>
 
+    <!--
+    <script type='text/javascript' 
+        src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+    -->
     <script src="include/vnc.js"></script>
     <script src="include/default_controls.js"></script>
     <script>

Some files were not shown because too many files changed in this diff