1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5part of engine; 6 7final _supportsDecode = 8 js_util.hasProperty(js.JsObject(js.context['Image']), 'decode'); 9 10class HtmlCodec implements ui.Codec { 11 final String src; 12 13 HtmlCodec(this.src); 14 15 @override 16 int get frameCount => 1; 17 18 @override 19 int get repetitionCount => 0; 20 21 @override 22 Future<ui.FrameInfo> getNextFrame() async { 23 StreamSubscription<html.Event> loadSubscription; 24 StreamSubscription<html.Event> errorSubscription; 25 final Completer<ui.FrameInfo> completer = Completer<ui.FrameInfo>(); 26 final html.ImageElement imgElement = html.ImageElement(); 27 // If the browser doesn't support asynchronous decoding of an image, 28 // then use the `onload` event to decide when it's ready to paint to the 29 // DOM. Unfortunately, this will case the image to be decoded synchronously 30 // on the main thread, and may cause dropped framed. 31 if (!_supportsDecode) { 32 loadSubscription = imgElement.onLoad.listen((html.Event event) { 33 loadSubscription.cancel(); 34 errorSubscription.cancel(); 35 final HtmlImage image = HtmlImage( 36 imgElement, 37 imgElement.naturalWidth, 38 imgElement.naturalHeight, 39 ); 40 completer.complete(SingleFrameInfo(image)); 41 }); 42 } 43 errorSubscription = imgElement.onError.listen((html.Event event) { 44 loadSubscription?.cancel(); 45 errorSubscription.cancel(); 46 completer.completeError(event); 47 }); 48 imgElement.src = src; 49 // If the browser supports asynchronous image decoding, use that instead 50 // of `onload`. 51 if (_supportsDecode) { 52 imgElement.decode().then((dynamic _) { 53 errorSubscription.cancel(); 54 final HtmlImage image = HtmlImage( 55 imgElement, 56 imgElement.naturalWidth, 57 imgElement.naturalHeight, 58 ); 59 completer.complete(SingleFrameInfo(image)); 60 }); 61 } 62 return completer.future; 63 } 64 65 @override 66 void dispose() {} 67} 68 69class HtmlBlobCodec extends HtmlCodec { 70 final html.Blob blob; 71 72 HtmlBlobCodec(this.blob) : super(html.Url.createObjectUrlFromBlob(blob)); 73 74 @override 75 void dispose() { 76 html.Url.revokeObjectUrl(src); 77 } 78} 79 80class SingleFrameInfo implements ui.FrameInfo { 81 SingleFrameInfo(this.image); 82 83 @override 84 Duration get duration => const Duration(milliseconds: 0); 85 86 @override 87 final ui.Image image; 88} 89 90class HtmlImage implements ui.Image { 91 final html.ImageElement imgElement; 92 93 HtmlImage(this.imgElement, this.width, this.height); 94 95 @override 96 void dispose() { 97 // Do nothing. The codec that owns this image should take care of 98 // releasing the object url. 99 } 100 101 @override 102 final int width; 103 104 @override 105 final int height; 106 107 @override 108 Future<ByteData> toByteData( 109 {ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) { 110 return futurize((Callback<ByteData> callback) { 111 return _toByteData(format.index, (Uint8List encoded) { 112 callback(encoded?.buffer?.asByteData()); 113 }); 114 }); 115 } 116 117 /// Returns an error message on failure, null on success. 118 String _toByteData(int format, Callback<Uint8List> callback) => null; 119} 120