Index: tests/Layer/ArcGIS93Rest.html
===================================================================
--- tests/Layer/ArcGIS93Rest.html	(revision 12311)
+++ tests/Layer/ArcGIS93Rest.html	(working copy)
@@ -75,10 +75,10 @@
         t.eq( tile.url,
              url + "?" + OpenLayers.Util.getParameterString(tParams),
              "image src is created correctly via addtile" );
-        t.eq( tile.frame.style.top, "6px", "image top is set correctly via addtile" );
-        t.eq( tile.frame.style.left, "5px", "image top is set correctly via addtile" );
+        t.eq( tile.getTile().style.top, "6px", "image top is set correctly via addtile" );
+        t.eq( tile.getTile().style.left, "5px", "image top is set correctly via addtile" );
 
-        var firstChild = layer.div.firstChild.firstChild;
+        var firstChild = layer.div.firstChild;
         t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
         t.ok( firstChild == img, "div first child is correct image object" );
         t.eq( tile.position.toString(), "x=5,y=6", "Position of tile is set correctly." );
@@ -203,10 +203,10 @@
         map.addLayer(tLayer);
         map.zoomToMaxExtent();
         t.eq(tLayer.opacity, "0.5", "Opacity is set correctly");
-        t.eq(parseFloat(tLayer.div.firstChild.firstChild.style.opacity), 0.5, "Opacity on tile is correct");
+        t.eq(parseFloat(tLayer.div.firstChild.style.opacity), 0.5, "Opacity on tile is correct");
         tLayer.setOpacity("0.6");
         t.eq(tLayer.opacity, "0.6", "setOpacity works properly");
-        t.eq(parseFloat(tLayer.div.firstChild.firstChild.style.opacity), 0.6, "Opacity on tile is changed correctly");
+        t.eq(parseFloat(tLayer.div.firstChild.style.opacity), 0.6, "Opacity on tile is changed correctly");
         var pixel = new OpenLayers.Pixel(5,6);
         var tile = tLayer.addTile(new OpenLayers.Bounds(1,2,3,4), pixel);
         tile.draw();
Index: tests/Layer/MapServer.html
===================================================================
--- tests/Layer/MapServer.html	(revision 12311)
+++ tests/Layer/MapServer.html	(working copy)
@@ -56,10 +56,10 @@
         t.eq( tile.url,
              url + "?" + OpenLayers.Util.getParameterString(tParams).replace(/,/g, "+"),
              "image src is created correctly via addtile" );
-        t.eq( tile.frame.style.top, "6px", "image top is set correctly via addtile" );
-        t.eq( tile.frame.style.left, "5px", "image top is set correctly via addtile" );
+        t.eq( tile.getTile().style.top, "6px", "image top is set correctly via addtile" );
+        t.eq( tile.getTile().style.left, "5px", "image top is set correctly via addtile" );
 
-        var firstChild = layer.div.firstChild.firstChild;
+        var firstChild = layer.div.firstChild;
         t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
         t.ok( firstChild == img, "div first child is correct image object" );
         t.eq( tile.position.toString(), "x=5,y=6", "Position of tile is set correctly." );
@@ -204,10 +204,10 @@
         map.addLayer(tLayer);
         map.zoomToMaxExtent();
         t.eq(tLayer.opacity, "0.5", "Opacity is set correctly");
-        t.eq(parseFloat(tLayer.div.firstChild.firstChild.style.opacity), 0.5, "Opacity on tile is correct");
+        t.eq(parseFloat(tLayer.div.firstChild.style.opacity), 0.5, "Opacity on tile is correct");
         tLayer.setOpacity("0.6");
         t.eq(tLayer.opacity, "0.6", "setOpacity works properly");
-        t.eq(parseFloat(tLayer.div.firstChild.firstChild.style.opacity), 0.6, "Opacity on tile is changed correctly");
+        t.eq(parseFloat(tLayer.div.firstChild.style.opacity), 0.6, "Opacity on tile is changed correctly");
         var pixel = new OpenLayers.Pixel(5,6);
         var tile = tLayer.addTile(new OpenLayers.Bounds(1,2,3,4), pixel);
         tile.draw();
@@ -419,10 +419,10 @@
         map.addLayer(tLayer);
         map.zoomToMaxExtent();
         t.eq(tLayer.opacity, "0.5", "Opacity is set correctly");
-        t.eq(parseFloat(tLayer.div.firstChild.firstChild.style.opacity), 0.5, "Opacity on tile is correct");
+        t.eq(parseFloat(tLayer.div.firstChild.style.opacity), 0.5, "Opacity on tile is correct");
         tLayer.setOpacity("0.6");
         t.eq(tLayer.opacity, "0.6", "setOpacity works properly");
-        t.eq(parseFloat(tLayer.div.firstChild.firstChild.style.opacity), 0.6, "Opacity on tile is changed correctly");
+        t.eq(parseFloat(tLayer.div.firstChild.style.opacity), 0.6, "Opacity on tile is changed correctly");
         map.destroy();
 
     }    
Index: tests/Layer/WMS.html
===================================================================
--- tests/Layer/WMS.html	(revision 12311)
+++ tests/Layer/WMS.html	(working copy)
@@ -86,10 +86,10 @@
         t.eq( tile.url,
              layer.getFullRequestString(tParams),
              "image src is created correctly via addtile" );
-        t.eq( tile.frame.style.top, "6px", "image top is set correctly via addtile" );
-        t.eq( tile.frame.style.left, "5px", "image top is set correctly via addtile" );
+        t.eq( tile.getTile().style.top, "6px", "image top is set correctly via addtile" );
+        t.eq( tile.getTile().style.left, "5px", "image top is set correctly via addtile" );
 
-        var firstChild = layer.div.firstChild.firstChild;
+        var firstChild = layer.div.firstChild;
         t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
         t.ok( firstChild == img, "div first child is correct image object" );
         t.eq( tile.position.toString(), "x=5,y=6", "Position of tile is set correctly." );
@@ -117,10 +117,10 @@
         t.eq( tile.url,
              layer.getFullRequestString(tParams),
              "image src is created correctly via addtile" );
-        t.eq( tile.frame.style.top, "6px", "image top is set correctly via addtile" );
-        t.eq( tile.frame.style.left, "5px", "image top is set correctly via addtile" );
+        t.eq( tile.getTile().style.top, "6px", "image top is set correctly via addtile" );
+        t.eq( tile.getTile().style.left, "5px", "image top is set correctly via addtile" );
 
-        var firstChild = layer.div.firstChild.firstChild;
+        var firstChild = layer.div.firstChild;
         t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
         t.ok( firstChild, img, "div first child is correct image object" );
         t.eq( tile.position.toString(), "x=5,y=6", "Position of tile is set correctly." );
@@ -284,10 +284,10 @@
         map.addLayer(tLayer);
         map.zoomToMaxExtent();
         t.eq(tLayer.opacity, "0.5", "Opacity is set correctly");
-        t.eq(parseFloat(tLayer.div.firstChild.firstChild.style.opacity), 0.5, "Opacity on tile is correct");
+        t.eq(parseFloat(tLayer.div.firstChild.style.opacity), 0.5, "Opacity on tile is correct");
         tLayer.setOpacity("0.6");
         t.eq(tLayer.opacity, "0.6", "setOpacity works properly");
-        t.eq(parseFloat(tLayer.div.firstChild.firstChild.style.opacity), 0.6, "Opacity on tile is changed correctly");
+        t.eq(parseFloat(tLayer.div.firstChild.style.opacity), 0.6, "Opacity on tile is changed correctly");
         var pixel = new OpenLayers.Pixel(5,6);
         var tile = tLayer.addTile(new OpenLayers.Bounds(1,2,3,4), pixel);
         tile.draw();
Index: tests/Tile/Image.html
===================================================================
--- tests/Tile/Image.html	(revision 12311)
+++ tests/Tile/Image.html	(working copy)
@@ -93,7 +93,7 @@
     }
 
     function test_Tile_Image_draw (t) {
-        t.plan( 7 );
+        t.plan( 8 );
 
         var map = new OpenLayers.Map('map');
         
@@ -140,8 +140,9 @@
                  "http://labs.metacarta.com/TESTURL?" + OpenLayers.Util.getParameterString(tParams),
                  "tile.draw creates an image");
         });
-        t.eq( tile.imgDiv.style.width, "100%", "Image width is correct" );
-        t.eq( tile.imgDiv.style.height, "100%", "Image height is correct" );
+        t.eq( tile.imgDiv.style.width, "5px", "Image width is correct" );
+        t.eq( tile.imgDiv.style.height, "6px", "Image height is correct" );
+        t.ok( tile.imgDiv.parentNode === layer.div, "Image is directly appended to the layer div" );
 
         // this should trigger a "reload" event (since the image never actually
         // loads in tests)
@@ -310,9 +311,10 @@
         map.setCenter(new OpenLayers.LonLat(0,0), 5);
         var tile = layer.grid[0][0];
         var img = tile.imgDiv;
+        var left = img.style.left;
         var backBuffer = tile.createBackBuffer();
-        t.eq(backBuffer.style.left, tile.frame.style.left, "backBuffer tile has same left style as frame");
-        t.ok(backBuffer.firstChild === img, "image appended to backBuffer");
+        t.eq(backBuffer.style.left, left, "backBuffer tile has same left style as frame");
+        t.ok(backBuffer === img, "image returned as backBuffer");
         t.ok(tile.imgDiv == null, "image reference removed from tile");
         map.destroy();
     }
Index: lib/OpenLayers/Tile/Image/IFrame.js
===================================================================
--- lib/OpenLayers/Tile/Image/IFrame.js	(revision 12311)
+++ lib/OpenLayers/Tile/Image/IFrame.js	(working copy)
@@ -65,9 +65,12 @@
     
     /**
      * Method: createImage
-     * Creates the content for the frame on the tile.
+     * Creates or returns the content for the frame on the tile.
+     *
+     * Returns:
+     * {HTMLImageElement|HTMLIframeElement}
      */
-    createImage: function() {
+    getImage: function() {
         if (this.useIFrame === true) {
             if (!this.frame.childNodes.length) {
                 var eventPane = document.createElement("div"),
@@ -121,7 +124,7 @@
             this.imgDiv = iframe;
             return iframe;
         } else {
-            return OpenLayers.Tile.Image.prototype.createImage.apply(this, arguments);
+            return OpenLayers.Tile.Image.prototype.getImage.apply(this, arguments);
         }
     },
 
@@ -171,7 +174,6 @@
         if (this.useIFrame === true) {
             if (url) {
                 var form = this.createRequestForm();
-                this.frame.appendChild(this.imgDiv);
                 this.frame.appendChild(form);
                 form.submit();
                 this.frame.removeChild(form);
Index: lib/OpenLayers/Tile/BackBufferable.js
===================================================================
--- lib/OpenLayers/Tile/BackBufferable.js	(revision 12311)
+++ lib/OpenLayers/Tile/BackBufferable.js	(working copy)
@@ -129,7 +129,7 @@
             // (2) the tile is not appended to the layer's div
             noParent = tile && tile.parentNode !== layer.div,
             // (3) we don't have a tile available that we could use as buffer
-            noTile = !(tile && tile.childNodes.length > 0),
+            noTile = !(tile && (tile.src || tile.childNodes.length > 0)),
             // (4) no backbuffer is displayed for a tile that's still loading
             noBackBuffer = !backBuffer && this.isLoading;            
         if (notNeeded || noParent || noTile || noBackBuffer) {
Index: lib/OpenLayers/Tile/Image.js
===================================================================
--- lib/OpenLayers/Tile/Image.js	(revision 12311)
+++ lib/OpenLayers/Tile/Image.js	(working copy)
@@ -35,7 +35,8 @@
     /**
      * Property: frame
      * {DOMElement} The image element is appended to the frame.  Any gutter on
-     * the image will be hidden behind the frame. 
+     * the image will be hidden behind the frame. If no gutter is set, this
+     * will be null.
      */ 
     frame: null, 
 
@@ -59,6 +60,13 @@
     asyncRequestId: null,
     
     /**
+     * Property: frameless
+     * {Boolean} false if an extra frame needs to be used for gutter handling.
+     * If true, <frame> will be null.
+     */
+    frameless: false,
+
+    /**
      * Property: blankImageUrl
      * {String} Using a data scheme url is not supported by all browsers, but
      * we don't care because we either set it as css backgroundImage, or the
@@ -99,13 +107,16 @@
         OpenLayers.Tile.BackBufferable.prototype.initialize.apply(this, arguments);
 
         this.url = url; //deprecated remove me
-
-        this.frame = document.createElement("div");
-        this.frame.style.position = "absolute";
-        this.frame.style.overflow = "hidden";
         
         this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();
 
+        this.frameless = this.maxGetUrlLength == null && !this.layer.gutter &&
+            !this.layerAlphaHack;
+        if (!this.frameless) {
+            this.frame = document.createElement("div");
+            this.frame.style.position = "absolute";
+            this.frame.style.overflow = "hidden";
+        }
         if (this.maxGetUrlLength != null) {
             OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
         }
@@ -116,7 +127,7 @@
      * nullify references to prevent circular references and memory leaks
      */
     destroy: function() {
-        if (this.frame != null)  {
+        if (this.imgDiv)  {
             this.clear();
             this.imgDiv = null;
             this.frame = null;
@@ -160,7 +171,7 @@
      *     position it correctly, and set its url.
      */
     renderTile: function() {
-        this.layer.div.appendChild(this.frame);
+        this.layer.div.appendChild(this.getTile());
         if (this.layer.async) {
             // Asynchronous image requests call the asynchronous getURL method
             // on the layer to fetch an image that covers 'this.bounds', in the scope of
@@ -186,7 +197,7 @@
      * code.
      */
     positionTile: function() {
-        var style = this.frame.style;
+        var style = this.getTile().style;
         style.left = this.position.x + "px";
         style.top = this.position.y + "px";
         style.width = this.size.w + "px";
@@ -202,8 +213,9 @@
         var img = this.imgDiv;
         if (img) {
             OpenLayers.Event.stopObservingElement(img);
-            if (this.frame.parentNode === this.layer.div) {
-                this.layer.div.removeChild(this.frame);
+            var tile = this.getTile();
+            if (tile.parentNode === this.layer.div) {
+                this.layer.div.removeChild(tile);
             }
             this.setImgSrc();
             if (this.layerAlphaHack === true) {
@@ -214,44 +226,51 @@
     },
     
     /**
-     * Method: createImage
-     * Creates the content for the frame on the tile.
+     * Method: getImage
+     * Returns or creates and returns the tile image.
+     *
+     * Returns: {HTMLImageElement}
      */
-    createImage: function() {
-        var img = document.createElement("img");
-        this.imgDiv = img;
+    getImage: function() {
+        var img = this.imgDiv;
+        if (!img) {
+            img = document.createElement("img");
+            this.imgDiv = img;
 
-        img.className = "olTileImage";
-        // avoid image gallery menu in IE6
-        img.galleryImg = "no";
+            img.className = "olTileImage";
+            // avoid image gallery menu in IE6
+            img.galleryImg = "no";
 
-        var style = img.style,
-            gutter = this.layer.gutter;
-        if (gutter) {
-            var tileSize = this.layer.tileSize,
-                left = (gutter / tileSize.w * 100),
-                top = (gutter / tileSize.h * 100);
-            style.left = -left + "%";
-            style.top = -top + "%";
-            style.width = (2 * left + 100) + "%";
-            style.height = (2 * top + 100) + "%";
+            var style = img.style,
+                gutter = this.layer.gutter,
+                tileSize = this.layer.tileSize;
+            if (gutter) {
+                var left = (gutter / tileSize.w * 100),
+                    top = (gutter / tileSize.h * 100);
+                style.left = -left + "%";
+                style.top = -top + "%";
+                style.width = (2 * left + 100) + "%";
+                style.height = (2 * top + 100) + "%";
+            } else {
+                style.width = "100%";
+                style.height = "100%";
+            }
             style.position = "absolute";
-        } else {
-            style.width = "100%";
-            style.height = "100%";
+                        style.display = "none";
+            if (this.layer.opacity < 1) {
+                OpenLayers.Util.modifyDOMElement(img, null, null, null, null, null,
+                    null, this.layer.opacity);
+            }
+            if (this.layerAlphaHack) {
+                // move the image out of sight
+                style.paddingTop = style.height;
+                style.height = "0";
+                style.width = "100%";
+            }
+            if (this.frameless === false) {
+                this.frame.appendChild(img);
+            }
         }
-        style.display = "none";
-        if (this.layer.opacity < 1) {
-            OpenLayers.Util.modifyDOMElement(img, null, null, null, null, null,
-                null, this.layer.opacity);
-        }
-        if (this.layerAlphaHack) {
-            // move the image out of sight
-            style.paddingTop = style.height;
-            style.height = "0";
-        }
-
-        this.frame.appendChild(img);
         return img;
     },
 
@@ -260,7 +279,7 @@
      * Creates the content for the frame on the tile.
      */
     initImage: function() {
-        var img = this.imgDiv || this.createImage();
+        var img = this.getImage();
         if (this.url && img.getAttribute("src") == this.url) {
             this.onImageLoad();
         } else {
@@ -313,10 +332,10 @@
      * Get the tile's markup.
      *
      * Returns:
-     * {DOMElement} The tile's markup
+     * {HTMLImageElement|HTMLDivElement} The tile's markup
      */
     getTile: function() {
-        return this.frame;
+        return this.frameless ? this.getImage() : this.frame;
     },
 
     /**
@@ -327,10 +346,15 @@
      * {DOMElement} a clone of the tile content
      */
     createBackBuffer: function() {
-        var frame = this.frame.cloneNode(false);
-        frame.appendChild(this.imgDiv);
-        this.imgDiv = null;
-        return frame;
+        var img = this.imgDiv, backBuffer;
+        if (this.frameless === true) {
+            backBuffer = img;
+        } else {
+            backBuffer = this.frame.cloneNode(false);
+            backBuffer.appendChild(img);
+        }
+         this.imgDiv = null;
+        return backBuffer;
     },
 
     /**
Index: lib/OpenLayers/Layer.js
===================================================================
--- lib/OpenLayers/Layer.js	(revision 12311)
+++ lib/OpenLayers/Layer.js	(working copy)
@@ -1270,8 +1270,9 @@
     setOpacity: function(opacity) {
         if (opacity != this.opacity) {
             this.opacity = opacity;
-            for(var i=0, len=this.div.childNodes.length; i<len; ++i) {
-                var element = this.div.childNodes[i].firstChild;
+            var childNodes = this.div.childNodes;
+            for(var i=0, len=childNodes.length; i<len; ++i) {
+                var element = childNodes[i].firstChild || childNodes[i];
                 OpenLayers.Util.modifyDOMElement(element, null, null, null, 
                                                  null, null, null, opacity);
             }
Index: lib/OpenLayers/Layer/Grid.js
===================================================================
--- lib/OpenLayers/Layer/Grid.js	(revision 12311)
+++ lib/OpenLayers/Layer/Grid.js	(working copy)
@@ -93,9 +93,13 @@
     /**
      * APIProperty: tileLoadingDelay
      * {Integer} - Number of milliseconds before we shift and load
-     *     tiles. Default is 100.
+     *     tiles. Default is 0. For applications targeting mobile devices, it
+     *     may make sense to set this to a value of something between 10 and
+     *     100, and set <buffer> for tiled layers to something between 1 and 4.
+     *     Kinetic dragging may then be smoother, but tiles won't load before
+     *     panning has stopped.
      */
-    tileLoadingDelay: 100,
+    tileLoadingDelay: 0,
 
     /**
      * Property: timerId
