diff --git a/src/lime/_internal/backend/html5/HTML5HTTPRequest.hx b/src/lime/_internal/backend/html5/HTML5HTTPRequest.hx index a9d83663a..5bc0cd5e8 100644 --- a/src/lime/_internal/backend/html5/HTML5HTTPRequest.hx +++ b/src/lime/_internal/backend/html5/HTML5HTTPRequest.hx @@ -2,13 +2,16 @@ package lime._internal.backend.html5; import haxe.io.Bytes; import js.html.AnchorElement; +import js.html.Blob; import js.html.ErrorEvent; import js.html.Event; import js.html.Image as JSImage; import js.html.ProgressEvent; +import js.html.URL; import js.html.XMLHttpRequest; import js.html.XMLHttpRequestResponseType; import js.Browser; +import lime._internal.format.Base64; import lime.app.Future; import lime.app.Promise; import lime.graphics.Image; @@ -21,6 +24,8 @@ import lime.utils.AssetType; @:access(lime.graphics.Image) class HTML5HTTPRequest { + private static inline var OPTION_REVOKE_URL:Int = 1 << 0; + private static var activeRequests = 0; private static var originElement:AnchorElement; private static var originHostname:String; @@ -172,7 +177,8 @@ class HTML5HTTPRequest instance: this, uri: uri, promise: promise, - type: AssetType.BINARY + type: AssetType.BINARY, + options: 0 }); } @@ -186,7 +192,7 @@ class HTML5HTTPRequest if (activeRequests < requestLimit) { activeRequests++; - __loadImage(uri, promise); + __loadImage(uri, promise, 0); } else { @@ -195,13 +201,46 @@ class HTML5HTTPRequest instance: null, uri: uri, promise: promise, - type: AssetType.IMAGE + type: AssetType.IMAGE, + options: 0 }); } return promise.future; } + private static function loadImageFromBytes(bytes:Bytes, type:String):Future + { + var uri = __createBlobURIFromBytes(bytes, type); + if (uri != null) + { + var promise = new Promise(); + + if (activeRequests < requestLimit) + { + activeRequests++; + __loadImage(uri, promise, OPTION_REVOKE_URL); + } + else + { + requestQueue.add( + { + instance: null, + uri: uri, + promise: promise, + type: AssetType.IMAGE, + options: OPTION_REVOKE_URL + }); + } + + return promise.future; + } + else + { + return loadImage("data:" + type + ";base64," + Base64.encode(bytes)); + } + } + public function loadText(uri:String):Future { var promise = new Promise(); @@ -218,7 +257,8 @@ class HTML5HTTPRequest instance: this, uri: uri, promise: promise, - type: AssetType.TEXT + type: AssetType.TEXT, + options: 0 }); } @@ -236,7 +276,7 @@ class HTML5HTTPRequest switch (queueItem.type) { case IMAGE: - __loadImage(queueItem.uri, queueItem.promise); + __loadImage(queueItem.uri, queueItem.promise, queueItem.options); case TEXT: queueItem.instance.__loadText(queueItem.uri, queueItem.promise); @@ -272,6 +312,11 @@ class HTML5HTTPRequest parent.responseStatus = request.status; } + private static inline function __createBlobURIFromBytes(bytes:Bytes, type:String):String + { + return URL.createObjectURL(new Blob([bytes.getData()], {type: type})); + } + private static function __fixHostname(hostname:String):String { return hostname == null ? "" : hostname; @@ -301,10 +346,15 @@ class HTML5HTTPRequest return (protocol == null || protocol == "") ? "http:" : protocol; } + private static function __isInMemoryURI(uri:String):Bool + { + return StringTools.startsWith(uri, "data:") || StringTools.startsWith(uri, "blob:"); + } + private static function __isSameOrigin(path:String):Bool { if (path == null || path == "") return true; - if (StringTools.startsWith(path, "data:")) return true; + if (__isInMemoryURI(path)) return true; if (originElement == null) { @@ -380,7 +430,7 @@ class HTML5HTTPRequest load(uri, progress, readyStateChange); } - private static function __loadImage(uri:String, promise:Promise):Void + private static function __loadImage(uri:String, promise:Promise, options:Int):Void { var image = new JSImage(); @@ -394,10 +444,11 @@ class HTML5HTTPRequest supportsImageProgress = untyped __js__("'onprogress' in image"); } - if (supportsImageProgress || StringTools.startsWith(uri, "data:")) + if (supportsImageProgress || __isInMemoryURI(uri)) { image.addEventListener("load", function(event) { + __revokeBlobURI(uri, options); var buffer = new ImageBuffer(null, image.width, image.height); buffer.__srcImage = cast image; @@ -414,6 +465,8 @@ class HTML5HTTPRequest image.addEventListener("error", function(event) { + __revokeBlobURI(uri, options); + activeRequests--; processQueue(); @@ -489,6 +542,14 @@ class HTML5HTTPRequest binary = false; load(uri, progress, readyStateChange); } + + private static function __revokeBlobURI(uri:String, options:Int):Void + { + if ((options & OPTION_REVOKE_URL) != 0) + { + URL.revokeObjectURL(uri); + } + } } @:dox(hide) typedef QueueItem = @@ -497,4 +558,5 @@ class HTML5HTTPRequest var type:AssetType; var promise:Dynamic; var uri:String; + var options:Int; } diff --git a/src/lime/graphics/Image.hx b/src/lime/graphics/Image.hx index aad56c223..00074bd75 100644 --- a/src/lime/graphics/Image.hx +++ b/src/lime/graphics/Image.hx @@ -387,7 +387,8 @@ class Image sourceRect.offset(sourceImage.offsetX, sourceImage.offsetY); destPoint.offset(offsetX, offsetY); - buffer.__srcBitmapData.copyChannel(sourceImage.buffer.src, sourceRect.__toFlashRectangle(), destPoint.__toFlashPoint(), srcChannel, dstChannel); + buffer.__srcBitmapData.copyChannel(sourceImage.buffer.src, sourceRect.__toFlashRectangle(), destPoint.__toFlashPoint(), srcChannel, + dstChannel); default: } @@ -616,20 +617,16 @@ class Image @param bitmapData A source `bitmapData` to use @return A new `Image` instance **/ - #if flash - public static function fromBitmapData(bitmapData:BitmapData):Image { - #else - public static function fromBitmapData(bitmapData:Dynamic):Image { - #end - if (bitmapData == null) return null; - #if flash - var buffer = new ImageBuffer(null, bitmapData.width, bitmapData.height); - - buffer.__srcBitmapData = bitmapData; - return new Image(buffer); - #else - return bitmapData.image; - #end + public static function fromBitmapData(bitmapData:#if flash BitmapData #else Dynamic #end):Image + { + if (bitmapData == null) return null; + #if flash + var buffer = new ImageBuffer(null, bitmapData.width, bitmapData.height); + buffer.__srcBitmapData = bitmapData; + return new Image(buffer); + #else + return bitmapData.image; + #end } #end @@ -664,18 +661,15 @@ class Image @param canvas A `CanvasElement` @return A new `Image` instance **/ - #if (js && html5) - public static function fromCanvas(canvas:CanvasElement):Image { - #else - public static function fromCanvas(canvas:Dynamic):Image { - #end - if (canvas == null) return null; - var buffer = new ImageBuffer(null, canvas.width, canvas.height); - buffer.src = canvas; - var image = new Image(buffer); + public static function fromCanvas(canvas:#if (js && html5) CanvasElement #else Dynamic #end):Image + { + if (canvas == null) return null; + var buffer = new ImageBuffer(null, canvas.width, canvas.height); + buffer.src = canvas; + var image = new Image(buffer); - image.type = CANVAS; - return image; + image.type = CANVAS; + return image; } #end @@ -710,18 +704,15 @@ class Image @param image An `ImageElement` instance @return A new `Image` instance **/ - #if (js && html5) - public static function fromImageElement(image:ImageElement):Image { - #else - public static function fromImageElement(image:Dynamic):Image { - #end - if (image == null) return null; - var buffer = new ImageBuffer(null, image.width, image.height); - buffer.src = image; - var _image = new Image(buffer); + public static function fromImageElement(image:#if (js && html5) ImageElement #else Dynamic #end):Image + { + if (image == null) return null; + var buffer = new ImageBuffer(null, image.width, image.height); + buffer.src = image; + var _image = new Image(buffer); - _image.type = CANVAS; - return _image; + _image.type = CANVAS; + return _image; } #end @@ -967,8 +958,11 @@ class Image // throw "Image tried to read PNG/JPG Bytes, but found an invalid header."; return Future.withValue(null); } - + #if !display + return HTML5HTTPRequest.loadImageFromBytes(bytes, type); + #else return loadFromBase64(Base64.encode(bytes), type); + #end #elseif flash var promise = new Promise();