[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 {
|
class CkImageElementCodec extends HtmlImageElementCodec {
|
||||||
CkImageElementCodec(super.src);
|
CkImageElementCodec(super.src, {super.chunkCallback});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.Image createImageFromHTMLImageElement(
|
ui.Image createImageFromHTMLImageElement(
|
||||||
@ -170,7 +170,7 @@ class CkImageElementCodec extends HtmlImageElementCodec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CkImageBlobCodec extends HtmlBlobCodec {
|
class CkImageBlobCodec extends HtmlBlobCodec {
|
||||||
CkImageBlobCodec(super.blob);
|
CkImageBlobCodec(super.blob, {super.chunkCallback});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.Image createImageFromHTMLImageElement(
|
ui.Image createImageFromHTMLImageElement(
|
||||||
@ -326,7 +326,7 @@ const String _kNetworkImageMessage = 'Failed to load network image.';
|
|||||||
/// requesting from URI.
|
/// requesting from URI.
|
||||||
Future<ui.Codec> skiaInstantiateWebImageCodec(
|
Future<ui.Codec> skiaInstantiateWebImageCodec(
|
||||||
String url, ui_web.ImageCodecChunkCallback? chunkCallback) async {
|
String url, ui_web.ImageCodecChunkCallback? chunkCallback) async {
|
||||||
final CkImageElementCodec imageElementCodec = CkImageElementCodec(url);
|
final CkImageElementCodec imageElementCodec = CkImageElementCodec(url, chunkCallback: chunkCallback);
|
||||||
try {
|
try {
|
||||||
await imageElementCodec.decode();
|
await imageElementCodec.decode();
|
||||||
return imageElementCodec;
|
return imageElementCodec;
|
||||||
@ -339,7 +339,7 @@ Future<ui.Codec> skiaInstantiateWebImageCodec(
|
|||||||
data: list, contentType: imageType.mimeType, debugSource: url);
|
data: list, contentType: imageType.mimeType, debugSource: url);
|
||||||
} else {
|
} else {
|
||||||
final DomBlob blob = createDomBlob(<ByteBuffer>[list.buffer]);
|
final DomBlob blob = createDomBlob(<ByteBuffer>[list.buffer]);
|
||||||
final CkImageBlobCodec codec = CkImageBlobCodec(blob);
|
final CkImageBlobCodec codec = CkImageBlobCodec(blob, chunkCallback: chunkCallback);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await codec.decode();
|
await codec.decode();
|
||||||
|
@ -990,6 +990,22 @@ extension DomHTMLImageElementExtension on DomHTMLImageElement {
|
|||||||
external set _height(JSNumber? value);
|
external set _height(JSNumber? value);
|
||||||
set height(double? value) => _height = value?.toJS;
|
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')
|
@JS('decode')
|
||||||
external JSPromise<JSAny?> _decode();
|
external JSPromise<JSAny?> _decode();
|
||||||
Future<Object?> decode() => js_util.promiseToFuture<Object?>(_decode());
|
Future<Object?> decode() => js_util.promiseToFuture<Object?>(_decode());
|
||||||
|
@ -43,8 +43,13 @@ abstract class HtmlImageElementCodec implements ui.Codec {
|
|||||||
// builders to create UI.
|
// builders to create UI.
|
||||||
chunkCallback?.call(0, 100);
|
chunkCallback?.call(0, 100);
|
||||||
imgElement = createDomHTMLImageElement();
|
imgElement = createDomHTMLImageElement();
|
||||||
imgElement!.src = src;
|
if (renderer is! HtmlRenderer) {
|
||||||
setJsProperty<String>(imgElement!, 'decoding', 'async');
|
imgElement!.crossOrigin = 'anonymous';
|
||||||
|
}
|
||||||
|
imgElement!
|
||||||
|
..decoding = 'async'
|
||||||
|
..src = src;
|
||||||
|
|
||||||
|
|
||||||
// Ignoring the returned future on purpose because we're communicating
|
// Ignoring the returned future on purpose because we're communicating
|
||||||
// through the `completer`.
|
// through the `completer`.
|
||||||
@ -91,7 +96,7 @@ abstract class HtmlImageElementCodec implements ui.Codec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class HtmlBlobCodec extends HtmlImageElementCodec {
|
abstract class HtmlBlobCodec extends HtmlImageElementCodec {
|
||||||
HtmlBlobCodec(this.blob)
|
HtmlBlobCodec(this.blob, {super.chunkCallback})
|
||||||
: super(
|
: super(
|
||||||
domWindow.URL.createObjectURL(blob),
|
domWindow.URL.createObjectURL(blob),
|
||||||
debugSource: 'encoded image bytes',
|
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();
|
_testCkAnimatedImage();
|
||||||
|
|
||||||
test('isAvif', () {
|
test('isAvif', () {
|
||||||
|
@ -7,12 +7,15 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:test/bootstrap/browser.dart';
|
import 'package:test/bootstrap/browser.dart';
|
||||||
import 'package:test/test.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.dart';
|
||||||
import 'package:ui/src/engine/html_image_element_codec.dart';
|
import 'package:ui/src/engine/html_image_element_codec.dart';
|
||||||
import 'package:ui/ui.dart' as ui;
|
import 'package:ui/ui.dart' as ui;
|
||||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||||
|
|
||||||
import '../../common/test_initialization.dart';
|
import '../../common/test_initialization.dart';
|
||||||
|
import '../../ui/utils.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
internalBootstrapBrowserTest(() => testMain);
|
internalBootstrapBrowserTest(() => testMain);
|
||||||
@ -60,16 +63,20 @@ Future<void> testMain() async {
|
|||||||
expect(image.height, height);
|
expect(image.height, height);
|
||||||
});
|
});
|
||||||
test('loads sample image', () async {
|
test('loads sample image', () async {
|
||||||
final HtmlImageElementCodec codec =
|
final HtmlImageElementCodec codec = createImageElementCodec('sample_image1.png');
|
||||||
HtmlRendererImageCodec('sample_image1.png');
|
|
||||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
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, isNotNull);
|
||||||
expect(frameInfo.image.width, 100);
|
expect(frameInfo.image.width, 100);
|
||||||
expect(frameInfo.image.toString(), '[100×100]');
|
expect(frameInfo.image.toString(), '[100×100]');
|
||||||
});
|
});
|
||||||
test('dispose image image', () async {
|
test('dispose image image', () async {
|
||||||
final HtmlImageElementCodec codec =
|
final HtmlImageElementCodec codec = createImageElementCodec('sample_image1.png');
|
||||||
HtmlRendererImageCodec('sample_image1.png');
|
|
||||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
||||||
expect(frameInfo.image, isNotNull);
|
expect(frameInfo.image, isNotNull);
|
||||||
expect(frameInfo.image.debugDisposed, isFalse);
|
expect(frameInfo.image.debugDisposed, isFalse);
|
||||||
@ -78,7 +85,7 @@ Future<void> testMain() async {
|
|||||||
});
|
});
|
||||||
test('provides image loading progress', () async {
|
test('provides image loading progress', () async {
|
||||||
final StringBuffer buffer = StringBuffer();
|
final StringBuffer buffer = StringBuffer();
|
||||||
final HtmlImageElementCodec codec = HtmlRendererImageCodec(
|
final HtmlImageElementCodec codec = createImageElementCodec(
|
||||||
'sample_image1.png', chunkCallback: (int loaded, int total) {
|
'sample_image1.png', chunkCallback: (int loaded, int total) {
|
||||||
buffer.write('$loaded/$total,');
|
buffer.write('$loaded/$total,');
|
||||||
});
|
});
|
||||||
@ -89,7 +96,7 @@ Future<void> testMain() async {
|
|||||||
/// Regression test for Firefox
|
/// Regression test for Firefox
|
||||||
/// https://github.com/flutter/flutter/issues/66412
|
/// https://github.com/flutter/flutter/issues/66412
|
||||||
test('Returns nonzero natural width/height', () async {
|
test('Returns nonzero natural width/height', () async {
|
||||||
final HtmlImageElementCodec codec = HtmlRendererImageCodec(
|
final HtmlImageElementCodec codec = createImageElementCodec(
|
||||||
'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9I'
|
'data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHZpZXdCb3g9I'
|
||||||
'jAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dG'
|
'jAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dG'
|
||||||
'l0bGU+QWJzdHJhY3QgaWNvbjwvdGl0bGU+PHBhdGggZD0iTTEyIDBjOS42MDEgMCAx'
|
'l0bGU+QWJzdHJhY3QgaWNvbjwvdGl0bGU+PHBhdGggZD0iTTEyIDBjOS42MDEgMCAx'
|
||||||
@ -103,7 +110,7 @@ Future<void> testMain() async {
|
|||||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
||||||
expect(frameInfo.image.width, isNot(0));
|
expect(frameInfo.image.width, isNot(0));
|
||||||
});
|
});
|
||||||
});
|
}, skip: isSkwasm);
|
||||||
|
|
||||||
group('ImageCodecUrl', () {
|
group('ImageCodecUrl', () {
|
||||||
test('loads sample image from web', () async {
|
test('loads sample image from web', () async {
|
||||||
@ -111,6 +118,12 @@ Future<void> testMain() async {
|
|||||||
final HtmlImageElementCodec codec =
|
final HtmlImageElementCodec codec =
|
||||||
await ui_web.createImageCodecFromUrl(uri) as HtmlImageElementCodec;
|
await ui_web.createImageCodecFromUrl(uri) as HtmlImageElementCodec;
|
||||||
final ui.FrameInfo frameInfo = await codec.getNextFrame();
|
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, isNotNull);
|
||||||
expect(frameInfo.image.width, 100);
|
expect(frameInfo.image.width, 100);
|
||||||
});
|
});
|
||||||
@ -124,5 +137,14 @@ Future<void> testMain() async {
|
|||||||
await codec.getNextFrame();
|
await codec.getNextFrame();
|
||||||
expect(buffer.toString(), '0/100,100/100,');
|
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