[web] Reland: (Add crossOrigin
property to <img>
tag used for decoding)++ (flutter/engine#57228)
Relands https://github.com/flutter/engine/pull/54961 with a few more changes and tests. Fixes https://github.com/flutter/flutter/issues/160127
This commit is contained in:
parent
37a7c44c11
commit
d2a5b9dcb1
@ -161,7 +161,7 @@ ui.Image createCkImageFromImageElement(
|
||||
}
|
||||
|
||||
class CkImageElementCodec extends HtmlImageElementCodec {
|
||||
CkImageElementCodec(super.src);
|
||||
CkImageElementCodec(super.src, {super.chunkCallback});
|
||||
|
||||
@override
|
||||
ui.Image createImageFromHTMLImageElement(
|
||||
@ -170,7 +170,7 @@ class CkImageElementCodec extends HtmlImageElementCodec {
|
||||
}
|
||||
|
||||
class CkImageBlobCodec extends HtmlBlobCodec {
|
||||
CkImageBlobCodec(super.blob);
|
||||
CkImageBlobCodec(super.blob, {super.chunkCallback});
|
||||
|
||||
@override
|
||||
ui.Image createImageFromHTMLImageElement(
|
||||
@ -326,7 +326,7 @@ const String _kNetworkImageMessage = 'Failed to load network image.';
|
||||
/// requesting from URI.
|
||||
Future<ui.Codec> skiaInstantiateWebImageCodec(
|
||||
String url, ui_web.ImageCodecChunkCallback? chunkCallback) async {
|
||||
final CkImageElementCodec imageElementCodec = CkImageElementCodec(url);
|
||||
final CkImageElementCodec imageElementCodec = CkImageElementCodec(url, chunkCallback: chunkCallback);
|
||||
try {
|
||||
await imageElementCodec.decode();
|
||||
return imageElementCodec;
|
||||
@ -339,7 +339,7 @@ Future<ui.Codec> skiaInstantiateWebImageCodec(
|
||||
data: list, contentType: imageType.mimeType, debugSource: url);
|
||||
} else {
|
||||
final DomBlob blob = createDomBlob(<ByteBuffer>[list.buffer]);
|
||||
final CkImageBlobCodec codec = CkImageBlobCodec(blob);
|
||||
final CkImageBlobCodec codec = CkImageBlobCodec(blob, chunkCallback: chunkCallback);
|
||||
|
||||
try {
|
||||
await codec.decode();
|
||||
|
@ -990,6 +990,22 @@ extension DomHTMLImageElementExtension on DomHTMLImageElement {
|
||||
external set _height(JSNumber? value);
|
||||
set height(double? value) => _height = value?.toJS;
|
||||
|
||||
@JS('crossOrigin')
|
||||
external JSString? get _crossOrigin;
|
||||
String? get crossOrigin => _crossOrigin?.toDart;
|
||||
|
||||
@JS('crossOrigin')
|
||||
external set _crossOrigin(JSString? value);
|
||||
set crossOrigin(String? value) => _crossOrigin = value?.toJS;
|
||||
|
||||
@JS('decoding')
|
||||
external JSString? get _decoding;
|
||||
String? get decoding => _decoding?.toDart;
|
||||
|
||||
@JS('decoding')
|
||||
external set _decoding(JSString? value);
|
||||
set decoding(String? value) => _decoding = value?.toJS;
|
||||
|
||||
@JS('decode')
|
||||
external JSPromise<JSAny?> _decode();
|
||||
Future<Object?> decode() => js_util.promiseToFuture<Object?>(_decode());
|
||||
|
@ -43,8 +43,13 @@ abstract class HtmlImageElementCodec implements ui.Codec {
|
||||
// builders to create UI.
|
||||
chunkCallback?.call(0, 100);
|
||||
imgElement = createDomHTMLImageElement();
|
||||
imgElement!.src = src;
|
||||
setJsProperty<String>(imgElement!, 'decoding', 'async');
|
||||
if (renderer is! HtmlRenderer) {
|
||||
imgElement!.crossOrigin = 'anonymous';
|
||||
}
|
||||
imgElement!
|
||||
..decoding = 'async'
|
||||
..src = src;
|
||||
|
||||
|
||||
// Ignoring the returned future on purpose because we're communicating
|
||||
// through the `completer`.
|
||||
@ -91,7 +96,7 @@ abstract class HtmlImageElementCodec implements ui.Codec {
|
||||
}
|
||||
|
||||
abstract class HtmlBlobCodec extends HtmlImageElementCodec {
|
||||
HtmlBlobCodec(this.blob)
|
||||
HtmlBlobCodec(this.blob, {super.chunkCallback})
|
||||
: super(
|
||||
domWindow.URL.createObjectURL(blob),
|
||||
debugSource: 'encoded image bytes',
|
||||
|
@ -253,6 +253,19 @@ Future<void> testMain() async {
|
||||
}
|
||||
});
|
||||
|
||||
test('crossOrigin requests cause an error', () async {
|
||||
final String otherOrigin =
|
||||
domWindow.location.origin.replaceAll('localhost', '127.0.0.1');
|
||||
bool gotError = false;
|
||||
try {
|
||||
final ui.Codec _ = await renderer.instantiateImageCodecFromUrl(
|
||||
Uri.parse('$otherOrigin/test_images/1x1.png'));
|
||||
} catch (e) {
|
||||
gotError = true;
|
||||
}
|
||||
expect(gotError, isTrue, reason: 'Should have got CORS error');
|
||||
});
|
||||
|
||||
_testCkAnimatedImage();
|
||||
|
||||
test('isAvif', () {
|
||||
|
@ -7,12 +7,15 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine/canvaskit/image.dart';
|
||||
import 'package:ui/src/engine/dom.dart';
|
||||
import 'package:ui/src/engine/html/image.dart';
|
||||
import 'package:ui/src/engine/html_image_element_codec.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
import '../../common/test_initialization.dart';
|
||||
import '../../ui/utils.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
@ -60,16 +63,20 @@ Future<void> testMain() async {
|
||||
expect(image.height, height);
|
||||
});
|
||||
test('loads sample image', () async {
|
||||
final HtmlImageElementCodec codec =
|
||||
HtmlRendererImageCodec('sample_image1.png');
|
||||
final HtmlImageElementCodec codec = createImageElementCodec('sample_image1.png');
|
||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
||||
|
||||
expect(codec.imgElement, isNotNull);
|
||||
expect(codec.imgElement!.src, contains('sample_image1.png'));
|
||||
expect(codec.imgElement!.crossOrigin, isHtml ? isNull : 'anonymous');
|
||||
expect(codec.imgElement!.decoding, 'async');
|
||||
|
||||
expect(frameInfo.image, isNotNull);
|
||||
expect(frameInfo.image.width, 100);
|
||||
expect(frameInfo.image.toString(), '[100×100]');
|
||||
});
|
||||
test('dispose image image', () async {
|
||||
final HtmlImageElementCodec codec =
|
||||
HtmlRendererImageCodec('sample_image1.png');
|
||||
final HtmlImageElementCodec codec = createImageElementCodec('sample_image1.png');
|
||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
||||
expect(frameInfo.image, isNotNull);
|
||||
expect(frameInfo.image.debugDisposed, isFalse);
|
||||
@ -78,7 +85,7 @@ Future<void> testMain() async {
|
||||
});
|
||||
test('provides image loading progress', () async {
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
final HtmlImageElementCodec codec = HtmlRendererImageCodec(
|
||||
final HtmlImageElementCodec codec = createImageElementCodec(
|
||||
'sample_image1.png', chunkCallback: (int loaded, int total) {
|
||||
buffer.write('$loaded/$total,');
|
||||
});
|
||||
@ -89,7 +96,7 @@ Future<void> testMain() async {
|
||||
/// Regression test for Firefox
|
||||
/// https://github.com/flutter/flutter/issues/66412
|
||||
test('Returns nonzero natural width/height', () async {
|
||||
final HtmlImageElementCodec codec = HtmlRendererImageCodec(
|
||||
final HtmlImageElementCodec codec = createImageElementCodec(
|
||||
'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9I'
|
||||
'jAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dG'
|
||||
'l0bGU+QWJzdHJhY3QgaWNvbjwvdGl0bGU+PHBhdGggZD0iTTEyIDBjOS42MDEgMCAx'
|
||||
@ -103,7 +110,7 @@ Future<void> testMain() async {
|
||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
||||
expect(frameInfo.image.width, isNot(0));
|
||||
});
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
|
||||
group('ImageCodecUrl', () {
|
||||
test('loads sample image from web', () async {
|
||||
@ -111,6 +118,12 @@ Future<void> testMain() async {
|
||||
final HtmlImageElementCodec codec =
|
||||
await ui_web.createImageCodecFromUrl(uri) as HtmlImageElementCodec;
|
||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
||||
|
||||
expect(codec.imgElement, isNotNull);
|
||||
expect(codec.imgElement!.src, contains('sample_image1.png'));
|
||||
expect(codec.imgElement!.crossOrigin, isHtml ? isNull : 'anonymous');
|
||||
expect(codec.imgElement!.decoding, 'async');
|
||||
|
||||
expect(frameInfo.image, isNotNull);
|
||||
expect(frameInfo.image.width, 100);
|
||||
});
|
||||
@ -124,5 +137,14 @@ Future<void> testMain() async {
|
||||
await codec.getNextFrame();
|
||||
expect(buffer.toString(), '0/100,100/100,');
|
||||
});
|
||||
});
|
||||
}, skip: isSkwasm);
|
||||
}
|
||||
|
||||
HtmlImageElementCodec createImageElementCodec(
|
||||
String src, {
|
||||
ui_web.ImageCodecChunkCallback? chunkCallback,
|
||||
}) {
|
||||
return isHtml
|
||||
? HtmlRendererImageCodec(src, chunkCallback: chunkCallback)
|
||||
: CkImageElementCodec(src, chunkCallback: chunkCallback);
|
||||
}
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Loading…
x
Reference in New Issue
Block a user