Throw exceptions rather than crashing when Canvas API is misused. (flutter/engine#3811)
This attempts to make the Canvas API and some related features more likely to throw a Dart exception than crash when exposed to bad input. Depends on rolling tonic to https://fuchsia-review.googlesource.com/c/35742/ which this patch does not yet do, but I wanted to put it up for review to see if it was even a reasonable approach.
This commit is contained in:
@@ -4,6 +4,15 @@
|
||||
|
||||
part of dart.ui;
|
||||
|
||||
// Some methods in this file assert that their arguments are not null. These
|
||||
// asserts are just to improve the error messages; they should only cover
|
||||
// arguments that are either dereferenced _in Dart_, before being passed to the
|
||||
// engine, or that the engine explicitly null-checks itself (after attempting to
|
||||
// convert the argument to a native type). It should not be possible for a null
|
||||
// or invalid value to be used by the engine even in release mode, since that
|
||||
// would cause a crash. It is, however, acceptable for error messages to be much
|
||||
// less useful or correct in release mode than in debug mode.
|
||||
|
||||
Color _scaleAlpha(Color a, double factor) {
|
||||
return a.withAlpha((a.alpha * factor).round());
|
||||
}
|
||||
@@ -808,6 +817,7 @@ class Path extends NativeFieldWrapperClass2 {
|
||||
/// The line segment added if [forceMoveTo] is false starts at the
|
||||
/// current point and ends at the start of the arc.
|
||||
void arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) {
|
||||
assert(rect != null);
|
||||
_arcTo(rect.left, rect.top, rect.right, rect.bottom, startAngle, sweepAngle, forceMoveTo);
|
||||
}
|
||||
void _arcTo(double left, double top, double right, double bottom,
|
||||
@@ -816,6 +826,7 @@ class Path extends NativeFieldWrapperClass2 {
|
||||
/// Adds a new subpath that consists of four lines that outline the
|
||||
/// given rectangle.
|
||||
void addRect(Rect rect) {
|
||||
assert(rect != null);
|
||||
_addRect(rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
void _addRect(double left, double top, double right, double bottom) native "Path_addRect";
|
||||
@@ -823,6 +834,7 @@ class Path extends NativeFieldWrapperClass2 {
|
||||
/// Adds a new subpath that consists of a curve that forms the
|
||||
/// ellipse that fills the given rectangle.
|
||||
void addOval(Rect oval) {
|
||||
assert(oval != null);
|
||||
_addOval(oval.left, oval.top, oval.right, oval.bottom);
|
||||
}
|
||||
void _addOval(double left, double top, double right, double bottom) native "Path_addOval";
|
||||
@@ -836,6 +848,7 @@ class Path extends NativeFieldWrapperClass2 {
|
||||
/// rectangle and with positive angles going clockwise around the
|
||||
/// oval.
|
||||
void addArc(Rect oval, double startAngle, double sweepAngle) {
|
||||
assert(oval != null);
|
||||
_addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);
|
||||
}
|
||||
void _addArc(double left, double top, double right, double bottom,
|
||||
@@ -849,6 +862,7 @@ class Path extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// The `points` argument is interpreted as offsets from the origin.
|
||||
void addPolygon(List<Offset> points, bool close) {
|
||||
assert(points != null);
|
||||
_addPolygon(_encodePointList(points), close);
|
||||
}
|
||||
void _addPolygon(Float32List points, bool close) native "Path_addPolygon";
|
||||
@@ -856,17 +870,28 @@ class Path extends NativeFieldWrapperClass2 {
|
||||
/// Adds a new subpath that consists of the straight lines and
|
||||
/// curves needed to form the rounded rectangle described by the
|
||||
/// argument.
|
||||
void addRRect(RRect rrect) => _addRRect(rrect._value);
|
||||
void addRRect(RRect rrect) {
|
||||
assert(rrect != null);
|
||||
_addRRect(rrect._value);
|
||||
}
|
||||
void _addRRect(Float32List rrect) native "Path_addRRect";
|
||||
|
||||
/// Adds a new subpath that consists of the given path offset by the given
|
||||
/// offset.
|
||||
void addPath(Path path, Offset offset) => _addPath(path, offset.dx, offset.dy);
|
||||
void addPath(Path path, Offset offset) {
|
||||
assert(path != null); // path is checked on the engine side
|
||||
assert(offset != null);
|
||||
_addPath(path, offset.dx, offset.dy);
|
||||
}
|
||||
void _addPath(Path path, double dx, double dy) native "Path_addPath";
|
||||
|
||||
/// Adds the given path to this path by extending the current segment of this
|
||||
/// path with the the first segment of the given path.
|
||||
void extendWithPath(Path path, Offset offset) => _extendWithPath(path, offset.dx, offset.dy);
|
||||
void extendWithPath(Path path, Offset offset) {
|
||||
assert(path != null); // path is checked on the engine side
|
||||
assert(offset != null);
|
||||
_extendWithPath(path, offset.dx, offset.dy);
|
||||
}
|
||||
void _extendWithPath(Path path, double dx, double dy) native "Path_extendWithPath";
|
||||
|
||||
/// Closes the last subpath, as if a straight line had been drawn
|
||||
@@ -885,17 +910,24 @@ class Path extends NativeFieldWrapperClass2 {
|
||||
/// The `point` argument is interpreted as an offset from the origin.
|
||||
///
|
||||
/// Returns true if the point is in the path, and false otherwise.
|
||||
bool contains(Offset point) => _contains(point.dx, point.dy);
|
||||
bool contains(Offset point) {
|
||||
assert(point != null);
|
||||
return _contains(point.dx, point.dy);
|
||||
}
|
||||
bool _contains(double x, double y) native "Path_contains";
|
||||
|
||||
/// Returns a copy of the path with all the segments of every
|
||||
/// subpath translated by the given offset.
|
||||
Path shift(Offset offset) => _shift(offset.dx, offset.dy);
|
||||
Path shift(Offset offset) {
|
||||
assert(offset != null);
|
||||
return _shift(offset.dx, offset.dy);
|
||||
}
|
||||
Path _shift(double dx, double dy) native "Path_shift";
|
||||
|
||||
/// Returns a copy of the path with all the segments of every
|
||||
/// subpath transformed by the given matrix.
|
||||
Path transform(Float64List matrix4) {
|
||||
assert(matrix4 != null);
|
||||
if (matrix4.length != 16)
|
||||
throw new ArgumentError('"matrix4" must have 16 entries.');
|
||||
return _transform(matrix4);
|
||||
@@ -946,6 +978,7 @@ class MaskFilter extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// A blur is an expensive operation and should therefore be used sparingly.
|
||||
MaskFilter.blur(BlurStyle style, double sigma) {
|
||||
assert(style != null);
|
||||
_constructor(style.index, sigma);
|
||||
}
|
||||
void _constructor(int style, double sigma) native "MaskFilter_constructor";
|
||||
@@ -1082,6 +1115,7 @@ Int32List _encodeColorList(List<Color> colors) {
|
||||
}
|
||||
|
||||
Float32List _encodePointList(List<Offset> points) {
|
||||
assert(points != null);
|
||||
final int pointCount = points.length;
|
||||
final Float32List result = new Float32List(pointCount * 2);
|
||||
for (int i = 0; i < pointCount; ++i) {
|
||||
@@ -1139,6 +1173,10 @@ class Gradient extends Shader {
|
||||
List<double> colorStops = null,
|
||||
TileMode tileMode = TileMode.clamp,
|
||||
]) {
|
||||
assert(from != null);
|
||||
assert(to != null);
|
||||
assert(colors != null);
|
||||
assert(tileMode != null);
|
||||
_validateColorStops(colors, colorStops);
|
||||
final Float32List endPointsBuffer = _encodeTwoPoints(from, to);
|
||||
final Int32List colorsBuffer = _encodeColorList(colors);
|
||||
@@ -1172,6 +1210,9 @@ class Gradient extends Shader {
|
||||
List<double> colorStops = null,
|
||||
TileMode tileMode = TileMode.clamp,
|
||||
]) {
|
||||
assert(center != null);
|
||||
assert(colors != null);
|
||||
assert(tileMode != null);
|
||||
_validateColorStops(colors, colorStops);
|
||||
final Int32List colorsBuffer = _encodeColorList(colors);
|
||||
final Float32List colorStopsBuffer = colorStops == null ? null : new Float32List.fromList(colorStops);
|
||||
@@ -1199,14 +1240,10 @@ class ImageShader extends Shader {
|
||||
/// matrix to apply to the effect. All the arguments are required and must not
|
||||
/// be null.
|
||||
ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4) {
|
||||
if (image == null)
|
||||
throw new ArgumentError('"image" argument cannot be null.');
|
||||
if (tmx == null)
|
||||
throw new ArgumentError('"tmx" argument cannot be null.');
|
||||
if (tmy == null)
|
||||
throw new ArgumentError('"tmy" argument cannot be null.');
|
||||
if (matrix4 == null)
|
||||
throw new ArgumentError('"matrix4" argument cannot be null.');
|
||||
assert(image != null); // image is checked on the engine side
|
||||
assert(tmx != null);
|
||||
assert(tmy != null);
|
||||
assert(matrix4 != null);
|
||||
if (matrix4.length != 16)
|
||||
throw new ArgumentError('"matrix4" must have 16 entries.');
|
||||
_constructor();
|
||||
@@ -1240,6 +1277,8 @@ class Vertices extends NativeFieldWrapperClass2 {
|
||||
List<Color> colors,
|
||||
List<int> indices,
|
||||
}) {
|
||||
assert(mode != null);
|
||||
assert(positions != null);
|
||||
if (textureCoordinates != null && textureCoordinates.length != positions.length)
|
||||
throw new ArgumentError('"positions" and "textureCoordinates" lengths must match.');
|
||||
if (colors != null && colors.length != positions.length)
|
||||
@@ -1250,8 +1289,8 @@ class Vertices extends NativeFieldWrapperClass2 {
|
||||
Float32List encodedPositions = _encodePointList(positions);
|
||||
Float32List encodedTextureCoordinates = (textureCoordinates != null) ?
|
||||
_encodePointList(textureCoordinates) : null;
|
||||
Int32List encodedColors = (colors != null) ? _encodeColorList(colors) : null;
|
||||
Int32List encodedIndices = (indices != null) ? new Int32List.fromList(indices) : null;
|
||||
Int32List encodedColors = colors != null ? _encodeColorList(colors) : null;
|
||||
Int32List encodedIndices = indices != null ? new Int32List.fromList(indices) : null;
|
||||
|
||||
_constructor();
|
||||
_init(mode.index, encodedPositions, encodedTextureCoordinates, encodedColors, encodedIndices);
|
||||
@@ -1264,6 +1303,8 @@ class Vertices extends NativeFieldWrapperClass2 {
|
||||
Int32List colors,
|
||||
Int32List indices,
|
||||
}) {
|
||||
assert(mode != null);
|
||||
assert(positions != null);
|
||||
if (textureCoordinates != null && textureCoordinates.length != positions.length)
|
||||
throw new ArgumentError('"positions" and "textureCoordinates" lengths must match.');
|
||||
if (colors != null && colors.length * 2 != positions.length)
|
||||
@@ -1346,10 +1387,10 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// To end the recording, call [PictureRecorder.endRecording] on the
|
||||
/// given recorder.
|
||||
Canvas(PictureRecorder recorder, Rect cullRect) {
|
||||
if (recorder == null)
|
||||
throw new ArgumentError('"recorder" must not be null.');
|
||||
assert(recorder != null);
|
||||
if (recorder.isRecording)
|
||||
throw new ArgumentError('"recorder" must not already be associated with another Canvas.');
|
||||
assert(cullRect != null);
|
||||
_constructor(recorder, cullRect.left, cullRect.top, cullRect.right, cullRect.bottom);
|
||||
}
|
||||
void _constructor(PictureRecorder recorder,
|
||||
@@ -1378,6 +1419,8 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// Call [restore] to pop the save stack and apply the paint to the group.
|
||||
void saveLayer(Rect bounds, Paint paint) {
|
||||
assert(bounds != null);
|
||||
assert(paint != null);
|
||||
if (bounds == null) {
|
||||
_saveLayerWithoutBounds(paint._objects, paint._data);
|
||||
} else {
|
||||
@@ -1387,7 +1430,6 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
}
|
||||
void _saveLayerWithoutBounds(List<dynamic> paintObjects, ByteData paintData)
|
||||
native "Canvas_saveLayerWithoutBounds";
|
||||
// TODO(jackson): Paint should be optional, but making it optional causes crash
|
||||
void _saveLayer(double left,
|
||||
double top,
|
||||
double right,
|
||||
@@ -1433,6 +1475,7 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// Multiply the current transform by the specified 4⨉4 transformation matrix
|
||||
/// specified as a list of values in column-major order.
|
||||
void transform(Float64List matrix4) {
|
||||
assert(matrix4 != null);
|
||||
if (matrix4.length != 16)
|
||||
throw new ArgumentError('"matrix4" must have 16 entries.');
|
||||
_transform(matrix4);
|
||||
@@ -1442,6 +1485,7 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// Reduces the clip region to the intersection of the current clip and the
|
||||
/// given rectangle.
|
||||
void clipRect(Rect rect) {
|
||||
assert(rect != null);
|
||||
_clipRect(rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
void _clipRect(double left,
|
||||
@@ -1451,17 +1495,26 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
|
||||
/// Reduces the clip region to the intersection of the current clip and the
|
||||
/// given rounded rectangle.
|
||||
void clipRRect(RRect rrect) => _clipRRect(rrect._value);
|
||||
void clipRRect(RRect rrect) {
|
||||
assert(rrect != null);
|
||||
_clipRRect(rrect._value);
|
||||
}
|
||||
void _clipRRect(Float32List rrect) native "Canvas_clipRRect";
|
||||
|
||||
/// Reduces the clip region to the intersection of the current clip and the
|
||||
/// given [Path].
|
||||
void clipPath(Path path) native "Canvas_clipPath";
|
||||
void clipPath(Path path) {
|
||||
assert(path != null); // path is checked on the engine side
|
||||
_clipPath(path);
|
||||
}
|
||||
void _clipPath(Path path) native "Canvas_clipPath";
|
||||
|
||||
/// Paints the given [Color] onto the canvas, applying the given
|
||||
/// [BlendMode], with the given color being the source and the background
|
||||
/// being the destination.
|
||||
void drawColor(Color color, BlendMode blendMode) {
|
||||
assert(color != null);
|
||||
assert(blendMode != null);
|
||||
_drawColor(color.value, blendMode.index);
|
||||
}
|
||||
void _drawColor(int color, int blendMode) native "Canvas_drawColor";
|
||||
@@ -1471,6 +1524,9 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// The `p1` and `p2` arguments are interpreted as offsets from the origin.
|
||||
void drawLine(Offset p1, Offset p2, Paint paint) {
|
||||
assert(p1 != null);
|
||||
assert(p2 != null);
|
||||
assert(paint != null);
|
||||
_drawLine(p1.dx, p1.dy, p2.dx, p2.dy, paint._objects, paint._data);
|
||||
}
|
||||
void _drawLine(double x1,
|
||||
@@ -1484,12 +1540,17 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// To fill the canvas with a solid color and blend mode, consider
|
||||
/// [drawColor] instead.
|
||||
void drawPaint(Paint paint) => _drawPaint(paint._objects, paint._data);
|
||||
void drawPaint(Paint paint) {
|
||||
assert(paint != null);
|
||||
_drawPaint(paint._objects, paint._data);
|
||||
}
|
||||
void _drawPaint(List<dynamic> paintObjects, ByteData paintData) native "Canvas_drawPaint";
|
||||
|
||||
/// Draws a rectangle with the given [Paint]. Whether the rectangle is filled
|
||||
/// or stroked (or both) is controlled by [Paint.style].
|
||||
void drawRect(Rect rect, Paint paint) {
|
||||
assert(rect != null);
|
||||
assert(paint != null);
|
||||
_drawRect(rect.left, rect.top, rect.right, rect.bottom,
|
||||
paint._objects, paint._data);
|
||||
}
|
||||
@@ -1503,6 +1564,8 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// Draws a rounded rectangle with the given [Paint]. Whether the rectangle is
|
||||
/// filled or stroked (or both) is controlled by [Paint.style].
|
||||
void drawRRect(RRect rrect, Paint paint) {
|
||||
assert(rrect != null);
|
||||
assert(paint != null);
|
||||
_drawRRect(rrect._value, paint._objects, paint._data);
|
||||
}
|
||||
void _drawRRect(Float32List rrect,
|
||||
@@ -1515,6 +1578,9 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// This shape is almost but not quite entirely unlike an annulus.
|
||||
void drawDRRect(RRect outer, RRect inner, Paint paint) {
|
||||
assert(outer != null);
|
||||
assert(inner != null);
|
||||
assert(paint != null);
|
||||
_drawDRRect(outer._value, inner._value, paint._objects, paint._data);
|
||||
}
|
||||
void _drawDRRect(Float32List outer,
|
||||
@@ -1526,6 +1592,8 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// with the given [Paint]. Whether the oval is filled or stroked (or both) is
|
||||
/// controlled by [Paint.style].
|
||||
void drawOval(Rect rect, Paint paint) {
|
||||
assert(rect != null);
|
||||
assert(paint != null);
|
||||
_drawOval(rect.left, rect.top, rect.right, rect.bottom,
|
||||
paint._objects, paint._data);
|
||||
}
|
||||
@@ -1541,6 +1609,8 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// the third argument. Whether the circle is filled or stroked (or both) is
|
||||
/// controlled by [Paint.style].
|
||||
void drawCircle(Offset c, double radius, Paint paint) {
|
||||
assert(c != null);
|
||||
assert(paint != null);
|
||||
_drawCircle(c.dx, c.dy, radius, paint._objects, paint._data);
|
||||
}
|
||||
void _drawCircle(double x,
|
||||
@@ -1560,6 +1630,8 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// This method is optimized for drawing arcs and should be faster than [Path.arcTo].
|
||||
void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint) {
|
||||
assert(rect != null);
|
||||
assert(paint != null);
|
||||
_drawArc(rect.left, rect.top, rect.right, rect.bottom, startAngle,
|
||||
sweepAngle, useCenter, paint._objects, paint._data);
|
||||
}
|
||||
@@ -1577,6 +1649,8 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// filled or stroked (or both) is controlled by [Paint.style]. If the path is
|
||||
/// filled, then subpaths within it are implicitly closed (see [Path.close]).
|
||||
void drawPath(Path path, Paint paint) {
|
||||
assert(path != null); // path is checked on the engine side
|
||||
assert(paint != null);
|
||||
_drawPath(path, paint._objects, paint._data);
|
||||
}
|
||||
void _drawPath(Path path,
|
||||
@@ -1586,6 +1660,9 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// Draws the given [Image] into the canvas with its top-left corner at the
|
||||
/// given [Offset]. The image is composited into the canvas using the given [Paint].
|
||||
void drawImage(Image image, Offset p, Paint paint) {
|
||||
assert(image != null); // image is checked on the engine side
|
||||
assert(p != null);
|
||||
assert(paint != null);
|
||||
_drawImage(image, p.dx, p.dy, paint._objects, paint._data);
|
||||
}
|
||||
void _drawImage(Image image,
|
||||
@@ -1600,6 +1677,10 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// This might sample from outside the `src` rect by up to half the width of
|
||||
/// an applied filter.
|
||||
void drawImageRect(Image image, Rect src, Rect dst, Paint paint) {
|
||||
assert(image != null); // image is checked on the engine side
|
||||
assert(src != null);
|
||||
assert(dst != null);
|
||||
assert(paint != null);
|
||||
_drawImageRect(image,
|
||||
src.left,
|
||||
src.top,
|
||||
@@ -1638,6 +1719,10 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// cover the destination rectangle while maintaining their relative
|
||||
/// positions.
|
||||
void drawImageNine(Image image, Rect center, Rect dst, Paint paint) {
|
||||
assert(image != null); // image is checked on the engine side
|
||||
assert(center != null);
|
||||
assert(dst != null);
|
||||
assert(paint != null);
|
||||
_drawImageNine(image,
|
||||
center.left,
|
||||
center.top,
|
||||
@@ -1664,7 +1749,11 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
|
||||
/// Draw the given picture onto the canvas. To create a picture, see
|
||||
/// [PictureRecorder].
|
||||
void drawPicture(Picture picture) native "Canvas_drawPicture";
|
||||
void drawPicture(Picture picture) {
|
||||
assert(picture != null); // picture is checked on the engine side
|
||||
_drawPicture(picture);
|
||||
}
|
||||
void _drawPicture(Picture picture) native "Canvas_drawPicture";
|
||||
|
||||
/// Draws the text in the given [Paragraph] into this canvas at the given
|
||||
/// [Offset].
|
||||
@@ -1687,6 +1776,8 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// described by adding half of the [ParagraphConstraints.width] given to
|
||||
/// [Paragraph.layout], to the `offset` argument's [Offset.dx] coordinate.
|
||||
void drawParagraph(Paragraph paragraph, Offset offset) {
|
||||
assert(paragraph != null);
|
||||
assert(offset != null);
|
||||
paragraph._paint(this, offset.dx, offset.dy);
|
||||
}
|
||||
|
||||
@@ -1699,6 +1790,9 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// * [drawRawPoints], which takes `points` as a [Float32List] rather than a
|
||||
/// [List<Offset>].
|
||||
void drawPoints(PointMode pointMode, List<Offset> points, Paint paint) {
|
||||
assert(pointMode != null);
|
||||
assert(points != null);
|
||||
assert(paint != null);
|
||||
_drawPoints(paint._objects, paint._data, pointMode.index, _encodePointList(points));
|
||||
}
|
||||
|
||||
@@ -1712,6 +1806,9 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
/// * [drawPoints], which takes `points` as a [List<Offset>] rather than a
|
||||
/// [List<Float32List>].
|
||||
void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) {
|
||||
assert(pointMode != null);
|
||||
assert(points != null);
|
||||
assert(paint != null);
|
||||
if (points.length % 2 != 0)
|
||||
throw new ArgumentError('"points" must have an even number of values.');
|
||||
_drawPoints(paint._objects, paint._data, pointMode.index, points);
|
||||
@@ -1723,11 +1820,13 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
Float32List points) native "Canvas_drawPoints";
|
||||
|
||||
void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) {
|
||||
_drawVertices(vertices, blendMode, paint._objects, paint._data);
|
||||
assert(vertices != null); // vertices is checked on the engine side
|
||||
assert(paint != null);
|
||||
assert(blendMode != null);
|
||||
_drawVertices(vertices, blendMode.index, paint._objects, paint._data);
|
||||
}
|
||||
|
||||
void _drawVertices(Vertices vertices,
|
||||
BlendMode blendMode,
|
||||
int blendMode,
|
||||
List<dynamic> paintObjects,
|
||||
ByteData paintData) native "Canvas_drawVertices";
|
||||
|
||||
@@ -1736,7 +1835,6 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
//
|
||||
// * [drawRawAtlas], which takes its arguments as typed data lists rather
|
||||
// than objects.
|
||||
// TODO(eseidel): Paint should be optional, but optional doesn't work.
|
||||
void drawAtlas(Image atlas,
|
||||
List<RSTransform> transforms,
|
||||
List<Rect> rects,
|
||||
@@ -1744,8 +1842,15 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
BlendMode blendMode,
|
||||
Rect cullRect,
|
||||
Paint paint) {
|
||||
final int rectCount = rects.length;
|
||||
assert(atlas != null); // atlas is checked on the engine side
|
||||
assert(transforms != null);
|
||||
assert(rects != null);
|
||||
assert(colors != null);
|
||||
assert(blendMode != null);
|
||||
assert(cullRect != null);
|
||||
assert(paint != null);
|
||||
|
||||
final int rectCount = rects.length;
|
||||
if (transforms.length != rectCount)
|
||||
throw new ArgumentError('"transforms" and "rects" lengths must match.');
|
||||
if (colors.isNotEmpty && colors.length != rectCount)
|
||||
@@ -1802,8 +1907,15 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
BlendMode blendMode,
|
||||
Rect cullRect,
|
||||
Paint paint) {
|
||||
final int rectCount = rects.length;
|
||||
assert(atlas != null); // atlas is checked on the engine side
|
||||
assert(rstTransforms != null);
|
||||
assert(rects != null);
|
||||
assert(colors != null);
|
||||
assert(blendMode != null);
|
||||
assert(cullRect != null);
|
||||
assert(paint != null);
|
||||
|
||||
final int rectCount = rects.length;
|
||||
if (rstTransforms.length != rectCount)
|
||||
throw new ArgumentError('"rstTransforms" and "rects" lengths must match.');
|
||||
if (rectCount % 4 != 0)
|
||||
@@ -1830,9 +1942,10 @@ class Canvas extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// transparentOccluder should be true if the occluding object is not opaque.
|
||||
void drawShadow(Path path, Color color, double elevation, bool transparentOccluder) {
|
||||
assert(path != null); // path is checked on the engine side
|
||||
assert(color != null);
|
||||
_drawShadow(path, color.value, elevation, transparentOccluder);
|
||||
}
|
||||
|
||||
void _drawShadow(Path path,
|
||||
int color,
|
||||
double elevation,
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "third_party/skia/include/core/SkCanvas.h"
|
||||
#include "third_party/skia/include/core/SkRSXform.h"
|
||||
|
||||
using tonic::ToDart;
|
||||
|
||||
namespace blink {
|
||||
|
||||
static void Canvas_constructor(Dart_NativeArguments args) {
|
||||
@@ -70,8 +72,9 @@ ftl::RefPtr<Canvas> Canvas::Create(PictureRecorder* recorder,
|
||||
double top,
|
||||
double right,
|
||||
double bottom) {
|
||||
FTL_DCHECK(recorder);
|
||||
FTL_DCHECK(!recorder->isRecording());
|
||||
if (!recorder)
|
||||
Dart_ThrowException(ToDart("Canvas constructor called with non-genuine PictureRecorder."));
|
||||
FTL_DCHECK(!recorder->isRecording()); // verified by Dart code
|
||||
ftl::RefPtr<Canvas> canvas = ftl::MakeRefCounted<Canvas>(
|
||||
recorder->BeginRecording(SkRect::MakeLTRB(left, top, right, bottom)));
|
||||
recorder->set_canvas(canvas);
|
||||
@@ -164,6 +167,8 @@ void Canvas::clipRRect(const RRect& rrect) {
|
||||
void Canvas::clipPath(const CanvasPath* path) {
|
||||
if (!canvas_)
|
||||
return;
|
||||
if (!path)
|
||||
Dart_ThrowException(ToDart("Canvas.clipPath called with non-genuine Path."));
|
||||
canvas_->clipPath(path->path(), true);
|
||||
}
|
||||
|
||||
@@ -260,7 +265,8 @@ void Canvas::drawPath(const CanvasPath* path,
|
||||
const PaintData& paint_data) {
|
||||
if (!canvas_)
|
||||
return;
|
||||
FTL_DCHECK(path);
|
||||
if (!path)
|
||||
Dart_ThrowException(ToDart("Canvas.drawPath called with non-genuine Path."));
|
||||
canvas_->drawPath(path->path(), *paint.paint());
|
||||
}
|
||||
|
||||
@@ -271,7 +277,8 @@ void Canvas::drawImage(const CanvasImage* image,
|
||||
const PaintData& paint_data) {
|
||||
if (!canvas_)
|
||||
return;
|
||||
FTL_DCHECK(image);
|
||||
if (!image)
|
||||
Dart_ThrowException(ToDart("Canvas.drawImage called with non-genuine Image."));
|
||||
canvas_->drawImage(image->image(), x, y, paint.paint());
|
||||
}
|
||||
|
||||
@@ -288,7 +295,8 @@ void Canvas::drawImageRect(const CanvasImage* image,
|
||||
const PaintData& paint_data) {
|
||||
if (!canvas_)
|
||||
return;
|
||||
FTL_DCHECK(image);
|
||||
if (!image)
|
||||
Dart_ThrowException(ToDart("Canvas.drawImageRect called with non-genuine Image."));
|
||||
SkRect src = SkRect::MakeLTRB(src_left, src_top, src_right, src_bottom);
|
||||
SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
|
||||
canvas_->drawImageRect(image->image(), src, dst, paint.paint(),
|
||||
@@ -308,7 +316,8 @@ void Canvas::drawImageNine(const CanvasImage* image,
|
||||
const PaintData& paint_data) {
|
||||
if (!canvas_)
|
||||
return;
|
||||
FTL_DCHECK(image);
|
||||
if (!image)
|
||||
Dart_ThrowException(ToDart("Canvas.drawImageNine called with non-genuine Image."));
|
||||
SkRect center =
|
||||
SkRect::MakeLTRB(center_left, center_top, center_right, center_bottom);
|
||||
SkIRect icenter;
|
||||
@@ -320,7 +329,8 @@ void Canvas::drawImageNine(const CanvasImage* image,
|
||||
void Canvas::drawPicture(Picture* picture) {
|
||||
if (!canvas_)
|
||||
return;
|
||||
FTL_DCHECK(picture);
|
||||
if (!picture)
|
||||
Dart_ThrowException(ToDart("Canvas.drawPicture called with non-genuine Picture."));
|
||||
canvas_->drawPicture(picture->picture().get());
|
||||
}
|
||||
|
||||
@@ -346,6 +356,8 @@ void Canvas::drawVertices(const Vertices* vertices,
|
||||
const PaintData& paint_data) {
|
||||
if (!canvas_)
|
||||
return;
|
||||
if (!vertices)
|
||||
Dart_ThrowException(ToDart("Canvas.drawVertices called with non-genuine Vertices."));
|
||||
|
||||
canvas_->drawVertices(vertices->vertices(),
|
||||
blend_mode,
|
||||
@@ -362,6 +374,8 @@ void Canvas::drawAtlas(const Paint& paint,
|
||||
const tonic::Float32List& cull_rect) {
|
||||
if (!canvas_)
|
||||
return;
|
||||
if (!atlas)
|
||||
Dart_ThrowException(ToDart("Canvas.drawAtlas or Canvas.drawRawAtlas called with non-genuine Image."));
|
||||
|
||||
sk_sp<SkImage> skImage = atlas->image();
|
||||
|
||||
@@ -383,6 +397,8 @@ void Canvas::drawShadow(const CanvasPath* path,
|
||||
SkColor color,
|
||||
double elevation,
|
||||
bool transparentOccluder) {
|
||||
if (!path)
|
||||
Dart_ThrowException(ToDart("Canvas.drawShader called with non-genuine Path."));
|
||||
flow::PhysicalModelLayer::DrawShadow(canvas_,
|
||||
path->path(),
|
||||
color,
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "lib/tonic/converter/dart_converter.h"
|
||||
#include "lib/tonic/dart_library_natives.h"
|
||||
|
||||
using tonic::ToDart;
|
||||
|
||||
namespace blink {
|
||||
|
||||
static void ImageShader_constructor(Dart_NativeArguments args) {
|
||||
@@ -35,7 +37,8 @@ void ImageShader::initWithImage(CanvasImage* image,
|
||||
SkShader::TileMode tmx,
|
||||
SkShader::TileMode tmy,
|
||||
const tonic::Float64List& matrix4) {
|
||||
FTL_DCHECK(image != NULL);
|
||||
if (!image)
|
||||
Dart_ThrowException(ToDart("ImageShader constructor called with non-genuine Image."));
|
||||
SkMatrix sk_matrix = ToSkMatrix(matrix4);
|
||||
set_shader(image->image()->makeShader(tmx, tmy, &sk_matrix));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "lib/tonic/converter/dart_converter.h"
|
||||
#include "lib/tonic/dart_library_natives.h"
|
||||
|
||||
using tonic::ToDart;
|
||||
|
||||
namespace blink {
|
||||
|
||||
typedef CanvasPath Path;
|
||||
@@ -165,13 +167,15 @@ void CanvasPath::addRRect(const RRect& rrect) {
|
||||
}
|
||||
|
||||
void CanvasPath::addPath(CanvasPath* path, double dx, double dy) {
|
||||
if (path)
|
||||
path_.addPath(path->path(), dx, dy, SkPath::kAppend_AddPathMode);
|
||||
if (!path)
|
||||
Dart_ThrowException(ToDart("Path.addPath called with non-genuine Path."));
|
||||
path_.addPath(path->path(), dx, dy, SkPath::kAppend_AddPathMode);
|
||||
}
|
||||
|
||||
void CanvasPath::extendWithPath(CanvasPath* path, double dx, double dy) {
|
||||
if (path)
|
||||
path_.addPath(path->path(), dx, dy, SkPath::kExtend_AddPathMode);
|
||||
if (!path)
|
||||
Dart_ThrowException(ToDart("Path.extendWithPath called with non-genuine Path."));
|
||||
path_.addPath(path->path(), dx, dy, SkPath::kExtend_AddPathMode);
|
||||
}
|
||||
|
||||
void CanvasPath::close() {
|
||||
|
||||
95
engine/src/flutter/testing/dart/canvas_test.dart
Normal file
95
engine/src/flutter/testing/dart/canvas_test.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class FakeEverything implements Canvas, PictureRecorder, Color {
|
||||
dynamic noSuchMethod(Invocation invocation) {
|
||||
return new FakeEverything();
|
||||
}
|
||||
}
|
||||
|
||||
class NegativeSpace implements Canvas, PictureRecorder, Color {
|
||||
dynamic noSuchMethod(Invocation invocation) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void testCanvas(callback(Canvas canvas)) {
|
||||
try {
|
||||
callback(new Canvas(new PictureRecorder(), new Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)));
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
void main() {
|
||||
test("canvas APIs should not crash", () {
|
||||
dynamic fake = new FakeEverything();
|
||||
dynamic no = new NegativeSpace();
|
||||
Paint paint = new Paint();
|
||||
Rect rect = new Rect.fromLTRB(double.NAN, double.NAN, double.NAN, double.NAN);
|
||||
List<dynamic> list = <dynamic>[fake, fake];
|
||||
Offset offset = new Offset(double.NAN, double.NAN);
|
||||
Path path = new Path();
|
||||
|
||||
try { new Canvas(null, null); } catch (error) { }
|
||||
try { new Canvas(null, rect); } catch (error) { }
|
||||
try { new Canvas(null, fake); } catch (error) { }
|
||||
try { new Canvas(fake, rect); } catch (error) { }
|
||||
try { new Canvas(no, rect); } catch (error) { }
|
||||
|
||||
try {
|
||||
new PictureRecorder()
|
||||
..endRecording()
|
||||
..endRecording()
|
||||
..endRecording();
|
||||
} catch (error) { }
|
||||
|
||||
testCanvas((Canvas canvas) => canvas.clipPath(fake));
|
||||
testCanvas((Canvas canvas) => canvas.clipRect(fake));
|
||||
testCanvas((Canvas canvas) => canvas.clipRRect(fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawArc(fake, 0.0, 0.0, false, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawArc(rect, 0.0, 0.0, false, fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawAtlas(fake, list, list, list, fake, rect, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawCircle(offset, double.NAN, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawColor(fake, fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawDRRect(fake, fake, fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawImage(fake, offset, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawImageNine(fake, rect, rect, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawImageRect(fake, rect, rect, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawLine(offset, offset, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawOval(rect, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawPaint(paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawPaint(fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawPaint(no));
|
||||
testCanvas((Canvas canvas) => canvas.drawParagraph(fake, offset));
|
||||
testCanvas((Canvas canvas) => canvas.drawPath(fake, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawPicture(fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawPoints(fake, list, fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawRawAtlas(fake, fake, fake, fake, fake, fake, fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawRawPoints(fake, list, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawRect(rect, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawRRect(fake, paint));
|
||||
testCanvas((Canvas canvas) => canvas.drawShadow(path, color, double.NAN, null));
|
||||
testCanvas((Canvas canvas) => canvas.drawShadow(path, color, double.NAN, false));
|
||||
testCanvas((Canvas canvas) => canvas.drawShadow(path, color, double.NAN, true));
|
||||
testCanvas((Canvas canvas) => canvas.drawShadow(path, color, double.NAN, no));
|
||||
testCanvas((Canvas canvas) => canvas.drawShadow(path, color, double.NAN, fake));
|
||||
testCanvas((Canvas canvas) => canvas.drawVertices(fake, null, paint));
|
||||
testCanvas((Canvas canvas) => canvas.getSaveCount());
|
||||
testCanvas((Canvas canvas) => canvas.restore());
|
||||
testCanvas((Canvas canvas) => canvas.rotate(double.NAN));
|
||||
testCanvas((Canvas canvas) => canvas.save());
|
||||
testCanvas((Canvas canvas) => canvas.saveLayer(rect, paint));
|
||||
testCanvas((Canvas canvas) => canvas.saveLayer(fake, fake));
|
||||
testCanvas((Canvas canvas) => canvas.saveLayer(null, null));
|
||||
testCanvas((Canvas canvas) => canvas.scale(double.NAN, double.NAN));
|
||||
testCanvas((Canvas canvas) => canvas.skew(double.NAN, double.NAN));
|
||||
testCanvas((Canvas canvas) => canvas.transform(fake));
|
||||
testCanvas((Canvas canvas) => canvas.transform(no));
|
||||
testCanvas((Canvas canvas) => canvas.transform(null));
|
||||
testCanvas((Canvas canvas) => canvas.translate(double.NAN, double.NAN));
|
||||
});
|
||||
}
|
||||
@@ -15,4 +15,5 @@ popd
|
||||
|
||||
for TEST_SCRIPT in flutter/testing/dart/*.dart; do
|
||||
out/host_debug_unopt/flutter_tester --disable-observatory --disable-diagnostic --non-interactive --enable-checked-mode $TEST_SCRIPT
|
||||
out/host_debug_unopt/flutter_tester --disable-observatory --disable-diagnostic --non-interactive $TEST_SCRIPT
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user