Skip to content

Commit

Permalink
[web] Reland: (Add crossOrigin property to <img> tag used for dec…
Browse files Browse the repository at this point in the history
…oding)++ (flutter/engine#57228)

Relands flutter/engine#54961 with a few more changes and tests.

Fixes flutter#160127
  • Loading branch information
mdebbar authored Dec 17, 2024
1 parent 37a7c44 commit d2a5b9d
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ ui.Image createCkImageFromImageElement(
}

class CkImageElementCodec extends HtmlImageElementCodec {
CkImageElementCodec(super.src);
CkImageElementCodec(super.src, {super.chunkCallback});

@override
ui.Image createImageFromHTMLImageElement(
Expand All @@ -170,7 +170,7 @@ class CkImageElementCodec extends HtmlImageElementCodec {
}

class CkImageBlobCodec extends HtmlBlobCodec {
CkImageBlobCodec(super.blob);
CkImageBlobCodec(super.blob, {super.chunkCallback});

@override
ui.Image createImageFromHTMLImageElement(
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down
16 changes: 16 additions & 0 deletions engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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,');
});
Expand All @@ -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'
Expand All @@ -103,14 +110,20 @@ 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 {
final Uri uri = Uri.base.resolve('sample_image1.png');
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);
});
Expand All @@ -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);
}

0 comments on commit d2a5b9d

Please sign in to comment.