Bug 1726704 [wpt PR 30103] - Add more extensive tests for .canShare(), a=testonly
[gecko.git] / layout / base / MotionPathUtils.cpp
blobb472949094e9d7b4df09164c68bce22739b1f1d7
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/MotionPathUtils.h"
9 #include "gfxPlatform.h"
10 #include "mozilla/dom/SVGPathData.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/Matrix.h"
13 #include "mozilla/layers/LayersMessages.h"
14 #include "mozilla/RefPtr.h"
15 #include "nsIFrame.h"
16 #include "nsStyleTransformMatrix.h"
18 #include <math.h>
20 namespace mozilla {
22 using nsStyleTransformMatrix::TransformReferenceBox;
24 RayReferenceData::RayReferenceData(const nsIFrame* aFrame) {
25 // We use GetContainingBlock() for now. TYLin said this function is buggy in
26 // modern CSS layout, but is ok for most cases.
27 // FIXME: Bug 1581237: This is still not clear that which box we should use
28 // for calculating the path length. We may need to update this.
29 // https://github.com/w3c/fxtf-drafts/issues/369
30 // FIXME: Bug 1579294: SVG layout may get a |container| with empty mRect
31 // (e.g. SVGOuterSVGAnonChildFrame), which makes the path length zero.
32 const nsIFrame* container = aFrame->GetContainingBlock();
33 if (!container) {
34 // If there is no parent frame, it's impossible to calculate the path
35 // length, so does the path.
36 return;
39 // The initial position is (0, 0) in |aFrame|, and we have to transform it
40 // into the space of |container|, so use GetOffsetsTo() to get the delta
41 // value.
42 // FIXME: Bug 1559232: The initial position will be adjusted after
43 // supporting `offset-position`.
44 mInitialPosition = CSSPoint::FromAppUnits(aFrame->GetOffsetTo(container));
45 // FIXME: We need a better definition for containing box in the spec. For now,
46 // we use border box for calculation.
47 // https://github.com/w3c/fxtf-drafts/issues/369
48 mContainingBlockRect =
49 CSSRect::FromAppUnits(container->GetRectRelativeToSelf());
52 // The distance is measured between the initial position and the intersection of
53 // the ray with the box
54 // https://drafts.fxtf.org/motion-1/#size-sides
55 static CSSCoord ComputeSides(const CSSPoint& aInitialPosition,
56 const CSSSize& aContainerSize,
57 const StyleAngle& aAngle) {
58 // Given an acute angle |theta| (i.e. |t|) of a right-angled triangle, the
59 // hypotenuse |h| is the side that connects the two acute angles. The side
60 // |b| adjacent to |theta| is the side of the triangle that connects |theta|
61 // to the right angle.
63 // e.g. if the angle |t| is 0 ~ 90 degrees, and b * tan(theta) <= b',
64 // h = b / cos(t):
65 // b*tan(t)
66 // (0, 0) #--------*-----*--# (aContainerSize.width, 0)
67 // | | / |
68 // | | / |
69 // | b h |
70 // | |t/ |
71 // | |/ |
72 // (aInitialPosition) *---b'---* (aContainerSize.width, aInitialPosition.y)
73 // | | |
74 // | | |
75 // | | |
76 // | | |
77 // | | |
78 // #-----------------# (aContainerSize.width,
79 // (0, aContainerSize.height) aContainerSize.height)
80 double theta = aAngle.ToRadians();
81 double sint = std::sin(theta);
82 double cost = std::cos(theta);
84 double b = cost >= 0 ? aInitialPosition.y
85 : aContainerSize.height - aInitialPosition.y;
86 double bPrime = sint >= 0 ? aContainerSize.width - aInitialPosition.x
87 : aInitialPosition.x;
88 sint = std::fabs(sint);
89 cost = std::fabs(cost);
91 // If |b * tan(theta)| is larger than |bPrime|, the intersection is
92 // on the other side, and |b'| is the opposite side of angle |theta| in this
93 // case.
95 // e.g. If b * tan(theta) > b', h = b' / sin(theta):
96 // *----*
97 // | |
98 // | /|
99 // b /t|
100 // |t/ |
101 // |/ |
102 // *-b'-*
103 if (b * sint > bPrime * cost) {
104 return bPrime / sint;
106 return b / cost;
109 static CSSCoord ComputeRayPathLength(const StyleRaySize aRaySizeType,
110 const StyleAngle& aAngle,
111 const RayReferenceData& aRayData) {
112 if (aRaySizeType == StyleRaySize::Sides) {
113 // If the initial position is not within the box, the distance is 0.
114 if (!aRayData.mContainingBlockRect.Contains(aRayData.mInitialPosition)) {
115 return 0.0;
118 return ComputeSides(aRayData.mInitialPosition,
119 aRayData.mContainingBlockRect.Size(), aAngle);
122 // left: the length between the initial point and the left side.
123 // right: the length between the initial point and the right side.
124 // top: the length between the initial point and the top side.
125 // bottom: the lenght between the initial point and the bottom side.
126 CSSCoord left = std::abs(aRayData.mInitialPosition.x);
127 CSSCoord right = std::abs(aRayData.mContainingBlockRect.width -
128 aRayData.mInitialPosition.x);
129 CSSCoord top = std::abs(aRayData.mInitialPosition.y);
130 CSSCoord bottom = std::abs(aRayData.mContainingBlockRect.height -
131 aRayData.mInitialPosition.y);
133 switch (aRaySizeType) {
134 case StyleRaySize::ClosestSide:
135 return std::min({left, right, top, bottom});
137 case StyleRaySize::FarthestSide:
138 return std::max({left, right, top, bottom});
140 case StyleRaySize::ClosestCorner:
141 case StyleRaySize::FarthestCorner: {
142 CSSCoord h = 0;
143 CSSCoord v = 0;
144 if (aRaySizeType == StyleRaySize::ClosestCorner) {
145 h = std::min(left, right);
146 v = std::min(top, bottom);
147 } else {
148 h = std::max(left, right);
149 v = std::max(top, bottom);
151 return sqrt(h.value * h.value + v.value * v.value);
153 default:
154 MOZ_ASSERT_UNREACHABLE("Unsupported ray size");
157 return 0.0;
160 static void ApplyRotationAndMoveRayToXAxis(
161 const StyleOffsetRotate& aOffsetRotate, const StyleAngle& aRayAngle,
162 AutoTArray<gfx::Point, 4>& aVertices) {
163 const StyleAngle directionAngle = aRayAngle - StyleAngle{90.0f};
164 // Get the final rotation which includes the direction angle and
165 // offset-rotate.
166 const StyleAngle rotateAngle =
167 (aOffsetRotate.auto_ ? directionAngle : StyleAngle{0.0f}) +
168 aOffsetRotate.angle;
169 // This is the rotation to rotate ray to positive x-axis (i.e. 90deg).
170 const StyleAngle rayToXAxis = StyleAngle{90.0} - aRayAngle;
172 gfx::Matrix m;
173 m.PreRotate((rotateAngle + rayToXAxis).ToRadians());
174 for (gfx::Point& p : aVertices) {
175 p = m.TransformPoint(p);
179 class RayPointComparator {
180 public:
181 bool Equals(const gfx::Point& a, const gfx::Point& b) const {
182 return std::fabs(a.y) == std::fabs(b.y);
185 bool LessThan(const gfx::Point& a, const gfx::Point& b) const {
186 return std::fabs(a.y) > std::fabs(b.y);
189 // Note: the calculation of contain doesn't take other transform-like properties
190 // into account. The spec doesn't mention the co-operation for this, so for now,
191 // we assume we only need to take motion-path into account.
192 static CSSCoord ComputeRayUsedDistance(const RayFunction& aRay,
193 const LengthPercentage& aDistance,
194 const StyleOffsetRotate& aRotate,
195 const StylePositionOrAuto& aAnchor,
196 const CSSPoint& aTransformOrigin,
197 TransformReferenceBox& aRefBox,
198 const CSSCoord& aPathLength) {
199 CSSCoord usedDistance = aDistance.ResolveToCSSPixels(aPathLength);
200 if (!aRay.contain) {
201 return usedDistance;
204 // We have to simulate the 4 vertices to check if any of them is outside the
205 // path circle. Here, we create a 2D Cartesian coordinate system and its
206 // origin is at the anchor point of the box. And then apply the rotation on
207 // these 4 vertices, calculate the range of |usedDistance| which makes the box
208 // entirely contained within the path.
209 // Note:
210 // "Contained within the path" means the rectangle is inside a circle whose
211 // radius is |aPathLength|.
212 CSSPoint usedAnchor = aTransformOrigin;
213 CSSSize size =
214 CSSPixel::FromAppUnits(nsSize(aRefBox.Width(), aRefBox.Height()));
215 if (!aAnchor.IsAuto()) {
216 const StylePosition& anchor = aAnchor.AsPosition();
217 usedAnchor.x = anchor.horizontal.ResolveToCSSPixels(size.width);
218 usedAnchor.y = anchor.vertical.ResolveToCSSPixels(size.height);
220 AutoTArray<gfx::Point, 4> vertices = {
221 {-usedAnchor.x, -usedAnchor.y},
222 {size.width - usedAnchor.x, -usedAnchor.y},
223 {size.width - usedAnchor.x, size.height - usedAnchor.y},
224 {-usedAnchor.x, size.height - usedAnchor.y}};
226 ApplyRotationAndMoveRayToXAxis(aRotate, aRay.angle, vertices);
228 // We have to check if all 4 vertices are inside the circle with radius |r|.
229 // Assume the position of the vertex is (x, y), and the box is moved by
230 // |usedDistance| along the path:
232 // (usedDistance + x)^2 + y^2 <= r^2
233 // ==> (usedDistance + x)^2 <= r^2 - y^2 = d
234 // ==> -x - sqrt(d) <= used distance <= -x + sqrt(d)
236 // Note: |usedDistance| is added into |x| because we convert the ray function
237 // to 90deg, x-axis):
238 float upperMin = std::numeric_limits<float>::max();
239 float lowerMax = std::numeric_limits<float>::min();
240 bool shouldIncreasePathLength = false;
241 for (const gfx::Point& p : vertices) {
242 float d = aPathLength.value * aPathLength.value - p.y * p.y;
243 if (d < 0) {
244 // Impossible to make the box inside the path circle. Need to increase
245 // the path length.
246 shouldIncreasePathLength = true;
247 break;
249 float sqrtD = sqrt(d);
250 upperMin = std::min(upperMin, -p.x + sqrtD);
251 lowerMax = std::max(lowerMax, -p.x - sqrtD);
254 if (!shouldIncreasePathLength) {
255 return std::max(lowerMax, std::min(upperMin, (float)usedDistance));
258 // Sort by the absolute value of y, so the first vertex of the each pair of
259 // vertices we check has a larger y value. (i.e. |yi| is always larger than or
260 // equal to |yj|.)
261 vertices.Sort(RayPointComparator());
263 // Assume we set |usedDistance| to |-vertices[0].x|, so the current radius is
264 // fabs(vertices[0].y). This is a possible solution.
265 double radius = std::fabs(vertices[0].y);
266 usedDistance = -vertices[0].x;
267 const double epsilon = 1e-5;
269 for (size_t i = 0; i < 3; ++i) {
270 for (size_t j = i + 1; j < 4; ++j) {
271 double xi = vertices[i].x;
272 double yi = vertices[i].y;
273 double xj = vertices[j].x;
274 double yj = vertices[j].y;
275 double dx = xi - xj;
277 // Check if any path that enclosed vertices[i] would also enclose
278 // vertices[j].
280 // For example, the initial setup:
281 // * (0, yi)
282 // |
283 // r
284 // | * (xj - xi, yj)
285 // xi | dx
286 // ----*-----------*----------*---
287 // (anchor point) | (0, 0)
289 // Assuming (0, yi) is on the path and (xj - xi, yj) is inside the path
290 // circle, we should use the inequality to check this:
291 // (xj - xi)^2 + yj^2 <= yi^2
293 // After the first iterations, the updated inequality is:
294 // (dx + d)^2 + yj^2 <= yi^2 + d^2
295 // ==> dx^2 + 2dx*d + yj^2 <= yi^2
296 // ==> dx^2 + yj^2 <= yi^2 - 2dx*d <= yi^2
297 // , |d| is the difference (or offset) between the old |usedDistance| and
298 // new |usedDistance|.
300 // Note: `2dx * d` must be positive because
301 // 1. if |xj| is larger than |xi|, only negative |d| could be used to get
302 // a new path length which encloses both vertices.
303 // 2. if |xj| is smaller than |xi|, only positive |d| could be used to get
304 // a new path length which encloses both vertices.
305 if (dx * dx + yj * yj <= yi * yi + epsilon) {
306 continue;
309 // We have to find a new usedDistance which let both vertices[i] and
310 // vertices[j] be on the path.
311 // (usedDistance + xi)^2 + yi^2 = (usedDistance + xj)^2 + yj^2
312 // = radius^2
313 // ==> usedDistance = (xj^2 + yj^2 - xi^2 - yi^2) / 2(xi-xj)
315 // Note: it's impossible to have a "divide by zero" problem here.
316 // If |dx| is zero, the if-condition above should always be true and so
317 // we skip the calculation.
318 double newUsedDistance =
319 (xj * xj + yj * yj - xi * xi - yi * yi) / dx / 2.0;
320 // Then, move vertices[i] and vertices[j] by |newUsedDistance|.
321 xi += newUsedDistance; // or xj += newUsedDistance; if we use |xj| to get
322 // |newRadius|.
323 double newRadius = sqrt(xi * xi + yi * yi);
324 if (newRadius > radius) {
325 // We have to increase the path length to make sure both vertices[i] and
326 // vertices[j] are contained by this new path length.
327 radius = newRadius;
328 usedDistance = (float)newUsedDistance;
333 return usedDistance;
336 /* static */
337 CSSPoint MotionPathUtils::ComputeAnchorPointAdjustment(const nsIFrame& aFrame) {
338 if (!aFrame.HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
339 return {};
342 auto transformBox = aFrame.StyleDisplay()->mTransformBox;
343 if (transformBox == StyleGeometryBox::ViewBox ||
344 transformBox == StyleGeometryBox::BorderBox) {
345 return {};
348 if (aFrame.IsFrameOfType(nsIFrame::eSVGContainer)) {
349 nsRect boxRect = nsLayoutUtils::ComputeGeometryBox(
350 const_cast<nsIFrame*>(&aFrame), StyleGeometryBox::FillBox);
351 return CSSPoint::FromAppUnits(boxRect.TopLeft());
353 return CSSPoint::FromAppUnits(aFrame.GetPosition());
356 /* static */
357 Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
358 const OffsetPathData& aPath, const LengthPercentage& aDistance,
359 const StyleOffsetRotate& aRotate, const StylePositionOrAuto& aAnchor,
360 const CSSPoint& aTransformOrigin, TransformReferenceBox& aRefBox,
361 const CSSPoint& aAnchorPointAdjustment) {
362 if (aPath.IsNone()) {
363 return Nothing();
366 // Compute the point and angle for creating the equivalent translate and
367 // rotate.
368 double directionAngle = 0.0;
369 gfx::Point point;
370 if (aPath.IsPath()) {
371 const auto& path = aPath.AsPath();
372 if (!path.mGfxPath) {
373 // Empty gfx::Path means it is path('') (i.e. empty path string).
374 return Nothing();
377 // Per the spec, we have to convert offset distance to pixels, with 100%
378 // being converted to total length. So here |gfxPath| is built with CSS
379 // pixel, and we calculate |pathLength| and |computedDistance| with CSS
380 // pixel as well.
381 gfx::Float pathLength = path.mGfxPath->ComputeLength();
382 gfx::Float usedDistance =
383 aDistance.ResolveToCSSPixels(CSSCoord(pathLength));
384 if (path.mIsClosedIntervals) {
385 // Per the spec, let used offset distance be equal to offset distance
386 // modulus the total length of the path. If the total length of the path
387 // is 0, used offset distance is also 0.
388 usedDistance = pathLength > 0.0 ? fmod(usedDistance, pathLength) : 0.0;
389 // We make sure |usedDistance| is 0.0 or a positive value.
390 // https://github.com/w3c/fxtf-drafts/issues/339
391 if (usedDistance < 0.0) {
392 usedDistance += pathLength;
394 } else {
395 // Per the spec, for unclosed interval, let used offset distance be equal
396 // to offset distance clamped by 0 and the total length of the path.
397 usedDistance = clamped(usedDistance, 0.0f, pathLength);
399 gfx::Point tangent;
400 point = path.mGfxPath->ComputePointAtLength(usedDistance, &tangent);
401 directionAngle = (double)atan2(tangent.y, tangent.x); // In Radian.
402 } else if (aPath.IsRay()) {
403 const auto& ray = aPath.AsRay();
404 MOZ_ASSERT(ray.mRay);
406 CSSCoord pathLength =
407 ComputeRayPathLength(ray.mRay->size, ray.mRay->angle, ray.mData);
408 CSSCoord usedDistance =
409 ComputeRayUsedDistance(*ray.mRay, aDistance, aRotate, aAnchor,
410 aTransformOrigin, aRefBox, pathLength);
412 // 0deg pointing up and positive angles representing clockwise rotation.
413 directionAngle =
414 StyleAngle{ray.mRay->angle.ToDegrees() - 90.0f}.ToRadians();
416 point.x = usedDistance * cos(directionAngle);
417 point.y = usedDistance * sin(directionAngle);
418 } else {
419 MOZ_ASSERT_UNREACHABLE("Unsupported offset-path value");
420 return Nothing();
423 // If |rotate.auto_| is true, the element should be rotated by the angle of
424 // the direction (i.e. directional tangent vector) of the offset-path, and the
425 // computed value of <angle> is added to this.
426 // Otherwise, the element has a constant clockwise rotation transformation
427 // applied to it by the specified rotation angle. (i.e. Don't need to
428 // consider the direction of the path.)
429 gfx::Float angle = static_cast<gfx::Float>(
430 (aRotate.auto_ ? directionAngle : 0.0) + aRotate.angle.ToRadians());
432 // Compute the offset for motion path translate.
433 // Bug 1559232: the translate parameters will be adjusted more after we
434 // support offset-position.
435 // Per the spec, the default offset-anchor is `auto`, so initialize the anchor
436 // point to transform-origin.
437 CSSPoint anchorPoint(aTransformOrigin);
438 gfx::Point shift;
439 if (!aAnchor.IsAuto()) {
440 const auto& pos = aAnchor.AsPosition();
441 anchorPoint = nsStyleTransformMatrix::Convert2DPosition(
442 pos.horizontal, pos.vertical, aRefBox);
443 // We need this value to shift the origin from transform-origin to
444 // offset-anchor (and vice versa).
445 // See nsStyleTransformMatrix::ReadTransform for more details.
446 shift = (anchorPoint - aTransformOrigin).ToUnknownPoint();
449 anchorPoint += aAnchorPointAdjustment;
451 return Some(ResolvedMotionPathData{point - anchorPoint.ToUnknownPoint(),
452 angle, shift});
455 static OffsetPathData GenerateOffsetPathData(const nsIFrame* aFrame) {
456 const StyleOffsetPath& path = aFrame->StyleDisplay()->mOffsetPath;
457 switch (path.tag) {
458 case StyleOffsetPath::Tag::Path: {
459 const StyleSVGPathData& pathData = path.AsPath();
460 RefPtr<gfx::Path> gfxPath =
461 aFrame->GetProperty(nsIFrame::OffsetPathCache());
462 MOZ_ASSERT(
463 gfxPath || pathData._0.IsEmpty(),
464 "Should have a valid cached gfx::Path or an empty path string");
465 return OffsetPathData::Path(pathData, gfxPath.forget());
467 case StyleOffsetPath::Tag::Ray:
468 return OffsetPathData::Ray(path.AsRay(), RayReferenceData(aFrame));
469 case StyleOffsetPath::Tag::None:
470 return OffsetPathData::None();
471 default:
472 MOZ_ASSERT_UNREACHABLE("Unknown offset-path");
473 return OffsetPathData::None();
477 /* static*/
478 Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
479 const nsIFrame* aFrame, TransformReferenceBox& aRefBox) {
480 MOZ_ASSERT(aFrame);
482 const nsStyleDisplay* display = aFrame->StyleDisplay();
484 // FIXME: It's possible to refactor the calculation of transform-origin, so we
485 // could calculate from the caller, and reuse the value in nsDisplayList.cpp.
486 CSSPoint transformOrigin = nsStyleTransformMatrix::Convert2DPosition(
487 display->mTransformOrigin.horizontal, display->mTransformOrigin.vertical,
488 aRefBox);
490 return ResolveMotionPath(GenerateOffsetPathData(aFrame),
491 display->mOffsetDistance, display->mOffsetRotate,
492 display->mOffsetAnchor, transformOrigin, aRefBox,
493 ComputeAnchorPointAdjustment(*aFrame));
496 static OffsetPathData GenerateOffsetPathData(
497 const StyleOffsetPath& aPath, const RayReferenceData& aRayReferenceData,
498 gfx::Path* aCachedMotionPath) {
499 switch (aPath.tag) {
500 case StyleOffsetPath::Tag::Path: {
501 const StyleSVGPathData& pathData = aPath.AsPath();
502 // If aCachedMotionPath is valid, we have a fixed path.
503 // This means we have pre-built it already and no need to update.
504 RefPtr<gfx::Path> path = aCachedMotionPath;
505 if (!path) {
506 RefPtr<gfx::PathBuilder> builder =
507 MotionPathUtils::GetCompositorPathBuilder();
508 path = MotionPathUtils::BuildPath(pathData, builder);
510 return OffsetPathData::Path(pathData, path.forget());
512 case StyleOffsetPath::Tag::Ray:
513 return OffsetPathData::Ray(aPath.AsRay(), aRayReferenceData);
514 case StyleOffsetPath::Tag::None:
515 default:
516 return OffsetPathData::None();
520 /* static */
521 Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
522 const StyleOffsetPath* aPath, const StyleLengthPercentage* aDistance,
523 const StyleOffsetRotate* aRotate, const StylePositionOrAuto* aAnchor,
524 const Maybe<layers::MotionPathData>& aMotionPathData,
525 TransformReferenceBox& aRefBox, gfx::Path* aCachedMotionPath) {
526 if (!aPath) {
527 return Nothing();
530 MOZ_ASSERT(aMotionPathData);
532 auto zeroOffsetDistance = LengthPercentage::Zero();
533 auto autoOffsetRotate = StyleOffsetRotate{true, StyleAngle::Zero()};
534 auto autoOffsetAnchor = StylePositionOrAuto::Auto();
535 return ResolveMotionPath(
536 GenerateOffsetPathData(*aPath, aMotionPathData->rayReferenceData(),
537 aCachedMotionPath),
538 aDistance ? *aDistance : zeroOffsetDistance,
539 aRotate ? *aRotate : autoOffsetRotate,
540 aAnchor ? *aAnchor : autoOffsetAnchor, aMotionPathData->origin(), aRefBox,
541 aMotionPathData->anchorAdjustment());
544 /* static */
545 StyleSVGPathData MotionPathUtils::NormalizeSVGPathData(
546 const StyleSVGPathData& aPath) {
547 StyleSVGPathData n;
548 Servo_SVGPathData_Normalize(&aPath, &n);
549 return n;
552 /* static */
553 already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
554 const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder) {
555 if (!aPathBuilder) {
556 return nullptr;
559 const Span<const StylePathCommand>& path = aPath._0.AsSpan();
560 return SVGPathData::BuildPath(path, aPathBuilder, StyleStrokeLinecap::Butt,
561 0.0);
564 /* static */
565 already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetCompositorPathBuilder() {
566 // FIXME: Perhaps we need a PathBuilder which is independent on the backend.
567 RefPtr<gfx::PathBuilder> builder =
568 gfxPlatform::Initialized()
569 ? gfxPlatform::GetPlatform()
570 ->ScreenReferenceDrawTarget()
571 ->CreatePathBuilder(gfx::FillRule::FILL_WINDING)
572 : gfx::Factory::CreateSimplePathBuilder();
573 return builder.forget();
576 } // namespace mozilla