@@ -190,6 +190,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface
190190 final ui.Color shadowColor;
191191 final ui.Clip clipBehavior;
192192 html.Element ? _clipElement;
193+ html.Element ? _svgElement;
193194
194195 @override
195196 void recomputeTransformAndClip () {
@@ -214,23 +215,18 @@ class PersistedPhysicalShape extends PersistedContainerSurface
214215 rootElement! .style.backgroundColor = colorToCssString (color);
215216 }
216217
217- void _applyShadow () {
218- applyCssShadow (rootElement, pathBounds, elevation, shadowColor);
219- }
220-
221218 @override
222219 html.Element createElement () {
223220 return super .createElement ()..setAttribute ('clip-type' , 'physical-shape' );
224221 }
225222
226223 @override
227224 void apply () {
228- _applyColor ();
229- _applyShadow ();
230225 _applyShape ();
231226 }
232227
233228 void _applyShape () {
229+ _applyColor ();
234230 // Handle special case of round rect physical shape mapping to
235231 // rounded div.
236232 final ui.RRect ? roundRect = path.toRoundedRect ();
@@ -251,6 +247,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface
251247 if (clipBehavior != ui.Clip .none) {
252248 style.overflow = 'hidden' ;
253249 }
250+ applyCssShadow (rootElement, pathBounds, elevation, shadowColor);
254251 return ;
255252 } else {
256253 final ui.Rect ? rect = path.toRect ();
@@ -268,6 +265,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface
268265 if (clipBehavior != ui.Clip .none) {
269266 style.overflow = 'hidden' ;
270267 }
268+ applyCssShadow (rootElement, pathBounds, elevation, shadowColor);
271269 return ;
272270 } else {
273271 final ui.Rect ? ovalRect = path.toCircle ();
@@ -291,26 +289,64 @@ class PersistedPhysicalShape extends PersistedContainerSurface
291289 if (clipBehavior != ui.Clip .none) {
292290 style.overflow = 'hidden' ;
293291 }
292+ applyCssShadow (rootElement, pathBounds, elevation, shadowColor);
294293 return ;
295294 }
296295 }
297296 }
298297
299- final String svgClipPath = _pathToSvgClipPath (path,
300- offsetX: - pathBounds.left,
301- offsetY: - pathBounds.top,
302- scaleX: 1.0 / pathBounds.width,
303- scaleY: 1.0 / pathBounds.height);
304- // If apply is called multiple times (without update) , remove prior
305- // svg clip element.
298+ /// If code reaches this point, we have a path we want to clip against and
299+ /// potentially have a shadow due to material surface elevation.
300+ ///
301+ /// When there is no shadow we can simply clip a div with a background
302+ /// color using a svg clip path.
303+ ///
304+ /// Otherwise we need to paint svg element for the path and clip
305+ /// contents against same path for shadow to work since box-shadow doesn't
306+ /// take clip-path into account.
307+ ///
308+ /// Webkit has a bug when applying clip-path on an element that has
309+ /// position: absolute and transform
310+ /// (https://bugs.webkit.org/show_bug.cgi?id=141731).
311+ /// To place clipping rectangle correctly
312+ /// we size the inner container to cover full pathBounds instead of sizing
313+ /// to clipping rect bounds (which is the case for elevation == 0.0 where
314+ /// we shift outer/inner clip area instead to position clip-path).
315+ final String svgClipPath = elevation == 0.0
316+ ? _pathToSvgClipPath (path,
317+ offsetX: - pathBounds.left,
318+ offsetY: - pathBounds.top,
319+ scaleX: 1.0 / pathBounds.width,
320+ scaleY: 1.0 / pathBounds.height)
321+ : _pathToSvgClipPath (path,
322+ offsetX: 0.0 ,
323+ offsetY: 0.0 ,
324+ scaleX: 1.0 / pathBounds.right,
325+ scaleY: 1.0 / pathBounds.bottom);
326+ /// If apply is called multiple times (without update), remove prior
327+ /// svg clip and render elements.
306328 _clipElement? .remove ();
329+ _svgElement? .remove ();
307330 _clipElement =
308331 html.Element .html (svgClipPath, treeSanitizer: _NullTreeSanitizer ());
309332 domRenderer.append (rootElement! , _clipElement! );
310- DomRenderer .setElementStyle (
311- rootElement! , 'clip-path' , 'url(#svgClip$_clipIdCounter )' );
312- DomRenderer .setElementStyle (
313- rootElement! , '-webkit-clip-path' , 'url(#svgClip$_clipIdCounter )' );
333+ if (elevation == 0.0 ) {
334+ DomRenderer .setClipPath (rootElement! , 'url(#svgClip$_clipIdCounter )' );
335+ final html.CssStyleDeclaration rootElementStyle = rootElement! .style;
336+ rootElementStyle
337+ ..overflow = ''
338+ ..left = '${pathBounds .left }px'
339+ ..top = '${pathBounds .top }px'
340+ ..width = '${pathBounds .width }px'
341+ ..height = '${pathBounds .height }px'
342+ ..borderRadius = '' ;
343+ childContainer! .style
344+ ..left = '-${pathBounds .left }px'
345+ ..top = '-${pathBounds .top }px' ;
346+ return ;
347+ }
348+
349+ DomRenderer .setClipPath (childContainer! , 'url(#svgClip$_clipIdCounter )' );
314350 final html.CssStyleDeclaration rootElementStyle = rootElement! .style;
315351 rootElementStyle
316352 ..overflow = ''
@@ -321,28 +357,45 @@ class PersistedPhysicalShape extends PersistedContainerSurface
321357 ..borderRadius = '' ;
322358 childContainer! .style
323359 ..left = '-${pathBounds .left }px'
324- ..top = '-${pathBounds .top }px' ;
360+ ..top = '-${pathBounds .top }px'
361+ ..width = '${pathBounds .right }px'
362+ ..height = '${pathBounds .bottom }px' ;
363+
364+ final ui.Rect pathBounds2 = path.getBounds ();
365+ _svgElement = _pathToSvgElement (
366+ path, SurfacePaintData ()..color = color, '${pathBounds2 .right }' , '${pathBounds2 .bottom }' );
367+ /// Render element behind the clipped content.
368+ rootElement! .insertBefore (_svgElement! , childContainer);
369+
370+ final SurfaceShadowData shadow = computeShadow (pathBounds, elevation)! ;
371+ final ui.Color boxShadowColor = toShadowColor (shadowColor);
372+ _svgElement! .style
373+ ..filter =
374+ 'drop-shadow(${shadow .offset .dx }px ${shadow .offset .dy }px '
375+ '${shadow .blurWidth }px '
376+ 'rgba(${boxShadowColor .red }, ${boxShadowColor .green }, '
377+ '${boxShadowColor .blue }, ${boxShadowColor .alpha / 255 }))'
378+ ..transform = 'translate(-${pathBounds2 .left }px, -${pathBounds2 .top }px)' ;
379+
380+ rootElement! .style.backgroundColor = '' ;
325381 }
326382
327383 @override
328384 void update (PersistedPhysicalShape oldSurface) {
329385 super .update (oldSurface);
330- if (oldSurface.color != color) {
331- _applyColor ();
332- }
333- if (oldSurface.elevation != elevation ||
334- oldSurface.shadowColor != shadowColor) {
335- _applyShadow ();
336- }
337- if (oldSurface.path != path) {
386+ if (oldSurface.path != path || oldSurface.elevation != elevation ||
387+ oldSurface.shadowColor != shadowColor || oldSurface.color != color) {
338388 oldSurface._clipElement? .remove ();
339389 oldSurface._clipElement = null ;
390+ oldSurface._svgElement? .remove ();
391+ oldSurface._svgElement = null ;
340392 _clipElement? .remove ();
341393 _clipElement = null ;
394+ _svgElement? .remove ();
395+ _svgElement = null ;
342396 // Reset style on prior element since we may have switched between
343397 // rect/rrect and arbitrary path.
344- DomRenderer .setElementStyle (rootElement! , 'clip-path' , '' );
345- DomRenderer .setElementStyle (rootElement! , '-webkit-clip-path' , '' );
398+ DomRenderer .setClipPath (rootElement! , '' );
346399 _applyShape ();
347400 } else {
348401 // Reuse clipElement from prior surface.
@@ -351,6 +404,10 @@ class PersistedPhysicalShape extends PersistedContainerSurface
351404 domRenderer.append (rootElement! , _clipElement! );
352405 }
353406 oldSurface._clipElement = null ;
407+ _svgElement = oldSurface._svgElement;
408+ if (_svgElement != null ) {
409+ rootElement! .insertBefore (_svgElement! , childContainer);
410+ }
354411 }
355412 }
356413}
@@ -416,10 +473,7 @@ String createSvgClipDef(html.HtmlElement element, ui.Path clipPath) {
416473 final ui.Rect pathBounds = clipPath.getBounds ();
417474 final String svgClipPath = _pathToSvgClipPath (clipPath,
418475 scaleX: 1.0 / pathBounds.right, scaleY: 1.0 / pathBounds.bottom);
419- DomRenderer .setElementStyle (
420- element, 'clip-path' , 'url(#svgClip$_clipIdCounter )' );
421- DomRenderer .setElementStyle (
422- element, '-webkit-clip-path' , 'url(#svgClip$_clipIdCounter )' );
476+ DomRenderer .setClipPath (element, 'url(#svgClip$_clipIdCounter )' );
423477 // We need to set width and height for the clipElement to cover the
424478 // bounds of the path since browsers such as Safari and Edge
425479 // seem to incorrectly intersect the element bounding rect with
0 commit comments